Files
workavia-calendar-front/__test__/features/websocket/createWebSocketConnection.test.tsx
T
Camille Moussu 2fa8a7f16a [#451] added websocket connexion (#453)
* [#451] added websocket connexion

* [#451] added tests

* [#451] add missing cleanup for web socket causing memory leaks
2026-01-14 15:17:29 +01:00

153 lines
4.5 KiB
TypeScript

import { waitFor } from "@testing-library/dom";
import { fetchWebSocketTicket } from "../../../src/websocket/api/fetchWebSocketTicket";
import { createWebSocketConnection } from "../../../src/websocket/createWebSocketConnection";
import { WS_INBOUND_EVENTS } from "../../../src/websocket/protocols";
import { setupWebsocket } from "./utils/setupWebsocket";
jest.mock("../../../src/websocket/api/fetchWebSocketTicket");
describe("createWebSocketConnection", () => {
let mockWebSocket: jest.Mock;
let cleanup: () => void;
let webSocketInstances: any[] = [];
const mockTicket = {
value: "test-ticket-123",
clientAddress: "127.0.0.1",
generatedOn: "2025-01-12T10:00:00Z",
validUntil: "2025-01-12T11:00:00Z",
username: "testuser",
};
/** ---------- Helpers ---------- */
const getWs = () => webSocketInstances[0];
const triggerEvent = (ws: any, event: string, payload?: any) => {
ws._listeners[event]?.[0]?.(payload);
};
const createAndOpenConnection = async () => {
const promise = createWebSocketConnection();
await waitFor(() => {
expect(webSocketInstances.length).toBe(1);
});
triggerEvent(getWs(), WS_INBOUND_EVENTS.CONNECTION_OPENED);
const socket = await promise;
return { socket, ws: getWs(), promise };
};
/** ---------- Setup ---------- */
beforeEach(() => {
({ webSocketInstances, mockWebSocket, cleanup } = setupWebsocket());
(window as any).WEBSOCKET_URL = "wss://calendar.example.com";
(fetchWebSocketTicket as jest.Mock).mockResolvedValue(mockTicket);
});
afterEach(() => {
jest.clearAllMocks();
delete (window as any).WEBSOCKET_URL;
delete (window as any).CALENDAR_BASE_URL;
cleanup();
});
/** ---------- Tests ---------- */
it("throws when WEBSOCKET_URL is not defined", async () => {
delete (window as any).WEBSOCKET_URL;
await expect(createWebSocketConnection()).rejects.toThrow(
"WEBSOCKET_URL is not defined"
);
});
it("fetches WebSocket ticket", async () => {
await createAndOpenConnection();
expect(fetchWebSocketTicket).toHaveBeenCalledTimes(1);
});
it("creates WebSocket with correct URL and ticket", async () => {
await createAndOpenConnection();
expect(mockWebSocket).toHaveBeenCalledWith(
"wss://calendar.example.com/ws?ticket=test-ticket-123"
);
});
it("creates WebSocket with correct URL and ticket without the WEBSOCKET_URL", async () => {
delete (window as any).WEBSOCKET_URL;
(window as any).CALENDAR_BASE_URL = "https://calendar.example.com";
await createAndOpenConnection();
expect(mockWebSocket).toHaveBeenCalledWith(
"wss://calendar.example.com/ws?ticket=test-ticket-123"
);
});
it("resolves with socket when connection opens", async () => {
const { socket, ws } = await createAndOpenConnection();
expect(socket).toBe(ws);
});
it("rejects when connection fails", async () => {
const promise = createWebSocketConnection();
await waitFor(() => {
expect(webSocketInstances.length).toBe(1);
});
triggerEvent(getWs(), WS_INBOUND_EVENTS.ERROR, new Event("error"));
await expect(promise).rejects.toThrow("WebSocket connection failed");
});
it("attaches message event listener", async () => {
const { ws } = await createAndOpenConnection();
expect(ws._listeners[WS_INBOUND_EVENTS.MESSAGE]).toBeDefined();
expect(ws._listeners[WS_INBOUND_EVENTS.MESSAGE].length).toBeGreaterThan(0);
});
it("attaches close event listener", async () => {
const { ws } = await createAndOpenConnection();
expect(ws._listeners[WS_INBOUND_EVENTS.CONNECTION_CLOSED]).toBeDefined();
});
it("parses and logs incoming messages", async () => {
const logSpy = jest.spyOn(console, "log").mockImplementation();
const { ws } = await createAndOpenConnection();
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, {
data: JSON.stringify({ type: "test", payload: "data" }),
});
expect(logSpy).toHaveBeenCalledWith("WebSocket message received:", {
type: "test",
payload: "data",
});
logSpy.mockRestore();
});
it("handles invalid JSON messages", async () => {
const errorSpy = jest.spyOn(console, "error").mockImplementation();
const { ws } = await createAndOpenConnection();
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, { data: "invalid json" });
expect(errorSpy).toHaveBeenCalledWith(
"Failed to parse WebSocket message:",
expect.any(Error)
);
errorSpy.mockRestore();
});
});