Casos de prueba — Widget
Casos de prueba unitarios del módulo src/app/modules/widget (widget web público embebible). Fuentes:
controllers/widget.controller.spec.tsservices/widget.service.spec.ts
Resumen: 2 archivos spec, 9 bloques describe, 30 tests (it): 6 en el controlador y 24 en el servicio. Todos usan Test.createTestingModule con repositorios y servicios mockeados.
widget.controller.spec.ts
Mocks comunes: WidgetService (validateToken, getMessages, resolveAccessOrThrow), FileService.upload y TranslateService.get (devuelve la clave).
validate
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
delegates to WidgetService.validateToken | validate(TOKEN, 'es') con el servicio devolviendo { status: 'active' } | Llama validateToken(TOKEN, 'es', undefined) y responde message: 'widget.tokenValid' con data.results | WidgetService.validateToken |
messages
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
delegates to WidgetService.getMessages | messages(TOKEN, EXTERNAL_ID, 'es') sin cabeceras | Llama getMessages(TOKEN, EXTERNAL_ID, 'es', undefined) y responde results: [] | WidgetService.getMessages |
forwards the Origin header (falling back to Referer) to the service | Se pasan Origin y Referer distintos | El servicio recibe el Origin (https://mi-sitio.com), no el Referer | WidgetService.getMessages |
uploadFile
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
valid token: resolves the tenant, uploads via FileService and returns the same contract as POST /files/upload | Token válido; resolveAccessOrThrow devuelve tenant | FileService.upload(file, tenant._id) y respuesta { message: FILE_RESPONSES.UPLOADED, data: doc } | resolveAccessOrThrow, FileService.upload |
invalid/expired token: rejects BEFORE ever touching FileService | resolveAccessOrThrow rechaza con NotFoundException | Rechaza con NotFoundException y FileService.upload no se invoca | resolveAccessOrThrow rechazado, FileService |
propagates a 400 with a clear message when the file exceeds the allowed limit | Token válido; upload rechaza con BadRequestException (archivo de 6 MB) | Propaga BadRequestException | resolveAccessOrThrow, FileService.upload rechazado |
widget.service.spec.ts
Mocks comunes: TenantRepository (findByTokenValue, findByMongoId), ContactRepository.findByExternalIdAndTenant, TicketRepository.findLatestByContact, MessageRepository.findAllByTicket, ConnectionRepository.findByToken, TranslateService. Por defecto la conexión es web, CONNECTED, con allowedDomains: ['https://mi-sitio.com'].
resolveAccessOrThrow — tenant tokens are no longer accepted
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should throw NotFoundException when the token matches no connection | findByToken devuelve null | NotFoundException y no se consulta findByTokenValue del tenant | connectionRepository, tenantRepository |
should throw NotFoundException when no token is provided | Token vacío ('') | NotFoundException sin consultar findByToken | connectionRepository |
resolveAccessOrThrow — connection token
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should resolve the connection first and return its tenant | Token de conexión y origen permitido | Consulta findByToken y findByMongoId(TENANT_ID); no usa findByTokenValue; devuelve { tenant, connection } | connectionRepository, tenantRepository |
should accept a Referer URL whose origin is whitelisted | Se envía una URL completa (con ruta y query) cuyo origen está en la lista blanca | Devuelve la conexión | connectionRepository, tenantRepository |
should throw ForbiddenException when the Origin is not whitelisted | Origen https://malicioso.com | ForbiddenException | connectionRepository |
should throw ForbiddenException when Origin/Referer is missing (e.g. curl) | Sin Origin ni Referer | ForbiddenException | connectionRepository |
should throw NotFoundException when the connection is not connected | Estado DISCONNECTED | NotFoundException sin consultar el tenant por token | connectionRepository |
should throw NotFoundException when the channel is not web | Conexión con canal WHATSAPP_QR | NotFoundException (un token de WhatsApp o Meta no sirve para el widget) | connectionRepository |
should throw NotFoundException when the connection tenant no longer exists | findByMongoId devuelve null | NotFoundException | tenantRepository |
getPublicConfig
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should reject a token that resolves to no connection | findByToken devuelve null | NotFoundException | connectionRepository |
should require auth when the connection predates the field | La conexión no define requiresAuth (conexiones previas al campo) | Responde { requiresAuth: true } | connectionRepository |
should report requiresAuth false when the switch is off | webConfig.requiresAuth: false | Responde { requiresAuth: false } | connectionRepository |
should reject an origin outside the connection whitelist | Origen https://otro-sitio.com | ForbiddenException | connectionRepository |
should reject an unknown token | Token nope sin conexión | NotFoundException | connectionRepository |
getMessages
| Test | Escenario | Resultado esperado | Mocks |
|---|---|---|---|
should return [] when externalId is not provided | externalId vacío | [] sin consultar contactos | contactRepository |
should return [] when the contact does not exist | findByExternalIdAndTenant devuelve null | [] | contactRepository |
should return [] when there is no open ticket for the contact | findLatestByContact devuelve null | [] | ticketRepository |
should return [] when the latest ticket is not open | Último ticket con sendType CLOSED | [] | ticketRepository |
should label an agent message without sendUserId as Sistema | Mensaje de agente con sendUserId: null | senderName igual a SYSTEM_SENDER_NAME y senderId null | messageRepository.findAllByTicket |
should map a plain chat message without a file field | Mensaje de contacto sin media | file es null | messageRepository |
should map `ack` as-is when the message document has it set | Mensaje con ack: 'read' | ack es read | messageRepository |
should default `ack` to `sent` when the message document has it `undefined` (legacy messages predating the `ack` schema field) | Mensaje antiguo sin ack | ack por defecto sent | messageRepository |
should include a `file` field with the already-signed URL for a file message, matching the new-file-message socket shape | Mensaje con media ya firmada | file con id, url firmada, filename, mimetype, type y size | messageRepository |
should query messages for the latest open ticket, page 1 limit 50 | Ticket abierto sin mensajes | findAllByTicket(ticketId, 1, 50, { excludeNotes: true, excludeInternal: true }) | messageRepository, ticketRepository |