@agentick/gateway 0.0.1

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 (78) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +477 -0
  3. package/dist/agent-registry.d.ts +51 -0
  4. package/dist/agent-registry.d.ts.map +1 -0
  5. package/dist/agent-registry.js +78 -0
  6. package/dist/agent-registry.js.map +1 -0
  7. package/dist/app-registry.d.ts +51 -0
  8. package/dist/app-registry.d.ts.map +1 -0
  9. package/dist/app-registry.js +78 -0
  10. package/dist/app-registry.js.map +1 -0
  11. package/dist/bin.d.ts +8 -0
  12. package/dist/bin.d.ts.map +1 -0
  13. package/dist/bin.js +37 -0
  14. package/dist/bin.js.map +1 -0
  15. package/dist/gateway.d.ts +165 -0
  16. package/dist/gateway.d.ts.map +1 -0
  17. package/dist/gateway.js +1339 -0
  18. package/dist/gateway.js.map +1 -0
  19. package/dist/http-transport.d.ts +65 -0
  20. package/dist/http-transport.d.ts.map +1 -0
  21. package/dist/http-transport.js +517 -0
  22. package/dist/http-transport.js.map +1 -0
  23. package/dist/index.d.ts +16 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +23 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/protocol.d.ts +162 -0
  28. package/dist/protocol.d.ts.map +1 -0
  29. package/dist/protocol.js +16 -0
  30. package/dist/protocol.js.map +1 -0
  31. package/dist/session-manager.d.ts +101 -0
  32. package/dist/session-manager.d.ts.map +1 -0
  33. package/dist/session-manager.js +208 -0
  34. package/dist/session-manager.js.map +1 -0
  35. package/dist/testing.d.ts +92 -0
  36. package/dist/testing.d.ts.map +1 -0
  37. package/dist/testing.js +129 -0
  38. package/dist/testing.js.map +1 -0
  39. package/dist/transport-protocol.d.ts +162 -0
  40. package/dist/transport-protocol.d.ts.map +1 -0
  41. package/dist/transport-protocol.js +16 -0
  42. package/dist/transport-protocol.js.map +1 -0
  43. package/dist/transport.d.ts +115 -0
  44. package/dist/transport.d.ts.map +1 -0
  45. package/dist/transport.js +56 -0
  46. package/dist/transport.js.map +1 -0
  47. package/dist/types.d.ts +314 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +37 -0
  50. package/dist/types.js.map +1 -0
  51. package/dist/websocket-server.d.ts +87 -0
  52. package/dist/websocket-server.d.ts.map +1 -0
  53. package/dist/websocket-server.js +245 -0
  54. package/dist/websocket-server.js.map +1 -0
  55. package/dist/ws-transport.d.ts +17 -0
  56. package/dist/ws-transport.d.ts.map +1 -0
  57. package/dist/ws-transport.js +174 -0
  58. package/dist/ws-transport.js.map +1 -0
  59. package/package.json +51 -0
  60. package/src/__tests__/custom-methods.spec.ts +220 -0
  61. package/src/__tests__/gateway-methods.spec.ts +262 -0
  62. package/src/__tests__/gateway.spec.ts +404 -0
  63. package/src/__tests__/guards.spec.ts +235 -0
  64. package/src/__tests__/protocol.spec.ts +58 -0
  65. package/src/__tests__/session-manager.spec.ts +220 -0
  66. package/src/__tests__/ws-transport.spec.ts +246 -0
  67. package/src/app-registry.ts +103 -0
  68. package/src/bin.ts +38 -0
  69. package/src/gateway.ts +1712 -0
  70. package/src/http-transport.ts +623 -0
  71. package/src/index.ts +94 -0
  72. package/src/session-manager.ts +272 -0
  73. package/src/testing.ts +236 -0
  74. package/src/transport-protocol.ts +249 -0
  75. package/src/transport.ts +191 -0
  76. package/src/types.ts +392 -0
  77. package/src/websocket-server.ts +303 -0
  78. package/src/ws-transport.ts +205 -0
@@ -0,0 +1,262 @@
1
+ /**
2
+ * Gateway Method Dispatch Tests
3
+ *
4
+ * Integration tests for Gateway custom method dispatch, including:
5
+ * - Method initialization and path resolution
6
+ * - Role and custom guard middleware
7
+ * - Schema validation
8
+ * - Auth hydrateUser hook
9
+ * - Express middleware
10
+ */
11
+
12
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
13
+ import { z } from "zod";
14
+ import { Gateway, createGateway, type ExpressRequestHandler } from "../gateway.js";
15
+ import { method, type AuthResult, type AuthConfig, type GatewayConfig } from "../types.js";
16
+ import { Context, type UserContext } from "@agentick/kernel";
17
+ import { createMockApp } from "@agentick/core/testing";
18
+
19
+ // ============================================================================
20
+ // Gateway initialization tests
21
+ // ============================================================================
22
+
23
+ describe("Gateway method initialization", () => {
24
+ it("should initialize simple function methods", () => {
25
+ const gateway = createGateway({
26
+ apps: { test: createMockApp() as any },
27
+ defaultApp: "test",
28
+ methods: {
29
+ ping: async () => ({ pong: true }),
30
+ },
31
+ });
32
+
33
+ // Gateway should be created without errors
34
+ expect(gateway).toBeInstanceOf(Gateway);
35
+ });
36
+
37
+ it("should initialize method() definition methods", () => {
38
+ const gateway = createGateway({
39
+ apps: { test: createMockApp() as any },
40
+ defaultApp: "test",
41
+ methods: {
42
+ create: method({
43
+ schema: z.object({ title: z.string() }),
44
+ handler: async (params) => ({ id: "1", title: params.title }),
45
+ }),
46
+ },
47
+ });
48
+
49
+ expect(gateway).toBeInstanceOf(Gateway);
50
+ });
51
+
52
+ it("should initialize nested namespace methods", () => {
53
+ const gateway = createGateway({
54
+ apps: { test: createMockApp() as any },
55
+ defaultApp: "test",
56
+ methods: {
57
+ tasks: {
58
+ list: async () => [],
59
+ create: method({
60
+ schema: z.object({ title: z.string() }),
61
+ handler: async (params) => ({ id: "1", title: params.title }),
62
+ }),
63
+ admin: {
64
+ archive: method({
65
+ roles: ["admin"],
66
+ handler: async () => ({ archived: true }),
67
+ }),
68
+ },
69
+ },
70
+ },
71
+ });
72
+
73
+ expect(gateway).toBeInstanceOf(Gateway);
74
+ });
75
+
76
+ it("should handle deeply nested namespaces", () => {
77
+ const gateway = createGateway({
78
+ apps: { test: createMockApp() as any },
79
+ defaultApp: "test",
80
+ methods: {
81
+ level1: {
82
+ level2: {
83
+ level3: {
84
+ level4: {
85
+ deepMethod: async () => ({ deep: true }),
86
+ },
87
+ },
88
+ },
89
+ },
90
+ },
91
+ });
92
+
93
+ expect(gateway).toBeInstanceOf(Gateway);
94
+ });
95
+ });
96
+
97
+ // ============================================================================
98
+ // Auth configuration tests
99
+ // ============================================================================
100
+
101
+ describe("Auth configuration", () => {
102
+ it("should support auth type none", () => {
103
+ const gateway = createGateway({
104
+ apps: { test: createMockApp() as any },
105
+ defaultApp: "test",
106
+ auth: { type: "none" },
107
+ });
108
+
109
+ expect(gateway).toBeInstanceOf(Gateway);
110
+ });
111
+
112
+ it("should support auth type token", () => {
113
+ const gateway = createGateway({
114
+ apps: { test: createMockApp() as any },
115
+ defaultApp: "test",
116
+ auth: { type: "token", token: "secret-token" },
117
+ });
118
+
119
+ expect(gateway).toBeInstanceOf(Gateway);
120
+ });
121
+
122
+ it("should support auth type custom with validate function", () => {
123
+ const gateway = createGateway({
124
+ apps: { test: createMockApp() as any },
125
+ defaultApp: "test",
126
+ auth: {
127
+ type: "custom",
128
+ validate: async (token) => ({
129
+ valid: token === "valid-token",
130
+ user: { id: "user-1" },
131
+ }),
132
+ },
133
+ });
134
+
135
+ expect(gateway).toBeInstanceOf(Gateway);
136
+ });
137
+
138
+ it("should support hydrateUser hook on any auth type", () => {
139
+ const hydrateUser = vi.fn(
140
+ async (authResult: AuthResult): Promise<UserContext> => ({
141
+ id: authResult.user?.id ?? "unknown",
142
+ roles: ["user", "premium"],
143
+ email: "test@example.com",
144
+ }),
145
+ );
146
+
147
+ // With token auth
148
+ const gateway1 = createGateway({
149
+ apps: { test: createMockApp() as any },
150
+ defaultApp: "test",
151
+ auth: {
152
+ type: "token",
153
+ token: "secret",
154
+ hydrateUser,
155
+ },
156
+ });
157
+ expect(gateway1).toBeInstanceOf(Gateway);
158
+
159
+ // With custom auth
160
+ const gateway2 = createGateway({
161
+ apps: { test: createMockApp() as any },
162
+ defaultApp: "test",
163
+ auth: {
164
+ type: "custom",
165
+ validate: async () => ({ valid: true, user: { id: "1" } }),
166
+ hydrateUser,
167
+ },
168
+ });
169
+ expect(gateway2).toBeInstanceOf(Gateway);
170
+ });
171
+ });
172
+
173
+ // ============================================================================
174
+ // Gateway status and lifecycle tests
175
+ // ============================================================================
176
+
177
+ describe("Gateway status", () => {
178
+ it("should return correct status before start", () => {
179
+ const gateway = createGateway({
180
+ apps: { test: createMockApp() as any },
181
+ defaultApp: "test",
182
+ });
183
+
184
+ expect(gateway.running).toBe(false);
185
+ expect(gateway.status.uptime).toBe(0);
186
+ expect(gateway.status.clients).toBe(0);
187
+ expect(gateway.status.sessions).toBe(0);
188
+ expect(gateway.status.apps).toContain("test");
189
+ });
190
+
191
+ it("should have a unique id when provided", () => {
192
+ const gateway1 = createGateway({
193
+ apps: { test: createMockApp() as any },
194
+ defaultApp: "test",
195
+ id: "gateway-1",
196
+ });
197
+
198
+ const gateway2 = createGateway({
199
+ apps: { test: createMockApp() as any },
200
+ defaultApp: "test",
201
+ id: "gateway-2",
202
+ });
203
+
204
+ expect(gateway1.id).toBe("gateway-1");
205
+ expect(gateway2.id).toBe("gateway-2");
206
+ expect(gateway1.id).not.toBe(gateway2.id);
207
+ });
208
+
209
+ it("should auto-generate id when not provided", () => {
210
+ const gateway = createGateway({
211
+ apps: { test: createMockApp() as any },
212
+ defaultApp: "test",
213
+ });
214
+
215
+ expect(gateway.id).toBeTruthy();
216
+ expect(gateway.id).toMatch(/^gw-/);
217
+ });
218
+
219
+ it("should use provided id", () => {
220
+ const gateway = createGateway({
221
+ apps: { test: createMockApp() as any },
222
+ defaultApp: "test",
223
+ id: "custom-gateway-id",
224
+ });
225
+
226
+ expect(gateway.id).toBe("custom-gateway-id");
227
+ });
228
+ });
229
+
230
+ // ============================================================================
231
+ // Config validation tests
232
+ // ============================================================================
233
+
234
+ describe("Gateway config validation", () => {
235
+ it("should throw if no apps provided", () => {
236
+ expect(() =>
237
+ createGateway({
238
+ apps: {},
239
+ defaultApp: "test",
240
+ }),
241
+ ).toThrow("At least one app is required");
242
+ });
243
+
244
+ it("should throw if no defaultApp provided", () => {
245
+ expect(() =>
246
+ createGateway({
247
+ apps: { test: createMockApp() as any },
248
+ defaultApp: "",
249
+ } as any),
250
+ ).toThrow("defaultApp is required");
251
+ });
252
+
253
+ it("should use default port and host", () => {
254
+ const gateway = createGateway({
255
+ apps: { test: createMockApp() as any },
256
+ defaultApp: "test",
257
+ });
258
+
259
+ // Gateway should be created with defaults
260
+ expect(gateway).toBeInstanceOf(Gateway);
261
+ });
262
+ });
@@ -0,0 +1,404 @@
1
+ /**
2
+ * Gateway Integration Tests
3
+ */
4
+
5
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
6
+ import { Gateway, createGateway } from "../gateway.js";
7
+ import { createMockApp as createCoreMockApp, type MockApp } from "@agentick/core/testing";
8
+ import WebSocket from "ws";
9
+
10
+ describe("Gateway", () => {
11
+ const TEST_PORT = 19998;
12
+ const TEST_HOST = "127.0.0.1";
13
+ let gateway: Gateway;
14
+ let chatApp: MockApp;
15
+ let researchApp: MockApp;
16
+
17
+ beforeEach(() => {
18
+ chatApp = createCoreMockApp();
19
+ researchApp = createCoreMockApp();
20
+ });
21
+
22
+ afterEach(async () => {
23
+ if (gateway?.running) {
24
+ await gateway.stop();
25
+ }
26
+ });
27
+
28
+ describe("createGateway", () => {
29
+ it("creates gateway with config", () => {
30
+ gateway = createGateway({
31
+ port: TEST_PORT,
32
+ host: TEST_HOST,
33
+ apps: { chat: chatApp },
34
+ defaultApp: "chat",
35
+ });
36
+
37
+ expect(gateway).toBeInstanceOf(Gateway);
38
+ expect(gateway.id).toBeDefined();
39
+ });
40
+
41
+ it("throws if no apps provided", () => {
42
+ expect(() =>
43
+ createGateway({
44
+ apps: {},
45
+ defaultApp: "chat",
46
+ }),
47
+ ).toThrow("At least one app is required");
48
+ });
49
+
50
+ it("throws if default app not found", () => {
51
+ expect(() =>
52
+ createGateway({
53
+ apps: { chat: chatApp },
54
+ defaultApp: "research",
55
+ }),
56
+ ).toThrow('Default app "research" not found');
57
+ });
58
+ });
59
+
60
+ describe("start/stop", () => {
61
+ it("starts and stops gateway", async () => {
62
+ gateway = createGateway({
63
+ port: TEST_PORT,
64
+ host: TEST_HOST,
65
+ apps: { chat: chatApp },
66
+ defaultApp: "chat",
67
+ });
68
+
69
+ await gateway.start();
70
+ expect(gateway.running).toBe(true);
71
+
72
+ await gateway.stop();
73
+ expect(gateway.running).toBe(false);
74
+ });
75
+
76
+ it("emits started event", async () => {
77
+ gateway = createGateway({
78
+ port: TEST_PORT,
79
+ host: TEST_HOST,
80
+ apps: { chat: chatApp },
81
+ defaultApp: "chat",
82
+ });
83
+
84
+ const startedPromise = new Promise<{ port: number; host: string }>((resolve) => {
85
+ gateway.on("started", resolve);
86
+ });
87
+
88
+ await gateway.start();
89
+ const event = await startedPromise;
90
+
91
+ expect(event.port).toBe(TEST_PORT);
92
+ expect(event.host).toBe(TEST_HOST);
93
+ });
94
+
95
+ it("throws if started twice", async () => {
96
+ gateway = createGateway({
97
+ port: TEST_PORT,
98
+ host: TEST_HOST,
99
+ apps: { chat: chatApp },
100
+ defaultApp: "chat",
101
+ });
102
+
103
+ await gateway.start();
104
+ await expect(gateway.start()).rejects.toThrow("already running");
105
+ });
106
+ });
107
+
108
+ describe("status", () => {
109
+ it("reports gateway status", async () => {
110
+ gateway = createGateway({
111
+ port: TEST_PORT,
112
+ host: TEST_HOST,
113
+ apps: { chat: chatApp, research: researchApp },
114
+ defaultApp: "chat",
115
+ });
116
+
117
+ await gateway.start();
118
+
119
+ const status = gateway.status;
120
+ expect(status.id).toBeDefined();
121
+ expect(status.uptime).toBeGreaterThanOrEqual(0);
122
+ expect(status.clients).toBe(0);
123
+ expect(status.sessions).toBe(0);
124
+ expect(status.apps).toEqual(["chat", "research"]);
125
+ });
126
+ });
127
+
128
+ describe("client connection", () => {
129
+ it("accepts client and emits connected event", async () => {
130
+ gateway = createGateway({
131
+ port: TEST_PORT,
132
+ host: TEST_HOST,
133
+ apps: { chat: chatApp },
134
+ defaultApp: "chat",
135
+ });
136
+
137
+ await gateway.start();
138
+
139
+ const connectedPromise = new Promise<{ clientId: string }>((resolve) => {
140
+ gateway.on("client:connected", resolve);
141
+ });
142
+
143
+ const client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
144
+ await new Promise<void>((r) => client.on("open", () => r()));
145
+
146
+ const event = await connectedPromise;
147
+ expect(event.clientId).toBeDefined();
148
+
149
+ client.close();
150
+ });
151
+
152
+ it("handles client disconnect", async () => {
153
+ gateway = createGateway({
154
+ port: TEST_PORT,
155
+ host: TEST_HOST,
156
+ apps: { chat: chatApp },
157
+ defaultApp: "chat",
158
+ });
159
+
160
+ await gateway.start();
161
+
162
+ const disconnectedPromise = new Promise<{ clientId: string }>((resolve) => {
163
+ gateway.on("client:disconnected", resolve);
164
+ });
165
+
166
+ const client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
167
+ await new Promise<void>((r) => client.on("open", () => r()));
168
+
169
+ // Connect
170
+ client.send(JSON.stringify({ type: "connect", clientId: "test" }));
171
+ await new Promise((r) => setTimeout(r, 50));
172
+
173
+ client.close();
174
+
175
+ const event = await disconnectedPromise;
176
+ expect(event.clientId).toBeDefined();
177
+ });
178
+ });
179
+
180
+ describe("RPC methods", () => {
181
+ let client: WebSocket;
182
+
183
+ beforeEach(async () => {
184
+ gateway = createGateway({
185
+ port: TEST_PORT,
186
+ host: TEST_HOST,
187
+ apps: { chat: chatApp, research: researchApp },
188
+ defaultApp: "chat",
189
+ });
190
+
191
+ await gateway.start();
192
+
193
+ client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
194
+ await new Promise<void>((r) => client.on("open", () => r()));
195
+
196
+ // Authenticate
197
+ client.send(JSON.stringify({ type: "connect", clientId: "test" }));
198
+ await new Promise((r) => setTimeout(r, 50));
199
+ });
200
+
201
+ afterEach(() => {
202
+ client?.close();
203
+ });
204
+
205
+ it("lists apps", async () => {
206
+ const responsePromise = new Promise<any>((resolve) => {
207
+ client.on("message", (data) => {
208
+ const msg = JSON.parse(data.toString());
209
+ if (msg.type === "res") resolve(msg);
210
+ });
211
+ });
212
+
213
+ client.send(
214
+ JSON.stringify({
215
+ type: "req",
216
+ id: "req-1",
217
+ method: "apps",
218
+ params: {},
219
+ }),
220
+ );
221
+
222
+ const response = await responsePromise;
223
+ expect(response.ok).toBe(true);
224
+ expect(response.payload.apps).toHaveLength(2);
225
+ expect(response.payload.apps[0].id).toBeDefined();
226
+ });
227
+
228
+ it("lists sessions", async () => {
229
+ const responsePromise = new Promise<any>((resolve) => {
230
+ client.on("message", (data) => {
231
+ const msg = JSON.parse(data.toString());
232
+ if (msg.type === "res") resolve(msg);
233
+ });
234
+ });
235
+
236
+ client.send(
237
+ JSON.stringify({
238
+ type: "req",
239
+ id: "req-1",
240
+ method: "sessions",
241
+ params: {},
242
+ }),
243
+ );
244
+
245
+ const response = await responsePromise;
246
+ expect(response.ok).toBe(true);
247
+ expect(response.payload.sessions).toEqual([]);
248
+ });
249
+
250
+ it("returns gateway status", async () => {
251
+ const responsePromise = new Promise<any>((resolve) => {
252
+ client.on("message", (data) => {
253
+ const msg = JSON.parse(data.toString());
254
+ if (msg.type === "res") resolve(msg);
255
+ });
256
+ });
257
+
258
+ client.send(
259
+ JSON.stringify({
260
+ type: "req",
261
+ id: "req-1",
262
+ method: "status",
263
+ params: {},
264
+ }),
265
+ );
266
+
267
+ const response = await responsePromise;
268
+ expect(response.ok).toBe(true);
269
+ expect(response.payload.gateway).toBeDefined();
270
+ expect(response.payload.gateway.apps).toContain("chat");
271
+ });
272
+
273
+ it("sends message to session", async () => {
274
+ const messages: any[] = [];
275
+ client.on("message", (data) => {
276
+ messages.push(JSON.parse(data.toString()));
277
+ });
278
+
279
+ client.send(
280
+ JSON.stringify({
281
+ type: "req",
282
+ id: "req-1",
283
+ method: "send",
284
+ params: {
285
+ sessionId: "main",
286
+ message: "Hello!",
287
+ },
288
+ }),
289
+ );
290
+
291
+ // Wait for response and events
292
+ await new Promise((r) => setTimeout(r, 200));
293
+
294
+ const response = messages.find((m) => m.type === "res" && m.id === "req-1");
295
+ expect(response?.ok).toBe(true);
296
+ expect(response?.payload?.messageId).toBeDefined();
297
+
298
+ // Should have created a session and sent to it
299
+ expect(chatApp._sessions.size).toBeGreaterThan(0);
300
+ });
301
+
302
+ it("handles unknown method", async () => {
303
+ const responsePromise = new Promise<any>((resolve) => {
304
+ client.on("message", (data) => {
305
+ const msg = JSON.parse(data.toString());
306
+ if (msg.type === "res") resolve(msg);
307
+ });
308
+ });
309
+
310
+ client.send(
311
+ JSON.stringify({
312
+ type: "req",
313
+ id: "req-1",
314
+ method: "unknown_method" as any,
315
+ params: {},
316
+ }),
317
+ );
318
+
319
+ const response = await responsePromise;
320
+ expect(response.ok).toBe(false);
321
+ expect(response.error.message).toContain("Unknown method");
322
+ });
323
+ });
324
+
325
+ describe("authentication", () => {
326
+ it("requires auth when configured", async () => {
327
+ gateway = createGateway({
328
+ port: TEST_PORT,
329
+ host: TEST_HOST,
330
+ apps: { chat: chatApp },
331
+ defaultApp: "chat",
332
+ auth: { type: "token", token: "secret123" },
333
+ });
334
+
335
+ await gateway.start();
336
+
337
+ const client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
338
+ await new Promise<void>((r) => client.on("open", () => r()));
339
+
340
+ const errorPromise = new Promise<any>((resolve) => {
341
+ client.on("message", (data) => {
342
+ const msg = JSON.parse(data.toString());
343
+ if (msg.type === "error") resolve(msg);
344
+ });
345
+ });
346
+
347
+ // Try to connect without token
348
+ client.send(JSON.stringify({ type: "connect", clientId: "test" }));
349
+
350
+ const error = await errorPromise;
351
+ expect(error.code).toBe("AUTH_FAILED");
352
+
353
+ client.close();
354
+ });
355
+
356
+ it("allows auth with valid token", async () => {
357
+ gateway = createGateway({
358
+ port: TEST_PORT,
359
+ host: TEST_HOST,
360
+ apps: { chat: chatApp },
361
+ defaultApp: "chat",
362
+ auth: { type: "token", token: "secret123" },
363
+ });
364
+
365
+ await gateway.start();
366
+
367
+ const client = new WebSocket(`ws://${TEST_HOST}:${TEST_PORT}`);
368
+ await new Promise<void>((r) => client.on("open", () => r()));
369
+
370
+ // Connect with valid token
371
+ client.send(
372
+ JSON.stringify({
373
+ type: "connect",
374
+ clientId: "test",
375
+ token: "secret123",
376
+ }),
377
+ );
378
+
379
+ // Should be able to make requests now
380
+ await new Promise((r) => setTimeout(r, 50));
381
+
382
+ const responsePromise = new Promise<any>((resolve) => {
383
+ client.on("message", (data) => {
384
+ const msg = JSON.parse(data.toString());
385
+ if (msg.type === "res") resolve(msg);
386
+ });
387
+ });
388
+
389
+ client.send(
390
+ JSON.stringify({
391
+ type: "req",
392
+ id: "req-1",
393
+ method: "apps",
394
+ params: {},
395
+ }),
396
+ );
397
+
398
+ const response = await responsePromise;
399
+ expect(response.ok).toBe(true);
400
+
401
+ client.close();
402
+ });
403
+ });
404
+ });