fix most tests

This commit is contained in:
StarAppeal
2025-09-27 01:21:57 +02:00
parent 67ca195c15
commit a1b02c3088
9 changed files with 162 additions and 105 deletions
+2 -1
View File
@@ -1,5 +1,6 @@
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { DecodedToken } from "../interfaces/decodedToken"; import { DecodedToken } from "../interfaces/decodedToken";
import logger from "./logger";
export class JwtAuthenticator { export class JwtAuthenticator {
constructor(private secret: string) {} constructor(private secret: string) {}
@@ -12,7 +13,7 @@ export class JwtAuthenticator {
try { try {
return jwt.verify(token, this.secret) as DecodedToken; return jwt.verify(token, this.secret) as DecodedToken;
} catch (error) { } catch (error) {
console.error("Error while verifying token:", error); logger.error("Error while verifying token:", error);
} }
return null; return null;
@@ -12,9 +12,7 @@ export class GetStateEvent extends CustomWebsocketEvent {
this.ws.send( this.ws.send(
JSON.stringify({ JSON.stringify({
type: "STATE", type: "STATE",
payload: { payload: this.ws.user.lastState,
state: this.ws.user.lastState,
},
}), }),
{ binary: false } { binary: false }
); );
+11 -6
View File
@@ -1,11 +1,21 @@
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
import { FileModel } from "../../../src/db/models/file"; import { FileModel } from "../../../src/db/models/file";
import { FileService } from "../../../src/services/db/fileService"; import { FileService } from "../../../src/services/db/fileService";
import logger from "../../../src/utils/logger";
vi.mock("../../../src/db/models/file"); vi.mock("../../../src/db/models/file");
const mockedFileModel = vi.mocked(FileModel); const mockedFileModel = vi.mocked(FileModel);
vi.mock("../../../src/utils/logger", () => ({
default: {
warn: vi.fn(),
info: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
describe("FileService", () => { describe("FileService", () => {
let fileService: FileService; let fileService: FileService;
@@ -165,15 +175,10 @@ describe("FileService", () => {
it("should return false if an error occurs", async () => { it("should return false if an error occurs", async () => {
mockedFileModel.countDocuments.mockRejectedValue(new Error("Database error")); mockedFileModel.countDocuments.mockRejectedValue(new Error("Database error"));
// Mock console.error to prevent test output cluttering
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
const result = await fileService.isFileDuplicate("error-file.txt", "user123"); const result = await fileService.isFileDuplicate("error-file.txt", "user123");
expect(result).toBe(false); expect(result).toBe(false);
expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining("Error in isFileDuplicate")); expect(logger.error).toHaveBeenCalledWith(expect.stringContaining("Error checking file duplicate"));
consoleSpy.mockRestore();
}); });
}); });
+23 -18
View File
@@ -1,9 +1,8 @@
import {describe, it, expect, vi, beforeEach, afterEach, Mocked} from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach, Mocked } from "vitest";
import {appEventBus, USER_UPDATED_EVENT, WEATHER_STATE_UPDATED_EVENT} from "../../src/utils/eventBus"; import { appEventBus, USER_UPDATED_EVENT, WEATHER_STATE_UPDATED_EVENT } from "../../src/utils/eventBus";
import {WeatherPollingService} from "../../src/services/weatherPollingService"; import { WeatherPollingService } from "../../src/services/weatherPollingService";
import {IUser} from "../../src/db/models/user"; import { IUser } from "../../src/db/models/user";
import {getCurrentWeather} from "../../src/services/owmApiService"; import { getCurrentWeather } from "../../src/services/owmApiService";
vi.mock("../../src/services/owmApiService"); vi.mock("../../src/services/owmApiService");
@@ -12,8 +11,8 @@ vi.mock("../../src/utils/eventBus", () => ({
on: vi.fn(), on: vi.fn(),
emit: vi.fn(), emit: vi.fn(),
}, },
WEATHER_STATE_UPDATED_EVENT: 'weather:state-updated', WEATHER_STATE_UPDATED_EVENT: "weather:state-updated",
USER_UPDATED_EVENT: 'user:updated', USER_UPDATED_EVENT: "user:updated",
})); }));
vi.mock("../../src/services/owmApiService", () => ({ vi.mock("../../src/services/owmApiService", () => ({
@@ -67,18 +66,26 @@ describe("WeatherPollingService", () => {
}); });
it("should stop the poll when the last user unsubscribes from a location", async () => { it("should stop the poll when the last user unsubscribes from a location", async () => {
// @ts-ignore - access to private property for test purposes
const activePolls = (pollingService as any).activeLocationPolls;
pollingService.subscribeUser("user-1", "Berlin"); pollingService.subscribeUser("user-1", "Berlin");
pollingService.subscribeUser("user-2", "Berlin"); pollingService.subscribeUser("user-2", "Berlin");
await vi.advanceTimersByTimeAsync(0); await vi.advanceTimersByTimeAsync(0);
expect(vi.getTimerCount()).toBe(1); expect(activePolls.has("Berlin")).toBe(true);
pollingService.unsubscribeUser("user-1", "Berlin"); pollingService.unsubscribeUser("user-1", "Berlin");
expect(vi.getTimerCount()).toBe(1); expect(activePolls.has("Berlin")).toBe(true);
// @ts-ignore - access to private property for test purposes
const stopPollingSpy = vi.spyOn(pollingService as any, "_stopPollingForLocation");
pollingService.unsubscribeUser("user-2", "Berlin"); pollingService.unsubscribeUser("user-2", "Berlin");
expect(vi.getTimerCount()).toBe(0);
expect(stopPollingSpy).toHaveBeenCalledWith("Berlin");
expect(activePolls.has("Berlin")).toBe(false);
}); });
}); });
@@ -109,9 +116,7 @@ describe("WeatherPollingService", () => {
let userUpdateListener: (user: IUser) => void; let userUpdateListener: (user: IUser) => void;
beforeEach(() => { beforeEach(() => {
const onCall = mockedAppEventBus.on.mock.calls.find( const onCall = mockedAppEventBus.on.mock.calls.find((call) => call[0] === USER_UPDATED_EVENT);
call => call[0] === USER_UPDATED_EVENT
);
if (onCall) { if (onCall) {
userUpdateListener = onCall[1]; userUpdateListener = onCall[1];
} }
@@ -123,8 +128,8 @@ describe("WeatherPollingService", () => {
}); });
it("should automatically move a user's subscription when their location changes", () => { it("should automatically move a user's subscription when their location changes", () => {
const unsubscribeSpy = vi.spyOn(pollingService, 'unsubscribeUser'); const unsubscribeSpy = vi.spyOn(pollingService, "unsubscribeUser");
const subscribeSpy = vi.spyOn(pollingService, 'subscribeUser'); const subscribeSpy = vi.spyOn(pollingService, "subscribeUser");
pollingService.subscribeUser("user-moving", "Berlin"); pollingService.subscribeUser("user-moving", "Berlin");
@@ -139,7 +144,7 @@ describe("WeatherPollingService", () => {
}); });
it("should do nothing if the user's location has not changed", () => { it("should do nothing if the user's location has not changed", () => {
const unsubscribeSpy = vi.spyOn(pollingService, 'unsubscribeUser'); const unsubscribeSpy = vi.spyOn(pollingService, "unsubscribeUser");
pollingService.subscribeUser("user-staying", "Berlin"); pollingService.subscribeUser("user-staying", "Berlin");
@@ -149,4 +154,4 @@ describe("WeatherPollingService", () => {
expect(unsubscribeSpy).not.toHaveBeenCalled(); expect(unsubscribeSpy).not.toHaveBeenCalled();
}); });
}); });
}); });
+12 -4
View File
@@ -9,8 +9,18 @@ vi.mock("jsonwebtoken", () => {
}; };
}); });
vi.mock("../../src/utils/logger", () => ({
default: {
warn: vi.fn(),
info: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { JwtAuthenticator } from "../../src/utils/jwtAuthenticator"; import { JwtAuthenticator } from "../../src/utils/jwtAuthenticator";
import logger from "../../src/utils/logger";
describe("JwtAuthenticator", () => { describe("JwtAuthenticator", () => {
const secret = "test-secret"; const secret = "test-secret";
@@ -40,15 +50,13 @@ describe("JwtAuthenticator", () => {
}); });
it("verifyToken returns null when verify throws error", () => { it("verifyToken returns null when verify throws error", () => {
const spy = vi.spyOn(console, "error").mockImplementation(() => {});
(jwt.verify as any).mockImplementation(() => { (jwt.verify as any).mockImplementation(() => {
throw new Error("invalid"); throw new Error("invalid");
}); });
const res = auth.verifyToken("broken.token"); const res = auth.verifyToken("broken.token");
expect(res).toBeNull(); expect(res).toBeNull();
expect(spy).toHaveBeenCalled(); expect(logger.error).toHaveBeenCalled();
spy.mockRestore();
}); });
it("generateToken signs payload with secret", () => { it("generateToken signs payload with secret", () => {
@@ -59,4 +67,4 @@ describe("JwtAuthenticator", () => {
expect(jwt.sign).toHaveBeenCalledWith(payload, secret); expect(jwt.sign).toHaveBeenCalledWith(payload, secret);
expect(token).toBe("signed.jwt"); expect(token).toBe("signed.jwt");
}); });
}); });
+19 -15
View File
@@ -1,17 +1,26 @@
import {describe, it, expect, vi, beforeEach, afterEach, Mocked} from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach, Mocked } from "vitest";
import type { IncomingMessage } from "node:http"; import type { IncomingMessage } from "node:http";
import { verifyClient } from "../../src/utils/verifyClient"; import { verifyClient } from "../../src/utils/verifyClient";
import {JwtAuthenticator} from "../../src/utils/jwtAuthenticator"; import { JwtAuthenticator } from "../../src/utils/jwtAuthenticator";
// @ts-ignore // @ts-ignore
import {createMockJwtAuthenticator} from "../helpers/testSetup"; import { createMockJwtAuthenticator } from "../helpers/testSetup";
import logger from "../../src/utils/logger";
vi.mock("../../src/utils/logger", () => ({
default: {
warn: vi.fn(),
info: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
describe("verifyClient", () => { describe("verifyClient", () => {
const payload = { id: "user-1", username: "hi", uuid: "1234" } const payload = { id: "user-1", username: "hi", uuid: "1234" };
const cb = vi.fn(); const cb = vi.fn();
let mockJwtAuthenticator: Mocked<JwtAuthenticator> let mockJwtAuthenticator: Mocked<JwtAuthenticator>;
let consoleSpy: ReturnType<typeof vi.spyOn>;
function makeReq(authHeader?: string) { function makeReq(authHeader?: string) {
const headers: Record<string, string> = {}; const headers: Record<string, string> = {};
@@ -24,18 +33,13 @@ describe("verifyClient", () => {
beforeEach(() => { beforeEach(() => {
cb.mockReset(); cb.mockReset();
mockJwtAuthenticator = createMockJwtAuthenticator() as any; mockJwtAuthenticator = createMockJwtAuthenticator() as any;
consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
});
afterEach(() => {
consoleSpy.mockRestore();
}); });
it("accepts connections with valid token and sets payload", () => { it("accepts connections with valid token and sets payload", () => {
const req = makeReq("Bearer valid.jwt"); const req = makeReq("Bearer valid.jwt");
mockJwtAuthenticator.verifyToken.mockReturnValue(payload); mockJwtAuthenticator.verifyToken.mockReturnValue(payload);
verifyClient(req, mockJwtAuthenticator ,cb); verifyClient(req, mockJwtAuthenticator, cb);
expect(mockJwtAuthenticator.verifyToken).toHaveBeenCalledWith("valid.jwt"); expect(mockJwtAuthenticator.verifyToken).toHaveBeenCalledWith("valid.jwt");
expect(cb).toHaveBeenCalledWith(true); expect(cb).toHaveBeenCalledWith(true);
@@ -49,7 +53,7 @@ describe("verifyClient", () => {
verifyClient(req, mockJwtAuthenticator, cb); verifyClient(req, mockJwtAuthenticator, cb);
expect(cb).toHaveBeenCalledWith(false, 401, "Unauthorized"); expect(cb).toHaveBeenCalledWith(false, 401, "Unauthorized");
expect(consoleSpy).toHaveBeenCalled(); expect(logger.warn).toHaveBeenCalled();
}); });
it("rejects connection, if token is invalid", () => { it("rejects connection, if token is invalid", () => {
@@ -67,9 +71,9 @@ describe("verifyClient", () => {
const req = makeReq(`Bearer ${expectedToken}`); const req = makeReq(`Bearer ${expectedToken}`);
mockJwtAuthenticator.verifyToken.mockReturnValue(payload); mockJwtAuthenticator.verifyToken.mockReturnValue(payload);
verifyClient(req,mockJwtAuthenticator, cb); verifyClient(req, mockJwtAuthenticator, cb);
expect(mockJwtAuthenticator.verifyToken).toHaveBeenCalledWith(expectedToken); expect(mockJwtAuthenticator.verifyToken).toHaveBeenCalledWith(expectedToken);
expect(cb).toHaveBeenCalledWith(true); expect(cb).toHaveBeenCalledWith(true);
}); });
}); });
@@ -1,18 +1,18 @@
import {describe, it, expect, vi, beforeEach, type Mocked, afterEach} from "vitest"; import { describe, it, expect, vi, beforeEach, type Mocked, afterEach } from "vitest";
import {ExtendedWebSocket} from "../../../../src/interfaces/extendedWebsocket"; import { ExtendedWebSocket } from "../../../../src/interfaces/extendedWebsocket";
import {GetStateEvent} from "../../../../src/utils/websocket/websocketCustomEvents/getStateEvent"; import { GetStateEvent } from "../../../../src/utils/websocket/websocketCustomEvents/getStateEvent";
import {GetSettingsEvent} from "../../../../src/utils/websocket/websocketCustomEvents/getSettingsEvent"; import { GetSettingsEvent } from "../../../../src/utils/websocket/websocketCustomEvents/getSettingsEvent";
import {GetSpotifyUpdatesEvent} from "../../../../src/utils/websocket/websocketCustomEvents/getSpotifyUpdatesEvent"; import { GetSpotifyUpdatesEvent } from "../../../../src/utils/websocket/websocketCustomEvents/getSpotifyUpdatesEvent";
import {SpotifyPollingService} from "../../../../src/services/spotifyPollingService"; import { SpotifyPollingService } from "../../../../src/services/spotifyPollingService";
// @ts-ignore // @ts-ignore
import {createMockSpotifyPollingService,} from "../../../helpers/testSetup"; import { createMockSpotifyPollingService } from "../../../helpers/testSetup";
import {StopSpotifyUpdatesEvent} from "../../../../src/utils/websocket/websocketCustomEvents/stopSpotifyUpdatesEvent"; import { StopSpotifyUpdatesEvent } from "../../../../src/utils/websocket/websocketCustomEvents/stopSpotifyUpdatesEvent";
import {GetWeatherUpdatesEvent import { GetWeatherUpdatesEvent } from "../../../../src/utils/websocket/websocketCustomEvents/getWeatherUpdatesEvent";
} from "../../../../src/utils/websocket/websocketCustomEvents/getWeatherUpdatesEvent"; import { ErrorEvent } from "../../../../src/utils/websocket/websocketCustomEvents/errorEvent";
import {ErrorEvent} from "../../../../src/utils/websocket/websocketCustomEvents/errorEvent"; import { UpdateUserSingleEvent } from "../../../../src/utils/websocket/websocketCustomEvents/updateUserEvent";
import {UpdateUserSingleEvent} from "../../../../src/utils/websocket/websocketCustomEvents/updateUserEvent"; import { StopWeatherUpdatesEvent } from "../../../../src/utils/websocket/websocketCustomEvents/stopWeatherUpdatesEvent";
import {StopWeatherUpdatesEvent} from "../../../../src/utils/websocket/websocketCustomEvents/stopWeatherUpdatesEvent"; import { WeatherPollingService } from "../../../../src/services/weatherPollingService";
import {WeatherPollingService} from "../../../../src/services/weatherPollingService"; import logger from "../../../../src/utils/logger";
const createMockWebSocket = (userPayload: any = {}): ExtendedWebSocket => { const createMockWebSocket = (userPayload: any = {}): ExtendedWebSocket => {
return { return {
@@ -20,11 +20,11 @@ const createMockWebSocket = (userPayload: any = {}): ExtendedWebSocket => {
emit: vi.fn(), emit: vi.fn(),
user: { user: {
timezone: "Europe/Berlin", timezone: "Europe/Berlin",
lastState: {global: {mode: "idle", brightness: 42}}, lastState: { global: { mode: "idle", brightness: 42 } },
...userPayload, ...userPayload,
}, },
payload: {uuid: "test-uuid-123"}, payload: { uuid: "test-uuid-123" },
asyncUpdates: new Map asyncUpdates: new Map(),
} as unknown as ExtendedWebSocket; } as unknown as ExtendedWebSocket;
}; };
@@ -34,8 +34,16 @@ vi.mock("../../../../src/services/owmApiService", () => ({
vi.mock("../../../../src/services/weatherPollingService"); vi.mock("../../../../src/services/weatherPollingService");
describe("WebSocket Custom Event Handlers", () => { vi.mock("../../../../src/utils/logger", () => ({
default: {
warn: vi.fn(),
info: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
describe("WebSocket Custom Event Handlers", () => {
let mockSpotifyPollingService: Mocked<SpotifyPollingService>; let mockSpotifyPollingService: Mocked<SpotifyPollingService>;
let mockWeatherPollingService: Mocked<WeatherPollingService>; let mockWeatherPollingService: Mocked<WeatherPollingService>;
@@ -44,41 +52,39 @@ describe("WebSocket Custom Event Handlers", () => {
vi.useFakeTimers(); vi.useFakeTimers();
mockSpotifyPollingService = createMockSpotifyPollingService() as any; mockSpotifyPollingService = createMockSpotifyPollingService() as any;
mockWeatherPollingService = new WeatherPollingService() as Mocked<WeatherPollingService>; mockWeatherPollingService = new WeatherPollingService() as Mocked<WeatherPollingService>;
}) });
afterEach(() => { afterEach(() => {
vi.useRealTimers(); vi.useRealTimers();
}); });
describe("GetStateEvent", () => { describe("GetStateEvent", () => {
it("should send the user's lastState when its handler is called", async () => { it("should send the user's lastState when its handler is called", async () => {
const mockLastState = {global: {mode: "music", brightness: 100}}; const mockLastState = { global: { mode: "music", brightness: 100 } };
const mockWs = createMockWebSocket({lastState: mockLastState}); const mockWs = createMockWebSocket({ lastState: mockLastState });
const event = new GetStateEvent(mockWs); const event = new GetStateEvent(mockWs);
await event.handler(); await event.handler();
expect(mockWs.send).toHaveBeenCalledOnce(); expect(mockWs.send).toHaveBeenCalledOnce();
expect(mockWs.send).toHaveBeenCalledWith( expect(mockWs.send).toHaveBeenCalledWith(JSON.stringify({ type: "STATE", payload: mockLastState }), {
JSON.stringify({type: "STATE", payload: mockLastState}), binary: false,
{binary: false} });
);
}); });
}); });
describe("GetSettingsEvent", () => { describe("GetSettingsEvent", () => {
it("should send the user's settings when its handler is called", async () => { it("should send the user's settings when its handler is called", async () => {
const mockTimezone = "America/New_York"; const mockTimezone = "America/New_York";
const mockWs = createMockWebSocket({timezone: mockTimezone}); const mockWs = createMockWebSocket({ timezone: mockTimezone });
const event = new GetSettingsEvent(mockWs); const event = new GetSettingsEvent(mockWs);
await event.handler(); await event.handler();
expect(mockWs.send).toHaveBeenCalledOnce(); expect(mockWs.send).toHaveBeenCalledOnce();
expect(mockWs.send).toHaveBeenCalledWith( expect(mockWs.send).toHaveBeenCalledWith(
JSON.stringify({type: "SETTINGS", payload: {timezone: mockTimezone}}), JSON.stringify({ type: "SETTINGS", payload: { timezone: mockTimezone } }),
{binary: false} { binary: false }
); );
}); });
}); });
@@ -111,7 +117,7 @@ describe("WebSocket Custom Event Handlers", () => {
it("should subscribe the user to the WeatherPollingService using their location", async () => { it("should subscribe the user to the WeatherPollingService using their location", async () => {
const mockWs = createMockWebSocket({ const mockWs = createMockWebSocket({
uuid: "user-uuid-weather", uuid: "user-uuid-weather",
location: "London" location: "London",
}); });
const event = new GetWeatherUpdatesEvent(mockWs, mockWeatherPollingService); const event = new GetWeatherUpdatesEvent(mockWs, mockWeatherPollingService);
@@ -131,12 +137,11 @@ describe("WebSocket Custom Event Handlers", () => {
}); });
}); });
describe("StopWeatherUpdatesEvent", () => { describe("StopWeatherUpdatesEvent", () => {
it("should unsubscribe the user from the WeatherPollingService using their location", async () => { it("should unsubscribe the user from the WeatherPollingService using their location", async () => {
const mockWs = createMockWebSocket({ const mockWs = createMockWebSocket({
uuid: "user-uuid-weather", uuid: "user-uuid-weather",
location: "Paris" location: "Paris",
}); });
const event = new StopWeatherUpdatesEvent(mockWs, mockWeatherPollingService); const event = new StopWeatherUpdatesEvent(mockWs, mockWeatherPollingService);
@@ -156,7 +161,6 @@ describe("WebSocket Custom Event Handlers", () => {
}); });
}); });
describe("UpdateUserSingleEvent", () => { describe("UpdateUserSingleEvent", () => {
it("should update the user property on the websocket object", async () => { it("should update the user property on the websocket object", async () => {
const mockWs = createMockWebSocket(); const mockWs = createMockWebSocket();
@@ -172,14 +176,13 @@ describe("WebSocket Custom Event Handlers", () => {
describe("ErrorEvent", () => { describe("ErrorEvent", () => {
it("should log the received error message and traceback", async () => { it("should log the received error message and traceback", async () => {
const mockWs = createMockWebSocket(); const mockWs = createMockWebSocket();
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
const event = new ErrorEvent(mockWs); const event = new ErrorEvent(mockWs);
const errorData = { message: "Client-Side Error", traceback: "Component > render > error" }; const errorData = { message: "Client-Side Error", traceback: "Component > render > error" };
await event.handler(errorData); await event.handler(errorData);
expect(consoleWarnSpy).toHaveBeenCalledWith("Error message received", errorData.message); expect(logger.warn).toHaveBeenCalledWith("Error message received", errorData.message);
expect(consoleWarnSpy).toHaveBeenCalledWith("Traceback", errorData.traceback); expect(logger.warn).toHaveBeenCalledWith("Traceback", errorData.traceback);
}); });
}); });
}); });
@@ -2,22 +2,29 @@ import { describe, it, expect, vi, beforeEach, afterEach, type Mocked } from "vi
import { WebsocketEventHandler } from "../../../src/utils/websocket/websocketEventHandler"; import { WebsocketEventHandler } from "../../../src/utils/websocket/websocketEventHandler";
import { ExtendedWebSocket } from "../../../src/interfaces/extendedWebsocket"; import { ExtendedWebSocket } from "../../../src/interfaces/extendedWebsocket";
import { CustomWebsocketEvent } from "../../../src/utils/websocket/websocketCustomEvents/customWebsocketEvent"; import { CustomWebsocketEvent } from "../../../src/utils/websocket/websocketCustomEvents/customWebsocketEvent";
import {SpotifyPollingService} from "../../../src/services/spotifyPollingService"; import { SpotifyPollingService } from "../../../src/services/spotifyPollingService";
import {WeatherPollingService} from "../../../src/services/weatherPollingService"; import { WeatherPollingService } from "../../../src/services/weatherPollingService";
import logger from "../../../src/utils/logger";
vi.mock("../../../src/utils/logger", () => ({
default: {
warn: vi.fn(),
info: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
describe("WebsocketEventHandler", () => { describe("WebsocketEventHandler", () => {
let mockWebSocket: Mocked<ExtendedWebSocket>; let mockWebSocket: Mocked<ExtendedWebSocket>;
let websocketEventHandler: WebsocketEventHandler; let websocketEventHandler: WebsocketEventHandler;
let mockSpotifyPollingService: Mocked<SpotifyPollingService> let mockSpotifyPollingService: Mocked<SpotifyPollingService>;
let mockWeatherPollingService: Mocked<WeatherPollingService> let mockWeatherPollingService: Mocked<WeatherPollingService>;
let registeredHandlers: Map<string, (...args: any[]) => void>; let registeredHandlers: Map<string, (...args: any[]) => void>;
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
vi.spyOn(console, "error").mockImplementation(() => {});
vi.spyOn(console, "log").mockImplementation(() => {});
registeredHandlers = new Map(); registeredHandlers = new Map();
mockWebSocket = { mockWebSocket = {
@@ -34,7 +41,11 @@ describe("WebsocketEventHandler", () => {
mockSpotifyPollingService = {} as Mocked<SpotifyPollingService>; mockSpotifyPollingService = {} as Mocked<SpotifyPollingService>;
mockWeatherPollingService = {} as Mocked<WeatherPollingService>; mockWeatherPollingService = {} as Mocked<WeatherPollingService>;
websocketEventHandler = new WebsocketEventHandler(mockWebSocket, mockSpotifyPollingService, mockWeatherPollingService); websocketEventHandler = new WebsocketEventHandler(
mockWebSocket,
mockSpotifyPollingService,
mockWeatherPollingService
);
}); });
afterEach(() => { afterEach(() => {
@@ -43,7 +54,16 @@ describe("WebsocketEventHandler", () => {
it("should register an error event handler", () => { it("should register an error event handler", () => {
websocketEventHandler.enableErrorEvent(); websocketEventHandler.enableErrorEvent();
expect(mockWebSocket.on).toHaveBeenCalledWith("error", console.error);
expect(mockWebSocket.on).toHaveBeenCalledWith("error", expect.any(Function));
const errorHandler = registeredHandlers.get("error");
expect(errorHandler).toBeDefined();
const testError = new Error("Test error");
errorHandler!(testError);
expect(logger.error).toHaveBeenCalledWith("WebSocket error:", testError);
}); });
it("should register a pong event handler that sets isAlive to true", () => { it("should register a pong event handler that sets isAlive to true", () => {
@@ -55,7 +75,7 @@ describe("WebsocketEventHandler", () => {
pongHandler!(); pongHandler!();
expect(mockWebSocket.isAlive).toBe(true); expect(mockWebSocket.isAlive).toBe(true);
expect(console.log).toHaveBeenCalledWith("Pong received"); expect(logger.debug).toHaveBeenCalledWith("Pong received from client");
}); });
describe("enableDisconnectEvent", () => { describe("enableDisconnectEvent", () => {
@@ -65,13 +85,16 @@ describe("WebsocketEventHandler", () => {
websocketEventHandler.enableDisconnectEvent(mockCallback); websocketEventHandler.enableDisconnectEvent(mockCallback);
expect(mockWebSocket.onclose).toBeInstanceOf(Function); expect(mockWebSocket.onclose).toBeInstanceOf(Function);
mockWebSocket.onclose!({ code: 1000, reason: "Normal" } as any); mockWebSocket.onclose!({ code: 1000, reason: "Normal", wasClean: true, type: "close" } as any);
expect(console.log).toHaveBeenCalledWith("User: testuser disconnected"); expect(logger.info).toHaveBeenCalledWith(
"WebSocket closed: code=1000, reason=Normal, wasClean=true, type=close"
);
expect(logger.info).toHaveBeenCalledWith(`User: ${mockWebSocket.payload.username} disconnected`);
expect(mockCallback).toHaveBeenCalledOnce(); expect(mockCallback).toHaveBeenCalledOnce();
}); });
it("should handle disconnect with", () => { it("should handle disconnect without calling clearInterval", () => {
const mockCallback = vi.fn(); const mockCallback = vi.fn();
const clearIntervalSpy = vi.spyOn(global, "clearInterval"); const clearIntervalSpy = vi.spyOn(global, "clearInterval");
@@ -95,7 +118,9 @@ describe("WebsocketEventHandler", () => {
messageHandler!(rawData); messageHandler!(rawData);
expect(console.log).toHaveBeenCalledWith("Received message:", JSON.stringify(message)); expect(logger.debug).toHaveBeenCalledWith(`Received WebSocket message of type "test_event"`, {
messageData: message,
});
expect(mockWebSocket.emit).toHaveBeenCalledWith("test_event", message); expect(mockWebSocket.emit).toHaveBeenCalledWith("test_event", message);
}); });
}); });
@@ -118,4 +143,4 @@ describe("WebsocketEventHandler", () => {
expect(bindSpy).toHaveBeenCalledWith(customEvent); expect(bindSpy).toHaveBeenCalledWith(customEvent);
}); });
}); });
}); });
@@ -1,12 +1,23 @@
import { describe, it, expect, vi, beforeEach, afterEach, type Mocked } from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach, type Mocked } from "vitest";
import { WebsocketServerEventHandler } from "../../../src/utils/websocket/websocketServerEventHandler"; import { WebsocketServerEventHandler } from "../../../src/utils/websocket/websocketServerEventHandler";
import {UserService} from "../../../src/services/db/UserService"; import { UserService } from "../../../src/services/db/UserService";
import logger from "../../../src/utils/logger";
const heartbeatSpy = vi.fn(); const heartbeatSpy = vi.fn();
vi.mock("../../../src/utils/websocket/websocketServerHeartbeatInterval", () => ({ vi.mock("../../../src/utils/websocket/websocketServerHeartbeatInterval", () => ({
heartbeat: () => heartbeatSpy, heartbeat: () => heartbeatSpy,
})); }));
vi.mock("../../../src/utils/logger", () => ({
default: {
warn: vi.fn(),
info: vi.fn(),
error: vi.fn(),
debug: vi.fn(),
},
}));
const userObj = { const userObj = {
name: "tester", name: "tester",
uuid: "uuid-1", uuid: "uuid-1",
@@ -81,14 +92,11 @@ describe("WebsocketServerEventHandler", () => {
it("enableCloseEvent registers Listener and calls callback on close", () => { it("enableCloseEvent registers Listener and calls callback on close", () => {
const handler = new WebsocketServerEventHandler(wss as any, mockUserService); const handler = new WebsocketServerEventHandler(wss as any, mockUserService);
const cb = vi.fn(); const cb = vi.fn();
const logSpy = vi.spyOn(console, "log").mockImplementation(() => {});
handler.enableCloseEvent(cb); handler.enableCloseEvent(cb);
wss.emit("close"); wss.emit("close");
expect(cb).toHaveBeenCalledTimes(1); expect(cb).toHaveBeenCalledTimes(1);
expect(logSpy).toHaveBeenCalledWith("WebSocket server closed"); expect(logger.info).toHaveBeenCalledWith("WebSocket server closed");
logSpy.mockRestore();
}); });
}); });