@flink-app/test-utils 1.0.0 → 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,64 @@
1
+ import { z } from "zod";
2
+ import type { FlinkTool, FlinkToolProps } from "@flink-app/flink/ai";
3
+ export interface MockToolConfig<Input, Output> {
4
+ name: string;
5
+ description?: string;
6
+ inputSchema: z.ZodType<Input>;
7
+ outputSchema?: z.ZodType<Output>;
8
+ response?: Output;
9
+ error?: {
10
+ error: string;
11
+ code?: string;
12
+ };
13
+ fn?: FlinkTool<any, Input, Output>;
14
+ permissions?: FlinkToolProps["permissions"];
15
+ }
16
+ export interface MockToolInvocation<Input> {
17
+ input: Input;
18
+ user?: any;
19
+ }
20
+ export interface MockToolResult<Input, Output> {
21
+ props: FlinkToolProps;
22
+ fn: FlinkTool<any, Input, Output>;
23
+ invocations: MockToolInvocation<Input>[];
24
+ getLastInvocation(): MockToolInvocation<Input> | undefined;
25
+ reset(): void;
26
+ }
27
+ /**
28
+ * Creates a mock tool with tracking and canned responses
29
+ *
30
+ * Features:
31
+ * - Simple canned responses
32
+ * - Error simulation
33
+ * - Custom function support
34
+ * - Automatic invocation tracking
35
+ * - Validation helpers
36
+ *
37
+ * @example
38
+ * // Simple canned response
39
+ * const weatherTool = mockTool({
40
+ * name: "get_weather",
41
+ * inputSchema: z.object({ city: z.string() }),
42
+ * response: { temperature: 22, conditions: "sunny" }
43
+ * });
44
+ *
45
+ * @example
46
+ * // Custom function with tracking
47
+ * const calculatorTool = mockTool({
48
+ * name: "calculate",
49
+ * inputSchema: z.object({ a: z.number(), b: z.number() }),
50
+ * fn: async ({ input }) => ({
51
+ * success: true,
52
+ * data: { result: input.a + input.b }
53
+ * })
54
+ * });
55
+ *
56
+ * @example
57
+ * // Error simulation
58
+ * const failingTool = mockTool({
59
+ * name: "fail",
60
+ * inputSchema: z.object({}),
61
+ * error: { error: "Tool failed", code: "MOCK_ERROR" }
62
+ * });
63
+ */
64
+ export declare function mockTool<Input = any, Output = any>(config: MockToolConfig<Input, Output>): MockToolResult<Input, Output>;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.mockTool = mockTool;
40
+ /**
41
+ * Creates a mock tool with tracking and canned responses
42
+ *
43
+ * Features:
44
+ * - Simple canned responses
45
+ * - Error simulation
46
+ * - Custom function support
47
+ * - Automatic invocation tracking
48
+ * - Validation helpers
49
+ *
50
+ * @example
51
+ * // Simple canned response
52
+ * const weatherTool = mockTool({
53
+ * name: "get_weather",
54
+ * inputSchema: z.object({ city: z.string() }),
55
+ * response: { temperature: 22, conditions: "sunny" }
56
+ * });
57
+ *
58
+ * @example
59
+ * // Custom function with tracking
60
+ * const calculatorTool = mockTool({
61
+ * name: "calculate",
62
+ * inputSchema: z.object({ a: z.number(), b: z.number() }),
63
+ * fn: async ({ input }) => ({
64
+ * success: true,
65
+ * data: { result: input.a + input.b }
66
+ * })
67
+ * });
68
+ *
69
+ * @example
70
+ * // Error simulation
71
+ * const failingTool = mockTool({
72
+ * name: "fail",
73
+ * inputSchema: z.object({}),
74
+ * error: { error: "Tool failed", code: "MOCK_ERROR" }
75
+ * });
76
+ */
77
+ function mockTool(config) {
78
+ var _this = this;
79
+ var invocations = [];
80
+ // Create the tool props
81
+ var props = {
82
+ id: config.name,
83
+ description: config.description || "Mock tool: ".concat(config.name),
84
+ inputSchema: config.inputSchema,
85
+ outputSchema: config.outputSchema,
86
+ permissions: config.permissions,
87
+ };
88
+ // Create the tool function with invocation tracking
89
+ var fn = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
90
+ var input = _b.input, ctx = _b.ctx, user = _b.user;
91
+ return __generator(this, function (_c) {
92
+ // Track invocation
93
+ invocations.push({ input: input, user: user });
94
+ // If custom function provided, use it
95
+ if (config.fn) {
96
+ return [2 /*return*/, config.fn({ input: input, ctx: ctx, user: user })];
97
+ }
98
+ // If error configured, return error
99
+ if (config.error) {
100
+ return [2 /*return*/, {
101
+ success: false,
102
+ error: config.error.error,
103
+ code: config.error.code,
104
+ }];
105
+ }
106
+ // If response configured, return success with data
107
+ if (config.response !== undefined) {
108
+ return [2 /*return*/, {
109
+ success: true,
110
+ data: config.response,
111
+ }];
112
+ }
113
+ // Default: return empty success
114
+ return [2 /*return*/, {
115
+ success: true,
116
+ data: {},
117
+ }];
118
+ });
119
+ }); };
120
+ var getLastInvocation = function () {
121
+ return invocations[invocations.length - 1];
122
+ };
123
+ var reset = function () {
124
+ invocations.length = 0;
125
+ };
126
+ return {
127
+ props: props,
128
+ fn: fn,
129
+ invocations: invocations,
130
+ getLastInvocation: getLastInvocation,
131
+ reset: reset,
132
+ };
133
+ }
package/dist/http.d.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
  export type HttpOpts = {
3
3
  /***
4
4
  * Optional query string
@@ -26,7 +26,7 @@ export type HttpOpts = {
26
26
  * Initializes test flink app.
27
27
  * Must be invoked prior to using test HTTP methods.
28
28
  */
29
- export declare function init(_app: FlinkApp<any>, host?: string): void;
29
+ export declare function init<C extends FlinkContext<any>>(_app: FlinkApp<C>, host?: string): void;
30
30
  export declare function get<Res = any>(path: string, opts?: HttpOpts): Promise<FlinkResponse<Res>>;
31
31
  export declare function post<Req = any, Res = any>(path: string, body: Req, opts?: HttpOpts): Promise<FlinkResponse<Res>>;
32
32
  export declare function put<Req = any, Res = any>(path: string, body: Req, opts?: HttpOpts): Promise<FlinkResponse<Res>>;
package/dist/index.d.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/dist/index.js CHANGED
@@ -16,3 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./http"), exports);
18
18
  __exportStar(require("./mocks"), exports);
19
+ __exportStar(require("./ai"), exports);
20
+ __exportStar(require("./requestContext"), exports);
package/dist/mocks.d.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
  interface TestFlinkRequest<T, P, Q> extends Omit<Partial<FlinkRequest<T, P, Q>>, "body" | "params" | "query"> {
3
3
  body?: T;
4
4
  params?: P;
@@ -17,4 +17,17 @@ export declare function mockReq<T, P, Q>(req?: TestFlinkRequest<T, P, Q>): Flink
17
17
  * @returns
18
18
  */
19
19
  export declare function noOpAuthPlugin(): FlinkAuthPlugin;
20
+ /**
21
+ * Creates a mock FlinkContext with only the parts you need.
22
+ * Useful for testing services with mocked dependencies.
23
+ *
24
+ * @example
25
+ * ```typescript
26
+ * const carService = new CarService();
27
+ * carService.ctx = mockCtx<AppCtx>({
28
+ * repos: { carRepo: mockCarRepo },
29
+ * });
30
+ * ```
31
+ */
32
+ export declare function mockCtx<C extends FlinkContext>(partial?: Partial<C>): C;
20
33
  export {};
package/dist/mocks.js CHANGED
@@ -49,6 +49,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
49
49
  Object.defineProperty(exports, "__esModule", { value: true });
50
50
  exports.mockReq = mockReq;
51
51
  exports.noOpAuthPlugin = noOpAuthPlugin;
52
+ exports.mockCtx = mockCtx;
52
53
  /**
53
54
  * Creates a mocked FlinkRequest object where only essential properties are required.
54
55
  * Will convert req body to JSON to ensure the body is a plain object.
@@ -81,3 +82,19 @@ function noOpAuthPlugin() {
81
82
  }); }); },
82
83
  };
83
84
  }
85
+ /**
86
+ * Creates a mock FlinkContext with only the parts you need.
87
+ * Useful for testing services with mocked dependencies.
88
+ *
89
+ * @example
90
+ * ```typescript
91
+ * const carService = new CarService();
92
+ * carService.ctx = mockCtx<AppCtx>({
93
+ * repos: { carRepo: mockCarRepo },
94
+ * });
95
+ * ```
96
+ */
97
+ function mockCtx(partial) {
98
+ if (partial === void 0) { partial = {}; }
99
+ return __assign({ repos: {}, plugins: {} }, partial);
100
+ }
@@ -0,0 +1,22 @@
1
+ import { RequestContext } from '@flink-app/flink';
2
+ /**
3
+ * Create a mock request context for testing
4
+ */
5
+ export declare function createMockRequestContext(overrides?: Partial<RequestContext>): RequestContext;
6
+ /**
7
+ * Execute a function within a mocked request context
8
+ * Useful for testing tools and agents that use AsyncLocalStorage
9
+ *
10
+ * @example
11
+ * const result = await withRequestContext(
12
+ * { user: { id: '123', username: 'test' }, reqId: 'req-123' },
13
+ * async () => {
14
+ * return await someTool.execute({ carId: 'car-1' });
15
+ * }
16
+ * );
17
+ */
18
+ export declare function withRequestContext<T>(context: Partial<RequestContext>, fn: () => T | Promise<T>): Promise<T>;
19
+ /**
20
+ * Create a test user with common properties
21
+ */
22
+ export declare function createMockUser(overrides?: any): any;
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
+ return new (P || (P = Promise))(function (resolve, reject) {
16
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
17
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
18
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
19
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
20
+ });
21
+ };
22
+ var __generator = (this && this.__generator) || function (thisArg, body) {
23
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
24
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
25
+ function verb(n) { return function (v) { return step([n, v]); }; }
26
+ function step(op) {
27
+ if (f) throw new TypeError("Generator is already executing.");
28
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
29
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
30
+ if (y = 0, t) op = [op[0] & 2, t.value];
31
+ switch (op[0]) {
32
+ case 0: case 1: t = op; break;
33
+ case 4: _.label++; return { value: op[1], done: false };
34
+ case 5: _.label++; y = op[1]; op = [0]; continue;
35
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
36
+ default:
37
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
38
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
39
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
40
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
41
+ if (t[2]) _.ops.pop();
42
+ _.trys.pop(); continue;
43
+ }
44
+ op = body.call(thisArg, _);
45
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
46
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
47
+ }
48
+ };
49
+ Object.defineProperty(exports, "__esModule", { value: true });
50
+ exports.createMockRequestContext = createMockRequestContext;
51
+ exports.withRequestContext = withRequestContext;
52
+ exports.createMockUser = createMockUser;
53
+ var flink_1 = require("@flink-app/flink");
54
+ /**
55
+ * Create a mock request context for testing
56
+ */
57
+ function createMockRequestContext(overrides) {
58
+ return __assign({ reqId: "test-req-" + Math.random().toString(36).substring(7), timestamp: Date.now() }, overrides);
59
+ }
60
+ /**
61
+ * Execute a function within a mocked request context
62
+ * Useful for testing tools and agents that use AsyncLocalStorage
63
+ *
64
+ * @example
65
+ * const result = await withRequestContext(
66
+ * { user: { id: '123', username: 'test' }, reqId: 'req-123' },
67
+ * async () => {
68
+ * return await someTool.execute({ carId: 'car-1' });
69
+ * }
70
+ * );
71
+ */
72
+ function withRequestContext(context, fn) {
73
+ return __awaiter(this, void 0, void 0, function () {
74
+ var fullContext;
75
+ return __generator(this, function (_a) {
76
+ fullContext = createMockRequestContext(context);
77
+ return [2 /*return*/, flink_1.requestContext.run(fullContext, fn)];
78
+ });
79
+ });
80
+ }
81
+ /**
82
+ * Create a test user with common properties
83
+ */
84
+ function createMockUser(overrides) {
85
+ return __assign({ id: "user-" + Math.random().toString(36).substring(7), username: "testuser", email: "test@example.com" }, overrides);
86
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/test-utils",
3
- "version": "1.0.0",
3
+ "version": "2.0.0-alpha.100",
4
4
  "description": "Test utils for Flink",
5
5
  "author": "joel@frost.se",
6
6
  "license": "MIT",
@@ -11,20 +11,24 @@
11
11
  },
12
12
  "dependencies": {
13
13
  "got": "^9.6.0",
14
- "qs": "^6.7.0"
14
+ "qs": "^6.7.0",
15
+ "zod": "^4.3.6"
15
16
  },
16
17
  "devDependencies": {
17
18
  "@types/got": "^9.6.12",
19
+ "@types/jasmine": "^3.7.1",
18
20
  "@types/node": "22.13.10",
19
21
  "@types/qs": "^6.9.7",
22
+ "jasmine": "^3.10.0",
23
+ "jasmine-ts": "^0.3.3",
20
24
  "ts-node": "^10.9.2",
21
25
  "tsc-watch": "^4.2.9",
22
- "@flink-app/flink": "1.0.0"
26
+ "@flink-app/flink": "2.0.0-alpha.100"
23
27
  },
24
28
  "gitHead": "4243e3b3cd6d4e1ca001a61baa8436bf2bbe4113",
25
29
  "scripts": {
26
- "test": "echo \"Error: no test specified\"",
27
- "build": "tsc",
30
+ "test": "jasmine-ts --config=./spec/support/jasmine.json",
31
+ "build": "tsc --project tsconfig.dist.json",
28
32
  "watch": "tsc-watch",
29
33
  "clean": "rimraf dist .flink"
30
34
  }