@flink-app/flink 0.14.3 → 2.0.0-alpha.100
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/CHANGELOG.md +1051 -0
- package/SCHEMA_EXTRACTION_ANALYSIS.md +494 -0
- package/SIMPLE_AST_FEASIBILITY.md +570 -0
- package/bin/flink.ts +13 -2
- package/cli/build.ts +24 -44
- package/cli/clean.ts +13 -25
- package/cli/cli-utils.ts +190 -17
- package/cli/dev.ts +252 -0
- package/cli/loadEnvFiles.ts +116 -0
- package/cli/run.ts +45 -62
- package/dist/bin/flink.js +61 -2
- package/dist/cli/build.js +20 -25
- package/dist/cli/clean.js +12 -10
- package/dist/cli/cli-utils.d.ts +34 -3
- package/dist/cli/cli-utils.js +193 -12
- package/dist/cli/dev.d.ts +2 -0
- package/dist/cli/dev.js +279 -0
- package/dist/cli/loadEnvFiles.d.ts +30 -0
- package/dist/cli/loadEnvFiles.js +113 -0
- package/dist/cli/run.js +47 -46
- package/dist/src/DependencyTracker.d.ts +44 -0
- package/dist/src/DependencyTracker.js +239 -0
- package/dist/src/FlinkApp.d.ts +163 -10
- package/dist/src/FlinkApp.js +847 -184
- package/dist/src/FlinkContext.d.ts +41 -0
- package/dist/src/FlinkErrors.d.ts +19 -6
- package/dist/src/FlinkErrors.js +36 -42
- package/dist/src/FlinkHttpHandler.d.ts +219 -26
- package/dist/src/FlinkHttpHandler.js +37 -1
- package/dist/src/FlinkJob.d.ts +10 -0
- package/dist/src/FlinkLog.d.ts +82 -18
- package/dist/src/FlinkLog.js +165 -13
- package/dist/src/FlinkLogFactory.d.ts +288 -0
- package/dist/src/FlinkLogFactory.js +619 -0
- package/dist/src/FlinkRepo.d.ts +10 -2
- package/dist/src/FlinkRepo.js +11 -1
- package/dist/src/FlinkRequestContext.d.ts +63 -0
- package/dist/src/FlinkRequestContext.js +74 -0
- package/dist/src/FlinkResponse.d.ts +6 -0
- package/dist/src/FlinkService.d.ts +38 -0
- package/dist/src/FlinkService.js +46 -0
- package/dist/src/LeaderElection.d.ts +45 -0
- package/dist/src/LeaderElection.js +269 -0
- package/dist/src/SchemaCache.d.ts +84 -0
- package/dist/src/SchemaCache.js +289 -0
- package/dist/src/TypeScriptCompiler.d.ts +161 -51
- package/dist/src/TypeScriptCompiler.js +1253 -617
- package/dist/src/TypeScriptUtils.js +4 -0
- package/dist/src/ai/AgentRunner.d.ts +39 -0
- package/dist/src/ai/AgentRunner.js +760 -0
- package/dist/src/ai/ConversationAgent.d.ts +279 -0
- package/dist/src/ai/ConversationAgent.js +404 -0
- package/dist/src/ai/ConversationFlinkAgent.d.ts +278 -0
- package/dist/src/ai/ConversationFlinkAgent.js +404 -0
- package/dist/src/ai/FlinkAgent.d.ts +690 -0
- package/dist/src/ai/FlinkAgent.js +729 -0
- package/dist/src/ai/FlinkTool.d.ts +135 -0
- package/dist/src/ai/FlinkTool.js +2 -0
- package/dist/src/ai/InMemoryConversationAgent.d.ts +121 -0
- package/dist/src/ai/InMemoryConversationAgent.js +209 -0
- package/dist/src/ai/LLMAdapter.d.ts +148 -0
- package/dist/src/ai/LLMAdapter.js +2 -0
- package/dist/src/ai/PersistentFlinkAgent.d.ts +278 -0
- package/dist/src/ai/PersistentFlinkAgent.js +403 -0
- package/dist/src/ai/SubAgentExecutor.d.ts +38 -0
- package/dist/src/ai/SubAgentExecutor.js +223 -0
- package/dist/src/ai/ToolExecutor.d.ts +64 -0
- package/dist/src/ai/ToolExecutor.js +497 -0
- package/dist/src/ai/agentInstructions.d.ts +68 -0
- package/dist/src/ai/agentInstructions.js +286 -0
- package/dist/src/ai/index.d.ts +8 -0
- package/dist/src/ai/index.js +26 -0
- package/dist/src/ai/instructionFileLoader.d.ts +44 -0
- package/dist/src/ai/instructionFileLoader.js +179 -0
- package/dist/src/auth/FlinkAuthPlugin.d.ts +1 -1
- package/dist/src/handlers/StreamWriterFactory.d.ts +20 -0
- package/dist/src/handlers/StreamWriterFactory.js +83 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.js +17 -0
- package/dist/src/loadPluginSchemas.d.ts +45 -0
- package/dist/src/loadPluginSchemas.js +143 -0
- package/dist/src/schema-extraction/ComplexTypeDetection.d.ts +40 -0
- package/dist/src/schema-extraction/ComplexTypeDetection.js +75 -0
- package/dist/src/schema-extraction/TypeScriptSourceParser.d.ts +321 -0
- package/dist/src/schema-extraction/TypeScriptSourceParser.js +925 -0
- package/dist/src/schema-extraction/TypeScriptSourceParser.spec.d.ts +1 -0
- package/dist/src/schema-extraction/TypeScriptSourceParser.spec.js +233 -0
- package/dist/src/schema-extraction/TypeScriptTokenizer.d.ts +57 -0
- package/dist/src/schema-extraction/TypeScriptTokenizer.js +177 -0
- package/dist/src/schema-extraction/index.d.ts +2 -0
- package/dist/src/schema-extraction/index.js +20 -0
- package/dist/src/schema-extraction/types.d.ts +31 -0
- package/dist/src/schema-extraction/types.js +2 -0
- package/dist/src/utils/loadFlinkConfig.d.ts +53 -0
- package/dist/src/utils/loadFlinkConfig.js +77 -0
- package/dist/src/utils.d.ts +30 -0
- package/dist/src/utils.js +52 -0
- package/dist/src/workers/SchemaGeneratorWorker.d.ts +1 -0
- package/dist/src/workers/SchemaGeneratorWorker.js +49 -0
- package/dist/src/workers/WorkerPool.d.ts +60 -0
- package/dist/src/workers/WorkerPool.js +306 -0
- package/examples/logging-hierarchical-example.ts +125 -0
- package/package.json +29 -4
- package/readme.md +499 -0
- package/spec/AgentDescendantDetection.spec.ts +335 -0
- package/spec/AgentDuplicateDetection.spec.ts +112 -0
- package/spec/AgentObserver.spec.ts +266 -0
- package/spec/AgentRunner.spec.ts +1062 -0
- package/spec/AsyncLocalStorageContext.spec.ts +223 -0
- package/spec/ConversationHooks.spec.ts +257 -0
- package/spec/FlinkAgent.spec.ts +681 -0
- package/spec/FlinkApp.htmlResponse.spec.ts +260 -0
- package/spec/FlinkApp.onError.invocation.spec.ts +151 -0
- package/spec/FlinkApp.onError.spec.ts +1 -2
- package/spec/FlinkApp.query.spec.ts +107 -0
- package/spec/FlinkApp.routeOrdering.spec.ts +61 -0
- package/spec/FlinkApp.undefinedResponse.spec.ts +123 -0
- package/spec/FlinkApp.validationMode.spec.ts +155 -0
- package/spec/FlinkJob.spec.ts +171 -0
- package/spec/FlinkLogFactory.spec.ts +337 -0
- package/spec/FlinkRepo.spec.ts +1 -1
- package/spec/LeaderElection.spec.ts +174 -0
- package/spec/StreamingIntegration.spec.ts +139 -0
- package/spec/ToolExecutor.spec.ts +465 -0
- package/spec/TypeScriptCompiler.spec.ts +1 -1
- package/spec/TypeScriptSourceParser.spec.ts +1215 -0
- package/spec/TypeScriptTokenizer.spec.ts +366 -0
- package/spec/ai/ContextCompaction.spec.ts +405 -0
- package/spec/ai/ConversationAgent.spec.ts +520 -0
- package/spec/ai/InMemoryConversationAgent.spec.ts +144 -0
- package/spec/ai/agentInstructions.spec.ts +358 -0
- package/spec/fixtures/agent-instructions/TestAgent.ts +24 -0
- package/spec/fixtures/agent-instructions/simple.md +3 -0
- package/spec/fixtures/agent-instructions/template.md +18 -0
- package/spec/fixtures/agent-instructions/yaml-format.yaml +9 -0
- package/spec/mock-project/dist/.tsbuildinfo +1 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar.js +56 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCar2.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema.js +52 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema2.js +52 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithArraySchema3.js +52 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema.js +54 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithLiteralSchema2.js +54 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile.js +57 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/GetCarWithSchemaInFile2.js +57 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler.js +53 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/ManuallyAddedHandler2.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchCar.js +57 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOnboardingSession.js +75 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchOrderWithComplexTypes.js +57 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchProductWithIntersection.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PatchUserWithUnion.js +58 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostCar.js +54 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogin.js +55 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PostLogout.js +54 -0
- package/spec/mock-project/dist/spec/mock-project/src/handlers/PutCar.js +54 -0
- package/spec/mock-project/dist/spec/mock-project/src/index.js +83 -0
- package/spec/mock-project/dist/spec/mock-project/src/repos/CarRepo.js +26 -0
- package/spec/mock-project/dist/spec/mock-project/src/schemas/Car.js +2 -0
- package/spec/mock-project/dist/spec/mock-project/src/schemas/DefaultExportSchema.js +2 -0
- package/spec/mock-project/dist/spec/mock-project/src/schemas/FileWithTwoSchemas.js +2 -0
- package/spec/mock-project/dist/src/FlinkApp.js +1000 -0
- package/spec/mock-project/dist/src/FlinkContext.js +2 -0
- package/spec/mock-project/dist/src/FlinkErrors.js +143 -0
- package/spec/mock-project/dist/src/FlinkHttpHandler.js +47 -0
- package/spec/mock-project/dist/src/FlinkJob.js +2 -0
- package/spec/mock-project/dist/src/FlinkLog.js +119 -0
- package/spec/mock-project/dist/src/FlinkLogFactory.js +617 -0
- package/spec/mock-project/dist/src/FlinkPlugin.js +2 -0
- package/spec/mock-project/dist/src/FlinkRepo.js +224 -0
- package/spec/mock-project/dist/src/FlinkRequestContext.js +74 -0
- package/spec/mock-project/dist/src/FlinkResponse.js +2 -0
- package/spec/mock-project/dist/src/ai/AgentExecutor.js +279 -0
- package/spec/mock-project/dist/src/ai/AgentRunner.js +632 -0
- package/spec/mock-project/dist/src/ai/ConversationAgent.js +402 -0
- package/spec/mock-project/dist/src/ai/ConversationFlinkAgent.js +422 -0
- package/spec/mock-project/dist/src/ai/FlinkAgent.js +699 -0
- package/spec/mock-project/dist/src/ai/FlinkTool.js +2 -0
- package/spec/mock-project/dist/src/ai/InMemoryConversationAgent.js +209 -0
- package/spec/mock-project/dist/src/ai/LLMAdapter.js +2 -0
- package/spec/mock-project/dist/src/ai/SubAgentExecutor.js +223 -0
- package/spec/mock-project/dist/src/ai/ToolExecutor.js +412 -0
- package/spec/mock-project/dist/src/ai/agentInstructions.js +246 -0
- package/spec/mock-project/dist/src/auth/FlinkAuthPlugin.js +2 -0
- package/spec/mock-project/dist/src/auth/FlinkAuthUser.js +2 -0
- package/spec/mock-project/dist/src/handlers/GetCar.js +26 -52
- package/spec/mock-project/dist/src/handlers/GetCar.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCar2.js +32 -54
- package/spec/mock-project/dist/src/handlers/GetCar2.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js +26 -48
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js +28 -48
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema2.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js +29 -48
- package/spec/mock-project/dist/src/handlers/GetCarWithArraySchema3.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js +26 -50
- package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js +28 -50
- package/spec/mock-project/dist/src/handlers/GetCarWithLiteralSchema2.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js +27 -53
- package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js +29 -53
- package/spec/mock-project/dist/src/handlers/GetCarWithSchemaInFile2.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js +16 -49
- package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js +25 -50
- package/spec/mock-project/dist/src/handlers/ManuallyAddedHandler2.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PatchCar.js +27 -53
- package/spec/mock-project/dist/src/handlers/PatchCar.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js +44 -70
- package/spec/mock-project/dist/src/handlers/PatchOnboardingSession.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js +27 -53
- package/spec/mock-project/dist/src/handlers/PatchOrderWithComplexTypes.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js +28 -54
- package/spec/mock-project/dist/src/handlers/PatchProductWithIntersection.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js +28 -54
- package/spec/mock-project/dist/src/handlers/PatchUserWithUnion.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PostCar.js +24 -50
- package/spec/mock-project/dist/src/handlers/PostCar.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PostLogin.js +25 -51
- package/spec/mock-project/dist/src/handlers/PostLogin.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PostLogout.js +24 -50
- package/spec/mock-project/dist/src/handlers/PostLogout.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/PutCar.js +24 -50
- package/spec/mock-project/dist/src/handlers/PutCar.js.map +1 -0
- package/spec/mock-project/dist/src/handlers/StreamWriterFactory.js +83 -0
- package/spec/mock-project/dist/src/index.js +52 -76
- package/spec/mock-project/dist/src/index.js.map +1 -0
- package/spec/mock-project/dist/src/mock-data-generator.js +9 -0
- package/spec/mock-project/dist/src/repos/CarRepo.js +12 -24
- package/spec/mock-project/dist/src/repos/CarRepo.js.map +1 -0
- package/spec/mock-project/dist/src/schemas/Car.js +3 -1
- package/spec/mock-project/dist/src/schemas/Car.js.map +1 -0
- package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js +3 -1
- package/spec/mock-project/dist/src/schemas/DefaultExportSchema.js.map +1 -0
- package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js +3 -1
- package/spec/mock-project/dist/src/schemas/FileWithTwoSchemas.js.map +1 -0
- package/spec/mock-project/dist/src/utils.js +290 -0
- package/spec/mock-project/tsconfig.json +6 -1
- package/spec/schema-generation-nested-objects.spec.ts +97 -0
- package/spec/testHelpers.ts +49 -0
- package/spec/utils.caseConversion.spec.ts +78 -0
- package/spec/utils.spec.ts +13 -13
- package/src/DependencyTracker.ts +166 -0
- package/src/FlinkApp.ts +919 -155
- package/src/FlinkContext.ts +43 -0
- package/src/FlinkErrors.ts +32 -12
- package/src/FlinkHttpHandler.ts +246 -28
- package/src/FlinkJob.ts +11 -0
- package/src/FlinkLog.ts +119 -12
- package/src/FlinkLogFactory.ts +699 -0
- package/src/FlinkRepo.ts +10 -3
- package/src/FlinkRequestContext.ts +95 -0
- package/src/FlinkResponse.ts +6 -0
- package/src/FlinkService.ts +49 -0
- package/src/LeaderElection.ts +203 -0
- package/src/SchemaCache.ts +232 -0
- package/src/TypeScriptCompiler.ts +1347 -610
- package/src/TypeScriptUtils.ts +5 -0
- package/src/ai/AgentRunner.ts +646 -0
- package/src/ai/ConversationAgent.ts +413 -0
- package/src/ai/FlinkAgent.ts +1069 -0
- package/src/ai/FlinkTool.ts +165 -0
- package/src/ai/InMemoryConversationAgent.ts +149 -0
- package/src/ai/LLMAdapter.ts +126 -0
- package/src/ai/ToolExecutor.ts +485 -0
- package/src/ai/agentInstructions.ts +245 -0
- package/src/ai/index.ts +8 -0
- package/src/ai/instructionFileLoader.ts +156 -0
- package/src/auth/FlinkAuthPlugin.ts +2 -1
- package/src/handlers/StreamWriterFactory.ts +84 -0
- package/src/index.ts +14 -0
- package/src/loadPluginSchemas.ts +141 -0
- package/src/schema-extraction/TypeScriptSourceParser.ts +1058 -0
- package/src/schema-extraction/TypeScriptTokenizer.ts +205 -0
- package/src/schema-extraction/index.ts +2 -0
- package/src/schema-extraction/types.ts +34 -0
- package/src/utils/loadFlinkConfig.ts +89 -0
- package/src/utils.ts +52 -0
- package/tsconfig.json +6 -1
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { FlinkApp } from "../src/FlinkApp";
|
|
2
|
+
import { FlinkContext } from "../src/FlinkContext";
|
|
3
|
+
import { GetHandler, Handler, HttpMethod } from "../src/FlinkHttpHandler";
|
|
4
|
+
|
|
5
|
+
const request = require("supertest");
|
|
6
|
+
|
|
7
|
+
interface TestContext extends FlinkContext {}
|
|
8
|
+
|
|
9
|
+
const resSchema = {
|
|
10
|
+
type: "object",
|
|
11
|
+
properties: {
|
|
12
|
+
id: { type: "string" },
|
|
13
|
+
},
|
|
14
|
+
required: ["id"],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("FlinkApp response validation when handler returns no data", () => {
|
|
18
|
+
let app: FlinkApp<TestContext>;
|
|
19
|
+
|
|
20
|
+
afterEach(async () => {
|
|
21
|
+
if (app && app.started) {
|
|
22
|
+
await app.stop();
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("should return 500 bad response when handler returns undefined data with a response schema", async () => {
|
|
27
|
+
const handler: GetHandler<TestContext, any> = async () => {
|
|
28
|
+
return { status: 200 } as any; // no data
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
app = new FlinkApp<TestContext>({ name: "test-undefined-data", port: 4200 });
|
|
32
|
+
await app.start();
|
|
33
|
+
|
|
34
|
+
app.addHandler({
|
|
35
|
+
default: handler,
|
|
36
|
+
Route: { method: HttpMethod.get, path: "/test", resSchema },
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const response = await request(app.expressApp).get("/test");
|
|
40
|
+
|
|
41
|
+
expect(response.status).toBe(500);
|
|
42
|
+
expect(response.body.error.title).toBe("Bad response");
|
|
43
|
+
expect(response.body.error.detail).toContain("no data");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should NOT return 500 when handler returns status 204 with no data (even if schema is defined)", async () => {
|
|
47
|
+
const handler: Handler<TestContext, any, any> = async () => {
|
|
48
|
+
return { status: 204 } as any;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
app = new FlinkApp<TestContext>({ name: "test-204-no-data", port: 4201 });
|
|
52
|
+
await app.start();
|
|
53
|
+
|
|
54
|
+
app.addHandler({
|
|
55
|
+
default: handler,
|
|
56
|
+
Route: { method: HttpMethod.patch, path: "/test", resSchema },
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const response = await request(app.expressApp).patch("/test");
|
|
60
|
+
|
|
61
|
+
expect(response.status).toBe(204);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("should validate normally when handler returns valid data", async () => {
|
|
65
|
+
const handler: GetHandler<TestContext, any> = async () => {
|
|
66
|
+
return { data: { id: "abc-123" } };
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
app = new FlinkApp<TestContext>({ name: "test-valid-data", port: 4202 });
|
|
70
|
+
await app.start();
|
|
71
|
+
|
|
72
|
+
app.addHandler({
|
|
73
|
+
default: handler,
|
|
74
|
+
Route: { method: HttpMethod.get, path: "/test", resSchema },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const response = await request(app.expressApp).get("/test");
|
|
78
|
+
|
|
79
|
+
expect(response.status).toBe(200);
|
|
80
|
+
expect(response.body.data.id).toBe("abc-123");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it("should return 500 bad response when handler returns invalid data against schema", async () => {
|
|
84
|
+
const handler: GetHandler<TestContext, any> = async () => {
|
|
85
|
+
return { data: { wrongField: 123 } }; // missing required "id"
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
app = new FlinkApp<TestContext>({ name: "test-invalid-data", port: 4203 });
|
|
89
|
+
await app.start();
|
|
90
|
+
|
|
91
|
+
app.addHandler({
|
|
92
|
+
default: handler,
|
|
93
|
+
Route: { method: HttpMethod.get, path: "/test", resSchema },
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const response = await request(app.expressApp).get("/test");
|
|
97
|
+
|
|
98
|
+
expect(response.status).toBe(500);
|
|
99
|
+
expect(response.body.error.title).toBe("Bad response");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("should not crash the server when handler returns undefined data", async () => {
|
|
103
|
+
const handler: GetHandler<TestContext, any> = async () => {
|
|
104
|
+
return { status: 200 } as any;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
app = new FlinkApp<TestContext>({ name: "test-no-crash", port: 4204 });
|
|
108
|
+
await app.start();
|
|
109
|
+
|
|
110
|
+
app.addHandler({
|
|
111
|
+
default: handler,
|
|
112
|
+
Route: { method: HttpMethod.get, path: "/test", resSchema },
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// First request triggers the bad response
|
|
116
|
+
await request(app.expressApp).get("/test");
|
|
117
|
+
|
|
118
|
+
// Server should still be running and able to handle further requests
|
|
119
|
+
const second = await request(app.expressApp).get("/test");
|
|
120
|
+
expect(second.status).toBe(500); // still returns 500, but doesn't crash
|
|
121
|
+
expect(app.started).toBe(true);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { FlinkApp } from "../src/FlinkApp";
|
|
2
|
+
import { FlinkContext } from "../src/FlinkContext";
|
|
3
|
+
import { Handler, RouteProps, ValidationMode } from "../src/FlinkHttpHandler";
|
|
4
|
+
|
|
5
|
+
interface TestContext extends FlinkContext {}
|
|
6
|
+
|
|
7
|
+
// Test schemas
|
|
8
|
+
interface ValidRequest {
|
|
9
|
+
name: string;
|
|
10
|
+
age: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface ValidResponse {
|
|
14
|
+
message: string;
|
|
15
|
+
status: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe("FlinkApp validation modes", () => {
|
|
19
|
+
let app: FlinkApp<TestContext>;
|
|
20
|
+
|
|
21
|
+
beforeEach(async () => {
|
|
22
|
+
app = new FlinkApp<TestContext>({
|
|
23
|
+
name: "validation-test-app",
|
|
24
|
+
debug: false,
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
afterEach(async () => {
|
|
29
|
+
if (app && app.started) {
|
|
30
|
+
await app.stop();
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("ValidationMode enum", () => {
|
|
35
|
+
it("should have all validation mode values", () => {
|
|
36
|
+
expect(ValidationMode.Validate).toBe("Validate");
|
|
37
|
+
expect(ValidationMode.SkipValidation).toBe("SkipValidation");
|
|
38
|
+
expect(ValidationMode.ValidateRequest).toBe("ValidateRequest");
|
|
39
|
+
expect(ValidationMode.ValidateResponse).toBe("ValidateResponse");
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("RouteProps validation property", () => {
|
|
44
|
+
it("should accept validation property with ValidationMode.Validate", () => {
|
|
45
|
+
const route: RouteProps = {
|
|
46
|
+
path: "/test",
|
|
47
|
+
validation: ValidationMode.Validate,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
expect(route.validation).toBe(ValidationMode.Validate);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should accept validation property with ValidationMode.SkipValidation", () => {
|
|
54
|
+
const route: RouteProps = {
|
|
55
|
+
path: "/test",
|
|
56
|
+
validation: ValidationMode.SkipValidation,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
expect(route.validation).toBe(ValidationMode.SkipValidation);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("should accept validation property with ValidationMode.ValidateRequest", () => {
|
|
63
|
+
const route: RouteProps = {
|
|
64
|
+
path: "/test",
|
|
65
|
+
validation: ValidationMode.ValidateRequest,
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
expect(route.validation).toBe(ValidationMode.ValidateRequest);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("should accept validation property with ValidationMode.ValidateResponse", () => {
|
|
72
|
+
const route: RouteProps = {
|
|
73
|
+
path: "/test",
|
|
74
|
+
validation: ValidationMode.ValidateResponse,
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
expect(route.validation).toBe(ValidationMode.ValidateResponse);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("should be optional and default to undefined", () => {
|
|
81
|
+
const route: RouteProps = {
|
|
82
|
+
path: "/test",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
expect(route.validation).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe("Handler type compatibility", () => {
|
|
90
|
+
it("should allow handlers with validation modes to compile", () => {
|
|
91
|
+
// This test verifies TypeScript compilation
|
|
92
|
+
|
|
93
|
+
const handler: Handler<TestContext, ValidRequest, ValidResponse> = async ({ ctx, req }) => {
|
|
94
|
+
return {
|
|
95
|
+
status: 200,
|
|
96
|
+
data: {
|
|
97
|
+
message: "success",
|
|
98
|
+
status: "ok",
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
expect(handler).toBeDefined();
|
|
104
|
+
expect(typeof handler).toBe("function");
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("should allow route props with different validation modes", () => {
|
|
108
|
+
const routes: RouteProps[] = [
|
|
109
|
+
{ path: "/test1", validation: ValidationMode.Validate },
|
|
110
|
+
{ path: "/test2", validation: ValidationMode.SkipValidation },
|
|
111
|
+
{ path: "/test3", validation: ValidationMode.ValidateRequest },
|
|
112
|
+
{ path: "/test4", validation: ValidationMode.ValidateResponse },
|
|
113
|
+
{ path: "/test5" }, // No validation specified
|
|
114
|
+
];
|
|
115
|
+
|
|
116
|
+
expect(routes.length).toBe(5);
|
|
117
|
+
expect(routes[0].validation).toBe(ValidationMode.Validate);
|
|
118
|
+
expect(routes[1].validation).toBe(ValidationMode.SkipValidation);
|
|
119
|
+
expect(routes[2].validation).toBe(ValidationMode.ValidateRequest);
|
|
120
|
+
expect(routes[3].validation).toBe(ValidationMode.ValidateResponse);
|
|
121
|
+
expect(routes[4].validation).toBeUndefined();
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("Backward compatibility", () => {
|
|
126
|
+
it("should not break existing code without validation property", () => {
|
|
127
|
+
const route: RouteProps = {
|
|
128
|
+
path: "/legacy",
|
|
129
|
+
permissions: ["admin"],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// TypeScript should compile this without errors
|
|
133
|
+
expect(route.path).toBe("/legacy");
|
|
134
|
+
expect(route.validation).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it("should allow all existing RouteProps properties with validation", () => {
|
|
138
|
+
const route: RouteProps = {
|
|
139
|
+
path: "/test",
|
|
140
|
+
permissions: ["admin", "user"],
|
|
141
|
+
skipAutoRegister: true,
|
|
142
|
+
docs: "Test endpoint",
|
|
143
|
+
order: -1,
|
|
144
|
+
validation: ValidationMode.SkipValidation,
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
expect(route.path).toBe("/test");
|
|
148
|
+
expect(route.permissions).toEqual(["admin", "user"]);
|
|
149
|
+
expect(route.skipAutoRegister).toBe(true);
|
|
150
|
+
expect(route.docs).toBe("Test endpoint");
|
|
151
|
+
expect(route.order).toBe(-1);
|
|
152
|
+
expect(route.validation).toBe(ValidationMode.SkipValidation);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { FlinkApp, autoRegisteredJobs } from "../src/FlinkApp";
|
|
2
|
+
import { FlinkContext } from "../src/FlinkContext";
|
|
3
|
+
import { FlinkJobFile } from "../src/FlinkJob";
|
|
4
|
+
import { FlinkLogFactory } from "../src/FlinkLogFactory";
|
|
5
|
+
|
|
6
|
+
interface TestContext extends FlinkContext {}
|
|
7
|
+
|
|
8
|
+
describe("FlinkJob error handling", () => {
|
|
9
|
+
let app: FlinkApp<TestContext>;
|
|
10
|
+
let consoleErrorSpy: jasmine.Spy;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
consoleErrorSpy = spyOn(console, "error");
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(async () => {
|
|
17
|
+
autoRegisteredJobs.length = 0;
|
|
18
|
+
if (app?.started) {
|
|
19
|
+
await app.stop();
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("should catch and log errors from afterDelay 0ms jobs without crashing", async () => {
|
|
24
|
+
const job: FlinkJobFile = {
|
|
25
|
+
Job: { id: "failing-job-0ms", afterDelay: "0ms" },
|
|
26
|
+
default: async () => {
|
|
27
|
+
throw new Error("Job error 0ms");
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
autoRegisteredJobs.push(job);
|
|
32
|
+
|
|
33
|
+
app = new FlinkApp<TestContext>({ name: "test-job-errors-0ms", disableHttpServer: true });
|
|
34
|
+
await app.start();
|
|
35
|
+
|
|
36
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
37
|
+
|
|
38
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should catch and log errors from afterDelay jobs without crashing", async () => {
|
|
42
|
+
const job: FlinkJobFile = {
|
|
43
|
+
Job: { id: "failing-job-delay", afterDelay: "10ms" },
|
|
44
|
+
default: async () => {
|
|
45
|
+
throw new Error("Job error with delay");
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
autoRegisteredJobs.push(job);
|
|
50
|
+
|
|
51
|
+
app = new FlinkApp<TestContext>({ name: "test-job-errors-delay", disableHttpServer: true });
|
|
52
|
+
await app.start();
|
|
53
|
+
|
|
54
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
55
|
+
|
|
56
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("should catch and log errors from interval jobs without crashing", async () => {
|
|
60
|
+
const job: FlinkJobFile = {
|
|
61
|
+
Job: { id: "failing-interval-job", interval: "10ms" },
|
|
62
|
+
default: async () => {
|
|
63
|
+
throw new Error("Interval job error");
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
autoRegisteredJobs.push(job);
|
|
68
|
+
|
|
69
|
+
app = new FlinkApp<TestContext>({ name: "test-job-errors-interval", disableHttpServer: true });
|
|
70
|
+
await app.start();
|
|
71
|
+
|
|
72
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
73
|
+
|
|
74
|
+
expect(consoleErrorSpy).toHaveBeenCalled();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("should run afterDelay 0ms job exactly once", async () => {
|
|
78
|
+
let runCount = 0;
|
|
79
|
+
|
|
80
|
+
const job: FlinkJobFile = {
|
|
81
|
+
Job: { id: "once-job-0ms", afterDelay: "0ms" },
|
|
82
|
+
default: async () => {
|
|
83
|
+
runCount++;
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
autoRegisteredJobs.push(job);
|
|
88
|
+
|
|
89
|
+
app = new FlinkApp<TestContext>({ name: "test-job-once-0ms", disableHttpServer: true });
|
|
90
|
+
await app.start();
|
|
91
|
+
|
|
92
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
93
|
+
|
|
94
|
+
expect(runCount).toBe(1);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("FlinkJob leader election with runOnAllInstances", () => {
|
|
99
|
+
let app: FlinkApp<TestContext>;
|
|
100
|
+
|
|
101
|
+
afterEach(async () => {
|
|
102
|
+
autoRegisteredJobs.length = 0;
|
|
103
|
+
if (app?.started) {
|
|
104
|
+
await app.stop();
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("should run all jobs when leader election is enabled but no db is configured", async () => {
|
|
109
|
+
const schedulerLog = FlinkLogFactory.createLogger("flink.scheduler");
|
|
110
|
+
const warnSpy = spyOn(schedulerLog, "warn");
|
|
111
|
+
|
|
112
|
+
let leaderJobRan = false;
|
|
113
|
+
let allInstanceJobRan = false;
|
|
114
|
+
|
|
115
|
+
autoRegisteredJobs.push({
|
|
116
|
+
Job: { id: "leader-only-job", afterDelay: "0ms" },
|
|
117
|
+
default: async () => {
|
|
118
|
+
leaderJobRan = true;
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
autoRegisteredJobs.push({
|
|
123
|
+
Job: { id: "all-instance-job", afterDelay: "0ms", runOnAllInstances: true },
|
|
124
|
+
default: async () => {
|
|
125
|
+
allInstanceJobRan = true;
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
app = new FlinkApp<TestContext>({
|
|
130
|
+
name: "test-leader-no-db",
|
|
131
|
+
disableHttpServer: true,
|
|
132
|
+
scheduling: { leaderElection: true },
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
await app.start();
|
|
136
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
137
|
+
|
|
138
|
+
// Without db, falls back to running all jobs
|
|
139
|
+
expect(leaderJobRan).toBe(true);
|
|
140
|
+
expect(allInstanceJobRan).toBe(true);
|
|
141
|
+
expect(warnSpy).toHaveBeenCalled();
|
|
142
|
+
const warnMessage = warnSpy.calls.mostRecent().args[0];
|
|
143
|
+
expect(warnMessage).toContain("Leader election is enabled but no database is configured");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("should not start scheduler for leader-only jobs when not leader", async () => {
|
|
147
|
+
// Without a real MongoDB, we can't fully test leader election.
|
|
148
|
+
// This test verifies that when leaderElection is enabled without db,
|
|
149
|
+
// the warning is shown and all jobs still run as a fallback.
|
|
150
|
+
let jobRanCount = 0;
|
|
151
|
+
|
|
152
|
+
autoRegisteredJobs.push({
|
|
153
|
+
Job: { id: "interval-job", interval: "50ms" },
|
|
154
|
+
default: async () => {
|
|
155
|
+
jobRanCount++;
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
app = new FlinkApp<TestContext>({
|
|
160
|
+
name: "test-no-db-fallback",
|
|
161
|
+
disableHttpServer: true,
|
|
162
|
+
scheduling: { leaderElection: true },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await app.start();
|
|
166
|
+
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
167
|
+
|
|
168
|
+
// Should have run at least once since it falls back without db
|
|
169
|
+
expect(jobRanCount).toBeGreaterThan(0);
|
|
170
|
+
});
|
|
171
|
+
});
|