Conexiones (Meta)
Casos de prueba del feature src/app/features/connections. Hay 2 specs propios (25 tests) y 1 spec de servicio relacionado que se documenta en core-services.md. Ninguno usa xit, fdescribe ni fit, y no hay specs vacíos.
src/app/features/connections/components/connections-table/connections-table.component.spec.ts
- SUT:
ConnectionsTableComponent. - Mocking: sin TestBed; se instancia con
new ConnectionsTableComponent(...).ConnectionServicees unjasmine.createSpyObj(findConnectionsPaginateddevuelveof({ results: [], total: 0 }),removeConnection,logoutWhatsappSession).ChatSocketServicees un objeto conwhatsappSessionStatus$respaldado por unSubject.NgbModales un spy conopen,ActivatedRouteun stub conqueryParamMapvacío,RouteryToastrNotificationServiceson spies.afterEachllamangOnDestroy().
ConnectionsTableComponent — whatsapp wiring
| it() título | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
openChannelSelector(): routes "whatsapp-qr" to openWhatsappModal() | Dado que el modal selector se abre, cuando emite closed con 'whatsapp-qr', entonces se delega al flujo de WhatsApp. | NgbModal.open recibe ConnectionChannelModalComponent y se llama openWhatsappModal. | ngbModal.open devuelve { closed }; spyOn(component, 'openWhatsappModal') |
openWhatsappModal(): opens the modal with the given connection and reloads on close | Dada una conexión, cuando se abre el modal y este se cierra (closed emite undefined), entonces recarga la lista. | Se abre ConnectionWhatsappModalComponent, componentInstance.connection es la misma conexión y se llama loadConnections. | ngbModal.open devuelve { componentInstance, closed }; spyOn(component, 'loadConnections') |
whatsappSessionStatus$ patches the matching card in place without a refetch | Dado ngOnInit y dos conexiones, cuando llega un evento de estado para conn-1, entonces solo esa tarjeta se actualiza (la otra, conn-2, queda intacta). | conn-1 tiene sessionState connected y phoneJid; conn-2.whatsappConfig sigue undefined; findConnectionsPaginated se llamó una sola vez (sin refetch). | Subject whatsappSessionStatusSubject, connectionService spy |
whatsappSessionState() falls back to "init" when whatsappConfig is absent | Dada una conexión sin whatsappConfig, cuando se consulta el estado de sesión, entonces usa el valor por defecto. | Devuelve 'init'. | Ninguno (función pura sobre el input) |
onWhatsappLogout(): calls the API and reloads only after the confirm dialog is accepted | Dado el diálogo de confirmación, cuando se cierra con false no pasa nada; cuando se cierra con true se cierra la sesión y se recarga. | Con false: logoutWhatsappSession no se llama. Con true: se llama con 'conn-1' y luego loadConnections. | ngbModal.open devuelve { componentInstance, closed }; logoutWhatsappSession devuelve of(undefined); spyOn(component, 'loadConnections') |
Tests en este archivo: 5.
src/app/features/connections/components/connection-whatsapp-modal/connection-whatsapp-modal.component.spec.ts
- SUT:
ConnectionWhatsappModalComponent(emparejamiento por QR y formulario de configuración). - Mocking: TestBed con
declarations: [ConnectionWhatsappModalComponent, SwitchComponent], importsTranslateModule.forRoot(),FormsModule,ReactiveFormsModule,NgSelectModuleyNO_ERRORS_SCHEMA. Providers:NgbActiveModal(spy conclose,dismiss),ConnectionService(spy concreateConnection,startWhatsappSession,logoutWhatsappSession,getWhatsappSessionStatus,updateConnection,removeConnection),DepartmentService(getAlldevuelveof([])) yChatSocketService(whatsappQr$ywhatsappSessionStatus$desdeSubject). Por defectoremoveConnectiondevuelveof(makeConnection())ygetWhatsappSessionStatusdevuelve estadoinit. HelpermakeConnection(); se usafakeAsync/ticken los casos con temporizador.
ConnectionWhatsappModalComponent (nivel raíz)
| it() título | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
salta al formulario si la conexión ya estaba emparejada | Dada una conexión existente cuya sesión ya responde connected con phoneJid, cuando se inicializa el componente, entonces omite el QR. | step es 'form' y phoneNumber es '5511999999999'. | startWhatsappSession devuelve connected |
closeModal() dismisses the active modal | Dada una creación y arranque de sesión exitosos, cuando se llama closeModal(), entonces se descarta el modal. | activeModal.dismiss fue llamado. | createConnection, startWhatsappSession (init), activeModal |
new connection (no @Input connection)
| it() título | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
creates a whatsapp-qr connection then starts the session | Dado que no hay conexión de entrada, cuando se inicializa, entonces crea la conexión y arranca la sesión que devuelve un QR. | createConnection con { channelType: 'whatsapp-qr' }; startWhatsappSession('conn-1'); sessionState 'qr', qr con el data URL, isLoading false. | createConnection y startWhatsappSession devuelven of(...) |
sets an error key when creation fails, without ever calling startWhatsappSession | Dado que la creación falla (error boom), cuando se inicializa, entonces se informa el error y no se arranca sesión. | errorMessageKey es 'connections.whatsapp.createError' y startWhatsappSession no se llama. | createConnection devuelve throwError |
reconnect (existing @Input connection)
| it() título | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
starts the session directly for the existing connectionId, skipping create | Dada una conexión existente disconnected, cuando se inicializa, entonces reutiliza su id sin crear otra. | createConnection no se llama; startWhatsappSession('conn-1'); sessionState 'connecting'. | startWhatsappSession devuelve connecting |
live socket updates
beforeEach crea la conexión, arranca la sesión en init y ejecuta detectChanges().
| it() título | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
ignores whatsappQr$ events for a different connectionId | Cuando llega un QR de otro connectionId, entonces se ignora. | qr sigue null. | whatsappQrSubject |
updates qr and sessionState on a matching whatsappQr$ event | Cuando llega un QR para conn-1, entonces se refleja en el componente. | qr con el data URL y sessionState 'qr'. | whatsappQrSubject |
updates sessionState/phoneJid on a matching whatsapp_session_status event and clears a stale QR | Dado un QR previo, cuando llega un estado connecting para conn-1, entonces se actualiza el estado y se limpia el QR obsoleto. | sessionState 'connecting' y qr null. | whatsappQrSubject, whatsappSessionStatusSubject |
advances to the form shortly after reaching "connected" | Cuando llega el estado connected, entonces el paso cambia al formulario tras 1200 ms (fakeAsync). | Antes del tick step es 'qr'; tras tick(1200) es 'form' y activeModal.close no se llamó. | whatsappSessionStatusSubject, fakeAsync/tick |
ignores a status event for a different connectionId | Cuando llega un estado connected de otro connectionId, entonces se ignora. | sessionState no es 'connected'. | whatsappSessionStatusSubject |
paso 2: formulario
El helper reachForm() asigna una conexión (Ventas, saludo Hola, tolerancia 30), arranca sesión en qr, hace detectChanges() y llama goToForm().
| it() título | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
Saltar avanza al formulario sin esperar el escaneo | Dado el QR sin escanear, cuando se usa Saltar (goToForm), entonces avanza al formulario. | step es 'form' y form está definido. | startWhatsappSession |
precarga el formulario con los datos de la conexión | Cuando se llega al formulario, entonces se precargan los valores de la conexión. | name 'Ventas', greetingMessage 'Hola', reopenToleranceMinutes 30. | Ninguno adicional |
guarda los seis campos y cierra el modal | Dado el formulario con valores nuevos, cuando se llama onSave(), entonces actualiza la conexión con los seis campos y cierra. | updateConnection('conn-1', { name, greetingMessage, farewellMessage, departmentIds, reopenToleranceMinutes, returnToLastAgent }) y activeModal.close('saved'). | updateConnection devuelve of(...) |
no guarda sin nombre | Dado name vacío, cuando se llama onSave(), entonces no se envía nada (validación). | updateConnection no se llama. | updateConnection |
startOnForm entra directo al formulario sin arrancar la sesión | Dado startOnForm = true, cuando se inicializa, entonces entra al formulario. | step es 'form' y startWhatsappSession no se llama. | startWhatsappSession |
relee el estado real de la sesión al entrar al formulario | Dado startOnForm y un estado remoto connected con phoneJid, cuando se inicializa, entonces consulta el estado real. | getWhatsappSessionStatus('conn-1') y phoneNumber '5511999999999'. | getWhatsappSessionStatus devuelve connected |
mantiene lo que traía la tarjeta si el estado no se puede releer | Dado startOnForm y que la relectura falla (offline), cuando se inicializa, entonces no se rompe el flujo. | step sigue 'form'. | getWhatsappSessionStatus devuelve throwError |
orphaned connection cleanup on destroy
| it() título | Escenario | Resultado esperado | Mocks/spies |
|---|---|---|---|
removes the connection it created here when destroyed before reaching "connected" | Dado que el modal creó new-conn y no llegó a connected, cuando se destruye el fixture, entonces limpia la conexión huérfana. | removeConnection('new-conn'). | createConnection, startWhatsappSession (qr), removeConnection |
does not remove an existing connection provided via @Input on destroy | Dada una conexión recibida por @Input, cuando se destruye, entonces no se elimina. | removeConnection no se llama. | startWhatsappSession (disconnected) |
does not remove the created connection once it reached "connected" | Dada una conexión creada que llegó a connected (con tick(1200)), cuando se destruye, entonces se conserva. | removeConnection no se llama. | whatsappSessionStatusSubject, fakeAsync/tick |
Tests en este archivo: 20.
Referencia cruzada: src/app/core/services/api/connection.service.spec.ts
Spec del servicio ConnectionService (fuera del feature; detalle completo en core-services.md). Usa HttpClientTestingModule y HttpTestingController, con ToastrNotificationService como spy. Casos, todos sobre describe('ConnectionService — whatsapp session endpoints'):
startWhatsappSession should POST /connections/whatsapp/:id/start and unwrap datalogoutWhatsappSession should DELETE /connections/whatsapp/:id/logoutgetWhatsappSessionStatus should GET /connections/whatsapp/:id/status and unwrap data
Tests en este archivo: 3 (no se cuentan en el total de la página).
Qué NO está cubierto
Verificado con rg sobre src: ningún spec referencia los siguientes elementos de la rama meta_connections. Solo existen en sus archivos de implementación, el módulo o las plantillas.
| Elemento | Dónde existe | Estado de pruebas |
|---|---|---|
ConnectionInstagramModalComponent | connection-instagram-modal.component.ts, connections.module.ts, connections-table.component.ts | Sin spec |
connection-web-modal (ConnectionWebModalComponent) | connection-web-modal.component.ts, módulo y tabla | Sin spec |
connections-filter | connections-filter.component.ts, connections-table.component.html | Sin spec |
authorizeInstagram | connection.service.ts, connections-table.component.ts | Sin test en connection.service.spec.ts ni en el spec de la tabla |
handleMetaOAuthReturn | connections-table.component.ts | Sin test |
instagramExpiry | connections-table.component.ts y su .html | Sin test |
connection-channel-modal | connection-channel-modal.component.ts | Sin spec propio. Solo se menciona como clase importada en el spec de la tabla, que verifica únicamente que NgbModal.open la recibe y el ruteo de 'whatsapp-qr'. |
Tampoco hay specs para connections-page ni para otros métodos de ConnectionsTableComponent (por ejemplo, borrado o filtrado); el spec de la tabla se limita al cableado de WhatsApp. Los otros canales (Instagram, web) y los endpoints de ConnectionService distintos de los tres de sesión WhatsApp no tienen cobertura.
Total de la página
25 tests documentados en 2 specs del feature: connections-table.component.spec.ts 5 y connection-whatsapp-modal.component.spec.ts 20. A ellos se suman 3 de connection.service.spec.ts solo como referencia cruzada.