d6e464afad
Co-authored-by: Camille Moussu <cmoussu@linagora.com>
212 lines
6.1 KiB
TypeScript
212 lines
6.1 KiB
TypeScript
import { fetchWebSocketTicket } from "@/websocket/api/fetchWebSocketTicket";
|
|
import { createWebSocketConnection } from "@/websocket/connection/createConnection";
|
|
import { WS_INBOUND_EVENTS } from "@/websocket/protocols";
|
|
import { waitFor } from "@testing-library/dom";
|
|
import { setupWebsocket } from "./utils/setupWebsocket";
|
|
|
|
jest.mock("@/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 mockCallbacks = {
|
|
onMessage: jest.fn(),
|
|
onClose: jest.fn(),
|
|
onError: jest.fn(),
|
|
};
|
|
const promise = createWebSocketConnection(mockCallbacks);
|
|
|
|
await waitFor(() => {
|
|
expect(webSocketInstances.length).toBe(1);
|
|
});
|
|
|
|
triggerEvent(getWs(), WS_INBOUND_EVENTS.CONNECTION_OPENED);
|
|
const socket = await promise;
|
|
|
|
return { socket, ws: getWs(), promise, mockCallbacks };
|
|
};
|
|
|
|
/** ---------- Setup ---------- */
|
|
|
|
beforeEach(() => {
|
|
({ webSocketInstances, mockWebSocket, cleanup } = setupWebsocket());
|
|
window.WEBSOCKET_URL = "wss://calendar.example.com";
|
|
|
|
(fetchWebSocketTicket as jest.Mock).mockResolvedValue(mockTicket);
|
|
});
|
|
|
|
afterEach(() => {
|
|
jest.clearAllMocks();
|
|
delete window.WEBSOCKET_URL;
|
|
delete window.CALENDAR_BASE_URL;
|
|
cleanup();
|
|
});
|
|
|
|
/** ---------- Tests ---------- */
|
|
|
|
it("throws when WEBSOCKET_URL is not defined", async () => {
|
|
delete window.WEBSOCKET_URL;
|
|
const mockCallbacks = {
|
|
onMessage: jest.fn(),
|
|
};
|
|
|
|
await expect(createWebSocketConnection(mockCallbacks)).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.WEBSOCKET_URL;
|
|
window.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 mockCallbacks = {
|
|
onMessage: jest.fn(),
|
|
};
|
|
const promise = createWebSocketConnection(mockCallbacks);
|
|
|
|
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("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();
|
|
});
|
|
|
|
it("rejects on timeout", async () => {
|
|
jest.useFakeTimers();
|
|
const mockCallbacks = {
|
|
onMessage: jest.fn(),
|
|
};
|
|
const promise = createWebSocketConnection(mockCallbacks);
|
|
|
|
await waitFor(() => {
|
|
expect(webSocketInstances.length).toBe(1);
|
|
});
|
|
|
|
jest.advanceTimersByTime(10000);
|
|
|
|
await expect(promise).rejects.toThrow("WebSocket connection timed out");
|
|
|
|
jest.useRealTimers();
|
|
});
|
|
|
|
it("calls onMessage callback when message received", async () => {
|
|
const { ws, mockCallbacks } = await createAndOpenConnection();
|
|
|
|
const testMessage = { type: "test", payload: "data" };
|
|
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, {
|
|
data: JSON.stringify(testMessage),
|
|
});
|
|
|
|
expect(mockCallbacks.onMessage).toHaveBeenCalledWith(testMessage);
|
|
});
|
|
|
|
it("does not call onMessage when JSON parsing fails", async () => {
|
|
const errorSpy = jest.spyOn(console, "error").mockImplementation();
|
|
const { ws, mockCallbacks } = await createAndOpenConnection();
|
|
|
|
triggerEvent(ws, WS_INBOUND_EVENTS.MESSAGE, { data: "invalid json" });
|
|
|
|
expect(mockCallbacks.onMessage).not.toHaveBeenCalled();
|
|
expect(errorSpy).toHaveBeenCalledWith(
|
|
"Failed to parse WebSocket message:",
|
|
expect.any(Error)
|
|
);
|
|
|
|
errorSpy.mockRestore();
|
|
});
|
|
|
|
it("calls onClose callback when connection closes", async () => {
|
|
const { ws, mockCallbacks } = await createAndOpenConnection();
|
|
|
|
const closeEvent = new CloseEvent("close", {
|
|
code: 1000,
|
|
reason: "Normal closure",
|
|
});
|
|
triggerEvent(ws, WS_INBOUND_EVENTS.CONNECTION_CLOSED, closeEvent);
|
|
|
|
expect(mockCallbacks.onClose).toHaveBeenCalledWith(closeEvent);
|
|
});
|
|
|
|
it("calls onError callback when error occurs", async () => {
|
|
const { ws, mockCallbacks } = await createAndOpenConnection();
|
|
|
|
const errorEvent = new Event("error");
|
|
triggerEvent(ws, WS_INBOUND_EVENTS.ERROR, errorEvent);
|
|
|
|
expect(mockCallbacks.onError).toHaveBeenCalledWith(errorEvent);
|
|
});
|
|
});
|