Casos de prueba: Usuarios y Departamentos
Documenta los tests de los módulos user y departments. Los specs de controladores y servicios usan mocks; los marcados como integración usan MongoDB en memoria (mongodb-memory-server) con el repositorio y el modelo reales.
Specs fuente (bajo src/app/modules/):
user/controllers/user.controller.spec.tsuser/controllers/user.controller.permissions.spec.tsuser/controllers/user.controller.integration.spec.tsuser/dtos/create-user.dto.spec.tsuser/repositories/user.repository.filter-agents.spec.tsuser/services/user.service.spec.tsdepartments/controllers/department.controller.spec.tsdepartments/repositories/department.repository.spec.tsdepartments/repositories/department-user.repository.spec.tsdepartments/services/department.service.spec.tsdepartments/services/department-user.service.spec.ts
Resumen: 11 specs, 132 tests (87 en user, 45 en departments). Los it.each se cuentan por fila.
Módulo user
user.controller.spec.ts
UserController / create
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return message and created user | Se crea un usuario con un DTO válido; el usuario autenticado tiene tenantId | El servicio recibe username, email y el tenantId del usuario autenticado; responde { message: 'user.created', data } | UserService.create, AuthGuard (permite todo) |
UserController / getMe
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns the resolved permission acronyms merged into the user payload | El resolver devuelve CONTACT_CREATE y CONTACT_READ | Resuelve con (tenantId, roleId); results[0].permissions contiene ambos acrónimos y no incluye password | PermissionResolverService.resolve, TenantSettingsService.getOrCreate |
returns an empty permissions array when the resolver returns no acronyms | El resolver devuelve un Set vacío | permissions es [] | PermissionResolverService.resolve |
exposes scheduledMessagesEnabled from tenant settings without leaking the rest of the config (CRITICAL-A fix) | Settings con scheduledMessages.enabled = true | featureFlags es { scheduledMessagesEnabled: true }; no aparecen general ni scheduledMessages en la respuesta | PermissionResolverService.resolve, TenantSettingsService.getOrCreate |
defaults scheduledMessagesEnabled to false when the tenant has not enabled the feature | Settings con scheduledMessages.enabled = false | featureFlags es { scheduledMessagesEnabled: false } | PermissionResolverService.resolve, TenantSettingsService.getOrCreate |
exposes tenantPreferences with the tenant language and timezone | Settings con language: 'en', timezone: 'America/Bogota' | tenantPreferences es { language: 'en', timezone: 'America/Bogota' } | PermissionResolverService.resolve, TenantSettingsService.getOrCreate |
does not leak the admin-only settings sections alongside tenantPreferences | Settings incluyen businessHours, autoResolution y csat | La respuesta no contiene esas secciones; tenantPreferences solo trae idioma y zona horaria | PermissionResolverService.resolve, TenantSettingsService.getOrCreate |
UserController / findAll
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return message and array of users | El servicio devuelve una lista | Llama findAll(tenantId) y responde { message: 'user.findAll', data: { results } } | UserService.findAll |
should return message and empty array when no users | El servicio devuelve [] | data.results es [] | UserService.findAll |
UserController / findOne
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return message and user when found | El usuario existe | Llama findOne(id, tenantId, lang) y responde { message: 'user.findOne', data: { results: [user] } } | UserService.findOne |
should propagate NotFoundException when user not found | El servicio rechaza con NotFoundException | Se propaga NotFoundException | UserService.findOne |
UserController / update
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return message and updated user scoped to the authenticated tenant | dto = { firstname: 'Jane' } | Llama updateById(id, tenantId, dto, lang) y responde { message: 'user.updated', data } | UserService.updateById |
should propagate NotFoundException when user not found | El servicio rechaza con NotFoundException | Se propaga NotFoundException | UserService.updateById |
UserController / remove
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return message and removed user scoped to the authenticated tenant | El usuario existe en el tenant | Llama removeById(id, tenantId, lang) y responde { message: 'user.deleted', data } | UserService.removeById |
should propagate NotFoundException when user not found | El servicio rechaza con NotFoundException | Se propaga NotFoundException | UserService.removeById |
user.controller.permissions.spec.ts
Integración del guard real PermissionsGuard + Reflector a través del pipeline HTTP (supertest). Un FakeAuthGuard inyecta tenantId y roleId; el resolver de permisos está mockeado.
UserController: exento self-service vs CRUD protegido
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
allows GET /get-me with zero granted permissions (self-service, no metadata) | GET /v1/users/get-me sin permisos concedidos | HTTP 200 (endpoint exento del guard) | FakeAuthGuard, PermissionResolverService.resolve, TenantSettingsService.getOrCreate, UserService |
allows PATCH me/avatar with zero granted permissions (self-service, no metadata) | PATCH /v1/users/me/avatar con archivo adjunto y sin permisos | HTTP 200 y se invoca updateAvatar | FakeAuthGuard, PermissionResolverService.resolve, UserService.updateAvatar |
returns 403 Forbidden for DELETE remove/:id when USER_DELETE is missing | DELETE /v1/users/remove/:id con solo USER_READ | HTTP 403 y removeById no se invoca | FakeAuthGuard, PermissionResolverService.resolve, UserService.removeById |
returns 200 OK for DELETE remove/:id when USER_DELETE is granted | Mismo endpoint con USER_DELETE concedido | HTTP 200 y se invoca removeById | FakeAuthGuard, PermissionResolverService.resolve, UserService.removeById |
user.controller.integration.spec.ts
Integración con MongoDB en memoria; solo se mockean permisos, settings, procesador de media y storage.
UserController: signature integration (raíz)
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
persists the signature via PATCH and returns it on a subsequent get-me call | updateSignature con signature, luego getMe sobre el usuario releído | La firma persiste en Mongo, la respuesta no trae password y getMe devuelve la firma | PermissionResolverService, TenantSettingsService |
never leaks the password field in the PATCH response | updateSignature con 'Saludos' | Las claves de data no incluyen password | PermissionResolverService, TenantSettingsService |
updateAvatar
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
uploads a valid JPG and reflects avatarUrl on a subsequent get-me call | Se sube un JPG válido | avatarUrl contiene avatars/{tenantId}/{userId}.webp y getMe devuelve la misma URL | MediaProcessorService.processImage, StorageService.uploadPublic |
overwrites the same storage key on re-upload and changes the cache-buster | Se sube el avatar dos veces | La URL base es igual y el parámetro ?v= cambia | MediaProcessorService, StorageService.uploadPublic |
rejects a non-JPG file with no storage write and no persisted change | Archivo image/png | Rechaza y avatarUrl sigue siendo '' | MediaProcessorService, StorageService |
rejects an oversized file with no storage write and no persisted change | Archivo de 2 MB + 1 byte | Rechaza y avatarUrl sigue siendo '' | MediaProcessorService, StorageService |
never leaks the password field in the PATCH response | Subida válida | Las claves de data no incluyen password | MediaProcessorService, StorageService.uploadPublic |
create
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
creates the user with the authenticated tenant, without a tenantId in the body | Alta sin tenantId en el body | El usuario queda en el tenant del autenticado, con isPlatformAdmin = false | Ninguno de dominio (Mongo real) |
ignores a tenantId injected in the body and keeps the authenticated one | El body incluye un tenantId ajeno | Se conserva el tenant del autenticado; el ajeno se ignora | Ninguno de dominio (Mongo real) |
toggleActive
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
flips isActive from true to false and persists it | Usuario activo | isActive pasa a false y se persiste | Ninguno de dominio (Mongo real) |
flips isActive back from false to true | Se alterna dos veces | Termina en isActive: true | Ninguno de dominio (Mongo real) |
findAll
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns only users from the authenticated tenant | Existe un usuario de otro tenant | La lista incluye johndoe y excluye ajeno | Ninguno de dominio (Mongo real) |
tenant isolation (SEC-014)
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
does not return a user that belongs to another tenant | findOne con el identifier de un usuario de otro tenant | NotFoundException | Mongo real |
does not change the password of a user that belongs to another tenant | update con password sobre usuario ajeno | NotFoundException y el hash de la víctima permanece intacto | Mongo real |
does not toggle a user that belongs to another tenant | toggleActive sobre usuario ajeno | NotFoundException y isActive sigue en true | Mongo real |
does not delete a user that belongs to another tenant | remove sobre usuario ajeno | NotFoundException y el usuario sigue existiendo | Mongo real |
still reads and updates users of the authenticated tenant | findOne y update sobre usuarios propios | Lectura correcta y firstname actualizado a Johnny | Mongo real |
responds NotFound for a malformed id instead of a cast error | remove('not-an-object-id') | NotFoundException (no un error de cast) | Mongo real |
rejects persisting a userType outside the allowed values | update con userType: 'atc' | Rechaza y el userType sigue siendo agent | Mongo real |
password hash never reaches serialized responses (B2.1)
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
omits the password from every user CRUD response | Se ejecutan create, findOne, findAll, update, toggleActive y remove | Ningún JSON de respuesta contiene "password" | Mongo real |
keeps the password hash available internally for authentication | Se relee el usuario desde el modelo | password y toObject().password conservan el hash | Mongo real |
hasOperationalTenant (B4)
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
reads the real state of a %s tenant (it.each: active, deactivated, with inactive status) | Tenant creado en Mongo con isActive/status distintos | Devuelve true solo si está activo y con status: 'active'; false en los otros dos casos | Mongo real |
returns false when the tenant does not exist | Tenant con id inexistente | Devuelve false | Mongo real |
user.dtos: create-user.dto.spec.ts
Validación con class-validator y class-transformer.
CreateUserDto: userType
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
accepts %s (it.each: admin, agent) | Payload válido con ese userType | Sin errores de validación en userType | Ninguno |
rejects %p (it.each: atc, external, superadmin, cadena vacía) | Payload válido con un userType no permitido | Exactamente un error en userType | Ninguno |
UpdateUserDto: userType
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
allows omitting userType | Payload solo con firstname | Sin errores en userType | Ninguno |
rejects a userType outside the allowed values | userType: 'atc' | Un error en userType | Ninguno |
User DTOs: isPlatformAdmin is not assignable through the API
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
rejects isPlatformAdmin on create | Create con isPlatformAdmin: true y whitelist + forbidNonWhitelisted | La propiedad isPlatformAdmin aparece entre los errores | Ninguno |
rejects isPlatformAdmin on update | Update con isPlatformAdmin: true y las mismas opciones | La propiedad isPlatformAdmin aparece entre los errores | Ninguno |
user.repository.filter-agents.spec.ts
Integración con MongoDB en memoria.
UserRepository.findFilterableByTenant (D11)
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns only _id, firstname, lastName, username — no email/password/roleId | Un usuario con todos los campos | La proyección solo trae _id, firstname, lastName, username (ignorando __v) | Mongo real |
scopes strictly by tenant — excludes users from other tenants | Usuarios en dos tenants | Solo devuelve el usuario del tenant consultado | Mongo real |
returns all tenant users regardless of userType/isActive — same result set as today's unfiltered get-all | Un admin y un agente inactivo en el mismo tenant | Devuelve ambos | Mongo real |
filterAssignableByIds
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
keeps active users of any userType, admins included | Un agente y un admin activos | Devuelve ambos ids | Mongo real |
drops inactive users regardless of their userType | Un activo, un agente inactivo y un admin inactivo | Solo devuelve el id del activo | Mongo real |
returns an empty list without querying when no ids are given | Lista de ids vacía | Resuelve [] | Mongo real |
user.service.spec.ts
UserService / create
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should hash password and create user | El email no existe | Busca por email, hashea con bcrypt.hash(password, 10) y crea con el hash | UserRepository, bcrypt |
should throw ConflictException when email already exists | findByEmail devuelve un usuario | ConflictException | UserRepository.findByEmail |
UserService / findAll
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return an array of users | El repositorio devuelve una lista | Llama findAll(tenantId) y devuelve la lista | UserRepository.findAll |
should return an empty array when no users exist | El repositorio devuelve [] | Devuelve [] | UserRepository.findAll |
UserService / hasOperationalTenant
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
reports a %s tenant correctly (it.each: active, deactivated, with inactive status, missing) | El repositorio devuelve el estado operativo (o null) | true solo para activo con status: 'active'; false para desactivado, inactivo o inexistente; consulta findOperationalStateById(tenantId) | TenantRepository.findOperationalStateById |
UserService / findOne
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should look the user up scoped to the tenant | El usuario existe | Llama findByIdentifierAndTenant(id, tenantId) y lo devuelve | UserRepository.findByIdentifierAndTenant |
should throw NotFoundException when the user is not in the tenant | El repositorio devuelve null | NotFoundException | UserRepository.findByIdentifierAndTenant |
UserService / updateById
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should update scoped to the tenant without hashing when no password | DTO sin password | No se llama a bcrypt.hash; actualiza con (id, tenantId, dto) | UserRepository.updateByIdAndTenant, bcrypt |
should hash password when password is provided | DTO con password | Hashea con costo 10 y envía el hash al repositorio | UserRepository.updateByIdAndTenant, bcrypt.hash |
should throw NotFoundException when the user is not in the tenant | El repositorio devuelve null | NotFoundException | UserRepository.updateByIdAndTenant |
should throw NotFoundException for a malformed id without querying | Id que no es ObjectId | NotFoundException y no se consulta el repositorio | UserRepository.updateByIdAndTenant |
UserService / removeById
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should remove scoped to the tenant | El usuario existe | Llama removeByIdAndTenant(id, tenantId) y lo devuelve | UserRepository.removeByIdAndTenant |
should throw NotFoundException when the user is not in the tenant | El repositorio devuelve null | NotFoundException | UserRepository.removeByIdAndTenant |
UserService / toggleActive
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should toggle scoped to the tenant | El usuario existe | Llama toggleActiveByIdAndTenant(id, tenantId) y lo devuelve | UserRepository.toggleActiveByIdAndTenant |
should throw NotFoundException when the user is not in the tenant | El repositorio devuelve null | NotFoundException | UserRepository.toggleActiveByIdAndTenant |
UserService / updateSignature
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should scope the update to the currently authenticated user id | Actualiza la firma del usuario actual | Llama updateSignatureById(userId, signature) | UserRepository.updateSignatureById |
should return the updated user without the password field | El repositorio devuelve un documento con toObject | El resultado incluye la firma y no contiene password | UserRepository.updateSignatureById |
should throw NotFoundException when the current user no longer exists | El repositorio devuelve null | NotFoundException | UserRepository.updateSignatureById |
UserService / updateAvatar
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
rejects a non-JPG file without touching storage | Archivo image/png | BadRequestException; no se procesa, sube ni persiste nada | MediaProcessorService, StorageService, UserRepository |
rejects an oversized JPG without touching storage | JPG de 2 MB + 1 byte | BadRequestException; no se procesa, sube ni persiste nada | MediaProcessorService, StorageService, UserRepository |
converts a valid JPG to WEBP and uploads it under the stable per-user key | JPG válido | Llama processImage(buffer) y uploadPublic('avatars/{tenantId}/{userId}.webp', buffer, 'image/webp') | MediaProcessorService.processImage, StorageService.uploadPublic, UserRepository.updateAvatarUrlById |
persists the avatar URL with a cache-busting query param | La subida devuelve la URL base | Persiste la URL con ?v= seguido de dígitos | StorageService.uploadPublic, UserRepository.updateAvatarUrlById |
returns the updated user without the password field | Subida válida | El resultado no contiene password | StorageService.uploadPublic, UserRepository.updateAvatarUrlById |
throws NotFoundException when the current user no longer exists | updateAvatarUrlById devuelve null | NotFoundException | StorageService.uploadPublic, UserRepository.updateAvatarUrlById |
Módulo departments
department.controller.spec.ts
DepartmentController / create
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
delegates to service and returns { message, data } | DTO { name: 'Soporte' } | Llama create(dto, tenantId) y responde { message: 'department.created', data } | DepartmentService.create, AuthGuard, PermissionsGuard (permiten todo) |
DepartmentController / findAll
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns { message, data: { results } } scoped to tenant | Listado sin paginación ni filtros | Llama findAll(tenantId, undefined, undefined, {}) y responde department.find_all con results | DepartmentService.findAll |
DepartmentController / findOne
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns { message, data: { results: [department] } } | El departamento existe | Llama findOne(id, tenantId, lang) y responde department.find_one | DepartmentService.findOne |
propagates NotFoundException | El servicio rechaza con NotFoundException | Se propaga NotFoundException | DepartmentService.findOne |
DepartmentController / update
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns { message, data } | DTO { name: 'Modificado' } | Llama update(id, tenantId, dto, lang) y responde department.updated | DepartmentService.update |
propagates NotFoundException | El servicio rechaza con NotFoundException | Se propaga NotFoundException | DepartmentService.update |
DepartmentController / getDepartmentUsers
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns { message, data: { results } } | El servicio devuelve usuarios con sus flags | Llama getUsersForDepartment(ObjectId(id), tenantId) y responde department.find_all | DepartmentUserService.getUsersForDepartment |
DepartmentController / updateDepartmentUsers
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns { message, data: { results } } | DTO { users: [] } | Llama updateUsersForDepartment(ObjectId(id), tenantId, dto) y responde department.updated | DepartmentUserService.updateUsersForDepartment |
DepartmentController / remove
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns { message, data } | El departamento existe | Llama remove(id, tenantId, lang) y responde department.deleted | DepartmentService.remove |
propagates NotFoundException | El servicio rechaza con NotFoundException | Se propaga NotFoundException | DepartmentService.remove |
department.repository.spec.ts
Integración con MongoDB en memoria.
DepartmentRepository / create
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
stores document with tenantId and returns it | Crea Soporte en el tenant A | Devuelve el documento con _id, tenantId y name | Mongo real |
applies schema defaults (color, autoAssignment) | Crea solo con name | color es #000000 y autoAssignment es false | Mongo real |
DepartmentRepository / findAll
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns only departments belonging to the tenant | Dos departamentos en A y uno en B | Devuelve solo los 2 del tenant A | Mongo real |
returns empty array when tenant has no departments | Tenant sin departamentos | Devuelve [] | Mongo real |
DepartmentRepository / findById
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns document when _id and tenantId match | Id y tenant correctos | Devuelve el documento | Mongo real |
returns null when tenantId does not match (cross-tenant) | Id válido pero tenant B | Devuelve null | Mongo real |
returns null when id does not exist | Id inexistente | Devuelve null | Mongo real |
DepartmentRepository / update
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
updates and returns updated document | Cambia el nombre en el tenant correcto | Devuelve el documento con el nombre nuevo | Mongo real |
returns null when id does not belong to tenant | Actualiza con el tenant B | Devuelve null | Mongo real |
DepartmentRepository / delete
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
removes document and returns it | Borra en el tenant correcto | Devuelve el documento y findById posterior devuelve null | Mongo real |
returns null when id does not belong to tenant | Borra con el tenant B | Devuelve null | Mongo real |
department-user.repository.spec.ts
Integración con MongoDB en memoria.
DepartmentUserRepository / upsert
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
creates a new record when one does not exist | Upsert de un par departamento-usuario nuevo | Devuelve el registro con ids y flags indicados | Mongo real |
updates existing record on second upsert | Se hace upsert dos veces sobre el mismo par | Los flags se actualizan y solo existe 1 documento | Mongo real |
DepartmentUserRepository / findByDepartment
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns only records for the given department and tenant | Registros mezclados de otros departamentos y tenants | Solo devuelve los 2 del departamento y tenant pedidos | Mongo real |
returns empty array when no assignments exist | Sin asignaciones | Devuelve [] | Mongo real |
DepartmentUserRepository / findAutoAssignUserIds
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns only userIds with autoAssign true for the department and tenant | Mezcla de flags, departamentos y tenants | Solo devuelve el usuario con autoAssign: true del departamento y tenant | Mongo real |
returns empty array when the department has no queue | Nadie con autoAssign | Devuelve [] | Mongo real |
DepartmentUserRepository / findDepartmentIdsByUser
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns only the departments where the user has assignToDepartment | Un departamento con assignToDepartment true y otro false | Solo devuelve el primero | Mongo real |
does not leak memberships from another tenant | Membresía en el tenant B | Devuelve [] para el tenant A | Mongo real |
returns an empty list for a user with no memberships | Usuario sin membresías | Devuelve [] | Mongo real |
DepartmentUserRepository / deleteByDepartment
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
removes all records for the department within the tenant | Registros del mismo departamento en dos tenants | Borra los del tenant A y conserva el del tenant B | Mongo real |
department.service.spec.ts
DepartmentService / create
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
delegates to repository and returns result | Alta de Soporte | Llama create(tenantId, dto) y devuelve el resultado | DepartmentRepository.create |
DepartmentService / findAll
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns all departments for tenant | Sin filtros | Llama findAll(tenantId, undefined) y devuelve { results } | DepartmentRepository.findAll |
restricts the listing to the departments the userId filter belongs to | Filtro userId | Resuelve membresías con findDepartmentIdsByUser y las pasa a findAll | DepartmentUserRepository.findDepartmentIdsByUser, DepartmentRepository.findAll |
passes the resolved membership ids down to the paginated query | Página 1, límite 10, filtro userId | Llama findAllPaginated(tenantId, 1, 10, filter, memberOf) y devuelve { results, total } | DepartmentUserRepository.findDepartmentIdsByUser, DepartmentRepository.findAllPaginated |
DepartmentService / findOne
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns department when found | findById devuelve el departamento | Lo devuelve | DepartmentRepository.findById |
throws NotFoundException when not found | findById devuelve null | NotFoundException | DepartmentRepository.findById |
DepartmentService / update
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
returns updated department | update devuelve el documento nuevo | Lo devuelve | DepartmentRepository.update |
throws NotFoundException when record not found for tenant | update devuelve null | NotFoundException | DepartmentRepository.update |
DepartmentService / remove
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
deletes department and cascades department-user records | El departamento existe | Borra el departamento y llama deleteByDepartment (cascada) con los mismos ids | DepartmentRepository.delete, DepartmentUserRepository.deleteByDepartment |
throws NotFoundException when department not found and does not cascade | delete devuelve null | NotFoundException y no se ejecuta la cascada | DepartmentRepository.delete, DepartmentUserRepository.deleteByDepartment |
department-user.service.spec.ts
DepartmentUserService / getUsersForDepartment
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
merges all tenant users with department assignments, defaulting flags to false | Dos usuarios del tenant y una asignación existente | Devuelve 2 entradas: la asignada con sus flags en true y la otra con ambos en false | UserRepository.findAll, DepartmentUserRepository.findByDepartment |
scopes the user lookup to the requesting tenant | Sin asignaciones | findAll se llama con el tenantId y el resultado solo contiene usuarios del tenant | UserRepository.findAll, DepartmentUserRepository.findByDepartment |
DepartmentUserService / updateUsersForDepartment
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
upserts each user entry and returns updated list | DTO con 2 usuarios | Se llama upsert 2 veces, con los flags de cada entrada, y se recompone la lista | DepartmentUserRepository.upsert, UserRepository.findAll, DepartmentUserRepository.findByDepartment |
DepartmentUserService / deleteUsersForDepartment
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
delegates to repository | Borrado de asignaciones de un departamento | Llama deleteByDepartment(deptId, tenantId) | DepartmentUserRepository.deleteByDepartment |