Skip to content

Commit 196da87

Browse files
committed
feat: implement memory unloading & cleanup integration test
1 parent e6fce52 commit 196da87

2 files changed

Lines changed: 100 additions & 1 deletion

File tree

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { describe, it, expect, beforeAll, afterAll, vi, beforeEach } from 'vitest';
2+
import { Server as HTTPServer } from 'http';
3+
import { Server } from 'socket.io';
4+
import { CONFIG } from '../../src/config.js';
5+
import { createAndJoinClient, mockLobbyId, tokenA, teardownTestServer, bootstrapTestServer, setupTestLobby } from './utils/socket-test-utils.js';
6+
import { CanvasService } from '../../src/services/canvas.service.js';
7+
import { canvasStore } from '../../src/store/canvas.store.js';
8+
9+
describe('Memory Unloading & Cleanup Integration', () => {
10+
let io: Server;
11+
let httpServer: HTTPServer;
12+
let port: number;
13+
14+
beforeAll(async () => {
15+
// bootstrapTestServer already sets up some mocks (JWT, LobbyService, CanvasService)
16+
const testSetup = await bootstrapTestServer();
17+
io = testSetup.io;
18+
httpServer = testSetup.httpServer;
19+
port = testSetup.port;
20+
});
21+
22+
beforeEach(() => {
23+
setupTestLobby();
24+
vi.spyOn(CanvasService, 'saveToDB').mockResolvedValue(undefined as any);
25+
});
26+
27+
afterAll(async () => {
28+
await teardownTestServer(io, httpServer);
29+
});
30+
31+
it('should unload lobby from memory and save to DB when the last client disconnects', async () => {
32+
const clientA = await createAndJoinClient(port, tokenA);
33+
34+
// Verify lobby is in memory
35+
expect(canvasStore.isLobbyInMemory(mockLobbyId)).toBe(true);
36+
37+
// Modify canvas to ensure it's "dirty" (this triggers scheduleSave)
38+
const drawData = { lobbyId: mockLobbyId, x: 10, y: 10, color: 1 };
39+
clientA.emit(CONFIG.EVENTS.CLIENT.DRAW, drawData);
40+
41+
// Give a small amount of time for the draw event to be processed on server
42+
await new Promise(resolve => setTimeout(resolve, 50));
43+
44+
// Setup spies
45+
// We want to verify these are called during disconnection
46+
const unloadSpy = vi.spyOn(CanvasService, 'unloadLobby');
47+
const saveSpy = vi.spyOn(CanvasService, 'saveToDB').mockResolvedValue(undefined as any);
48+
49+
// Disconnect Client A
50+
// This should trigger the 'disconnecting' handler which calls unloadLobby if room is empty
51+
clientA.disconnect();
52+
53+
// The disconnection handler is async on the server side
54+
// We need to wait for it to finish its work.
55+
// 500ms should be plenty for the async handler to await getUsersInLobby and unloadLobby.
56+
await new Promise(resolve => setTimeout(resolve, 500));
57+
58+
// ASSERTIONS
59+
// 1. unloadLobby was triggered
60+
expect(unloadSpy).toHaveBeenCalledWith(mockLobbyId);
61+
62+
// 2. saveToDB was called as part of unloading
63+
expect(saveSpy).toHaveBeenCalledWith(mockLobbyId);
64+
65+
// 3. Lobby was removed from RAM
66+
expect(canvasStore.isLobbyInMemory(mockLobbyId)).toBe(false);
67+
68+
unloadSpy.mockRestore();
69+
saveSpy.mockRestore();
70+
});
71+
72+
it('should NOT unload lobby if other clients are still connected', async () => {
73+
const clientA = await createAndJoinClient(port, tokenA);
74+
const clientB = await createAndJoinClient(port, 'token-b'); // Use tokenB for second user
75+
76+
// Setup spies
77+
const unloadSpy = vi.spyOn(CanvasService, 'unloadLobby');
78+
79+
// Disconnect Client A, but B is still there
80+
clientA.disconnect();
81+
82+
await new Promise(resolve => setTimeout(resolve, 500));
83+
84+
// ASSERTIONS
85+
// Lobby should NOT be unloaded because Client B is still connected
86+
expect(unloadSpy).not.toHaveBeenCalled();
87+
expect(canvasStore.isLobbyInMemory(mockLobbyId)).toBe(true);
88+
89+
// Now disconnect Client B
90+
clientB.disconnect();
91+
await new Promise(resolve => setTimeout(resolve, 500));
92+
93+
// Now it should be unloaded
94+
expect(unloadSpy).toHaveBeenCalledWith(mockLobbyId);
95+
expect(canvasStore.isLobbyInMemory(mockLobbyId)).toBe(false);
96+
97+
unloadSpy.mockRestore();
98+
});
99+
});

server/test/integration/utils/socket-test-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { LobbyService } from '../../../src/services/lobby.service.js';
99
import { CanvasService } from '../../../src/services/canvas.service.js';
1010
import { canvasStore } from '../../../src/store/canvas.store.js';
1111

12-
export const mockLobbyId = 'test-lobby-id';
12+
export const mockLobbyId = '507f1f77bcf86cd799439011';
1313
export const userA = { id: 'user-a', username: 'Alice' };
1414
export const userB = { id: 'user-b', username: 'Bob' };
1515
export const tokenA = 'token-a';

0 commit comments

Comments
 (0)