Skip to content

Latest commit

 

History

History
339 lines (253 loc) · 18.1 KB

File metadata and controls

339 lines (253 loc) · 18.1 KB

Evolith Tracker — Estrategia de Testing

Navegación Bilingüe: English · Español (este documento)

Estado del Documento: Borrador Tipo: Estrategia de Testing Satélite: Evolith Tracker Upstream: Evolith Core Fecha: 2026-06-07 Autor: QA Agent + Architect Agent (BMAD) Resuelve: FINDING-008 / GAP-005


1. Propósito y Alcance

Este documento define la estrategia de testing para Evolith Tracker. Es la referencia autoritativa sobre qué se prueba, en qué capa, con qué herramientas y contra qué umbrales — y define cómo se evalúa la Quality Gate de la Fase QA (Gate 4).

Deriva sus mandatos de la gobernanza existente:

Fuente Mandato
.harness/standards/tech-standards.md "Consumer-Driven Contract Tests (Pact) required for all core integrations"; "Strict TypeScript"
PRD EPIC-003 Ejecución de contract tests, Change Failure Rate (CFR) < 2%, heatmaps de cobertura, Root Cleanliness
TAD Capas hexagonales (domain/application/infrastructure/presentation), Unit of Work, Transactional Outbox
React Frontend Design Vitest + React Testing Library + Playwright; topología microfrontend
Global Rules R-15 Multi-tenancy: aislamiento en capa de aplicación (primario), RLS (secundario) — ambos deben probarse

Fuera de alcance (este documento): metodología de load/performance testing (cubierta en Hardening, Roadmap Fase 8) y pruebas de penetración de seguridad (cubierta en la Especificación de Seguridad).


2. Principios de Testing

# Principio
P1 Probar comportamiento, no implementación. Los tests de dominio validan reglas de negocio; nunca validan llamadas al ORM.
P2 La capa de dominio se prueba en aislamiento puro — cero NestJS, cero TypeORM, cero base de datos (refleja la "Regla de la Capa de Dominio" del TAD).
P3 Cada criterio de aceptación mapea a al menos un test. La trazabilidad es obligatoria (R-07).
P4 Los contratos son consumer-driven. Cada integración cross-system (UMS, Core, .harness, frontend↔backend) se verifica con Pact.
P5 El aislamiento de tenant es un objetivo de test de primera clase. Tanto el scoping de capa de aplicación como el failsafe RLS se prueban (R-15).
P6 Los tests son determinísticos e idempotentes. Sin dependencia de orden; sin estado mutable compartido; relojes UTC inyectados.
P7 Se respeta la pirámide. Muchos tests unitarios rápidos, menos tests de integración, pocos tests E2E.

3. Pirámide de Tests y Niveles

        ╱╲          E2E (Playwright) — solo journeys SDLC críticos
       ╱  ╲         ── pocos, lentos, alta confianza
      ╱────╲        Contract (Pact) — cada frontera externa
     ╱      ╲       ── consumer-driven, ejecutados en CI consumidor & proveedor
    ╱────────╲      Integration (Jest + Testcontainers) — adapters, repos, RLS
   ╱          ╲     ── PostgreSQL real, transacciones reales
  ╱────────────╲    Unit (Vitest/Jest) — domain + application handlers
 ╱──────────────╲   ── muchos, rápidos, sin I/O
Nivel Objetivo Herramienta Velocidad ¿BD?
Unit — Domain Aggregates, entities, value objects, domain services, policies Vitest (o Jest) ms No
Unit — Application Command/query handlers, orquestación Unit of Work (ports mockeados) Vitest/Jest + test doubles ms No
Integration Repositories, ACL adapters, outbox, RLS, migraciones Jest + Testcontainers (PostgreSQL 15) segundos Sí (efímera)
Contract UMS, Evolith Core, .harness, frontend↔backend REST Pact (consumer-driven) segundos No (provider state stubs)
Component (FE) Componentes React, hooks, UI permission-driven Vitest + React Testing Library ms No
E2E Journeys cross-gate a través del Shell Host + remotes Playwright minutos Stack completo

4. Testing Backend por Capa Hexagonal

4.1 Capa de Dominio (Unit — pura)

Sin NestJS, sin ORM, sin BD. Construir aggregates directamente y validar invariantes y domain events emitidos.

// modules/discovery/domain/aggregates/initiative.spec.ts
describe('Initiative', () => {
  it('emits InitiativeApprovedEvent and transitions to APPROVED when the canvas gate passes', () => {
    const initiative = Initiative.create({ /* valid canvas, ROI, KPIs */ });
    initiative.submitForApproval();

    const result = initiative.approve({ approver: poUser, justification: 'ROI validated' });

    expect(result.isSuccess).toBe(true);
    expect(initiative.status).toBe(InitiativeStatus.APPROVED);
    expect(initiative.domainEvents).toContainEqual(
      expect.objectContaining({ eventType: 'InitiativeApprovedEvent' }),
    );
  });

  it('rejects approval when the RequirementChecklist is incomplete (BR-001)', () => {
    const initiative = Initiative.create({ /* incomplete canvas */ });
    const result = initiative.approve({ approver: poUser, justification: 'x' });

    expect(result.isFailure).toBe(true);
    expect(result.error).toBeInstanceOf(IncompleteChecklistError);
  });
});

Objetivos de cobertura de dominio: transiciones de estado del aggregate, cada regla de negocio (BR-001…BR-009), validación de value objects, lógica de domain services (ej. DriftDetectionService, PhaseGateEvaluator).

4.2 Capa de Aplicación (Unit — handlers con test doubles)

Command/query handlers probados con fakes de repositorio in-memory y un fake IUnitOfWork. Validar el contrato de orquestación: resultado del handler, eventos persistidos en outbox, transaction boundary respetado.

// modules/discovery/application/commands/approve-initiative.handler.spec.ts
describe('ApproveInitiativeHandler', () => {
  it('persists the initiative and writes InitiativeApprovedEvent to the outbox in one UoW', async () => {
    const repo = new InMemoryInitiativeRepository([draftInitiative]);
    const uow = new FakeUnitOfWork();
    const outbox = new FakeOutbox();
    const handler = new ApproveInitiativeHandler(repo, uow, outbox);

    const result = await handler.handle(new ApproveInitiativeCommand(initiativeId, poUser));

    expect(result.isSuccess).toBe(true);
    expect(uow.committed).toBe(true);
    expect(outbox.events.map(e => e.eventType)).toContain('InitiativeApprovedEvent');
  });

  it('rolls back the UoW and writes no outbox event when approval fails', async () => {
    // ... assert uow.rolledBack === true && outbox.events.length === 0
  });
});

4.3 Capa de Infraestructura (Integration — PostgreSQL real)

Ejecución contra PostgreSQL efímero vía Testcontainers. Cubre repositories, outbox, migraciones y multi-tenancy.

// modules/discovery/infrastructure/persistence/typeorm-initiative.repository.int-spec.ts
describe('TypeOrmInitiativeRepository (integration)', () => {
  it('round-trips an aggregate with optimistic concurrency (xmin)', async () => { /* ... */ });

  it('enforces tenant isolation via RLS — a query under tenant B cannot read tenant A rows', async () => {
    await withTenant(tenantA, () => repo.save(initiativeA));
    const leaked = await withTenant(tenantB, () => repo.findById(initiativeA.id));
    expect(leaked).toBeNull(); // RLS failsafe (R-15 secondary layer)
  });

  it('OutboxProcessor delivers pending events and marks processedAt (at-least-once)', async () => { /* ... */ });
});

Escenarios de integración obligatorios:

Escenario Valida
Aislamiento cross-schema Sin FK entre schemas tracker_*; solo referencias UUID
Aislamiento RLS por tenant Tenant B no puede leer/escribir filas de Tenant A (R-15 secundario)
Scoping de tenant en capa de aplicación TenantContextMiddleware establece app.current_tenant_id (R-15 primario)
Atomicidad de Unit of Work Fallo del handler revierte TANTO escrituras del aggregate COMO inserts del outbox
Outbox at-least-once Eventos sobreviven un crash simulado entre publish y actualización de processedAt
Migración up/down Cada migración es reversible; schema coincide con el diseño de datos

4.4 Capa de Presentación (Integration — NestJS e2e por módulo)

Usar @nestjs/testing + supertest para ejercitar controllers, guards, pipes e interceptors con el adapter de UMS stubbed.

// modules/discovery/presentation/initiative.controller.e2e-spec.ts
it('returns 403 when the caller lacks tracker:initiative:approve (fail-closed)', async () => {
  umsAdapter.setPermissions(['tracker:initiative:read']);
  await request(app.getHttpServer())
    .post(`/api/v1/initiatives/${id}/approve`)
    .expect(403);
});

5. Contract Testing (Pact — Consumer-Driven)

Per tech-standards.md, cada frontera de integración core se cubre con un contrato Pact consumer-driven. El consumidor define expectativas; el proveedor las verifica en su propio CI.

Consumidor Proveedor El contrato cubre
Tracker backend UMS Validación de token, resolución del grafo de autorización, permisos efectivos
Tracker backend Evolith Core Fetch de rulesets, definiciones de artifacts, taxonomía, validación de compliance
Tracker backend .harness Trigger de pipeline, ingesta de resultados de test, señal de quality-gate
Tracker frontend (cada remote) Tracker backend REST endpoints consumidos por cada microfrontend (alineado con OpenAPI)
Tracker backend (webhook consumer) GitHub / Jira ACLs Forma del payload del webhook inbound → mapeo a evento canónico

Flujo Pact:

  1. El test del consumidor genera un pact file (request/response esperados por interacción).
  2. El pact file se publica en un Pact Broker (o artifact compartido en CI).
  3. El CI del proveedor ejecuta pact-verify contra las expectativas del consumidor usando provider states.
  4. Un cambio del proveedor que rompe un contrato del consumidor falla el build del proveedor — surfaceando drift antes del deploy.

¿Por qué Pact y no solo validación OpenAPI? OpenAPI valida forma; Pact valida las interacciones reales de las que depende un consumidor, capturando breaking changes que el schema solo no detectaría (ej. un campo del que depende el consumidor siendo removido de una respuesta específica).


6. Testing Frontend (Microfrontends)

Nivel Herramienta Objetivo
Unit/Component Vitest + React Testing Library Componentes, hooks, gating usePermission, validación de forms (Zod)
Contract Pact (lado consumidor) Llamadas REST de cada remote al backend
Integración Module Federation Vitest + MF test harness Shell Host carga cada remote; error boundary aísla un remote que crashea
E2E Playwright Journeys completos a través del Shell + remotes compuestos

Mandatos de test específicos de microfrontend (per T-002):

Mandato Test
Aislamiento de fallos Un error lanzado en mfe-qa es capturado por el error boundary del Shell; otros remotes siguen interactivos
Singletons compartidos react, react-dom, @tanstack/react-query resuelven a una única instancia entre remotes
UI permission-driven Un remote no renderiza ninguna acción privilegiada cuando el auth context compartido deniega el permiso
Consistencia del Design System Los remotes consumen solo primitivas de ui-kit (lint + checks visuales)
// packages/auth — test de componente UI permission-driven
it('hides the Approve button when the user lacks tracker:initiative:approve', () => {
  renderWithPermissions(<ApproveButton initiativeId="x" />, { permissions: [] });
  expect(screen.queryByRole('button', { name: /approve/i })).toBeNull();
});

7. Testing End-to-End (Playwright)

Los tests E2E cubren solo journeys cross-gate críticos — no cobertura UI exhaustiva. Cada uno mapea a un caso de uso del PRD.

Journey E2E Caso de Uso Gates recorridos
Enviar → aprobar Discovery Canvas → backlog generado UC-001 Gate 1
Iniciativa aprobada → contrato + ADR enviados → blueprint aprobado UC-002 Gate 2
Tarea de Construction → drift alert levantado ante desviación UC-003 Gate 3
Ciclo QA → contract tests ejecutados → veredicto quality gate UC-004 Gate 4
Release planeado → autorizado → deployment registrado UC-005 Gate 5
Ejecución AI-Native completa vía CLI/MCP (agent assignment → gate pass) UC-006, UC-008 Todos

E2E se ejecuta contra un entorno multi-tenant seedeado con UMS y .harness en modo stub/sandbox.


8. Umbrales de Cobertura

La cobertura se aplica por capa en CI. Los umbrales reflejan riesgo: la lógica de dominio se mantiene al estándar más alto; código generado/presentación más bajo.

Capa Línea Branch Justificación
Domain ≥ 95% ≥ 90% Las reglas de negocio son el producto; cobertura casi total
Application (handlers) ≥ 90% ≥ 85% Correctitud de orquestación + UoW
Infrastructure (adapters) ≥ 80% ≥ 70% Integration-tested; algo de glue es bajo riesgo
Presentation (controllers) ≥ 80% ≥ 70% Paths de guard/permission deben estar cubiertos
Frontend components ≥ 80% ≥ 70% Gating de permisos completamente cubierto

La cobertura es un piso, no un objetivo. Un número de cobertura verde con assertions débiles igualmente falla la revisión (P1).


9. Test Data y Fixtures

Concern Enfoque
Aggregate builders Test data builders (anInitiative().approved().build()) — sin literals de objeto crudos
Fixtures de tenant Al menos dos tenants seedeados en cada suite de integración para probar aislamiento
Clock Puerto IClock inyectado; los tests usan un reloj UTC fijo para determinismo
IDs UUIDs determinísticos en tests; gen_random_uuid() solo en paths de producción
Sistemas externos UMS / Core / .harness reemplazados por stubs Pact (contract) o fakes (unit)

10. Integración CI y Quality Gate (Gate 4)

PR pipeline (cada commit):
  lint (strict TS, sin unused imports) → unit (domain+application) → component (FE)
  → integration (Testcontainers PG) → contract (Pact consumer) → build

main / provider pipeline:
  pact-verify (provider) → e2e (Playwright, seedeado) → coverage gateRoot Cleanliness

Criterios de paso Gate 4 (QA Phase Gate) — mapean a métricas de éxito del PRD:

Criterio Umbral Fuente
Change Failure Rate (CFR) < 2% PRD §9, BR-003
Todas las verificaciones Pact del proveedor pass tech-standards.md
Pisos de cobertura (§8) cumplidos por capa este documento
Journeys E2E críticos green §7
Root Cleanliness cero archivos root no autorizados PRD EPIC-003
Sin defecto Critical/High abierto enforced PRD EPIC-003

Un Gate 4 fallido bloquea la autorización de Release (BR-003 — lock del sistema no negociable).


11. Integración con .harness

El sistema CI externo .harness es la superficie de ejecución para contract testing y regresión. El Tracker se integra vía un ACL (contexto tracker-integration):

Dirección Interacción
Tracker → .harness Dispara ejecución de contract-test + regression al inicio del ciclo QA
.harness → Tracker Postea resultados (pass/fail, CFR, cobertura) vía webhook → TestExecutionCompletedEvent
Tracker → Gate El aggregate TestCycle computa pass rate; PhaseGateEvaluator emite QualityGatePassed/FailedEvent

El contrato del payload de .harness es verificado por Pact (§5) así un cambio de formato no puede romper silenciosamente la evaluación del gate.


12. Trazabilidad

Per R-07, cada test traza a un criterio de aceptación, y cada regla de negocio traza a un test.

Regla de Negocio Nivel de Test Verificador Ejemplo
BR-001 (sin Design sin Canvas aprobado) Domain + E2E Test de checklist Initiative.approve; UC-001 E2E
BR-002 (sin Construction sin Blueprint) Application + E2E Handler del gate de Design; UC-002 E2E
BR-003 (sin deploy sin QA gate) Integration + Gate 4 PhaseGateEvaluator; umbral CFR
BR-004 (drift dispara alerta) Domain Unit test de DriftDetectionService
BR-006 (aislamiento de tenant absoluto) Integration Tests de RLS + app-layer scoping (§4.3)
BR-007 (entregables de agent = humanos) Application Handler de validación de deliverables
BR-008 (CLI/MCP = Web paridad) Contract + E2E Mismos contratos backend ejercitados por todas las superficies
BR-009 (acciones de agent auditadas) Integration Assertion de append al audit trail

Un pendiente Story breakdown (GAP-019, BMAD SM Agent) producirá criterios de aceptación a nivel de story; cada criterio debe vincularse a un ID de test en ese punto.


Referencias