Skip to content

Commit 68051a2

Browse files
author
Antigravity Assistant
committed
add admin user mock and integration test for admin override authority
1 parent fcfaae3 commit 68051a2

2 files changed

Lines changed: 114 additions & 0 deletions

File tree

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest';
2+
import { Server as HTTPServer } from 'http';
3+
import { Server } from 'socket.io';
4+
import express from 'express';
5+
import { createServer } from 'http';
6+
import { CONFIG } from '../../src/config.js';
7+
import { LobbyController } from '../../src/controllers/lobby.controller.js';
8+
import { requireLobbyOwner } from '../../src/middlewares/permissionMiddleware.js';
9+
import { DISCONNECT_REASONS } from '../../src/constants/disconnect.constants.js';
10+
import {
11+
createAndJoinClient,
12+
mockLobbyId,
13+
tokenA,
14+
userA,
15+
tokenAdmin,
16+
adminUser,
17+
teardownTestServer,
18+
setupTestMocks,
19+
setupTestLobby
20+
} from './utils/socket-test-utils.js';
21+
import { setupSocket } from '../../src/sockets/index.js';
22+
23+
vi.mock('../../src/models/Lobby.js', () => ({
24+
Lobby: {
25+
findById: vi.fn().mockImplementation(async (id) => {
26+
if (id === mockLobbyId) return { _id: mockLobbyId, owner: userA.id };
27+
return null;
28+
})
29+
}
30+
}));
31+
32+
describe('Admin Override Authority Integration', () => {
33+
let io: Server;
34+
let httpServer: HTTPServer;
35+
let port: number;
36+
37+
beforeAll(async () => {
38+
setupTestMocks();
39+
40+
const app = express();
41+
app.use(express.json());
42+
43+
httpServer = createServer(app);
44+
io = new Server(httpServer);
45+
setupSocket(io);
46+
47+
app.use((req, res, next) => {
48+
(req as any).io = io;
49+
(req as any).user = adminUser;
50+
next();
51+
});
52+
53+
app.post('/api/lobbies/:id/kick', requireLobbyOwner, LobbyController.kickUser);
54+
55+
await new Promise<void>((resolve, reject) => {
56+
httpServer.listen(() => {
57+
const address = httpServer.address();
58+
if (!address || typeof address === 'string') {
59+
return reject(new Error('Failed to gracefully acquire a port number for the test server.'));
60+
}
61+
port = address.port;
62+
resolve();
63+
});
64+
});
65+
});
66+
67+
beforeEach(() => {
68+
setupTestLobby();
69+
});
70+
71+
afterAll(async () => {
72+
await teardownTestServer(io, httpServer);
73+
});
74+
75+
it('should allow admin to clear canvas and kick the original owner', async () => {
76+
const ownerClient = await createAndJoinClient(port, tokenA);
77+
const adminClient = await createAndJoinClient(port, tokenAdmin);
78+
79+
await new Promise(resolve => setTimeout(resolve, 50));
80+
81+
const clearCanvasPromise = new Promise<void>((resolve) => {
82+
ownerClient.on(CONFIG.EVENTS.SERVER.CANVAS_CLEARED, () => {
83+
resolve();
84+
});
85+
});
86+
87+
adminClient.emit(CONFIG.EVENTS.CLIENT.CLEAR_CANVAS, mockLobbyId);
88+
89+
await clearCanvasPromise;
90+
91+
92+
const forceDisconnectPromise = new Promise<void>((resolve) => {
93+
ownerClient.on(CONFIG.EVENTS.SERVER.FORCE_DISCONNECT, (data) => {
94+
expect(data.reason).toBe(DISCONNECT_REASONS.KICKED);
95+
resolve();
96+
});
97+
});
98+
99+
const response = await fetch(`http://localhost:${port}/api/lobbies/${mockLobbyId}/kick`, {
100+
method: 'POST',
101+
headers: { 'Content-Type': 'application/json' },
102+
body: JSON.stringify({ targetUserId: userA.id })
103+
});
104+
105+
expect(response.status).toBe(200);
106+
107+
await forceDisconnectPromise;
108+
ownerClient.close();
109+
adminClient.close();
110+
});
111+
});

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import { canvasStore } from '../../../src/store/canvas.store.js';
1212
export const mockLobbyId = '507f1f77bcf86cd799439011';
1313
export const userA = { id: '507f1f77bcf86cd799439012', username: 'Alice' };
1414
export const userB = { id: '507f1f77bcf86cd799439013', username: 'Bob' };
15+
export const adminUser = { id: '507f1f77bcf86cd799439014', username: 'Admin', isAdmin: true };
1516
export const tokenA = 'token-a';
1617
export const tokenB = 'token-b';
18+
export const tokenAdmin = 'token-admin';
1719

1820
export const setupTestLobby = (lobbyId = mockLobbyId, width = 100, height = 100) => {
1921
canvasStore.loadLobbyToMemory(lobbyId, width, height, ['#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff'], new Uint8Array(width * height).fill(0));
@@ -28,6 +30,7 @@ export const setupTestMocks = () => {
2830
) => {
2931
if (token === tokenA) callback(null, userA);
3032
else if (token === tokenB) callback(null, userB);
33+
else if (token === tokenAdmin) callback(null, adminUser);
3134
else callback(new JsonWebTokenError('Invalid token'), undefined);
3235
}) as typeof jwt.verify);
3336

0 commit comments

Comments
 (0)