Skip to main content

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.ts
  • services/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​

TestEscenarioResultado esperadoMocks
delegates to WidgetService.validateTokenvalidate(TOKEN, 'es') con el servicio devolviendo { status: 'active' }Llama validateToken(TOKEN, 'es', undefined) y responde message: 'widget.tokenValid' con data.resultsWidgetService.validateToken

messages​

TestEscenarioResultado esperadoMocks
delegates to WidgetService.getMessagesmessages(TOKEN, EXTERNAL_ID, 'es') sin cabecerasLlama getMessages(TOKEN, EXTERNAL_ID, 'es', undefined) y responde results: []WidgetService.getMessages
forwards the Origin header (falling back to Referer) to the serviceSe pasan Origin y Referer distintosEl servicio recibe el Origin (https://mi-sitio.com), no el RefererWidgetService.getMessages

uploadFile​

TestEscenarioResultado esperadoMocks
valid token: resolves the tenant, uploads via FileService and returns the same contract as POST /files/uploadToken válido; resolveAccessOrThrow devuelve tenantFileService.upload(file, tenant._id) y respuesta { message: FILE_RESPONSES.UPLOADED, data: doc }resolveAccessOrThrow, FileService.upload
invalid/expired token: rejects BEFORE ever touching FileServiceresolveAccessOrThrow rechaza con NotFoundExceptionRechaza con NotFoundException y FileService.upload no se invocaresolveAccessOrThrow rechazado, FileService
propagates a 400 with a clear message when the file exceeds the allowed limitToken válido; upload rechaza con BadRequestException (archivo de 6 MB)Propaga BadRequestExceptionresolveAccessOrThrow, 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​

TestEscenarioResultado esperadoMocks
should throw NotFoundException when the token matches no connectionfindByToken devuelve nullNotFoundException y no se consulta findByTokenValue del tenantconnectionRepository, tenantRepository
should throw NotFoundException when no token is providedToken vacío ('')NotFoundException sin consultar findByTokenconnectionRepository

resolveAccessOrThrow — connection token​

TestEscenarioResultado esperadoMocks
should resolve the connection first and return its tenantToken de conexión y origen permitidoConsulta findByToken y findByMongoId(TENANT_ID); no usa findByTokenValue; devuelve { tenant, connection }connectionRepository, tenantRepository
should accept a Referer URL whose origin is whitelistedSe envía una URL completa (con ruta y query) cuyo origen está en la lista blancaDevuelve la conexiónconnectionRepository, tenantRepository
should throw ForbiddenException when the Origin is not whitelistedOrigen https://malicioso.comForbiddenExceptionconnectionRepository
should throw ForbiddenException when Origin/Referer is missing (e.g. curl)Sin Origin ni RefererForbiddenExceptionconnectionRepository
should throw NotFoundException when the connection is not connectedEstado DISCONNECTEDNotFoundException sin consultar el tenant por tokenconnectionRepository
should throw NotFoundException when the channel is not webConexión con canal WHATSAPP_QRNotFoundException (un token de WhatsApp o Meta no sirve para el widget)connectionRepository
should throw NotFoundException when the connection tenant no longer existsfindByMongoId devuelve nullNotFoundExceptiontenantRepository

getPublicConfig​

TestEscenarioResultado esperadoMocks
should reject a token that resolves to no connectionfindByToken devuelve nullNotFoundExceptionconnectionRepository
should require auth when the connection predates the fieldLa conexión no define requiresAuth (conexiones previas al campo)Responde { requiresAuth: true }connectionRepository
should report requiresAuth false when the switch is offwebConfig.requiresAuth: falseResponde { requiresAuth: false }connectionRepository
should reject an origin outside the connection whitelistOrigen https://otro-sitio.comForbiddenExceptionconnectionRepository
should reject an unknown tokenToken nope sin conexiónNotFoundExceptionconnectionRepository

getMessages​

TestEscenarioResultado esperadoMocks
should return [] when externalId is not providedexternalId vacío[] sin consultar contactoscontactRepository
should return [] when the contact does not existfindByExternalIdAndTenant devuelve null[]contactRepository
should return [] when there is no open ticket for the contactfindLatestByContact 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 SistemaMensaje de agente con sendUserId: nullsenderName igual a SYSTEM_SENDER_NAME y senderId nullmessageRepository.findAllByTicket
should map a plain chat message without a file fieldMensaje de contacto sin mediafile es nullmessageRepository
should map `ack` as-is when the message document has it setMensaje con ack: 'read'ack es readmessageRepository
should default `ack` to `sent` when the message document has it `undefined` (legacy messages predating the `ack` schema field)Mensaje antiguo sin ackack por defecto sentmessageRepository
should include a `file` field with the already-signed URL for a file message, matching the new-file-message socket shapeMensaje con media ya firmadafile con id, url firmada, filename, mimetype, type y sizemessageRepository
should query messages for the latest open ticket, page 1 limit 50Ticket abierto sin mensajesfindAllByTicket(ticketId, 1, 50, { excludeNotes: true, excludeInternal: true })messageRepository, ticketRepository