@flink-app/test-utils 0.14.1 → 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.
@@ -0,0 +1,135 @@
1
+ import { z } from "zod";
2
+ import { FlinkContext } from "@flink-app/flink";
3
+ import type { FlinkTool, FlinkToolProps, ToolResult } from "@flink-app/flink/ai";
4
+
5
+ export interface MockToolConfig<Input, Output> {
6
+ name: string;
7
+ description?: string;
8
+ inputSchema: z.ZodType<Input>;
9
+ outputSchema?: z.ZodType<Output>;
10
+
11
+ // Response strategies
12
+ response?: Output; // Canned success response
13
+ error?: { error: string; code?: string }; // Canned error response
14
+ fn?: FlinkTool<any, Input, Output>; // Custom function
15
+
16
+ // Permissions
17
+ permissions?: FlinkToolProps["permissions"];
18
+ }
19
+
20
+ export interface MockToolInvocation<Input> {
21
+ input: Input;
22
+ user?: any;
23
+ }
24
+
25
+ export interface MockToolResult<Input, Output> {
26
+ props: FlinkToolProps;
27
+ fn: FlinkTool<any, Input, Output>;
28
+
29
+ // Invocation tracking
30
+ invocations: MockToolInvocation<Input>[];
31
+ getLastInvocation(): MockToolInvocation<Input> | undefined;
32
+ reset(): void;
33
+ }
34
+
35
+ /**
36
+ * Creates a mock tool with tracking and canned responses
37
+ *
38
+ * Features:
39
+ * - Simple canned responses
40
+ * - Error simulation
41
+ * - Custom function support
42
+ * - Automatic invocation tracking
43
+ * - Validation helpers
44
+ *
45
+ * @example
46
+ * // Simple canned response
47
+ * const weatherTool = mockTool({
48
+ * name: "get_weather",
49
+ * inputSchema: z.object({ city: z.string() }),
50
+ * response: { temperature: 22, conditions: "sunny" }
51
+ * });
52
+ *
53
+ * @example
54
+ * // Custom function with tracking
55
+ * const calculatorTool = mockTool({
56
+ * name: "calculate",
57
+ * inputSchema: z.object({ a: z.number(), b: z.number() }),
58
+ * fn: async ({ input }) => ({
59
+ * success: true,
60
+ * data: { result: input.a + input.b }
61
+ * })
62
+ * });
63
+ *
64
+ * @example
65
+ * // Error simulation
66
+ * const failingTool = mockTool({
67
+ * name: "fail",
68
+ * inputSchema: z.object({}),
69
+ * error: { error: "Tool failed", code: "MOCK_ERROR" }
70
+ * });
71
+ */
72
+ export function mockTool<Input = any, Output = any>(
73
+ config: MockToolConfig<Input, Output>
74
+ ): MockToolResult<Input, Output> {
75
+ const invocations: MockToolInvocation<Input>[] = [];
76
+
77
+ // Create the tool props
78
+ const props: FlinkToolProps = {
79
+ id: config.name,
80
+ description: config.description || `Mock tool: ${config.name}`,
81
+ inputSchema: config.inputSchema,
82
+ outputSchema: config.outputSchema,
83
+ permissions: config.permissions,
84
+ };
85
+
86
+ // Create the tool function with invocation tracking
87
+ const fn: FlinkTool<any, Input, Output> = async ({ input, ctx, user }) => {
88
+ // Track invocation
89
+ invocations.push({ input, user });
90
+
91
+ // If custom function provided, use it
92
+ if (config.fn) {
93
+ return config.fn({ input, ctx, user });
94
+ }
95
+
96
+ // If error configured, return error
97
+ if (config.error) {
98
+ return {
99
+ success: false,
100
+ error: config.error.error,
101
+ code: config.error.code,
102
+ };
103
+ }
104
+
105
+ // If response configured, return success with data
106
+ if (config.response !== undefined) {
107
+ return {
108
+ success: true,
109
+ data: config.response,
110
+ };
111
+ }
112
+
113
+ // Default: return empty success
114
+ return {
115
+ success: true,
116
+ data: {} as Output,
117
+ };
118
+ };
119
+
120
+ const getLastInvocation = (): MockToolInvocation<Input> | undefined => {
121
+ return invocations[invocations.length - 1];
122
+ };
123
+
124
+ const reset = (): void => {
125
+ invocations.length = 0;
126
+ };
127
+
128
+ return {
129
+ props,
130
+ fn,
131
+ invocations,
132
+ getLastInvocation,
133
+ reset,
134
+ };
135
+ }
package/src/http.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { FlinkApp, FlinkResponse } from "@flink-app/flink";
1
+ import { FlinkApp, FlinkContext, FlinkResponse } from "@flink-app/flink";
2
2
  import got, { GotJSONOptions } from "got";
3
3
  import qs from "qs";
4
4
 
@@ -34,8 +34,8 @@ export type HttpOpts = {
34
34
  * Initializes test flink app.
35
35
  * Must be invoked prior to using test HTTP methods.
36
36
  */
37
- export function init(_app: FlinkApp<any>, host = "localhost") {
38
- app = _app;
37
+ export function init<C extends FlinkContext<any>>(_app: FlinkApp<C>, host = "localhost") {
38
+ app = _app as unknown as FlinkApp<any>;
39
39
  baseUrl = `http://${host}:${app.port}`;
40
40
  }
41
41
 
package/src/index.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export * from "./http";
2
2
  export * from "./mocks";
3
+ export * from "./ai";
4
+ export * from "./requestContext";
package/src/mocks.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { FlinkAuthPlugin, FlinkRequest } from "@flink-app/flink";
1
+ import { FlinkAuthPlugin, FlinkContext, FlinkRequest } from "@flink-app/flink";
2
2
 
3
3
  interface TestFlinkRequest<T, P, Q> extends Omit<Partial<FlinkRequest<T, P, Q>>, "body" | "params" | "query"> {
4
4
  body?: T;
@@ -42,3 +42,23 @@ export function noOpAuthPlugin(): FlinkAuthPlugin {
42
42
  createToken: async () => "mock-token",
43
43
  };
44
44
  }
45
+
46
+ /**
47
+ * Creates a mock FlinkContext with only the parts you need.
48
+ * Useful for testing services with mocked dependencies.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const carService = new CarService();
53
+ * carService.ctx = mockCtx<AppCtx>({
54
+ * repos: { carRepo: mockCarRepo },
55
+ * });
56
+ * ```
57
+ */
58
+ export function mockCtx<C extends FlinkContext>(partial: Partial<C> = {}): C {
59
+ return {
60
+ repos: {},
61
+ plugins: {},
62
+ ...partial,
63
+ } as C;
64
+ }
@@ -0,0 +1,44 @@
1
+ import { RequestContext, requestContext } from '@flink-app/flink';
2
+
3
+ /**
4
+ * Create a mock request context for testing
5
+ */
6
+ export function createMockRequestContext(overrides?: Partial<RequestContext>): RequestContext {
7
+ return {
8
+ reqId: "test-req-" + Math.random().toString(36).substring(7),
9
+ timestamp: Date.now(),
10
+ ...overrides,
11
+ };
12
+ }
13
+
14
+ /**
15
+ * Execute a function within a mocked request context
16
+ * Useful for testing tools and agents that use AsyncLocalStorage
17
+ *
18
+ * @example
19
+ * const result = await withRequestContext(
20
+ * { user: { id: '123', username: 'test' }, reqId: 'req-123' },
21
+ * async () => {
22
+ * return await someTool.execute({ carId: 'car-1' });
23
+ * }
24
+ * );
25
+ */
26
+ export async function withRequestContext<T>(
27
+ context: Partial<RequestContext>,
28
+ fn: () => T | Promise<T>
29
+ ): Promise<T> {
30
+ const fullContext = createMockRequestContext(context);
31
+ return requestContext.run(fullContext, fn);
32
+ }
33
+
34
+ /**
35
+ * Create a test user with common properties
36
+ */
37
+ export function createMockUser(overrides?: any) {
38
+ return {
39
+ id: "user-" + Math.random().toString(36).substring(7),
40
+ username: "testuser",
41
+ email: "test@example.com",
42
+ ...overrides,
43
+ };
44
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "exclude": ["spec/**/*.ts", "node_modules/*", "dist/*"]
4
+ }
package/tsconfig.json CHANGED
@@ -8,16 +8,17 @@
8
8
  "allowSyntheticDefaultImports": true,
9
9
  "strict": true,
10
10
  "forceConsistentCasingInFileNames": true,
11
- "module": "commonjs",
12
- "moduleResolution": "node",
11
+ "module": "node16",
12
+ "moduleResolution": "node16",
13
13
  "resolveJsonModule": true,
14
14
  "isolatedModules": true,
15
15
  "noEmit": false,
16
16
  "declaration": true,
17
17
  "experimentalDecorators": true,
18
18
  "checkJs": false,
19
- "outDir": "dist"
19
+ "outDir": "dist",
20
+ "types": ["jasmine", "node"]
20
21
  },
21
- "include": ["./src/**/*.ts", "./.flink/**/*.ts"],
22
- "exclude": ["./node_modules/*"]
22
+ "include": ["./src/**/*.ts", "./.flink/**/*.ts", "./spec/**/*.ts"],
23
+ "exclude": ["./node_modules/*", "./dist/*"]
23
24
  }