Skip to main content

Casos de prueba: RBAC (roles, permisos y catálogo)

Documenta los tests de los módulos role, role-permission, permission, permission-catalog-seed y module. Los specs de controladores y servicios usan mocks; los de repositorio y el seed usan MongoDB en memoria (mongodb-memory-server); los *.auth.spec.ts y *.permissions.spec.ts levantan una app Nest real con supertest para probar los guards a través del pipeline HTTP.

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

  • role/controllers/role.controller.auth.spec.ts
  • role/controllers/role.controller.permissions.spec.ts
  • role/controllers/role.controller.spec.ts
  • role/services/role.service.spec.ts
  • role-permission/controllers/role-permission.controller.auth.spec.ts
  • role-permission/controllers/role-permission.controller.spec.ts
  • role-permission/services/role-permission.service.spec.ts
  • permission/controllers/permission.controller.spec.ts
  • permission/repositories/permission.repository.spec.ts
  • permission/services/permission.service.spec.ts
  • permission-catalog-seed/services/permission-catalog-seed.service.spec.ts
  • module/controllers/module.controller.spec.ts
  • module/migrations/catalog-clone-plan.spec.ts
  • module/repositories/module.repository.spec.ts
  • module/services/module.service.spec.ts

Resumen: 15 specs, 127 tests (role 25, role-permission 28, permission 28, permission-catalog-seed 14, module 32). Los describe.each se cuentan por fila.


Módulo role

role.controller.auth.spec.ts​

RoleController: auth guard integration (bug fix)​

Se usa el AuthGuard real (con JwtService y UserService mockeados) y se anula solo PermissionsGuard. Un describe.each recorre 5 rutas.

TestEscenarioResultado esperadoMocks
returns 401 Unauthorized without an x-api-key header (ejecutado para post /v1/roles/create, get /v1/roles/get-all, get /v1/roles/get-one/abc, patch /v1/roles/update/abc, delete /v1/roles/remove/abc)Petición sin cabecera x-api-key a cada rutaHTTP 401 en las 5 rutas (el @Auth() de clase protege todo el controlador)RoleService, JwtService.verify, UserService.findByEmail, PermissionsGuard (permite todo)

role.controller.permissions.spec.ts​

RoleController: ACCESS_CONTROL permission guard integration​

PermissionsGuard y Reflector reales; FakeAuthGuard inyecta tenantId y roleId; el resolver de permisos está mockeado.

TestEscenarioResultado esperadoMocks
returns 403 Forbidden for GET /get-all when ACCESS_CONTROL_READ is missingGET /v1/roles/get-all sin permisos concedidosHTTP 403 y findAll no se invocaFakeAuthGuard, PermissionResolverService.resolve, RoleService.findAll
returns 200 OK for GET /get-all when ACCESS_CONTROL_READ is grantedMismo endpoint con ACCESS_CONTROL_READ concedidoHTTP 200 y findAll se invocaFakeAuthGuard, PermissionResolverService.resolve, RoleService.findAll

role.controller.spec.ts​

RoleController / create​

TestEscenarioResultado esperadoMocks
should pass tenantId from user to service and return created roleAlta de rol con name y acronymLlama create(dto, tenantId) y responde { message: 'role.created', data }RoleService.create, AuthGuard y PermissionsGuard (permiten todo)

RoleController / findAll​

TestEscenarioResultado esperadoMocks
should scope roles to the current user tenantEl servicio devuelve un rolLlama findAll(tenantId) y responde role.findAll con resultsRoleService.findAll
should return message and empty array when tenant has no rolesEl servicio devuelve []data.results es []RoleService.findAll

RoleController / findOne​

TestEscenarioResultado esperadoMocks
should return message and role when foundEl rol existeLlama findOne(id, lang) y responde role.findOneRoleService.findOne
should propagate NotFoundException when role not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionRoleService.findOne

RoleController / update​

TestEscenarioResultado esperadoMocks
should return message and updated roleDTO { name: 'Admin Updated' }Llama update(id, dto, lang) y responde role.updatedRoleService.update
should propagate NotFoundException when role not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionRoleService.update

RoleController / remove​

TestEscenarioResultado esperadoMocks
should return message and removed roleEl rol existeLlama remove(id, lang) y responde role.deletedRoleService.remove
should propagate NotFoundException when role not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionRoleService.remove

role.service.spec.ts​

RoleService / create​

TestEscenarioResultado esperadoMocks
should create and return a roleAlta con DTO y tenantIdLlama create(dto, tenantId) y devuelve el rolRoleRepository.create

RoleService / findAll​

TestEscenarioResultado esperadoMocks
should return roles scoped to the given tenantEl repositorio devuelve roles del tenantUsa findByTenantId(tenantId), nunca findAll, y devuelve la listaRoleRepository.findByTenantId, RoleRepository.findAll
should return an empty array when tenant has no rolesEl tenant no tiene rolesDevuelve []RoleRepository.findByTenantId

RoleService / findOne​

TestEscenarioResultado esperadoMocks
should return a role when foundfindById devuelve el rolLlama findById(id) y lo devuelveRoleRepository.findById
should throw NotFoundException when role does not existfindById devuelve nullNotFoundExceptionRoleRepository.findById

RoleService / update​

TestEscenarioResultado esperadoMocks
should update and return the roleDTO { name: 'Admin Updated' }Llama update(id, dto) y devuelve el rol actualizadoRoleRepository.update
should throw NotFoundException when role does not existupdate devuelve nullNotFoundExceptionRoleRepository.update

RoleService / remove​

TestEscenarioResultado esperadoMocks
should remove and return the roleremove devuelve el rolLlama remove(id) y lo devuelveRoleRepository.remove
should throw NotFoundException when role does not existremove devuelve nullNotFoundExceptionRoleRepository.remove

Módulo role-permission

role-permission.controller.auth.spec.ts​

RolePermissionController: auth guard integration (bug fix)​

TestEscenarioResultado esperadoMocks
returns 401 Unauthorized without an x-api-key header (ejecutado para post /v1/role-permissions/create, get /v1/role-permissions/get-all, get /v1/role-permissions/get-one/abc, patch /v1/role-permissions/update/abc, delete /v1/role-permissions/remove/abc)Petición sin x-api-key a cada ruta, con AuthGuard realHTTP 401 en las 5 rutasRolePermissionService, JwtService.verify, UserService.findByEmail, PermissionsGuard (permite todo)

role-permission.controller.spec.ts​

RolePermissionController / create​

TestEscenarioResultado esperadoMocks
should return message and created role permissionAlta con name, acronym, roleId, permissions y tenantIdLlama create(dto) y responde { message: 'rolePermission.created', data }RolePermissionService.create

RolePermissionController / findAll​

TestEscenarioResultado esperadoMocks
should scope role permissions to the current user tenantEl servicio devuelve una listaLlama findAll(tenantId) y responde rolePermission.findAll con resultsRolePermissionService.findAll
should return message and empty array when tenant has no role permissionsEl servicio devuelve []data.results es []RolePermissionService.findAll

RolePermissionController / findOne​

TestEscenarioResultado esperadoMocks
should return message and role permission when foundEl registro existeLlama findOne(id, lang) y responde rolePermission.findOneRolePermissionService.findOne
should propagate NotFoundException when role permission not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionRolePermissionService.findOne

RolePermissionController / update​

TestEscenarioResultado esperadoMocks
should return message and updated role permissionDTO { name: 'Admin Updated' }Llama update(id, dto, lang) y responde rolePermission.updatedRolePermissionService.update
should propagate NotFoundException when role permission not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionRolePermissionService.update

RolePermissionController / remove​

TestEscenarioResultado esperadoMocks
should return message and removed role permissionEl registro existeLlama remove(id, lang) y responde rolePermission.deletedRolePermissionService.remove
should propagate NotFoundException when role permission not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionRolePermissionService.remove

role-permission.service.spec.ts​

El servicio invalida la caché del PermissionResolverService cada vez que cambian los permisos de un rol.

RolePermissionService / create​

TestEscenarioResultado esperadoMocks
should create and return a role permissionAlta con DTO válidoLlama create(dto) y devuelve el registroRolePermissionRepository.create
invalidates the permission-resolver cache for the affected role (bugfix: role edits were not reflected until the 60s cache expired)Alta correctaSe llama invalidate(tenantId, roleId) del registro creadoRolePermissionRepository.create, PermissionResolverService.invalidate

RolePermissionService / findAll​

TestEscenarioResultado esperadoMocks
should return role permissions scoped to the given tenantEl repositorio devuelve registros del tenantUsa findByTenantId(tenantId), nunca findAllRolePermissionRepository.findByTenantId, RolePermissionRepository.findAll
should return an empty array when tenant has no role permissionsSin registrosDevuelve []RolePermissionRepository.findByTenantId

RolePermissionService / findOne​

TestEscenarioResultado esperadoMocks
should return a role permission when foundfindById devuelve el registroLlama findById(id) y lo devuelveRolePermissionRepository.findById
should throw NotFoundException when role permission does not existfindById devuelve nullNotFoundExceptionRolePermissionRepository.findById

RolePermissionService / update​

TestEscenarioResultado esperadoMocks
should update and return the role permissionDTO { name: 'Admin Updated' }Llama update(id, dto) y devuelve el registroRolePermissionRepository.update
should throw NotFoundException when role permission does not existupdate devuelve nullNotFoundExceptionRolePermissionRepository.update
invalidates the permission-resolver cache for the affected role so the new grants apply immediatelyActualización correctaSe llama invalidate(tenantId, roleId) con los datos del registro actualizadoRolePermissionRepository.update, PermissionResolverService.invalidate
does not touch the permission-resolver cache when the role permission does not existupdate devuelve nullNotFoundException y invalidate no se llamaRolePermissionRepository.update, PermissionResolverService.invalidate

RolePermissionService / remove​

TestEscenarioResultado esperadoMocks
should remove and return the role permissionremove devuelve el registroLlama remove(id) y lo devuelveRolePermissionRepository.remove
should throw NotFoundException when role permission does not existremove devuelve nullNotFoundExceptionRolePermissionRepository.remove
invalidates the permission-resolver cache for the affected role so a revoked permission stops applying immediatelyBorrado correctoSe llama invalidate(tenantId, roleId)RolePermissionRepository.remove, PermissionResolverService.invalidate
does not touch the permission-resolver cache when the role permission does not existremove devuelve nullNotFoundException y invalidate no se llamaRolePermissionRepository.remove, PermissionResolverService.invalidate

Módulo permission

permission.controller.spec.ts​

PermissionController / create​

TestEscenarioResultado esperadoMocks
should pass the dto to the service and return the created permissionAlta con name, acronym, moduleIdLlama create(dto) y responde { message: 'permission.created', data }PermissionService.create, AuthGuard y PermissionsGuard (permiten todo)

PermissionController / findAll​

TestEscenarioResultado esperadoMocks
should return every permission in the global catalogEl servicio devuelve un permisoLlama findAll() sin argumentos (catálogo global) y responde permission.findAllPermissionService.findAll
should return message and empty array when there are no permissionsEl servicio devuelve []data.results es []PermissionService.findAll

PermissionController / findOne​

TestEscenarioResultado esperadoMocks
should return message and permission when foundEl permiso existeLlama findOne(id, lang) y responde permission.findOnePermissionService.findOne
should propagate NotFoundException when permission not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionPermissionService.findOne

PermissionController / update​

TestEscenarioResultado esperadoMocks
should return message and updated permissionDTO { name: 'Create Users Updated' }Llama update(id, dto, lang) y responde permission.updatedPermissionService.update
should propagate NotFoundException when permission not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionPermissionService.update

PermissionController / remove​

TestEscenarioResultado esperadoMocks
should return message and removed permissionEl permiso existeLlama remove(id, lang) y responde permission.deletedPermissionService.remove
should propagate NotFoundException when permission not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionPermissionService.remove

permission.repository.spec.ts​

Integración con MongoDB en memoria; se esperan los índices (createIndexes) antes de empezar para que el índice único de acronym exista.

PermissionRepository: global catalog​

TestEscenarioResultado esperadoMocks
creates a permission without any tenant scopingAlta de CRM_CREATEEl documento no tiene tenantIdMongo real
rejects a duplicate acronym globally (unique index)Se crea dos veces DUPLa segunda creación lanza error (índice único)Mongo real

findAll​

TestEscenarioResultado esperadoMocks
returns every permission in the global catalogDos permisos creadosDevuelve los 2Mongo real

findById​

TestEscenarioResultado esperadoMocks
returns the permission by identifierPermiso existenteLo devuelve por identifierMongo real
returns null when the identifier does not existIdentifier inexistenteDevuelve nullMongo real

findByModuleId​

TestEscenarioResultado esperadoMocks
returns only permissions belonging to the given modulePermisos de dos módulos distintosSolo devuelve el del módulo pedidoMongo real

update​

TestEscenarioResultado esperadoMocks
updates the permission by identifierCambia el nombre a EditedDevuelve el permiso con el nombre nuevoMongo real
returns null when the identifier does not existActualiza un identifier inexistenteDevuelve nullMongo real

remove​

TestEscenarioResultado esperadoMocks
removes the permission by identifierPermiso existenteDevuelve el permiso y findById posterior devuelve nullMongo real
returns null when the identifier does not existBorra un identifier inexistenteDevuelve nullMongo real

permission.service.spec.ts​

PermissionService / create​

TestEscenarioResultado esperadoMocks
should create a permission without any tenant scopingAlta con DTO válidoLlama create(dto) y devuelve el permisoPermissionRepository.create

PermissionService / findAll​

TestEscenarioResultado esperadoMocks
should return every permission in the global catalogEl repositorio devuelve un permisoLlama findAll() sin argumentos y devuelve la listaPermissionRepository.findAll
should return an empty array when there are no permissionsSin permisosDevuelve []PermissionRepository.findAll

PermissionService / findOne​

TestEscenarioResultado esperadoMocks
should return a permission when foundfindById devuelve el permisoLlama findById(id) y lo devuelvePermissionRepository.findById
should throw NotFoundException when permission does not existfindById devuelve nullNotFoundExceptionPermissionRepository.findById

PermissionService / update​

TestEscenarioResultado esperadoMocks
should update and return the permissionDTO { name: 'Create Users Updated' }Llama update(id, dto) y devuelve el permisoPermissionRepository.update
should throw NotFoundException when permission does not existupdate devuelve nullNotFoundExceptionPermissionRepository.update

PermissionService / remove​

TestEscenarioResultado esperadoMocks
should remove and return the permissionremove devuelve el permisoLlama remove(id) y lo devuelvePermissionRepository.remove
should throw NotFoundException when permission does not existremove devuelve nullNotFoundExceptionPermissionRepository.remove

Módulo permission-catalog-seed

permission-catalog-seed.service.spec.ts​

Integración con MongoDB en memoria y los modelos reales de Module, Permission, Tenant, Role y RolePermission. Se restaura process.env.MIGRATION_DB_DATA tras cada test.

PermissionCatalogSeedService / seedGlobalCatalog: idempotency​

TestEscenarioResultado esperadoMocks
inserts every catalog module and permission on first callPrimera ejecución sobre base vacíaExisten tantos módulos y permisos como CATALOG_MODULES y CATALOG_PERMISSIONSMongo real
does not duplicate rows when called twiceSe ejecuta dos vecesLos conteos se mantienen igualesMongo real
never touches updatedAt on an already-existing row when called againRe-seed tras 5 msEl updatedAt del módulo USER no cambiaMongo real
links each permission to its own module via moduleIdTras el seedUSER_CREATE apunta al _id del módulo USERMongo real

seedDefaultRoleGrantsForTenant: default role grants​

TestEscenarioResultado esperadoMocks
grants ADMIN every catalog permission acronym and AGENT only the agent-facing subsetSeed del catálogo y de los grants del tenant ASe crean los roles ADMIN (Administrador) y AGENT (Agente); ADMIN recibe todos los acrónimos del catálogo y AGENT solo los de módulos de agente que no son adminOnly; TICKET_UNLOCK_ANY y TICKET_FILTER_BY_AGENT no llegan a AGENT y TICKET_VIEW_AGENT_AVATAR síMongo real
does not duplicate roles or grants when called twice for the same tenantSe ejecuta dos veces para el mismo tenantQuedan 2 roles y 2 grantsMongo real
leaves a tenant's own pre-existing ADMIN role untouched (name, no duplicate) and still grants it DEFAULT_ADMIN_GRANTEl tenant ya tiene un rol ADMIN personalizadoNo se duplica ni se cambia su nombre/descripción, y recibe el grant por defecto con todos los permisosMongo real
never restores or adds back into a manually-trimmed DEFAULT_ADMIN_GRANT on a later callSe recorta manualmente el grant ADMIN a 1 permiso y se vuelve a ejecutar el seedEl grant sigue con 1 solo permiso (no se restaura)Mongo real
recovers via refetch when Role.create() loses a duplicate-key raceRole.create falla con error E11000 porque otra llamada insertó el rol antesSe recupera releyendo el rol: queda 1 solo ADMIN y su grant con todos los permisosjest.spyOn(roleModel, 'create')
seeding tenant B's default roles/grants never touches tenant A'sSe siembra el tenant B después del ARoles y grants del tenant A quedan idénticos; el tenant B tiene 2 rolesMongo real
every tenant's grant only ever references permissions from the single global catalog, regardless of seeding orderSe siembra B antes que ATodos los permisos del grant ADMIN del tenant A pertenecen al catálogo globalMongo real

onModuleInit: MIGRATION_DB_DATA gate​

TestEscenarioResultado esperadoMocks
does nothing (no query at all) when the flag is not the string "true"MIGRATION_DB_DATA = 'false'tenantModel.find no se llamajest.spyOn(tenantModel, 'find')
does nothing when the flag is unsetVariable de entorno eliminadatenantModel.find no se llamajest.spyOn(tenantModel, 'find')
seeds the global catalog once and default role grants for every existing tenant when the flag is "true"MIGRATION_DB_DATA = 'true' y dos tenants existentesSe siembra el catálogo global y cada tenant recibe 2 rolesMongo real

Módulo module

module.controller.spec.ts​

ModuleController / create​

TestEscenarioResultado esperadoMocks
should pass the dto to the service and return the created moduleAlta { name: 'CRM', acronym: 'CRM' }Llama create(dto) y responde { message: 'module.created', data }ModuleService.create, AuthGuard y PermissionsGuard (permiten todo)

ModuleController / findAll​

TestEscenarioResultado esperadoMocks
should return every module in the global catalogEl servicio devuelve un móduloLlama findAll() sin argumentos y responde module.findAllModuleService.findAll
should return message and empty array when there are no modulesEl servicio devuelve []data.results es []ModuleService.findAll

ModuleController / findOne​

TestEscenarioResultado esperadoMocks
should return message and module when foundEl módulo existeLlama findOne(id, lang) y responde module.findOneModuleService.findOne
should propagate NotFoundException when module not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionModuleService.findOne

ModuleController / update​

TestEscenarioResultado esperadoMocks
should return message and updated moduleDTO { name: 'CRM Updated' }Llama update(id, dto, lang) y responde module.updatedModuleService.update
should propagate NotFoundException when module not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionModuleService.update

ModuleController / remove​

TestEscenarioResultado esperadoMocks
should return message and removed moduleEl módulo existeLlama remove(id, lang) y responde module.deletedModuleService.remove
should propagate NotFoundException when module not foundEl servicio rechaza con NotFoundExceptionSe propaga NotFoundExceptionModuleService.remove

catalog-clone-plan.spec.ts​

Pruebas de funciones puras de la migración que clona el catálogo legacy a un tenant; no usan mocks.

buildClonePlan​

TestEscenarioResultado esperadoMocks
clones every legacy module and permission into new tenant-owned docs on first run2 módulos y 3 permisos legacy, sin clones existentesPlanea insertar 2 módulos (con el tenantId) y 3 permisos, cada permiso apunta a un módulo clonado, y los mapas de reasignación tienen 2 y 3 entradasNinguno
is idempotent — re-running with the already-cloned catalog produces zero insertsSe vuelve a planificar con los clones de la primera ejecución0 inserciones y los mapas de reasignación resuelven a los mismos ids ya clonadosNinguno
skips a permission whose legacy moduleId is not in the given module setPermiso huérfano cuyo moduleId no está entre los módulosNo se inserta ningún permiso ni se crea entrada de reasignaciónNinguno

rewireRolePermissionIds​

TestEscenarioResultado esperadoMocks
maps every legacy permission id to its cloned counterpart, preserving orderIds legacy [B, A] con mapa de reasignaciónDevuelve [clonedB, clonedA] conservando el ordenNinguno
drops ids that have no entry in the rewire mapUn id legacy sin entrada en el mapaEl id huérfano se descarta y devuelve solo [clonedA]Ninguno

module.repository.spec.ts​

Integración con MongoDB en memoria; se esperan los índices antes de empezar.

ModuleRepository: global catalog​

TestEscenarioResultado esperadoMocks
creates a module without any tenant scopingAlta del módulo CRMEl documento no tiene tenantIdMongo real
rejects a duplicate acronym globally (unique index)Se crea dos veces DUPLa segunda creación lanza errorMongo real

findAll​

TestEscenarioResultado esperadoMocks
returns every module in the global catalogDos módulos creadosDevuelve los 2Mongo real

findById​

TestEscenarioResultado esperadoMocks
returns the module by identifierMódulo existenteLo devuelve por identifierMongo real
returns null when the identifier does not existIdentifier inexistenteDevuelve nullMongo real

update​

TestEscenarioResultado esperadoMocks
updates the module by identifierCambia el nombre a EditedDevuelve el módulo con el nombre nuevoMongo real
returns null when the identifier does not existActualiza un identifier inexistenteDevuelve nullMongo real

remove​

TestEscenarioResultado esperadoMocks
removes the module by identifierMódulo existenteDevuelve el módulo y findById posterior devuelve nullMongo real
returns null when the identifier does not existBorra un identifier inexistenteDevuelve nullMongo real

module.service.spec.ts​

ModuleService / create​

TestEscenarioResultado esperadoMocks
should create a module without any tenant scopingAlta con DTO válidoLlama create(dto) y devuelve el móduloModuleRepository.create

ModuleService / findAll​

TestEscenarioResultado esperadoMocks
should return every module in the global catalogEl repositorio devuelve un móduloLlama findAll() sin argumentos y devuelve la listaModuleRepository.findAll
should return an empty array when there are no modulesSin módulosDevuelve []ModuleRepository.findAll

ModuleService / findOne​

TestEscenarioResultado esperadoMocks
should return a module when foundfindById devuelve el móduloLlama findById(id) y lo devuelveModuleRepository.findById
should throw NotFoundException when module does not existfindById devuelve nullNotFoundExceptionModuleRepository.findById

ModuleService / update​

TestEscenarioResultado esperadoMocks
should update and return the moduleDTO { name: 'CRM Updated' }Llama update(id, dto) y devuelve el móduloModuleRepository.update
should throw NotFoundException when module does not existupdate devuelve nullNotFoundExceptionModuleRepository.update

ModuleService / remove​

TestEscenarioResultado esperadoMocks
should remove and return the moduleremove devuelve el móduloLlama remove(id) y lo devuelveModuleRepository.remove
should throw NotFoundException when module does not existremove devuelve nullNotFoundExceptionModuleRepository.remove