Skip to main content

Casos de prueba: Tenant

Documenta los tests unitarios del módulo tenant (controlador, autorización de plataforma, servicio y cliente HTTP externo). Todos usan mocks; el spec de autorización levanta una app Nest real con supertest y los guards reales.

Specs fuente (bajo src/app/modules/tenant/):

  • controllers/tenant.controller.auth.spec.ts
  • controllers/tenant.controller.spec.ts
  • services/external-request.service.spec.ts
  • services/tenant.service.spec.ts

Resumen: 4 specs, 82 tests: autorización 35 (5 tests × 7 rutas con describe.each), controlador 12, ExternalRequestService 11 y TenantService 24.

tenant.controller.auth.spec.ts​

TenantController: platform admin authorization (SEC-001)​

Usa AuthGuard y PlatformAdminGuard reales; JwtService.verify devuelve { sub, email } y UserService.hasOperationalTenant resuelve true por defecto. Un describe.each repite los 5 tests siguientes para cada una de estas 7 rutas:

RutaHandler
post /v1/tenants/createcreate
get /v1/tenants/get-allfindAll
get /v1/tenants/get-one/abcfindOne
patch /v1/tenants/update/abcupdate
delete /v1/tenants/remove/abcremove
post /v1/tenants/abc/tokensaddToken
delete /v1/tenants/abc/tokens/some-tokenremoveToken
TestEscenarioResultado esperadoMocks
returns 401 for an anonymous requestPetición sin cabecera x-api-keyHTTP 401 y el handler del servicio no se invocaJwtService.verify, UserService, TenantService
returns 403 for a tenant admin without the platform flagUsuario admin con x-api-key válida pero sin isPlatformAdminHTTP 403 y el handler no se invocaUserService.findByEmail (usuario admin), JwtService.verify
returns 403 for a user whose userType claims to be superadminUsuario con userType: 'superadmin' pero sin flag de plataformaHTTP 403 (el userType no concede acceso) y el handler no se invocaUserService.findByEmail (userType: 'superadmin'), JwtService.verify
reaches the handler for a platform adminUsuario con isPlatformAdmin: trueEstado HTTP menor a 300 y el handler se invocaUserService.findByEmail (isPlatformAdmin: true), UserService.hasOperationalTenant, TenantService
returns 401 for a platform admin whose tenant is suspendedAdmin de plataforma cuyo tenant no está operativo (hasOperationalTenant devuelve false)HTTP 401 y el handler no se invocaUserService.findByEmail, UserService.hasOperationalTenant

tenant.controller.spec.ts​

El Logger.prototype.log se espía para verificar la auditoría; AuthGuard y PlatformAdminGuard se sustituyen por guards que permiten todo.

TenantController / create​

TestEscenarioResultado esperadoMocks
should return message, tenant and tokenValue on creationAlta con { name: 'Acme Tenant' }Llama create(dto) y responde { message: 'tenant.created', data: { tenant, tokenValue } }TenantService.create, Logger.log
audits the creation with the actor and never logs the token valueEl servicio devuelve un tokenValue secretoRegistra [platform] tenant.created tenant=uuid-001 actor=platform-admin-uuid y el valor del token no aparece en ningún logTenantService.create, Logger.log

TenantController / findAll​

TestEscenarioResultado esperadoMocks
should return message and array of tenantsEl servicio devuelve una listaResponde { message: 'tenant.findAll', data: { results } }TenantService.findAll
should return message and empty array when no tenantsEl servicio devuelve []data.results es []TenantService.findAll

TenantController / findOne​

TestEscenarioResultado esperadoMocks
should return message and tenant when foundEl tenant existeLlama findOne(id, lang) y responde tenant.findOneTenantService.findOne
should propagate NotFoundException when tenant not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionTenantService.findOne

TenantController / update​

TestEscenarioResultado esperadoMocks
should return message and updated tenant and audit the changeDTO { name: 'Acme Updated' }Llama update(id, dto, lang), responde tenant.updated y registra [platform] tenant.updated tenant=uuid-001 actor=platform-admin-uuidTenantService.update, Logger.log
should propagate NotFoundException and not audit when tenant not foundEl servicio rechaza con NotFoundExceptionSe propaga el error y no se registra auditoríaTenantService.update, Logger.log

TenantController / remove​

TestEscenarioResultado esperadoMocks
should return message and removed tenant and audit the deletionEl tenant existeLlama remove(id, lang), responde tenant.deleted y registra [platform] tenant.deleted tenant=uuid-001 actor=platform-admin-uuidTenantService.remove, Logger.log
should propagate NotFoundException when tenant not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionTenantService.remove

TenantController / tokens​

TestEscenarioResultado esperadoMocks
returns the new token once and audits without logging its valueaddToken con dateExpirationLa respuesta incluye tokenValue, se audita [platform] tenant.token_added ... y el valor del token no aparece en los logsTenantService.addToken, Logger.log
audits the token removal without logging its valueremoveToken con un token secretoSe audita [platform] tenant.token_removed ... y el valor del token no aparece en los logsTenantService.removeToken, Logger.log

external-request.service.spec.ts​

ExternalRequestService / searchContact​

El fetch global se reemplaza por un mock. La respuesta cruda del backend externo se mapea a ExternalContactData.

TestEscenarioResultado esperadoMocks
should send correct body with type and idLlamada con URL, método, token e idfetch recibe la URL, el método, cabecera Content-Type: application/json y body { token, type: 'contact', id }global.fetch
should extract data from the response wrapperRespuesta { statusCode, status, message, data }Devuelve el contenido de data; externalId es '31912'global.fetch
should map firstName → firstname and secondFirstName → secondFirstnameRespuesta con firstName y secondFirstNameDevuelve firstname y secondFirstname; no existe firstName en el resultadoglobal.fetch
should convert numeric externalId to stringexternalId numérico (31912)externalId es de tipo string con valor '31912'global.fetch
keeps the phone as text, preserving its leading zerophone es '0999999999'El teléfono se conserva como texto con su cero inicialglobal.fetch
should wrap metadata object in an arraymetadata viene como objetometadata es un arreglo de 1 elemento igual al objeto originalglobal.fetch
should return null when endpoint returns non-200 statusLa respuesta tiene ok: false (404)Devuelve nullglobal.fetch
should return null when fetch throws a network errorfetch rechaza con Connection refusedDevuelve nullglobal.fetch
should return null when fetch times outfetch rechaza con AbortErrorDevuelve nullglobal.fetch
should return empty metadata array when metadata is absentLa respuesta no trae metadatametadata es []global.fetch
should return undefined phone when phone is absentLa respuesta no trae phonephone es undefinedglobal.fetch

tenant.service.spec.ts​

process.env.MIGRATION_DB_DATA se restaura antes de cada test (se elimina si estaba sin definir).

TenantService / create​

TestEscenarioResultado esperadoMocks
should create tenant and return it with the generated tokenValueAlta con name y domainWhitelistPersiste el tenant con un token (value, dateExpedition, dateExpiration); devuelve tenant y un tokenValue de 64 caracteresTenantRepository.create
should use provided tokenExpiration when givenDTO con tokenExpiration explícitoEl token persistido usa esa fecha de expiraciónTenantRepository.create
should default tokenExpiration to ~1 year from now when not providedDTO sin tokenExpirationLa expiración queda a aproximadamente 1 año desde ahora (tolerancia de 1 s)TenantRepository.create
should not include tokenExpiration field in the persisted tenant dataDTO con tokenExpirationEl objeto persistido no contiene el campo tokenExpirationTenantRepository.create
seeds the global catalog and this tenant default role grants when MIGRATION_DB_DATA=trueMIGRATION_DB_DATA = 'true'Se llama seedGlobalCatalog() y seedDefaultRoleGrantsForTenant(tenant._id)TenantRepository.create, PermissionCatalogSeedService
never calls the seeding service when MIGRATION_DB_DATA is not "true"MIGRATION_DB_DATA = 'false'No se llama a ningún método de seedTenantRepository.create, PermissionCatalogSeedService
never calls the seeding service when MIGRATION_DB_DATA is unsetVariable eliminadaNo se llama a ningún método de seedTenantRepository.create, PermissionCatalogSeedService
still returns the created tenant and tokenValue when catalog seeding throwsseedGlobalCatalog rechaza con transient mongo errorLa creación no falla: devuelve tenant y tokenValueTenantRepository.create, PermissionCatalogSeedService.seedGlobalCatalog

TenantService / findAll​

TestEscenarioResultado esperadoMocks
should return an array of tenantsEl repositorio devuelve una listaLa devuelveTenantRepository.findAll
should return empty array when no tenants existSin tenantsDevuelve []TenantRepository.findAll

TenantService / findOne​

TestEscenarioResultado esperadoMocks
should return a tenant when foundfindById devuelve el tenantLlama findById(id) y lo devuelveTenantRepository.findById
should throw NotFoundException when tenant does not existfindById devuelve nullNotFoundExceptionTenantRepository.findById

TenantService / update​

TestEscenarioResultado esperadoMocks
should update and return the tenantDTO { name: 'Acme Updated' }Llama update(id, dto) y devuelve el tenant con el nombre nuevoTenantRepository.update
should throw NotFoundException when tenant does not existupdate devuelve nullNotFoundExceptionTenantRepository.update

TenantService / remove​

TestEscenarioResultado esperadoMocks
should remove and return the tenantremove devuelve el tenantLlama remove(id) y lo devuelveTenantRepository.remove
should throw NotFoundException when tenant does not existremove devuelve nullNotFoundExceptionTenantRepository.remove

TenantService / addToken​

TestEscenarioResultado esperadoMocks
should generate a token and add it to the tenantEl tenant existeLlama addToken(id, { value, dateExpedition, dateExpiration }); devuelve tokenValue de 64 caracteres (32 bytes en hex)TenantRepository.findByIdentifier, TenantRepository.addToken
should throw NotFoundException when tenant does not existfindByIdentifier devuelve nullNotFoundException y no se agrega ningún tokenTenantRepository.findByIdentifier, TenantRepository.addToken
should set dateExpedition to now at call timeSe llama addTokendateExpedition queda entre el instante previo y posterior a la llamadaTenantRepository.findByIdentifier, TenantRepository.addToken
should return a unique token each callSe llama addToken dos vecesLos dos tokenValue son distintosTenantRepository.findByIdentifier, TenantRepository.addToken

TenantService / removeToken​

TestEscenarioResultado esperadoMocks
should remove token and return updated tenantEl tenant existeLlama removeToken(id, tokenValue) y devuelve el tenant actualizadoTenantRepository.findByIdentifier, TenantRepository.removeToken
should throw NotFoundException when tenant does not existfindByIdentifier devuelve nullNotFoundException y no se elimina nadaTenantRepository.findByIdentifier, TenantRepository.removeToken

TenantService / findByTokenValue​

TestEscenarioResultado esperadoMocks
should delegate to repository.findByTokenValueSe busca por valor de tokenDelega con el valor y devuelve el tenantTenantRepository.findByTokenValue
should return null when token does not existEl repositorio devuelve nullDevuelve nullTenantRepository.findByTokenValue