@flink-app/streaming-plugin 0.12.1-alpha.45
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.
- package/LICENSE +21 -0
- package/README.md +476 -0
- package/dist/StreamingPlugin.d.ts +45 -0
- package/dist/StreamingPlugin.d.ts.map +1 -0
- package/dist/StreamingPlugin.js +276 -0
- package/dist/StreamingPlugin.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +86 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/examples/authenticated-stream.ts +88 -0
- package/examples/client-ndjson.html +157 -0
- package/examples/client-sse.html +202 -0
- package/package.json +41 -0
- package/spec/StreamingPlugin.spec.ts +513 -0
- package/spec/support/jasmine.json +7 -0
- package/src/StreamingPlugin.ts +281 -0
- package/src/index.ts +13 -0
- package/src/types.ts +101 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
import { FlinkApp, FlinkContext, FlinkAuthPlugin, FlinkRequest } from "@flink-app/flink";
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { streamingPlugin, StreamHandler, StreamingRouteProps } from "../src";
|
|
4
|
+
|
|
5
|
+
interface TestContext extends FlinkContext {
|
|
6
|
+
repos: {};
|
|
7
|
+
plugins: {};
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Mock auth plugin
|
|
11
|
+
class MockAuthPlugin implements FlinkAuthPlugin {
|
|
12
|
+
public shouldAuthenticate: boolean = true;
|
|
13
|
+
|
|
14
|
+
async authenticateRequest(req: FlinkRequest, permissions: string | string[]): Promise<boolean> {
|
|
15
|
+
// Simulate authentication check
|
|
16
|
+
const token = req.headers?.authorization;
|
|
17
|
+
|
|
18
|
+
if (!token) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (token === "Bearer valid-token") {
|
|
23
|
+
// Set user info on request
|
|
24
|
+
(req as any).user = { userId: "test-user", name: "Test User" };
|
|
25
|
+
return this.shouldAuthenticate;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
createToken(userId: string): Promise<string> {
|
|
32
|
+
// Mock token creation
|
|
33
|
+
return Promise.resolve(`mock-token-${userId}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Mock response object
|
|
38
|
+
class MockResponse extends EventEmitter {
|
|
39
|
+
public headers: { [key: string]: string } = {};
|
|
40
|
+
public statusCode?: number;
|
|
41
|
+
public data: string[] = [];
|
|
42
|
+
public jsonData: any[] = [];
|
|
43
|
+
public ended = false;
|
|
44
|
+
|
|
45
|
+
setHeader(key: string, value: string) {
|
|
46
|
+
this.headers[key] = value;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
status(code: number) {
|
|
50
|
+
this.statusCode = code;
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
json(data: any) {
|
|
55
|
+
this.jsonData.push(data);
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
flushHeaders() {
|
|
60
|
+
// No-op for mock
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
write(chunk: string) {
|
|
64
|
+
this.data.push(chunk);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
end() {
|
|
68
|
+
this.ended = true;
|
|
69
|
+
this.emit("close");
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Mock request object
|
|
74
|
+
class MockRequest {
|
|
75
|
+
public query: { [key: string]: any } = {};
|
|
76
|
+
public params: { [key: string]: any } = {};
|
|
77
|
+
public body: any = {};
|
|
78
|
+
public headers: { [key: string]: any } = {};
|
|
79
|
+
|
|
80
|
+
on(event: string, callback: () => void) {
|
|
81
|
+
// No-op for basic tests
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
describe("StreamingPlugin", () => {
|
|
86
|
+
let app: FlinkApp<TestContext>;
|
|
87
|
+
let plugin: ReturnType<typeof streamingPlugin>;
|
|
88
|
+
|
|
89
|
+
beforeEach(async () => {
|
|
90
|
+
plugin = streamingPlugin({ debug: false });
|
|
91
|
+
|
|
92
|
+
app = new FlinkApp<TestContext>({
|
|
93
|
+
name: "Test App",
|
|
94
|
+
port: 3999,
|
|
95
|
+
plugins: [plugin],
|
|
96
|
+
disableHttpServer: false,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
await app.start();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(async () => {
|
|
103
|
+
await app.stop();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe("SSE Format", () => {
|
|
107
|
+
it("should stream data in SSE format", (done) => {
|
|
108
|
+
const route: StreamingRouteProps = {
|
|
109
|
+
path: "/test-sse",
|
|
110
|
+
format: "sse",
|
|
111
|
+
skipAutoRegister: true,
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const handler: StreamHandler<TestContext> = async ({ stream }) => {
|
|
115
|
+
stream.write({ message: "Hello" });
|
|
116
|
+
stream.write({ message: "World" });
|
|
117
|
+
stream.end();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
plugin.registerStreamHandler(handler, route);
|
|
121
|
+
|
|
122
|
+
// Simulate request
|
|
123
|
+
const req = new MockRequest();
|
|
124
|
+
const res = new MockResponse();
|
|
125
|
+
|
|
126
|
+
// Find the registered route and call it
|
|
127
|
+
const expressApp = app.expressApp;
|
|
128
|
+
if (!expressApp) {
|
|
129
|
+
fail("Express app not initialized");
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Make request to the endpoint
|
|
134
|
+
expressApp._router.stack.forEach((layer: any) => {
|
|
135
|
+
if (layer.route?.path === "/test-sse") {
|
|
136
|
+
layer.route.stack[0].handle(req, res, () => {});
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
setTimeout(() => {
|
|
141
|
+
expect(res.headers["Content-Type"]).toBe("text/event-stream");
|
|
142
|
+
expect(res.headers["Cache-Control"]).toBe("no-cache");
|
|
143
|
+
expect(res.headers["Connection"]).toBe("keep-alive");
|
|
144
|
+
expect(res.data.length).toBe(2);
|
|
145
|
+
expect(res.data[0]).toContain('data: {"message":"Hello"}');
|
|
146
|
+
expect(res.data[1]).toContain('data: {"message":"World"}');
|
|
147
|
+
expect(res.ended).toBe(true);
|
|
148
|
+
done();
|
|
149
|
+
}, 100);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should send error events in SSE format", (done) => {
|
|
153
|
+
const route: StreamingRouteProps = {
|
|
154
|
+
path: "/test-sse-error",
|
|
155
|
+
format: "sse",
|
|
156
|
+
skipAutoRegister: true,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const handler: StreamHandler<TestContext> = async ({ stream }) => {
|
|
160
|
+
stream.error(new Error("Test error"));
|
|
161
|
+
stream.end();
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
plugin.registerStreamHandler(handler, route);
|
|
165
|
+
|
|
166
|
+
const req = new MockRequest();
|
|
167
|
+
const res = new MockResponse();
|
|
168
|
+
|
|
169
|
+
const expressApp = app.expressApp;
|
|
170
|
+
if (!expressApp) {
|
|
171
|
+
fail("Express app not initialized");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
expressApp._router.stack.forEach((layer: any) => {
|
|
176
|
+
if (layer.route?.path === "/test-sse-error") {
|
|
177
|
+
layer.route.stack[0].handle(req, res, () => {});
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
setTimeout(() => {
|
|
182
|
+
expect(res.data[0]).toContain("event: error");
|
|
183
|
+
expect(res.data[0]).toContain("Test error");
|
|
184
|
+
done();
|
|
185
|
+
}, 100);
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
describe("NDJSON Format", () => {
|
|
190
|
+
it("should stream data in NDJSON format", (done) => {
|
|
191
|
+
const route: StreamingRouteProps = {
|
|
192
|
+
path: "/test-ndjson",
|
|
193
|
+
format: "ndjson",
|
|
194
|
+
skipAutoRegister: true,
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const handler: StreamHandler<TestContext> = async ({ stream }) => {
|
|
198
|
+
stream.write({ delta: "Hello" });
|
|
199
|
+
stream.write({ delta: "World", done: true });
|
|
200
|
+
stream.end();
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
plugin.registerStreamHandler(handler, route);
|
|
204
|
+
|
|
205
|
+
const req = new MockRequest();
|
|
206
|
+
const res = new MockResponse();
|
|
207
|
+
|
|
208
|
+
const expressApp = app.expressApp;
|
|
209
|
+
if (!expressApp) {
|
|
210
|
+
fail("Express app not initialized");
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
expressApp._router.stack.forEach((layer: any) => {
|
|
215
|
+
if (layer.route?.path === "/test-ndjson") {
|
|
216
|
+
layer.route.stack[0].handle(req, res, () => {});
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
setTimeout(() => {
|
|
221
|
+
expect(res.headers["Content-Type"]).toBe("application/x-ndjson");
|
|
222
|
+
expect(res.data.length).toBe(2);
|
|
223
|
+
expect(res.data[0]).toBe('{"delta":"Hello"}\n');
|
|
224
|
+
expect(res.data[1]).toBe('{"delta":"World","done":true}\n');
|
|
225
|
+
expect(res.ended).toBe(true);
|
|
226
|
+
done();
|
|
227
|
+
}, 100);
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
describe("Stream Writer", () => {
|
|
232
|
+
it("should detect closed connections", (done) => {
|
|
233
|
+
const route: StreamingRouteProps = {
|
|
234
|
+
path: "/test-closed",
|
|
235
|
+
format: "sse",
|
|
236
|
+
skipAutoRegister: true,
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
const handler: StreamHandler<TestContext> = async ({ stream }) => {
|
|
240
|
+
expect(stream.isOpen()).toBe(true);
|
|
241
|
+
stream.end();
|
|
242
|
+
// After end, isOpen should be false (handled by plugin)
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
plugin.registerStreamHandler(handler, route);
|
|
246
|
+
|
|
247
|
+
const req = new MockRequest();
|
|
248
|
+
const res = new MockResponse();
|
|
249
|
+
|
|
250
|
+
const expressApp = app.expressApp;
|
|
251
|
+
if (!expressApp) {
|
|
252
|
+
fail("Express app not initialized");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
expressApp._router.stack.forEach((layer: any) => {
|
|
257
|
+
if (layer.route?.path === "/test-closed") {
|
|
258
|
+
layer.route.stack[0].handle(req, res, () => {});
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
setTimeout(() => {
|
|
263
|
+
expect(res.ended).toBe(true);
|
|
264
|
+
done();
|
|
265
|
+
}, 100);
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
describe("Authentication", () => {
|
|
270
|
+
let appWithAuth: FlinkApp<TestContext>;
|
|
271
|
+
let pluginWithAuth: ReturnType<typeof streamingPlugin>;
|
|
272
|
+
let mockAuth: MockAuthPlugin;
|
|
273
|
+
|
|
274
|
+
beforeEach(async () => {
|
|
275
|
+
mockAuth = new MockAuthPlugin();
|
|
276
|
+
pluginWithAuth = streamingPlugin({ debug: false });
|
|
277
|
+
|
|
278
|
+
appWithAuth = new FlinkApp<TestContext>({
|
|
279
|
+
name: "Test App With Auth",
|
|
280
|
+
port: 4000,
|
|
281
|
+
plugins: [pluginWithAuth],
|
|
282
|
+
auth: mockAuth,
|
|
283
|
+
disableHttpServer: false,
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
await appWithAuth.start();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
afterEach(async () => {
|
|
290
|
+
await appWithAuth.stop();
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it("should allow authenticated requests with valid token", (done) => {
|
|
294
|
+
const route: StreamingRouteProps = {
|
|
295
|
+
path: "/test-auth-success",
|
|
296
|
+
format: "sse",
|
|
297
|
+
skipAutoRegister: true,
|
|
298
|
+
permissions: ["admin"],
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const handler: StreamHandler<TestContext> = async ({ stream, req }) => {
|
|
302
|
+
const user = (req as any).user;
|
|
303
|
+
stream.write({ message: `Hello ${user?.name}` });
|
|
304
|
+
stream.end();
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
pluginWithAuth.registerStreamHandler(handler, route);
|
|
308
|
+
|
|
309
|
+
const req = new MockRequest();
|
|
310
|
+
req.headers.authorization = "Bearer valid-token";
|
|
311
|
+
const res = new MockResponse();
|
|
312
|
+
|
|
313
|
+
const expressApp = appWithAuth.expressApp;
|
|
314
|
+
if (!expressApp) {
|
|
315
|
+
fail("Express app not initialized");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
expressApp._router.stack.forEach((layer: any) => {
|
|
320
|
+
if (layer.route?.path === "/test-auth-success") {
|
|
321
|
+
layer.route.stack[0].handle(req, res, () => {});
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
setTimeout(() => {
|
|
326
|
+
expect(res.statusCode).not.toBe(401);
|
|
327
|
+
expect(res.data.length).toBe(1);
|
|
328
|
+
expect(res.data[0]).toContain("Hello Test User");
|
|
329
|
+
expect(res.ended).toBe(true);
|
|
330
|
+
done();
|
|
331
|
+
}, 100);
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("should reject unauthenticated requests without token", (done) => {
|
|
335
|
+
const route: StreamingRouteProps = {
|
|
336
|
+
path: "/test-auth-fail-no-token",
|
|
337
|
+
format: "sse",
|
|
338
|
+
skipAutoRegister: true,
|
|
339
|
+
permissions: ["admin"],
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const handler: StreamHandler<TestContext> = async ({ stream }) => {
|
|
343
|
+
stream.write({ message: "Should not see this" });
|
|
344
|
+
stream.end();
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
pluginWithAuth.registerStreamHandler(handler, route);
|
|
348
|
+
|
|
349
|
+
const req = new MockRequest();
|
|
350
|
+
// No authorization header
|
|
351
|
+
const res = new MockResponse();
|
|
352
|
+
|
|
353
|
+
const expressApp = appWithAuth.expressApp;
|
|
354
|
+
if (!expressApp) {
|
|
355
|
+
fail("Express app not initialized");
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
expressApp._router.stack.forEach((layer: any) => {
|
|
360
|
+
if (layer.route?.path === "/test-auth-fail-no-token") {
|
|
361
|
+
layer.route.stack[0].handle(req, res, () => {});
|
|
362
|
+
}
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
setTimeout(() => {
|
|
366
|
+
expect(res.statusCode).toBe(401);
|
|
367
|
+
expect(res.jsonData.length).toBe(1);
|
|
368
|
+
expect(res.jsonData[0].error.title).toBe("Unauthorized");
|
|
369
|
+
expect(res.data.length).toBe(0); // Should not stream any data
|
|
370
|
+
done();
|
|
371
|
+
}, 100);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("should reject requests with invalid token", (done) => {
|
|
375
|
+
const route: StreamingRouteProps = {
|
|
376
|
+
path: "/test-auth-fail-invalid-token",
|
|
377
|
+
format: "sse",
|
|
378
|
+
skipAutoRegister: true,
|
|
379
|
+
permissions: ["admin"],
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
const handler: StreamHandler<TestContext> = async ({ stream }) => {
|
|
383
|
+
stream.write({ message: "Should not see this" });
|
|
384
|
+
stream.end();
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
pluginWithAuth.registerStreamHandler(handler, route);
|
|
388
|
+
|
|
389
|
+
const req = new MockRequest();
|
|
390
|
+
req.headers.authorization = "Bearer invalid-token";
|
|
391
|
+
const res = new MockResponse();
|
|
392
|
+
|
|
393
|
+
const expressApp = appWithAuth.expressApp;
|
|
394
|
+
if (!expressApp) {
|
|
395
|
+
fail("Express app not initialized");
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
expressApp._router.stack.forEach((layer: any) => {
|
|
400
|
+
if (layer.route?.path === "/test-auth-fail-invalid-token") {
|
|
401
|
+
layer.route.stack[0].handle(req, res, () => {});
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
setTimeout(() => {
|
|
406
|
+
expect(res.statusCode).toBe(401);
|
|
407
|
+
expect(res.jsonData.length).toBe(1);
|
|
408
|
+
expect(res.jsonData[0].error.title).toBe("Unauthorized");
|
|
409
|
+
expect(res.data.length).toBe(0);
|
|
410
|
+
done();
|
|
411
|
+
}, 100);
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it("should allow streaming without permissions when no auth is required", (done) => {
|
|
415
|
+
const route: StreamingRouteProps = {
|
|
416
|
+
path: "/test-no-auth-required",
|
|
417
|
+
format: "sse",
|
|
418
|
+
skipAutoRegister: true,
|
|
419
|
+
// No permissions specified
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
const handler: StreamHandler<TestContext> = async ({ stream }) => {
|
|
423
|
+
stream.write({ message: "Public endpoint" });
|
|
424
|
+
stream.end();
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
pluginWithAuth.registerStreamHandler(handler, route);
|
|
428
|
+
|
|
429
|
+
const req = new MockRequest();
|
|
430
|
+
// No authorization header, but endpoint doesn't require it
|
|
431
|
+
const res = new MockResponse();
|
|
432
|
+
|
|
433
|
+
const expressApp = appWithAuth.expressApp;
|
|
434
|
+
if (!expressApp) {
|
|
435
|
+
fail("Express app not initialized");
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
expressApp._router.stack.forEach((layer: any) => {
|
|
440
|
+
if (layer.route?.path === "/test-no-auth-required") {
|
|
441
|
+
layer.route.stack[0].handle(req, res, () => {});
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
setTimeout(() => {
|
|
446
|
+
expect(res.statusCode).not.toBe(401);
|
|
447
|
+
expect(res.data.length).toBe(1);
|
|
448
|
+
expect(res.data[0]).toContain("Public endpoint");
|
|
449
|
+
expect(res.ended).toBe(true);
|
|
450
|
+
done();
|
|
451
|
+
}, 100);
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
it("should provide access to req.user populated by auth plugin", (done) => {
|
|
455
|
+
const route: StreamingRouteProps = {
|
|
456
|
+
path: "/test-req-user-access",
|
|
457
|
+
format: "ndjson",
|
|
458
|
+
skipAutoRegister: true,
|
|
459
|
+
permissions: ["admin"],
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
const handler: StreamHandler<TestContext> = async ({ stream, req }) => {
|
|
463
|
+
// Access req.user that was set by auth plugin
|
|
464
|
+
const user = (req as any).user;
|
|
465
|
+
|
|
466
|
+
// Verify user object exists and has expected properties
|
|
467
|
+
expect(user).toBeDefined();
|
|
468
|
+
expect(user.userId).toBe("test-user");
|
|
469
|
+
expect(user.name).toBe("Test User");
|
|
470
|
+
|
|
471
|
+
// Stream user info
|
|
472
|
+
stream.write({
|
|
473
|
+
userId: user.userId,
|
|
474
|
+
userName: user.name,
|
|
475
|
+
message: "User info from req.user"
|
|
476
|
+
});
|
|
477
|
+
stream.end();
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
pluginWithAuth.registerStreamHandler(handler, route);
|
|
481
|
+
|
|
482
|
+
const req = new MockRequest();
|
|
483
|
+
req.headers.authorization = "Bearer valid-token";
|
|
484
|
+
const res = new MockResponse();
|
|
485
|
+
|
|
486
|
+
const expressApp = appWithAuth.expressApp;
|
|
487
|
+
if (!expressApp) {
|
|
488
|
+
fail("Express app not initialized");
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
expressApp._router.stack.forEach((layer: any) => {
|
|
493
|
+
if (layer.route?.path === "/test-req-user-access") {
|
|
494
|
+
layer.route.stack[0].handle(req, res, () => {});
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
setTimeout(() => {
|
|
499
|
+
expect(res.statusCode).not.toBe(401);
|
|
500
|
+
expect(res.data.length).toBe(1);
|
|
501
|
+
|
|
502
|
+
// Parse the NDJSON line
|
|
503
|
+
const data = JSON.parse(res.data[0].trim());
|
|
504
|
+
expect(data.userId).toBe("test-user");
|
|
505
|
+
expect(data.userName).toBe("Test User");
|
|
506
|
+
expect(data.message).toBe("User info from req.user");
|
|
507
|
+
|
|
508
|
+
expect(res.ended).toBe(true);
|
|
509
|
+
done();
|
|
510
|
+
}, 100);
|
|
511
|
+
});
|
|
512
|
+
});
|
|
513
|
+
});
|