@roomkit/gateway 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. package/dist/src/ws/message-router.service.d.ts.map +1 -1
  2. package/dist/src/ws/message-router.service.js +6 -6
  3. package/dist/src/ws/message-router.service.js.map +1 -1
  4. package/package.json +1 -1
  5. package/dist/test/connection/connection.service.spec.d.ts +0 -2
  6. package/dist/test/connection/connection.service.spec.d.ts.map +0 -1
  7. package/dist/test/connection/connection.service.spec.js +0 -204
  8. package/dist/test/connection/connection.service.spec.js.map +0 -1
  9. package/dist/test/e2e/gateway-worker.e2e.spec.d.ts +0 -2
  10. package/dist/test/e2e/gateway-worker.e2e.spec.d.ts.map +0 -1
  11. package/dist/test/e2e/gateway-worker.e2e.spec.js +0 -412
  12. package/dist/test/e2e/gateway-worker.e2e.spec.js.map +0 -1
  13. package/dist/test/integration/admin-api.spec.d.ts +0 -2
  14. package/dist/test/integration/admin-api.spec.d.ts.map +0 -1
  15. package/dist/test/integration/admin-api.spec.js +0 -218
  16. package/dist/test/integration/admin-api.spec.js.map +0 -1
  17. package/dist/test/ratelimit/rate-limiter.service.spec.d.ts +0 -2
  18. package/dist/test/ratelimit/rate-limiter.service.spec.d.ts.map +0 -1
  19. package/dist/test/ratelimit/rate-limiter.service.spec.js +0 -139
  20. package/dist/test/ratelimit/rate-limiter.service.spec.js.map +0 -1
  21. package/dist/test/setup.d.ts +0 -2
  22. package/dist/test/setup.d.ts.map +0 -1
  23. package/dist/test/setup.js +0 -56
  24. package/dist/test/setup.js.map +0 -1
  25. package/dist/test/ws/message-router.service.spec.d.ts +0 -2
  26. package/dist/test/ws/message-router.service.spec.d.ts.map +0 -1
  27. package/dist/test/ws/message-router.service.spec.js +0 -403
  28. package/dist/test/ws/message-router.service.spec.js.map +0 -1
@@ -1,412 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- /// <reference types="jest" />
40
- /**
41
- * Gateway-Worker 端到端集成测试
42
- * 测试完整的消息流:Client -> Gateway -> Redis -> Worker -> Redis -> Gateway -> Client
43
- */
44
- const dotenv = __importStar(require("dotenv"));
45
- const path = __importStar(require("path"));
46
- // 加载根目录的 .env 文件
47
- dotenv.config({ path: path.resolve(__dirname, '../../../../.env') });
48
- const testing_1 = require("@nestjs/testing");
49
- const ws_1 = __importDefault(require("ws"));
50
- const ioredis_1 = __importDefault(require("ioredis"));
51
- const gateway_module_1 = require("../../src/gateway.module");
52
- const core_1 = require("@roomkit/core");
53
- // 模拟Worker服务
54
- class MockWorkerService {
55
- redisClient;
56
- redisSub;
57
- workerId;
58
- constructor(workerId) {
59
- this.workerId = workerId;
60
- this.redisClient = new ioredis_1.default({
61
- host: process.env.REDIS_HOST || 'localhost',
62
- port: parseInt(process.env.REDIS_PORT || '16379'),
63
- password: process.env.REDIS_PASSWORD,
64
- });
65
- this.redisSub = this.redisClient.duplicate();
66
- }
67
- async start() {
68
- // 注册Worker
69
- await this.redisClient.hset(`gf:worker:${this.workerId}`, 'status', 'active', 'connectedAt', Date.now().toString());
70
- // 订阅Worker的RPC通道
71
- await this.redisSub.subscribe(`gf:rpc:worker:${this.workerId}`);
72
- this.redisSub.on('message', this.handleMessage.bind(this));
73
- }
74
- async handleMessage(channel, message) {
75
- try {
76
- const msg = JSON.parse(message);
77
- switch (msg.type) {
78
- case 'auth':
79
- await this.handleAuth(msg);
80
- break;
81
- case 'create-room':
82
- await this.handleCreateRoom(msg);
83
- break;
84
- case 'join-room':
85
- await this.handleJoinRoom(msg);
86
- break;
87
- case 'client-message':
88
- await this.handleClientMessage(msg);
89
- break;
90
- }
91
- }
92
- catch (error) {
93
- console.error('MockWorker error:', error);
94
- }
95
- }
96
- async handleAuth(msg) {
97
- const { gatewayId, connectionId, payload } = msg;
98
- // 模拟token验证
99
- const isValid = payload.token && payload.token.startsWith('token_');
100
- const userId = isValid ? payload.token.replace('token_', 'user_') : null;
101
- const response = {
102
- type: 'auth-response',
103
- workerId: this.workerId,
104
- requestId: msg.requestId,
105
- connectionId,
106
- success: isValid,
107
- userId,
108
- error: isValid ? undefined : 'INVALID_TOKEN',
109
- timestamp: Date.now(),
110
- };
111
- await this.redisClient.publish(`gf:rpc:gateway:${gatewayId}`, JSON.stringify(response));
112
- }
113
- async handleCreateRoom(msg) {
114
- const { gatewayId, connectionId, payload } = msg;
115
- const roomId = payload.roomId || `room_${Date.now()}`;
116
- // 注册房间到Worker的映射
117
- await this.redisClient.set(`gf:room:${roomId}:worker`, this.workerId);
118
- const response = {
119
- type: 'client-response',
120
- workerId: this.workerId,
121
- connectionId,
122
- msgId: core_1.MessageId.CREATE_ROOM_RES,
123
- payload: {
124
- success: true,
125
- roomId,
126
- workerId: this.workerId,
127
- },
128
- timestamp: Date.now(),
129
- };
130
- await this.redisClient.publish(`gf:rpc:gateway:${gatewayId}`, JSON.stringify(response));
131
- }
132
- async handleJoinRoom(msg) {
133
- const { gatewayId, connectionId, userId, payload } = msg;
134
- const { roomId } = payload;
135
- // 验证房间是否存在
136
- const workerId = await this.redisClient.get(`gf:room:${roomId}:worker`);
137
- if (!workerId) {
138
- const errorResponse = {
139
- type: 'client-response',
140
- workerId: this.workerId,
141
- connectionId,
142
- msgId: core_1.MessageId.ERROR,
143
- payload: {
144
- error: 'ROOM_NOT_FOUND',
145
- message: 'Room does not exist',
146
- },
147
- timestamp: Date.now(),
148
- };
149
- await this.redisClient.publish(`gf:rpc:gateway:${gatewayId}`, JSON.stringify(errorResponse));
150
- return;
151
- }
152
- // 添加成员到房间
153
- await this.redisClient.hset(`gf:room:${roomId}:members`, userId, JSON.stringify({ gatewayId, connectionId, joinedAt: Date.now() }));
154
- // 发送加入成功响应
155
- const response = {
156
- type: 'client-response',
157
- workerId: this.workerId,
158
- connectionId,
159
- msgId: core_1.MessageId.JOIN_ROOM_RES,
160
- payload: {
161
- success: true,
162
- roomId,
163
- players: await this.getRoomPlayers(roomId),
164
- },
165
- timestamp: Date.now(),
166
- };
167
- await this.redisClient.publish(`gf:rpc:gateway:${gatewayId}`, JSON.stringify(response));
168
- // 广播玩家加入消息
169
- const broadcastMsg = {
170
- type: 'room-broadcast',
171
- workerId: this.workerId,
172
- roomId,
173
- msgId: core_1.MessageId.PLAYER_JOINED,
174
- payload: {
175
- userId,
176
- timestamp: Date.now(),
177
- },
178
- excludeUserId: userId,
179
- timestamp: Date.now(),
180
- };
181
- await this.redisClient.publish('gf:broadcast', JSON.stringify(broadcastMsg));
182
- }
183
- async handleClientMessage(msg) {
184
- const { gatewayId, connectionId, msgId, payload } = msg;
185
- // 简单回显消息
186
- const response = {
187
- type: 'client-response',
188
- workerId: this.workerId,
189
- connectionId,
190
- msgId: msgId + 1, // 响应ID = 请求ID + 1
191
- payload: {
192
- echo: payload,
193
- serverTime: Date.now(),
194
- },
195
- timestamp: Date.now(),
196
- };
197
- await this.redisClient.publish(`gf:rpc:gateway:${gatewayId}`, JSON.stringify(response));
198
- }
199
- async getRoomPlayers(roomId) {
200
- const members = await this.redisClient.hgetall(`gf:room:${roomId}:members`);
201
- return Object.entries(members).map(([userId, data]) => ({
202
- userId,
203
- ...JSON.parse(data),
204
- }));
205
- }
206
- async stop() {
207
- await this.redisSub.unsubscribe();
208
- await this.redisSub.quit();
209
- await this.redisClient.del(`gf:worker:${this.workerId}`);
210
- await this.redisClient.quit();
211
- }
212
- }
213
- // 测试客户端
214
- class TestClient {
215
- ws = null;
216
- messages = [];
217
- messageResolvers = new Map();
218
- async connect(url) {
219
- return new Promise((resolve, reject) => {
220
- this.ws = new ws_1.default(url);
221
- this.ws.on('open', () => resolve());
222
- this.ws.on('error', reject);
223
- this.ws.on('message', (data) => {
224
- const msg = JSON.parse(data.toString());
225
- this.messages.push(msg);
226
- const resolver = this.messageResolvers.get(msg.msgId);
227
- if (resolver) {
228
- resolver(msg);
229
- this.messageResolvers.delete(msg.msgId);
230
- }
231
- });
232
- });
233
- }
234
- send(msgId, payload) {
235
- if (!this.ws || this.ws.readyState !== ws_1.default.OPEN) {
236
- throw new Error('WebSocket not connected');
237
- }
238
- this.ws.send(JSON.stringify({ msgId, payload }));
239
- }
240
- async sendAndWait(msgId, payload, expectedResMsgId, timeout = 5000) {
241
- return new Promise((resolve, reject) => {
242
- const timer = setTimeout(() => {
243
- this.messageResolvers.delete(expectedResMsgId);
244
- reject(new Error(`Timeout waiting for message ${expectedResMsgId}`));
245
- }, timeout);
246
- this.messageResolvers.set(expectedResMsgId, (msg) => {
247
- clearTimeout(timer);
248
- resolve(msg);
249
- });
250
- this.send(msgId, payload);
251
- });
252
- }
253
- getMessages() {
254
- return this.messages;
255
- }
256
- clearMessages() {
257
- this.messages = [];
258
- }
259
- disconnect() {
260
- if (this.ws) {
261
- this.ws.close();
262
- this.ws = null;
263
- }
264
- }
265
- }
266
- describe('Gateway-Worker E2E Tests', () => {
267
- let app;
268
- let mockWorker;
269
- let redis;
270
- const workerId = 'worker-e2e-test';
271
- const gatewayUrl = 'ws://localhost:3001/ws';
272
- beforeAll(async () => {
273
- // 创建Gateway应用
274
- const moduleFixture = await testing_1.Test.createTestingModule({
275
- imports: [gateway_module_1.GatewayModule],
276
- }).compile();
277
- app = moduleFixture.createNestApplication();
278
- await app.listen(3001);
279
- // 创建Redis客户端
280
- redis = new ioredis_1.default({
281
- host: process.env.REDIS_HOST || 'localhost',
282
- port: parseInt(process.env.REDIS_PORT || '6379'),
283
- });
284
- // 启动模拟Worker
285
- mockWorker = new MockWorkerService(workerId);
286
- await mockWorker.start();
287
- // 等待服务初始化
288
- await new Promise((resolve) => setTimeout(resolve, 1000));
289
- });
290
- afterAll(async () => {
291
- await mockWorker.stop();
292
- await redis.quit();
293
- await app.close();
294
- });
295
- beforeEach(async () => {
296
- // 清理测试数据
297
- const keys = await redis.keys('gf:*');
298
- if (keys.length > 0) {
299
- await redis.del(...keys);
300
- }
301
- // 重新注册Worker
302
- await redis.hset(`gf:worker:${workerId}`, 'status', 'active', 'connectedAt', Date.now().toString());
303
- });
304
- describe('鉴权流程', () => {
305
- it('应该成功完成鉴权', async () => {
306
- const client = new TestClient();
307
- await client.connect(gatewayUrl);
308
- const authRes = await client.sendAndWait(core_1.MessageId.AUTH_REQ, { token: 'token_test123' }, core_1.MessageId.AUTH_RES);
309
- expect(authRes.payload.success).toBe(true);
310
- expect(authRes.payload.userId).toBe('user_test123');
311
- client.disconnect();
312
- });
313
- it('应该拒绝无效token', async () => {
314
- const client = new TestClient();
315
- await client.connect(gatewayUrl);
316
- const authRes = await client.sendAndWait(core_1.MessageId.AUTH_REQ, { token: 'invalid' }, core_1.MessageId.AUTH_RES);
317
- expect(authRes.payload.success).toBe(false);
318
- expect(authRes.payload.error).toBe('INVALID_TOKEN');
319
- client.disconnect();
320
- });
321
- });
322
- describe('房间管理', () => {
323
- it('应该成功创建房间', async () => {
324
- const client = new TestClient();
325
- await client.connect(gatewayUrl);
326
- // 先鉴权
327
- await client.sendAndWait(core_1.MessageId.AUTH_REQ, { token: 'token_test123' }, core_1.MessageId.AUTH_RES);
328
- // 创建房间
329
- const createRes = await client.sendAndWait(core_1.MessageId.CREATE_ROOM_REQ, { gameType: 'test', maxPlayers: 4 }, core_1.MessageId.CREATE_ROOM_RES);
330
- expect(createRes.payload.success).toBe(true);
331
- expect(createRes.payload.roomId).toBeDefined();
332
- expect(createRes.payload.workerId).toBe(workerId);
333
- client.disconnect();
334
- });
335
- it('应该成功加入房间', async () => {
336
- const client1 = new TestClient();
337
- const client2 = new TestClient();
338
- await client1.connect(gatewayUrl);
339
- await client2.connect(gatewayUrl);
340
- // Client1 鉴权并创建房间
341
- await client1.sendAndWait(core_1.MessageId.AUTH_REQ, { token: 'token_user1' }, core_1.MessageId.AUTH_RES);
342
- const createRes = await client1.sendAndWait(core_1.MessageId.CREATE_ROOM_REQ, { roomId: 'test-room-123', gameType: 'test', maxPlayers: 4 }, core_1.MessageId.CREATE_ROOM_RES);
343
- const roomId = createRes.payload.roomId;
344
- // Client2 鉴权并加入房间
345
- await client2.sendAndWait(core_1.MessageId.AUTH_REQ, { token: 'token_user2' }, core_1.MessageId.AUTH_RES);
346
- client1.clearMessages();
347
- client2.clearMessages();
348
- const joinRes = await client2.sendAndWait(core_1.MessageId.JOIN_ROOM_REQ, { roomId }, core_1.MessageId.JOIN_ROOM_RES);
349
- expect(joinRes.payload.success).toBe(true);
350
- expect(joinRes.payload.roomId).toBe(roomId);
351
- // 等待广播消息
352
- await new Promise((resolve) => setTimeout(resolve, 500));
353
- // Client1 应该收到玩家加入的广播
354
- const client1Messages = client1.getMessages();
355
- const joinBroadcast = client1Messages.find((msg) => msg.msgId === core_1.MessageId.PLAYER_JOINED);
356
- expect(joinBroadcast).toBeDefined();
357
- expect(joinBroadcast.payload.userId).toBe('user_user2');
358
- client1.disconnect();
359
- client2.disconnect();
360
- });
361
- });
362
- describe('消息路由', () => {
363
- it('应该正确转发并响应客户端消息', async () => {
364
- const client = new TestClient();
365
- await client.connect(gatewayUrl);
366
- // 鉴权
367
- await client.sendAndWait(core_1.MessageId.AUTH_REQ, { token: 'token_test123' }, core_1.MessageId.AUTH_RES);
368
- // 创建房间
369
- await client.sendAndWait(core_1.MessageId.CREATE_ROOM_REQ, { gameType: 'test', maxPlayers: 4 }, core_1.MessageId.CREATE_ROOM_RES);
370
- // 发送自定义消息
371
- const customMsgId = 3001;
372
- const customPayload = { action: 'test', data: 'hello' };
373
- const response = await client.sendAndWait(customMsgId, customPayload, customMsgId + 1);
374
- expect(response.payload.echo).toEqual(customPayload);
375
- expect(response.payload.serverTime).toBeDefined();
376
- client.disconnect();
377
- });
378
- });
379
- describe('心跳机制', () => {
380
- it('应该正确响应心跳', async () => {
381
- const client = new TestClient();
382
- await client.connect(gatewayUrl);
383
- const timestamp = Date.now();
384
- const heartbeatRes = await client.sendAndWait(core_1.MessageId.HEARTBEAT, { timestamp }, core_1.MessageId.HEARTBEAT_ACK);
385
- expect(heartbeatRes.payload.timestamp).toBe(timestamp);
386
- expect(heartbeatRes.payload.serverTime).toBeDefined();
387
- client.disconnect();
388
- });
389
- });
390
- describe('并发场景', () => {
391
- it('应该处理多个客户端同时连接', async () => {
392
- const clients = [];
393
- const clientCount = 10;
394
- // 创建并连接多个客户端
395
- for (let i = 0; i < clientCount; i++) {
396
- const client = new TestClient();
397
- await client.connect(gatewayUrl);
398
- clients.push(client);
399
- }
400
- // 所有客户端同时鉴权
401
- const authPromises = clients.map((client, i) => client.sendAndWait(core_1.MessageId.AUTH_REQ, { token: `token_user${i}` }, core_1.MessageId.AUTH_RES));
402
- const authResults = await Promise.all(authPromises);
403
- authResults.forEach((res, i) => {
404
- expect(res.payload.success).toBe(true);
405
- expect(res.payload.userId).toBe(`user_user${i}`);
406
- });
407
- // 清理
408
- clients.forEach((client) => client.disconnect());
409
- });
410
- });
411
- });
412
- //# sourceMappingURL=gateway-worker.e2e.spec.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"gateway-worker.e2e.spec.js","sourceRoot":"","sources":["../../../test/e2e/gateway-worker.e2e.spec.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=admin-api.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"admin-api.spec.d.ts","sourceRoot":"","sources":["../../../test/integration/admin-api.spec.ts"],"names":[],"mappings":""}
@@ -1,218 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- /// <reference types="jest" />
7
- const testing_1 = require("@nestjs/testing");
8
- const supertest_1 = __importDefault(require("supertest"));
9
- const admin_controller_1 = require("../../src/admin/admin.controller");
10
- const admin_service_1 = require("../../src/admin/admin.service");
11
- const config_service_1 = require("../../src/config/config.service");
12
- const connection_service_1 = require("../../src/connection/connection.service");
13
- const redis_service_1 = require("../../src/redis/redis.service");
14
- const rate_limiter_service_1 = require("../../src/ratelimit/rate-limiter.service");
15
- /**
16
- * Gateway Admin API 集成测试
17
- * 只测试 Admin API 模块,不启动 WebSocket
18
- */
19
- describe('Gateway Admin API (Integration)', () => {
20
- let app;
21
- const mockConfigService = {
22
- gatewayId: 'gateway-test-integration',
23
- port: 18080,
24
- adminPort: 18081,
25
- redisHost: process.env.REDIS_HOST || 'localhost',
26
- redisPort: parseInt(process.env.REDIS_PORT || '6379', 10),
27
- redisPassword: process.env.REDIS_PASSWORD,
28
- heartbeatInterval: 30000,
29
- heartbeatTimeout: 90000,
30
- maxConnections: 10000,
31
- connectionRateLimit: 100,
32
- messageRateLimitPerConnection: 50,
33
- messageBurstLimit: 100,
34
- globalMessageRateLimit: 50000,
35
- };
36
- const mockConnectionService = {
37
- getConnectionCount: jest.fn().mockReturnValue(0),
38
- getAuthenticatedCount: jest.fn().mockReturnValue(0),
39
- getAllConnections: jest.fn().mockReturnValue(new Map()),
40
- getConnection: jest.fn().mockReturnValue(null),
41
- getConnectionByUserId: jest.fn().mockReturnValue(null),
42
- getConnectionIdByUserId: jest.fn().mockReturnValue(null),
43
- getAllUsers: jest.fn().mockReturnValue([]),
44
- broadcast: jest.fn().mockReturnValue({ success: 0, failed: 0 }),
45
- removeConnection: jest.fn(),
46
- send: jest.fn(),
47
- };
48
- const mockRedisService = {
49
- client: {
50
- status: 'ready',
51
- },
52
- };
53
- const mockRateLimiterService = {
54
- getCurrentConnectionRate: jest.fn().mockReturnValue(0),
55
- getCurrentGlobalMessageRate: jest.fn().mockReturnValue(0),
56
- getConnectionRateLimitStats: jest.fn().mockReturnValue({
57
- count: 0,
58
- window: 1000,
59
- limit: 100,
60
- }),
61
- getMessageRateLimitStats: jest.fn().mockReturnValue({
62
- trackedConnections: 0,
63
- totalMessages: 0,
64
- }),
65
- getGlobalMessageRateLimitStats: jest.fn().mockReturnValue({
66
- count: 0,
67
- window: 1000,
68
- limit: 50000,
69
- }),
70
- getTrackedConnectionsCount: jest.fn().mockReturnValue(0),
71
- getConfig: jest.fn().mockReturnValue({
72
- connectionRateLimit: 100,
73
- messageRateLimitPerConnection: 50,
74
- messageBurstLimit: 100,
75
- globalMessageRateLimit: 50000,
76
- }),
77
- getStats: jest.fn().mockReturnValue({
78
- connectionRate: {
79
- current: 0,
80
- limit: 100,
81
- window: 1000,
82
- },
83
- globalMessageRate: {
84
- current: 0,
85
- limit: 50000,
86
- window: 1000,
87
- },
88
- trackedConnections: 0,
89
- }),
90
- };
91
- beforeAll(async () => {
92
- const moduleFixture = await testing_1.Test.createTestingModule({
93
- controllers: [admin_controller_1.AdminController],
94
- providers: [
95
- admin_service_1.AdminService,
96
- { provide: config_service_1.ConfigService, useValue: mockConfigService },
97
- { provide: connection_service_1.ConnectionService, useValue: mockConnectionService },
98
- { provide: redis_service_1.RedisService, useValue: mockRedisService },
99
- { provide: rate_limiter_service_1.RateLimiterService, useValue: mockRateLimiterService },
100
- ],
101
- }).compile();
102
- app = moduleFixture.createNestApplication();
103
- await app.init();
104
- });
105
- afterAll(async () => {
106
- await app.close();
107
- });
108
- describe('GET /admin/health', () => {
109
- it('should return health status', async () => {
110
- const response = await (0, supertest_1.default)(app.getHttpServer())
111
- .get('/admin/health')
112
- .expect(200);
113
- expect(response.body).toHaveProperty('status', 'ok');
114
- expect(response.body).toHaveProperty('gatewayId');
115
- expect(response.body).toHaveProperty('timestamp');
116
- });
117
- });
118
- describe('GET /admin/status', () => {
119
- it('should return system status', async () => {
120
- const response = await (0, supertest_1.default)(app.getHttpServer())
121
- .get('/admin/status')
122
- .expect(200);
123
- expect(response.body).toHaveProperty('gatewayId');
124
- expect(response.body).toHaveProperty('uptime');
125
- expect(response.body).toHaveProperty('connections');
126
- expect(response.body.connections).toHaveProperty('total');
127
- expect(response.body.connections).toHaveProperty('authenticated');
128
- expect(response.body).toHaveProperty('memory');
129
- });
130
- });
131
- describe('GET /admin/connections', () => {
132
- it('should return empty connections list initially', async () => {
133
- const response = await (0, supertest_1.default)(app.getHttpServer())
134
- .get('/admin/connections')
135
- .expect(200);
136
- expect(response.body).toHaveProperty('count', 0);
137
- expect(response.body).toHaveProperty('connections');
138
- expect(response.body.connections).toEqual([]);
139
- });
140
- });
141
- describe('GET /admin/connections/:id', () => {
142
- it('should return 404 for non-existent connection', async () => {
143
- await (0, supertest_1.default)(app.getHttpServer())
144
- .get('/admin/connections/non-existent-id')
145
- .expect(404);
146
- });
147
- });
148
- describe('GET /admin/ratelimit/stats', () => {
149
- it('should return rate limit statistics', async () => {
150
- const response = await (0, supertest_1.default)(app.getHttpServer())
151
- .get('/admin/ratelimit/stats')
152
- .expect(200);
153
- expect(response.body).toHaveProperty('connectionRate');
154
- expect(response.body).toHaveProperty('globalMessageRate');
155
- expect(response.body).toHaveProperty('trackedConnections');
156
- expect(response.body).toHaveProperty('config');
157
- });
158
- });
159
- describe('GET /admin/redis/status', () => {
160
- it('should return Redis status', async () => {
161
- const response = await (0, supertest_1.default)(app.getHttpServer())
162
- .get('/admin/redis/status')
163
- .expect(200);
164
- expect(response.body).toHaveProperty('status');
165
- expect(response.body).toHaveProperty('host');
166
- expect(response.body).toHaveProperty('port');
167
- });
168
- });
169
- describe('GET /admin/users', () => {
170
- it('should return empty users list initially', async () => {
171
- const response = await (0, supertest_1.default)(app.getHttpServer())
172
- .get('/admin/users')
173
- .expect(200);
174
- expect(response.body).toHaveProperty('count', 0);
175
- expect(response.body).toHaveProperty('users');
176
- expect(response.body.users).toEqual([]);
177
- });
178
- });
179
- describe('GET /admin/users/:id', () => {
180
- it('should return 404 for non-existent user', async () => {
181
- await (0, supertest_1.default)(app.getHttpServer())
182
- .get('/admin/users/non-existent-user')
183
- .expect(404);
184
- });
185
- });
186
- describe('POST /admin/users/:id/kick', () => {
187
- it('should return 404 for non-existent user', async () => {
188
- await (0, supertest_1.default)(app.getHttpServer())
189
- .post('/admin/users/non-existent-user/kick')
190
- .send({ reason: 'test' })
191
- .expect(404);
192
- });
193
- });
194
- describe('POST /admin/broadcast', () => {
195
- it('should broadcast message (0 recipients when no connections)', async () => {
196
- const response = await (0, supertest_1.default)(app.getHttpServer())
197
- .post('/admin/broadcast')
198
- .send({ msgId: 9999, payload: { message: 'test' } })
199
- .expect(200);
200
- expect(response.body).toHaveProperty('success', true);
201
- expect(response.body).toHaveProperty('sent', 0);
202
- expect(response.body).toHaveProperty('total', 0);
203
- });
204
- });
205
- describe('GET /admin/metrics', () => {
206
- it('should return metrics data', async () => {
207
- const response = await (0, supertest_1.default)(app.getHttpServer())
208
- .get('/admin/metrics')
209
- .expect(200);
210
- expect(response.body).toHaveProperty('timestamp');
211
- expect(response.body).toHaveProperty('gateway');
212
- expect(response.body).toHaveProperty('connections');
213
- expect(response.body).toHaveProperty('rateLimit');
214
- expect(response.body).toHaveProperty('memory');
215
- });
216
- });
217
- });
218
- //# sourceMappingURL=admin-api.spec.js.map