Casos de prueba — Bootstrap, Config, Seeds y E2E
Specs de src/bootstrap/**, src/config/**, src/database/** y test/app.e2e-spec.ts.
Resumen: 8 archivos spec, 59 bloques it() declarados. En env.validation.spec.ts un describe.each repite 6 casos para 2 claves, por lo que se ejecutan 13 tests en ese archivo.
Los specs de migraciones y de seeds usan mongodb-memory-server (MongoDB real en memoria), no mocks de driver.
setupCors
| Test | Escenario | Resultado esperado | Mocks |
|---|
should call enableCors with origin:true and credentials:true | NODE_ENV=development | enableCors recibe origin: true y credentials: true | App Nest falsa (enableCors jest.fn) |
should include all HTTP methods | NODE_ENV=development | config.methods es igual a CORS_METHODS | App falsa |
should use wildcard allowedHeaders in non-production | NODE_ENV=development | allowedHeaders es '*' | App falsa |
should use restricted allowedHeaders in production | NODE_ENV=production | allowedHeaders es CORS_ALLOWED_HEADERS_PROD | App falsa |
should use wildcard allowedHeaders when NODE_ENV is undefined | NODE_ENV eliminado | allowedHeaders es '*' | App falsa |
cors constants
| Test | Escenario | Resultado esperado | Mocks |
|---|
CORS_METHODS should contain standard HTTP methods | Se lee la constante | Contiene GET, POST, PUT, PATCH, DELETE y OPTIONS | Ninguno |
CORS_ALLOWED_HEADERS_PROD should include Content-Type and Authorization | Se lee la constante | Incluye Content-Type y Authorization | Ninguno |
NODE_ENV.PRODUCTION should equal "production" | Se lee la constante | Es 'production' | Ninguno |
src/bootstrap/load-env.spec.ts
Cada test corre en un directorio temporal (cwd cambiado) y restaura process.env al finalizar.
loadEnv
| Test | Escenario | Resultado esperado | Mocks |
|---|
does nothing when .env does not exist | Directorio sin .env | No lanza error y BASE_ONLY queda sin definir | Sistema de archivos temporal |
loads values from the base .env file | .env con BASE_ONLY=base-value | process.env.BASE_ONLY es base-value | Archivo .env temporal |
never overwrites a value already present in process.env before .env loads | PRE_SET ya definido y distinto en .env | Se conserva already-set | Archivo .env temporal |
src/bootstrap/migration-001-ensure-catalog-indexes.spec.ts
Prueba el archivo real migrations/001-ensure-catalog-indexes.js cargado con loadMigrations: colapsa duplicados de módulos y permisos por acrónimo y crea índices únicos globales.
001-ensure-catalog-indexes
| Test | Escenario | Resultado esperado | Mocks |
|---|
is a safe no-op against an empty database | Base vacía | up resuelve y modules/permissions siguen con 0 documentos | mongodb-memory-server |
is a safe no-op against an already-collapsed database (one doc per acronym) | Un módulo, un permiso y un role-permission ya consistentes | Conteos intactos y permissions[] del role-permission sin cambios | mongodb-memory-server |
collapses duplicate Module docs by acronym down to one deterministic survivor | Dos módulos USER, el más nuevo se inserta primero | Queda uno solo: el de createdAt más antiguo | mongodb-memory-server |
rewires Permission.moduleId from a deleted duplicate Module onto its survivor | Un permiso apunta al módulo duplicado que se elimina | moduleId pasa al módulo sobreviviente | mongodb-memory-server |
collapses duplicate Permission docs by acronym down to one deterministic survivor | Dos permisos USER_CREATE, el más nuevo se inserta primero | Queda solo el más antiguo | mongodb-memory-server |
rewires and de-duplicates RolePermission.permissions[] when it referenced both tenant-duplicate ids for the same acronym | Un role-permission referencia ambos ids duplicados más un permiso distinto | permissions[] queda con el sobreviviente y el otro permiso, sin duplicado | mongodb-memory-server |
drops a RolePermission reference to a permission that no longer exists at all (truly orphaned) | Referencia a un permiso inexistente | La referencia huérfana se elimina | mongodb-memory-server |
every surviving Permission still points at a Module that exists after collapsing both | Módulos y permisos duplicados a la vez | Un solo permiso, cuyo moduleId existe en modules | mongodb-memory-server |
creates a unique acronym-only index on modules (not the old tenantId compound one) | Se ejecuta up | Existe índice único solo por acronym y no existe tenantId_1_acronym_1 | mongodb-memory-server |
creates a unique acronym-only index on permissions (not the old tenantId compound one) | Se ejecuta up | Igual que el anterior, sobre permissions | mongodb-memory-server |
actually enforces global uniqueness on modules once created | Se inserta dos veces el mismo acronym | La segunda inserción es rechazada | mongodb-memory-server |
actually enforces global uniqueness on permissions once created | Se inserta dos veces el mismo acronym | La segunda inserción es rechazada | mongodb-memory-server |
is idempotent — running it twice does not throw | Se ejecuta up dos veces | No lanza y queda un solo módulo | mongodb-memory-server |
src/bootstrap/migration-002-ensure-role-grant-indexes.spec.ts
Prueba migrations/002-ensure-role-grant-indexes.js cargada con loadMigrations.
002-ensure-role-grant-indexes
| Test | Escenario | Resultado esperado | Mocks |
|---|
creates a unique (tenantId, acronym) index on roles | Se ejecuta up | Existe índice único compuesto tenantId + acronym en roles | mongodb-memory-server |
creates a unique (tenantId, roleId, acronym) index on role-permissions | Se ejecuta up | Existe índice único tenantId + roleId + acronym en role-permissions | mongodb-memory-server |
is idempotent — running it twice does not throw | Se ejecuta up dos veces | La segunda ejecución resuelve sin error | mongodb-memory-server |
actually enforces uniqueness on roles once created | Se inserta dos veces el mismo tenantId + acronym | La segunda inserción es rechazada | mongodb-memory-server |
actually enforces uniqueness on role-permissions once created | Se inserta dos veces el mismo tenantId + roleId + acronym | La segunda inserción es rechazada | mongodb-memory-server |
src/bootstrap/run-migrations.spec.ts
loadMigrations
| Test | Escenario | Resultado esperado | Mocks |
|---|
returns an empty array when the migrations directory does not exist | Ruta inexistente | Devuelve [] | Directorio temporal |
loads .js files sorted by filename regardless of write order | Archivos 010, 001, 002 y un README.md | Solo los .js, ordenados: first, second, third | Archivos temporales |
throws a clear error when a file does not export { name, up } | Archivo que exporta { run } | Lanza error Invalid migration file | Archivo temporal |
applyMigrations / runMigrations — applyMigrations
| Test | Escenario | Resultado esperado | Mocks |
|---|
runs pending migrations in the given order and records them | Dos migraciones pendientes | Se ejecutan en orden, resultado { applied: ['mig-a','mig-b'], skipped: [] } y quedan registradas en migrations | mongodb-memory-server |
does not re-run a migration already recorded in the tracking collection | Se aplica dos veces la misma migración | 1.ª ejecución la aplica, 2.ª la omite; up se llama una vez | jest.fn para up |
does not run up() again for a name concurrently claimed by another caller | Ya existe el registro de seguimiento (carrera TOCTOU) | Resultado skipped y up no se llama | jest.fn para up |
rolls back the claim when up() throws, so the migration remains retryable | up falla la primera vez y funciona la segunda | Lanza boom, no queda registro y el reintento la aplica (up llamado 2 veces) | jest.fn (rechaza una vez) |
applyMigrations / runMigrations — runMigrations
| Test | Escenario | Resultado esperado | Mocks |
|---|
connects, applies pending migrations from disk, and is idempotent on a second run | Una migración en disco, dos ejecuciones | 1.ª applied: ['create-marker'], 2.ª skipped: ['create-marker'] | Archivo temporal, mongodb-memory-server |
returns empty results without connecting when there are no migration files | Directorio sin migraciones | { applied: [], skipped: [] } | Directorio temporal |
runMigrationsIfEnabled
| Test | Escenario | Resultado esperado | Mocks |
|---|
skips entirely when MIGRATION_DB is unset | Entorno {} | { ranMigrations: false, applied: [], skipped: [] } | Entorno inyectado |
skips entirely when MIGRATION_DB is 'false' | MIGRATION_DB='false' | Mismo resultado: no corre | Entorno inyectado |
requires an exact lowercase match — "TRUE" is treated as disabled | MIGRATION_DB='TRUE' | No corre (solo true en minúscula habilita) | Entorno inyectado |
never attempts a database connection when disabled, even with a garbage MONGODB_URI | Deshabilitado y MONGODB_URI inválida | Resultado de omitido, sin intentar conectar | Entorno inyectado |
src/config/env.validation.spec.ts
Valida el esquema Joi envValidationSchema con un entorno base válido. El describe.each se ejecuta para WHATSAPP_SESSION_ENCRYPTION_KEY y META_TOKEN_ENCRYPTION_KEY.
envValidationSchema — %s (describe.each, 2 variantes)
| Test | Escenario | Resultado esperado | Mocks |
|---|
passes validation with a valid base64-encoded 32-byte key | Clave base64 de 32 bytes | Sin error y el valor se conserva | Ninguno |
fails validation when the key is missing entirely | Variable ausente | Error <VAR> is required | Ninguno |
fails validation when the key is an empty string | Variable '' | Error <VAR> is required | Ninguno |
fails validation when the decoded key is shorter than 32 bytes | Clave de 16 bytes | Error <VAR> must be a base64-encoded 32-byte key | Ninguno |
fails validation when the decoded key is longer than 32 bytes | Clave de 48 bytes | Error <VAR> must be a base64-encoded 32-byte key | Ninguno |
does NOT silently fall back to a default — an invalid key never passes | Clave not-even-close-to-32-bytes | Hay error (no se usa valor por defecto) | Ninguno |
En la tabla, <VAR> representa el nombre de la variable de la variante en curso.
envValidationSchema — key isolation
| Test | Escenario | Resultado esperado | Mocks |
|---|
rejects a Meta key equal to the WhatsApp key | META_TOKEN_ENCRYPTION_KEY igual a la clave de WhatsApp | Error META_TOKEN_ENCRYPTION_KEY must be a base64-encoded 32-byte key different from WHATSAPP_SESSION_ENCRYPTION_KEY | Ninguno |
src/database/seeds/seed.service.spec.ts
SeedService contra MongoDB en memoria con los modelos reales; TenantService, repositorio y PermissionCatalogSeedService reales. Restaura variables de entorno (APP_ENV, RUN_SEEDS, FORCE_SEED_CLEANUP, MIGRATION_DB_DATA) tras cada test.
SeedService (mongodb-memory-server) — onApplicationBootstrap
| Test | Escenario | Resultado esperado | Mocks |
|---|
does nothing when APP_ENV is not "development", regardless of RUN_SEEDS | APP_ENV=production, RUN_SEEDS=false | run no se invoca | Spy de service.run |
logs an error (does not throw) when APP_ENV is not development but RUN_SEEDS==="true" | APP_ENV=staging, RUN_SEEDS=true | No lanza, run no se invoca y se registra un error | Spy de run y del logger |
does nothing when APP_ENV==="development" but RUN_SEEDS is not "true" | Development sin RUN_SEEDS | run no se invoca | Spy de run |
calls run() when APP_ENV==="development" and RUN_SEEDS==="true" | Development, RUN_SEEDS=true, FORCE_SEED_CLEANUP=true | run se llama con { force: true } | Spy de run |
SeedService (mongodb-memory-server) — run — defense in depth
| Test | Escenario | Resultado esperado | Mocks |
|---|
throws when called directly with APP_ENV not development | APP_ENV=production y run({ count: 1 }) | Rechaza con APP_ENV is not "development" | Ninguno |
SeedService (mongodb-memory-server) — run — force cleanup
| Test | Escenario | Resultado esperado | Mocks |
|---|
wipes only the 8 in-scope collections, leaving an unrelated collection (Role) untouched | Datos previos en tenants, labels, roles, departments, users y department-users; run({ force: true, count: 0 }) | Las 8 colecciones en alcance quedan en 0 y el Role previo sobrevive | MongoDB en memoria |
SeedService (mongodb-memory-server) — run — non-destructive default (force=false)
| Test | Escenario | Resultado esperado | Mocks |
|---|
skips seeding entirely when the tenants collection already has data | Ya existe un tenant y run({ force: false, count: 5 }) | Sigue habiendo 1 tenant y 0 usuarios y contactos | MongoDB en memoria |
SeedService (mongodb-memory-server) — run — roleId lookup failure
| Test | Escenario | Resultado esperado | Mocks |
|---|
throws a clear, actionable error when a created tenant has no ADMIN/AGENT roles | MIGRATION_DB_DATA sin definir, por lo que no se siembran roles | Rechaza con un error que menciona MIGRATION_DB_DATA=true y no se crea ningún usuario | MongoDB en memoria |
SeedService (mongodb-memory-server) — run — happy path
| Test | Escenario | Resultado esperado | Mocks |
|---|
seeds one full tenant with users (real roleIds), contacts, labels, tickets and messages | APP_ENV=development, MIGRATION_DB_DATA=true, run({ count: 1 }) | Un tenant con 2 roles; usuarios con roleId real; labels, departamentos, asignaciones que cubren todos los departamentos, contactos, tickets con departamento válido y mensajes con timestamps variados (timeout 30 s) | MongoDB en memoria |
test/app.e2e-spec.ts
AppController (e2e)
| Test | Escenario | Resultado esperado | Mocks |
|---|
/ (GET) | Se levanta AppModule completo y se hace GET / con supertest | Status 200 y cuerpo Hello World! | Ninguno (aplicación real) |
Casos it() documentados en esta página: 59 (8 + 3 + 13 + 5 + 13 + 7 + 9 + 1 en el orden de las secciones: cors, load-env, migration 001, migration 002, run-migrations, env.validation, seeds, e2e).