Ir al contenido

Testing Guide

Referencia operativa para escribir y correr tests. Las decisiones de arquitectura están en [[adr-009-testing-strategy|ADR-009]] y sus sub-documentos.


Ventana de terminal
bun test # todos los tests, modo watch
bunx vitest run # todos los tests, modo CI
bunx vitest run path/to/file.spec.ts # un spec específico
bunx vitest run --coverage # con reporte de cobertura v8
bun run test:static # guards de arquitectura estáticos (vitest.static.config.ts)
Ventana de terminal
pytest # todos los tests
pytest -m api # suite de contratos únicamente
pytest -m "not slow" # omitir tests lentos
pytest -m regression # suite de regresión
pytest apps/tags/tests/ # tests de una app
pytest tests/contract/test_employees_contract.py::TestEmployeeList # una clase

backend/
├── conftest.py # fixtures raíz (db, client, auth)
├── tests/
│ ├── test_health.py
│ ├── test_api_contract.py
│ ├── test_regression_backlog.py
│ ├── test_contract_coverage.py # verifica que todos los endpoints de API.md tengan test
│ └── contract/
│ ├── test_health_contract.py
│ ├── test_auth_contract.py
│ ├── test_employees_contract.py
│ ├── test_org_units_contract.py
│ ├── test_tags_contract.py
│ ├── test_positions_contract.py
│ ├── test_assignments_contract.py
│ ├── test_audit_log_contract.py
│ ├── test_hours_ledger_contract.py
│ ├── test_coverage_contract.py
│ └── test_business_rules_contract.py
├── apps/
│ ├── fsm/tests/
│ │ ├── test_employee_fsm.py
│ │ ├── test_audit_log.py
│ │ └── test_expire_proposals.py
│ ├── tags/tests/
│ │ ├── conftest.py
│ │ ├── test_models.py
│ │ ├── test_endpoints.py
│ │ ├── test_hours_ledger.py
│ │ └── test_tag_requirement_rule.py
│ ├── demand/tests/
│ │ ├── test_org_unit.py
│ │ ├── test_coverage_summary.py
│ │ ├── test_position_eligibility.py
│ │ ├── test_position_duplicate.py
│ │ └── test_position_delete.py
│ ├── audit_log/tests/
│ │ ├── test_endpoint.py
│ │ └── test_tag_change_log.py
│ └── business_rules/tests/
│ └── test_engine.py

49 archivos .spec.ts en frontend/src/. Estructura representativa:

src/app/
├── app.routes.spec.ts # guard de rutas
├── auth/
│ ├── login.component.spec.ts
│ └── login-card.component.spec.ts
├── core/
│ ├── guards/auth.guard.spec.ts
│ ├── interceptors/auth.interceptor.spec.ts
│ ├── interceptors/error.interceptor.spec.ts
│ └── services/auth.service.spec.ts
├── features/
│ ├── no-mock-imports.spec.ts # guard arquitectónico: prohíbe mocks en features
│ ├── roster/...
│ ├── structure/...
│ └── org-chart/...
└── shared/
├── mocks/clinic-bienestar.spec.ts # valida integridad del JSON de mock data
└── components/... # un spec por componente shared

Replicar la función pura y testear con inputs explícitos — sin TestBed.

// El componente tiene: protected occupancyColor = computed(() => { ... });
// El spec replica la función:
function occupancyColor(h: HoursData | null): string { ... }
it('returns red below 50%', () => {
expect(occupancyColor({ occupancy: 49, ... })).toBe('var(--p-red-500)');
});

Igual que computed(). Si depende de un padre: TestBed + TestBed.flushEffects().

TestBed.runInInjectionContext(() => {
const s = signal(0);
effect(() => { sideEffectFn(s()); });
});
TestBed.flushEffects();
expect(sideEffectFn).toHaveBeenCalledWith(0);
TestBed.configureTestingModule({
providers: [provideHttpClient(), provideHttpClientTesting(), MyService],
});
const svc = TestBed.inject(MyService);
const httpMock = TestBed.inject(HttpTestingController);
httpMock.expectOne('/api/v1/employees/').flush({ results: [...] });
await flushMicrotasks();
expect(svc.employees()).toEqual([...]);
httpMock.verify();

  1. Definir el regex prohibido en el spec del componente (o en un helper de auditoría si es global).
  2. Afirmar forbiddenPattern.test(templateSource) es false.
  3. Si es global, agregar al helper y verificar que corra en cada spec de componente.

Tests que evalúan lógica de horas dependen de la zona horaria de la máquina. Opciones:

  • Fijar TZ=UTC en CI (preferido).
  • Mockear Date con vi.setSystemTime() y offset UTC explícito.
  • Documentar la dependencia en el encabezado del spec.

No usar getUTCHours() en lógica de aplicación — solo en el harness de tests.


Cada archivo en tests/contract/ verifica: happy path (status + campos), 401 sin auth, un caso de error, campos de paginación si aplica.

El verificador tests/test_contract_coverage.py parsea API.md, extrae todos los endpoints ### METHOD /api/... y falla si alguno no tiene test. Mantiene un conjunto KNOWN_UNTESTED para deferrals intencionales.


ÁmbitoEstado
backend/ (raíz)conftest.py presente — fixtures globales
apps/tags/tests/conftest.py presente
apps/users/tests/sin conftest — agregar cuando se expandan tests
apps/demand/tests/sin conftest — agregar cuando se expandan tests
apps/assignments/tests/sin conftest — agregar cuando se escriban tests
apps/fsm/tests/sin conftest
apps/audit_log/tests/sin conftest
apps/business_rules/tests/sin conftest

Identificados en el gap analysis 2026-04. Cubrir cuando se implementen los módulos.

Hours Ledger (apps/hours/tests/test_services.py)

Sección titulada «Hours Ledger (apps/hours/tests/test_services.py)»
EscenarioComportamiento
Empleado sin contrato activocompute_balance() lanza error o retorna cero
Contrato que inicia en mitad de períodoProrrateado desde inicio de contrato, no del período
Solapamiento de assignments concurrentesRechazado a nivel de modelo
Assignment de cero horasRechazado (constraint de modelo)
Delta de horas por tagAjusta el balance computado correctamente

Coverage Rollup (apps/coverage/tests/test_aggregation.py)

Sección titulada «Coverage Rollup (apps/coverage/tests/test_aggregation.py)»
EscenarioComportamiento
Todos los puestos cubiertos100%
Sin assignments0%
Servicio agrega desde unidades hijasPromedio ponderado
Departamento agrega desde servicios hijosCorrecto en dos niveles
Clínica agrega desde todos los descendientesRollup de árbol completo
OrgUnit sin puestosnull o excluida del promedio padre

FaltantePrioridad
Tests de employee.serviceAlta
Tests de assignment.serviceAlta
Tests de hours-ledger.serviceMedia
Tests de shell.componentBaja
Guard no-subscribe.spec.tsAlta
Automatización de accesibilidad (axe-core)Diferido
Tests E2EDiferido