Skip to main content

Casos de prueba: mensajería auxiliar

Cubre los módulos starred-message (mensajes destacados), quick-response (respuestas rápidas), label (etiquetas) y system-message (mensajes de sistema).

Specs fuente (src/app/modules/):

  • starred-message/controllers/starred-message.controller.spec.ts
  • starred-message/services/starred-message.service.spec.ts
  • starred-message/repositories/starred-message.repository.spec.ts
  • starred-message/dtos/query-starred-messages.dto.spec.ts
  • starred-message/dtos/toggle-starred-message.dto.spec.ts
  • starred-message/models/starred-message.model.spec.ts
  • quick-response/controllers/quick-response.controller.spec.ts
  • quick-response/services/quick-response.service.spec.ts
  • quick-response/repositories/quick-response.repository.spec.ts
  • quick-response/repositories/quick-response.repository.integration.spec.ts
  • label/controllers/label.controller.spec.ts
  • label/services/label.service.spec.ts
  • system-message/services/system-message.service.spec.ts

Resumen: 13 archivos de spec y 132 tests it(): starred-message 46, quick-response 46, label 32, system-message 8.


Módulo: Starred message

starred-message.controller.spec.ts​

Suite raíz: StarredMessageController. Servicio simulado.

toggle​

TestEscenarioResultado esperadoMocks
delegates to the service with user, messageId, dto and langUn agente destaca/quita un mensajeLlama a service.toggle con usuario, messageId, DTO e idioma y responde con starred: true en el sobre estándarmockService.toggle
propagates NotFoundException for a cross-tenant messageEl servicio rechaza por mensaje de otro tenantLa NotFoundException se propagamockService.toggle

findByContact​

TestEscenarioResultado esperadoMocks
delegates to the service with user, contactId and queryDestacados de un contactoDelegación con usuario, contactId y query; respuesta paginada en el sobremockService.findByContact

findByInternalConversation​

TestEscenarioResultado esperadoMocks
delegates to the service with user, conversationId and queryDestacados de una conversación internaDelegación con usuario, conversationId y querymockService.findByInternalConversation

findGlobal​

TestEscenarioResultado esperadoMocks
delegates to the service with user and queryDestacados globales del agenteDelegación con usuario y querymockService.findGlobal

findIds​

TestEscenarioResultado esperadoMocks
delegates to the service and wraps the id list under data.idsSe piden solo los ids destacadosLlama a findIdsByScope y envuelve la lista en data.idsmockService.findIdsByScope

starred-message.service.spec.ts​

Suite raíz: StarredMessageService. Mocks: repositorio de favoritos, repositorio de tickets y modelos de mensaje y mensaje interno.

toggle — contact messages​

TestEscenarioResultado esperadoMocks
resolves contactId from the message ticket and delegates to the repositoryMensaje de contacto del tenantResuelve el contactId desde el ticket del mensaje, llama a repository.toggle y devuelve { starred: true }modelo de mensaje, findByIdAndTenant, toggle
throws NotFoundException (not 500) when the message belongs to another tenantEl mensaje no se encuentra en el tenantLanza NotFoundException y no llama a togglemodelo de mensaje (findOne devuelve null)
scopes the message lookup by tenantId so a cross-tenant messageId never resolvesId de mensaje de otro tenantLa consulta incluye el tenantId correcto (no el ajeno)modelo de mensaje

toggle — internal messages​

TestEscenarioResultado esperadoMocks
resolves conversationId from the internal message and delegates to the repositoryMensaje interno del tenantResuelve conversationId, delega y devuelve { starred: false }modelo de mensaje interno, toggle
throws NotFoundException when the internal message does not exist in the tenantEl mensaje interno no existeLanza NotFoundException y no llama a togglemodelo de mensaje interno

findByContact​

TestEscenarioResultado esperadoMocks
computes pagination meta from the repository totalEl repositorio devuelve resultados y totalCalcula meta (página, límite, total, totalPages) a partir del totalrepository.findByContact

findIdsByScope​

TestEscenarioResultado esperadoMocks
delegates to the repository scoped by agent and tenantConsulta de idsDelegación con agente y tenant; devuelve los idsrepository.findIdsByScope

starred-message.repository.spec.ts​

Suite raíz: StarredMessageRepository. Modelo Mongoose simulado y StorageService falso.

toggle​

TestEscenarioResultado esperadoMocks
creates a StarredMessage and returns true (starred) when none exists yetNo existe el favoritoCrea el registro y devuelve truefindOne, create
removes the existing StarredMessage and returns false (unstarred) when one already existsYa existe el favoritoElimina con deleteOne, no crea y devuelve falsefindOne, deleteOne
treats a concurrent duplicate-key error (E11000) as already-starred success, not a crashcreate falla con clave duplicada por concurrenciaDevuelve true sin lanzarcreate rechaza con E11000
rethrows non-duplicate-key errors from createcreate falla con otro errorRelanza el errorcreate rechaza

findByContact​

TestEscenarioResultado esperadoMocks
scopes the aggregation match stage by agentId, tenantId and contactIdConsulta por contactoEl primer $match filtra por agente, tenant y contactoaggregate
returns hydrated results and total from the facet, defaulting to 0 when the facet is emptyFacet con filas y totalDevuelve { results, total } con total 1aggregate
defaults to empty results and 0 total when the facet returns no documentSin documento de facetDevuelve { results: [], total: 0 }aggregate

findByInternalConversation​

TestEscenarioResultado esperadoMocks
scopes the aggregation match stage by agentId, tenantId and conversationIdConsulta por conversación internaEl primer $match filtra por agente, tenant y conversaciónaggregate

findGlobal​

TestEscenarioResultado esperadoMocks
matches only by agentId and tenantId, with no contact/conversation scopeConsulta globalEl primer $match usa solo agente y tenantaggregate
drops orphaned rows whose message no longer exists via a $match on the joined messageFavoritos cuyo mensaje fue eliminadoEl pipeline incluye $match con message distinto de nullaggregate

list hydration (WU5 — sender + file, fixes verify-report W1)​

TestEscenarioResultado esperadoMocks
adds sender + file $lookup stages scoped to Contacts/Users/Files inside the facet results branchHidratación de la listaAgrega $lookup de remitente y archivo dentro de la rama de resultadosaggregate
projects sender and file alongside the existing row fieldsEtapa de proyección$project incluye sender y file junto a los campos existentesaggregate
resolves a fresh signed URL from file.storageKey and strips storageKey from the responseFila con archivoFirma una URL desde storageKey y elimina storageKey de la respuestaaggregate, getSignedUrl
does nothing when a row has no file (text-only starred message)Fila sin archivoNo se llama a getSignedUrlaggregate, getSignedUrl

removeAllByMessage​

TestEscenarioResultado esperadoMocks
deletes every StarredMessage row matching the messageId and messageType, across all agentsSe elimina un mensajedeleteMany con messageId y messageType, sin filtrar por agentedeleteMany
returns the deleteMany resultResultado del borradoDevuelve el resultado de deleteManydeleteMany

findIdsByScope​

TestEscenarioResultado esperadoMocks
returns the messageId strings for the agent, scoped by tenantIds del agente en el tenantDevuelve los messageId como texto; find filtra por agente y tenantfind
adds the contactId filter to the query when providedSe indica contactIdEl filtro de find incluye contactIdfind

query-starred-messages.dto.spec.ts​

Suite raíz: QueryStarredMessagesDto. Validación con class-validator y class-transformer, sin mocks.

TestEscenarioResultado esperadoMocks
passes validation with no fields set (all optional)DTO vacíoSin erroresNinguno
coerces page/limit query strings to integerspage y limit como textoSe convierten a 2 y 25Ninguno
rejects a limit above PAGINATION.MAX_LIMIT (100)limit mayor a 100Un error en la propiedad limitNinguno
rejects a messageType outside the contact/internal enummessageType inválidoUn error en messageTypeNinguno
rejects a contactId that is not a valid Mongo idcontactId no es id MongoUn error en contactIdNinguno
accepts a valid Mongo-id contactId and conversationIdIds Mongo válidosSin erroresNinguno

toggle-starred-message.dto.spec.ts​

Suite raíz: ToggleStarredMessageDto.

TestEscenarioResultado esperadoMocks
passes validation for a valid contact messageTypemessageType contactoSin erroresNinguno
passes validation for a valid internal messageTypemessageType internoSin erroresNinguno
rejects a messageType outside the contact/internal enumValor fuera del enumUn error en messageTypeNinguno
rejects a missing messageTypeFalta messageTypeUn error en messageTypeNinguno

starred-message.model.spec.ts​

Suite raíz: StarredMessageSchema. Inspecciona el esquema Mongoose sin base de datos.

TestEscenarioResultado esperadoMocks
defines a unique compound index on agentId, messageId and messageTypeÍndices del esquemaExiste un índice compuesto único por agente, mensaje y tipoNinguno
defines the three non-unique scoped indexes from the design (contact scope, conversation scope, global scope)Índices por alcanceExisten los índices de alcance contacto, conversación y globalNinguno
requires tenantId, agentId, messageType, messageRefModel and messageIdCampos obligatoriosEsos cinco campos son requiredNinguno
does not require contactId or conversationId (scope-dependent optional fields)Campos opcionalescontactId y conversationId no son obligatoriosNinguno
restricts messageType to the contact/internal enum valuesEnum de tipoValores permitidos: contact e internalNinguno

Módulo: Quick response

quick-response.controller.spec.ts​

Suite raíz: QuickResponseController. Servicio simulado; el tenant sale del usuario autenticado.

create​

TestEscenarioResultado esperadoMocks
should pass user.tenantId and return created recordCrear respuesta rápidaLlama a service.create con el tenant del usuario y devuelve el registro en el sobremockService.create
should propagate ConflictException when shortcut already existsEl atajo ya existeLa ConflictException se propagamockService.create

findAll​

TestEscenarioResultado esperadoMocks
should return quick responses scoped to user tenantListadoLlama a findAll(tenantId, {}) y devuelve los registrosmockService.findAll
should return empty array when no records existSin registrosDevuelve lista vacíamockService.findAll

findOne​

TestEscenarioResultado esperadoMocks
should return record scoped to user tenantRegistro existenteDelegación con tenant del usuario y devuelve el registromockService.findOne
should propagate NotFoundException when not found in this tenantNo existe en el tenantLa excepción se propagamockService.findOne

update​

TestEscenarioResultado esperadoMocks
should update record scoped to user tenantActualización válidaDelegación con tenant y devuelve el registro actualizadomockService.update
should propagate ConflictException when shortcut already usedAtajo en usoLa ConflictException se propagamockService.update
should propagate NotFoundException when record not found in this tenantNo existe en el tenantLa excepción se propagamockService.update

remove​

TestEscenarioResultado esperadoMocks
should remove record scoped to user tenantEliminación válidaDelegación con tenant y devuelve el registro eliminadomockService.remove
should propagate NotFoundException when record not found in this tenantNo existe en el tenantLa excepción se propagamockService.remove

quick-response.service.spec.ts​

Suite raíz: QuickResponseService. Mocks: repositorio de respuestas rápidas, repositorio de departamentos, repositorio de tickets y traducción.

create​

TestEscenarioResultado esperadoMocks
should throw ConflictException when shortcut already exists for this tenantAtajo existenteLanza ConflictException y no creafindByShortcutAndTenant
should create the quick response with its departments as ObjectIds scoped to the tenantAtajo libre y departamentos válidosValida departamentos del tenant y crea con los ids como ObjectIdfindByShortcutAndTenant, departmentRepository.findAll, create
should collapse repeated department ids before validating themIds de departamento repetidosLos deduplica antes de validar y creardepartmentRepository.findAll, create
should throw BadRequestException when a department does not belong to the tenantDepartamento ajeno al tenantLanza BadRequestException con mensaje traducido y no creadepartmentRepository.findAll, traducción
should throw BadRequestException when no department is givenSin departamentosLanza BadRequestException y no creadepartmentRepository.findAll

findAll​

TestEscenarioResultado esperadoMocks
should return quick responses scoped to the tenantSin ticketIdLlama a findAll(tenantId, undefined) sin consultar ticketsrepository.findAll
should return empty array when no records exist for the tenantSin registrosDevuelve []repository.findAll
should return only active responses of the ticket department when a ticketId is givenCon ticketId con departamentoFiltra respuestas activas del departamento del ticketticketRepository.findByIdAndTenant, findAll
should return nothing when the ticket has no departmentTicket sin departamentoDevuelve [] y no consulta el repositorioticketRepository.findByIdAndTenant
should return nothing when the ticket does not belong to the tenantTicket de otro tenantDevuelve [] y no consulta el repositorioticketRepository.findByIdAndTenant (null)

findAllPaginated​

TestEscenarioResultado esperadoMocks
should paginate across the whole tenant when no ticketId is givenSin ticketIdPagina en todo el tenant y devuelve { results, total }repository.findAllPaginated
should paginate only active responses of the ticket department when a ticketId is givenCon ticketIdPagina solo las activas del departamento del ticketfindByIdAndTenant, findAllPaginated
should return an empty page when the ticket has no departmentTicket sin departamentoDevuelve { results: [], total: 0 } sin consultar el repositoriofindByIdAndTenant

findOne​

TestEscenarioResultado esperadoMocks
should return quick response when found within the tenantRegistro existenteBusca por identificador y tenant y lo devuelvefindByIdentifierAndTenant
should throw NotFoundException when not found in this tenantNo existeLanza NotFoundExceptionfindByIdentifierAndTenant (null)

update​

TestEscenarioResultado esperadoMocks
should return updated record when foundActualización sin departamentosActualiza y devuelve; no valida departamentosfindByShortcutAndTenant, update
should validate and persist the departments when they are part of the updateLa actualización incluye departamentosValida con departmentRepository.findAll y los persistedepartmentRepository.findAll, update
should throw BadRequestException when an updated department does not belong to the tenantDepartamento ajenoLanza BadRequestException y no actualizadepartmentRepository.findAll ([])
should throw ConflictException when updating shortcut to one already used by another recordAtajo usado por otro registroLanza ConflictException y no actualizafindByShortcutAndTenant
should allow updating shortcut to its own current valueEl atajo pertenece al mismo registroPermite actualizar y devuelve el registrofindByShortcutAndTenant, update
should throw NotFoundException when record does not belong to this tenantupdate devuelve nullLanza NotFoundExceptionfindByShortcutAndTenant, update

remove​

TestEscenarioResultado esperadoMocks
should return removed record when found within tenantRegistro del tenantLlama a remove(id, tenantId) y lo devuelverepository.remove
should throw NotFoundException when record does not belong to this tenantremove devuelve nullLanza NotFoundExceptionrepository.remove

quick-response.repository.spec.ts​

Suite raíz: QuickResponseRepository — buildFilter. Pruebas puras del armado de filtros.

TestEscenarioResultado esperadoMocks
returns only the tenant scope when no filter is providedSin filtroLa consulta es { tenantId }Ninguno
builds an $or of case-insensitive regex on shortcut and message when search is presentCon searchArma un $or de regex insensible a mayúsculas sobre atajo y mensajeNinguno
keeps the existing shortcut-only behavior unchanged when search is absentFiltro por atajo sin searchSe mantiene el filtro solo por atajoNinguno
still applies the isActive filter alongside a search filterisActive junto con searchAmbos filtros se aplicanNinguno
restricts the query to quick responses assigned to the given departmentFiltro por departamentoLa consulta restringe a las asignadas a ese departamentoNinguno
ignores the ticketId filter field, which the service resolves into a departmentFiltro con ticketIdSe ignora (el servicio lo resuelve a un departamento); queda solo el tenantNinguno

quick-response.repository.integration.spec.ts​

Suite raíz: QuickResponseRepository — integration (mongodb-memory-server). MongoDB real en memoria.

TestEscenarioResultado esperadoMocks
returns a quick response whose message body matches, even when the shortcut does notsearch coincide solo con el cuerpo del mensajeDevuelve 1 resultado (/saludo)MongoDB en memoria
returns no results when the search value matches neither shortcut nor messagesearch sin coincidenciasDevuelve 0 resultadosMongoDB en memoria

findAll scoped to a department​

TestEscenarioResultado esperadoMocks
returns only the responses assigned to that department within the tenantFiltro por departamentoSolo las asignadas a ese departamento en el tenantMongoDB en memoria
excludes inactive responses, responses without departments and other tenantsDatos mezcladosExcluye inactivas, sin departamentos y de otros tenants; queda 1 (/saludo) del tenant AMongoDB en memoria
persists department ids as ObjectIds so the department filter matches themSe guarda un registro con departamentosdepartmentIds[0] es ObjectIdMongoDB en memoria
paginates the department scope with a matching totalPaginación con 2 coincidencias y límite 1Devuelve 1 resultado y total 2MongoDB en memoria

Módulo: Label

label.controller.spec.ts​

Suite raíz: LabelController. Servicio simulado.

create​

TestEscenarioResultado esperadoMocks
should pass user.tenantId and return created labelCrear etiquetaDelegación con tenant del usuario; responde { message: 'label.created', data }mockService.create
should propagate ConflictException when label name already exists for tenantNombre duplicadoLa ConflictException se propagamockService.create

findAll​

TestEscenarioResultado esperadoMocks
should return labels scoped to user tenant without paginationSin page/limitLlama a findAll(tenantId, {}) y devuelve la listamockService.findAll
should return empty array when no labels exist for tenantSin etiquetasLista vacíamockService.findAll
should forward filter to service when provided without paginationFiltro sin paginaciónEl filtro se reenvía a findAllmockService.findAll
should call findAllPaginated and return meta when page and limit are providedCon page y limitUsa findAllPaginated y devuelve metamockService.findAllPaginated
should call findAllPaginated with filter when all params providedFiltro, page y limitfindAllPaginated recibe el filtro; responde con resultados vacíos y metamockService.findAllPaginated

findOne​

TestEscenarioResultado esperadoMocks
should return label scoped to user tenantEtiqueta existenteDelegación con tenant y devuelve la etiquetamockService.findOne
should propagate NotFoundException when label not found in this tenantNo existeLa excepción se propagamockService.findOne

update​

TestEscenarioResultado esperadoMocks
should update label scoped to user tenantActualización válidaResponde label.updated con la etiqueta actualizadamockService.update
should propagate NotFoundException when label not found in this tenantNo existeLa excepción se propagamockService.update

remove​

TestEscenarioResultado esperadoMocks
should remove label scoped to user tenantEliminación válidaResponde label.deleted con la etiquetamockService.remove
should propagate NotFoundException when label not found in this tenantNo existeLa excepción se propagamockService.remove

label.service.spec.ts​

Suite raíz: LabelService. Repositorio de etiquetas y traducción simulados.

create​

TestEscenarioResultado esperadoMocks
should throw ConflictException when label with same name exists for tenantNombre existenteLanza ConflictException y no creafindByNameAndTenant
should create and return label when validNombre libreCrea con el tenant y devuelve la etiquetafindByNameAndTenant, create

findAll​

TestEscenarioResultado esperadoMocks
should return labels scoped to the tenantSin filtroLlama a findAll(tenantId, undefined)repository.findAll
should return empty array when no labels exist for the tenantSin etiquetasDevuelve []repository.findAll
should forward filter to repositoryCon filtroEl filtro llega al repositoriorepository.findAll

findAllPaginated​

TestEscenarioResultado esperadoMocks
should return paginated results scoped to the tenantPaginaciónDelegación con tenant y devuelve el resultado paginadorepository.findAllPaginated
should return empty results when no labels match for the tenantSin coincidenciasDevuelve { results: [], total: 0 }repository.findAllPaginated
should forward filter to repositoryCon filtroEl filtro llega al repositoriorepository.findAllPaginated

findOne​

TestEscenarioResultado esperadoMocks
should return label when found within the tenantEtiqueta del tenantBusca por id y tenant y la devuelvefindByIdAndTenant
should throw NotFoundException when label does not exist in this tenantNo existeLanza NotFoundExceptionfindByIdAndTenant (null)

update​

TestEscenarioResultado esperadoMocks
should return updated label when found within tenantNuevo nombre libreActualiza y devuelve la etiquetafindByNameAndTenant, update
should throw ConflictException when new name is taken by another label in the tenantNombre usado por otra etiquetaLanza ConflictException y no actualizafindByNameAndTenant
should allow update when the same label keeps its own nameEl nombre es de la misma etiquetaPermite actualizarfindByNameAndTenant, update
should throw NotFoundException when label does not belong to this tenantupdate devuelve nullLanza NotFoundExceptionfindByNameAndTenant, update

remove​

TestEscenarioResultado esperadoMocks
should return removed label when found within tenantEtiqueta del tenantLlama a remove con id y tenant y la devuelverepository.remove
should throw NotFoundException when label does not belong to this tenantremove devuelve nullLanza NotFoundExceptionrepository.remove

validateLabelsBelongToTenant​

TestEscenarioResultado esperadoMocks
should resolve without error when all labels belong to tenantTodas las etiquetas son del tenantResuelve sin errorfindManyByIdsAndTenant
should throw NotFoundException when some labels do not belong to tenantSe piden más ids de los que existenLanza NotFoundExceptionfindManyByIdsAndTenant
should skip validation when labelIds array is emptyArreglo de ids vacíoResuelve sin consultar el repositoriofindManyByIdsAndTenant

Módulo: System message

system-message.service.spec.ts​

Suite raíz: SystemMessageService. Mocks: modelo de mensaje, repositorio de contactos, MessageTemplateService, broadcast de sockets y OutboundRouterService.

sendToContact​

TestEscenarioResultado esperadoMocks
builds, persists, emits and delivers a plain system message through the channelMensaje de sistema simple a un contactoBusca el contacto, interpola la plantilla, persiste, emite por socket y despacha por el canal; devuelve el mensaje guardadorepositorio de contactos, plantilla, Message.create, broadcast, router
persists a connection message as chat and emits it authored by "Sistema"Mensaje de conexiónSe persiste como chat, se emite con autor "Sistema" (no usa broadcastSystemMessage) y se despacha al canalMessage.create, broadcast, router
persists a department greeting as chat and emits it authored by the departmentSaludo de departamentoSe persiste como chat, se emite con el departamento como autor y se despachaMessage.create, broadcast, router
omits connectionId when the ticket has no connectionTicket sin conexiónEl mensaje se crea sin connectionIdMessage.create
does nothing when the contact does not belong to the tenantContacto no encontrado en el tenantDevuelve null; no persiste, no emite ni despachafindByIdAndTenant (null)
does nothing when the template interpolates to an empty bodyLa plantilla queda vacíaDevuelve null; no persiste ni despachainterpolate devuelve vacío
keeps the message persisted and emitted when the channel delivery failsEl router rechaza con canal caídoLa promesa resuelve con el mensaje; se persistió y se emitió igualmenteroute rechaza

recordInternal​

TestEscenarioResultado esperadoMocks
persists an internal message, emits it to agents only and never delivers it to the channelRegistro de mensaje internoPersiste, emite solo a agentes, no busca contacto, no interpola y no despacha al canal; devuelve el mensajeMessage.create, broadcast, repositorio de contactos, router