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
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).
| # | 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. |
╱╲ 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 |
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).
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
});
});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 |
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);
});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:
- El test del consumidor genera un pact file (request/response esperados por interacción).
- El pact file se publica en un Pact Broker (o artifact compartido en CI).
- El CI del proveedor ejecuta
pact-verifycontra las expectativas del consumidor usando provider states. - 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).
| 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();
});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.
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).
| 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) |
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 gate → Root CleanlinessCriterios 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).
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.
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.
- Tracker Target Architecture (TAD) — capas, Unit of Work (§12.1), Outbox (§13)
- PostgreSQL Data Design — schemas, RLS, migraciones
- React Frontend Design — topología microfrontend
- PRD — EPIC-003, métricas de éxito
- Tech Standards — mandato Pact
- Global Rules — R-07, R-15
- Pact — Consumer-Driven Contracts