@schmock/core 1.0.0
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/dist/builder.d.ts +62 -0
- package/dist/builder.d.ts.map +1 -0
- package/dist/builder.js +432 -0
- package/dist/errors.d.ts +56 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +92 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/dist/parser.d.ts +19 -0
- package/dist/parser.d.ts.map +1 -0
- package/dist/parser.js +40 -0
- package/dist/types.d.ts +15 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/package.json +39 -0
- package/src/builder.d.ts.map +1 -0
- package/src/builder.test.ts +289 -0
- package/src/builder.ts +580 -0
- package/src/debug.test.ts +241 -0
- package/src/delay.test.ts +319 -0
- package/src/errors.d.ts.map +1 -0
- package/src/errors.test.ts +223 -0
- package/src/errors.ts +124 -0
- package/src/factory.test.ts +133 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.ts +80 -0
- package/src/namespace.test.ts +273 -0
- package/src/parser.d.ts.map +1 -0
- package/src/parser.test.ts +131 -0
- package/src/parser.ts +61 -0
- package/src/plugin-system.test.ts +511 -0
- package/src/response-parsing.test.ts +255 -0
- package/src/route-matching.test.ts +351 -0
- package/src/smart-defaults.test.ts +361 -0
- package/src/steps/async-support.steps.ts +427 -0
- package/src/steps/basic-usage.steps.ts +316 -0
- package/src/steps/developer-experience.steps.ts +439 -0
- package/src/steps/error-handling.steps.ts +387 -0
- package/src/steps/fluent-api.steps.ts +252 -0
- package/src/steps/http-methods.steps.ts +397 -0
- package/src/steps/performance-reliability.steps.ts +459 -0
- package/src/steps/plugin-integration.steps.ts +279 -0
- package/src/steps/route-key-format.steps.ts +118 -0
- package/src/steps/state-concurrency.steps.ts +643 -0
- package/src/steps/stateful-workflows.steps.ts +351 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.ts +17 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
PluginError,
|
|
4
|
+
ResourceLimitError,
|
|
5
|
+
ResponseGenerationError,
|
|
6
|
+
RouteDefinitionError,
|
|
7
|
+
RouteNotFoundError,
|
|
8
|
+
RouteParseError,
|
|
9
|
+
SchemaGenerationError,
|
|
10
|
+
SchemaValidationError,
|
|
11
|
+
SchmockError,
|
|
12
|
+
} from "./errors";
|
|
13
|
+
|
|
14
|
+
describe("error classes", () => {
|
|
15
|
+
describe("SchmockError", () => {
|
|
16
|
+
it("creates base error with code and context", () => {
|
|
17
|
+
const error = new SchmockError("test message", "TEST_CODE", {
|
|
18
|
+
data: "test",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
expect(error.message).toBe("test message");
|
|
22
|
+
expect(error.code).toBe("TEST_CODE");
|
|
23
|
+
expect(error.context).toEqual({ data: "test" });
|
|
24
|
+
expect(error.name).toBe("SchmockError");
|
|
25
|
+
expect(error).toBeInstanceOf(Error);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("creates error without context", () => {
|
|
29
|
+
const error = new SchmockError("test message", "TEST_CODE");
|
|
30
|
+
|
|
31
|
+
expect(error.context).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("captures stack trace", () => {
|
|
35
|
+
const error = new SchmockError("test", "TEST");
|
|
36
|
+
|
|
37
|
+
expect(error.stack).toBeDefined();
|
|
38
|
+
expect(error.stack).toContain("SchmockError");
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("RouteNotFoundError", () => {
|
|
43
|
+
it("formats message with method and path", () => {
|
|
44
|
+
const error = new RouteNotFoundError("GET", "/users/123");
|
|
45
|
+
|
|
46
|
+
expect(error.message).toBe("Route not found: GET /users/123");
|
|
47
|
+
expect(error.code).toBe("ROUTE_NOT_FOUND");
|
|
48
|
+
expect(error.context).toEqual({ method: "GET", path: "/users/123" });
|
|
49
|
+
expect(error.name).toBe("RouteNotFoundError");
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("RouteParseError", () => {
|
|
54
|
+
it("includes route key and reason in message", () => {
|
|
55
|
+
const error = new RouteParseError("INVALID /test", "Missing method");
|
|
56
|
+
|
|
57
|
+
expect(error.message).toBe(
|
|
58
|
+
'Invalid route key format: "INVALID /test". Missing method',
|
|
59
|
+
);
|
|
60
|
+
expect(error.code).toBe("ROUTE_PARSE_ERROR");
|
|
61
|
+
expect(error.context).toEqual({
|
|
62
|
+
routeKey: "INVALID /test",
|
|
63
|
+
reason: "Missing method",
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe("ResponseGenerationError", () => {
|
|
69
|
+
it("wraps original error", () => {
|
|
70
|
+
const originalError = new Error("Original failure");
|
|
71
|
+
const error = new ResponseGenerationError("GET /users", originalError);
|
|
72
|
+
|
|
73
|
+
expect(error.message).toBe(
|
|
74
|
+
"Failed to generate response for route GET /users: Original failure",
|
|
75
|
+
);
|
|
76
|
+
expect(error.code).toBe("RESPONSE_GENERATION_ERROR");
|
|
77
|
+
expect(error.context).toEqual({
|
|
78
|
+
route: "GET /users",
|
|
79
|
+
originalError,
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
describe("PluginError", () => {
|
|
85
|
+
it("includes plugin name and wraps error", () => {
|
|
86
|
+
const originalError = new Error("Plugin failed");
|
|
87
|
+
const error = new PluginError("test-plugin", originalError);
|
|
88
|
+
|
|
89
|
+
expect(error.message).toBe('Plugin "test-plugin" failed: Plugin failed');
|
|
90
|
+
expect(error.code).toBe("PLUGIN_ERROR");
|
|
91
|
+
expect(error.context).toEqual({
|
|
92
|
+
pluginName: "test-plugin",
|
|
93
|
+
originalError,
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("RouteDefinitionError", () => {
|
|
99
|
+
it("includes route key and reason", () => {
|
|
100
|
+
const error = new RouteDefinitionError(
|
|
101
|
+
"GET /test",
|
|
102
|
+
"Missing response function",
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
expect(error.message).toBe(
|
|
106
|
+
'Invalid route definition for "GET /test": Missing response function',
|
|
107
|
+
);
|
|
108
|
+
expect(error.code).toBe("ROUTE_DEFINITION_ERROR");
|
|
109
|
+
expect(error.context).toEqual({
|
|
110
|
+
routeKey: "GET /test",
|
|
111
|
+
reason: "Missing response function",
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe("SchemaValidationError", () => {
|
|
117
|
+
it("includes path and issue", () => {
|
|
118
|
+
const error = new SchemaValidationError(
|
|
119
|
+
"users.name",
|
|
120
|
+
"Required field missing",
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(error.message).toBe(
|
|
124
|
+
"Schema validation failed at users.name: Required field missing",
|
|
125
|
+
);
|
|
126
|
+
expect(error.code).toBe("SCHEMA_VALIDATION_ERROR");
|
|
127
|
+
expect(error.context).toEqual({
|
|
128
|
+
schemaPath: "users.name",
|
|
129
|
+
issue: "Required field missing",
|
|
130
|
+
suggestion: undefined,
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("includes suggestion when provided", () => {
|
|
135
|
+
const error = new SchemaValidationError(
|
|
136
|
+
"users.age",
|
|
137
|
+
"Invalid type",
|
|
138
|
+
"Use number instead of string",
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
expect(error.message).toBe(
|
|
142
|
+
"Schema validation failed at users.age: Invalid type. Use number instead of string",
|
|
143
|
+
);
|
|
144
|
+
expect(error.context?.suggestion).toBe("Use number instead of string");
|
|
145
|
+
});
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe("SchemaGenerationError", () => {
|
|
149
|
+
it("wraps schema generation failure", () => {
|
|
150
|
+
const originalError = new Error("Invalid schema");
|
|
151
|
+
const schema = { type: "object" };
|
|
152
|
+
const error = new SchemaGenerationError(
|
|
153
|
+
"GET /users",
|
|
154
|
+
originalError,
|
|
155
|
+
schema,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
expect(error.message).toBe(
|
|
159
|
+
"Schema generation failed for route GET /users: Invalid schema",
|
|
160
|
+
);
|
|
161
|
+
expect(error.code).toBe("SCHEMA_GENERATION_ERROR");
|
|
162
|
+
expect(error.context).toEqual({
|
|
163
|
+
route: "GET /users",
|
|
164
|
+
originalError,
|
|
165
|
+
schema,
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("works without schema context", () => {
|
|
170
|
+
const originalError = new Error("Invalid schema");
|
|
171
|
+
const error = new SchemaGenerationError("GET /users", originalError);
|
|
172
|
+
|
|
173
|
+
expect(error.context?.schema).toBeUndefined();
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("ResourceLimitError", () => {
|
|
178
|
+
it("includes resource and limit", () => {
|
|
179
|
+
const error = new ResourceLimitError("memory", 1024);
|
|
180
|
+
|
|
181
|
+
expect(error.message).toBe(
|
|
182
|
+
"Resource limit exceeded for memory: limit=1024",
|
|
183
|
+
);
|
|
184
|
+
expect(error.code).toBe("RESOURCE_LIMIT_ERROR");
|
|
185
|
+
expect(error.context).toEqual({
|
|
186
|
+
resource: "memory",
|
|
187
|
+
limit: 1024,
|
|
188
|
+
actual: undefined,
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("includes actual value when provided", () => {
|
|
193
|
+
const error = new ResourceLimitError("connections", 100, 150);
|
|
194
|
+
|
|
195
|
+
expect(error.message).toBe(
|
|
196
|
+
"Resource limit exceeded for connections: limit=100, actual=150",
|
|
197
|
+
);
|
|
198
|
+
expect(error.context?.actual).toBe(150);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
describe("error inheritance", () => {
|
|
203
|
+
it("all custom errors inherit from SchmockError", () => {
|
|
204
|
+
const errors = [
|
|
205
|
+
new RouteNotFoundError("GET", "/test"),
|
|
206
|
+
new RouteParseError("invalid", "reason"),
|
|
207
|
+
new ResponseGenerationError("route", new Error("test")),
|
|
208
|
+
new PluginError("plugin", new Error("test")),
|
|
209
|
+
new RouteDefinitionError("route", "reason"),
|
|
210
|
+
new SchemaValidationError("path", "issue"),
|
|
211
|
+
new SchemaGenerationError("route", new Error("test")),
|
|
212
|
+
new ResourceLimitError("resource", 100),
|
|
213
|
+
];
|
|
214
|
+
|
|
215
|
+
for (const error of errors) {
|
|
216
|
+
expect(error).toBeInstanceOf(SchmockError);
|
|
217
|
+
expect(error).toBeInstanceOf(Error);
|
|
218
|
+
expect(error.code).toBeDefined();
|
|
219
|
+
expect(error.name).toBeDefined();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
});
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all Schmock errors
|
|
3
|
+
*/
|
|
4
|
+
export class SchmockError extends Error {
|
|
5
|
+
constructor(
|
|
6
|
+
message: string,
|
|
7
|
+
public readonly code: string,
|
|
8
|
+
public readonly context?: unknown,
|
|
9
|
+
) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "SchmockError";
|
|
12
|
+
Error.captureStackTrace(this, this.constructor);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown when a route is not found
|
|
18
|
+
*/
|
|
19
|
+
export class RouteNotFoundError extends SchmockError {
|
|
20
|
+
constructor(method: string, path: string) {
|
|
21
|
+
super(`Route not found: ${method} ${path}`, "ROUTE_NOT_FOUND", {
|
|
22
|
+
method,
|
|
23
|
+
path,
|
|
24
|
+
});
|
|
25
|
+
this.name = "RouteNotFoundError";
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Error thrown when route parsing fails
|
|
31
|
+
*/
|
|
32
|
+
export class RouteParseError extends SchmockError {
|
|
33
|
+
constructor(routeKey: string, reason: string) {
|
|
34
|
+
super(
|
|
35
|
+
`Invalid route key format: "${routeKey}". ${reason}`,
|
|
36
|
+
"ROUTE_PARSE_ERROR",
|
|
37
|
+
{ routeKey, reason },
|
|
38
|
+
);
|
|
39
|
+
this.name = "RouteParseError";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Error thrown when response generation fails
|
|
45
|
+
*/
|
|
46
|
+
export class ResponseGenerationError extends SchmockError {
|
|
47
|
+
constructor(route: string, error: Error) {
|
|
48
|
+
super(
|
|
49
|
+
`Failed to generate response for route ${route}: ${error.message}`,
|
|
50
|
+
"RESPONSE_GENERATION_ERROR",
|
|
51
|
+
{ route, originalError: error },
|
|
52
|
+
);
|
|
53
|
+
this.name = "ResponseGenerationError";
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Error thrown when a plugin fails
|
|
59
|
+
*/
|
|
60
|
+
export class PluginError extends SchmockError {
|
|
61
|
+
constructor(pluginName: string, error: Error) {
|
|
62
|
+
super(`Plugin "${pluginName}" failed: ${error.message}`, "PLUGIN_ERROR", {
|
|
63
|
+
pluginName,
|
|
64
|
+
originalError: error,
|
|
65
|
+
});
|
|
66
|
+
this.name = "PluginError";
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Error thrown when route definition is invalid
|
|
72
|
+
*/
|
|
73
|
+
export class RouteDefinitionError extends SchmockError {
|
|
74
|
+
constructor(routeKey: string, reason: string) {
|
|
75
|
+
super(
|
|
76
|
+
`Invalid route definition for "${routeKey}": ${reason}`,
|
|
77
|
+
"ROUTE_DEFINITION_ERROR",
|
|
78
|
+
{ routeKey, reason },
|
|
79
|
+
);
|
|
80
|
+
this.name = "RouteDefinitionError";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Error thrown when schema validation fails
|
|
86
|
+
*/
|
|
87
|
+
export class SchemaValidationError extends SchmockError {
|
|
88
|
+
constructor(schemaPath: string, issue: string, suggestion?: string) {
|
|
89
|
+
super(
|
|
90
|
+
`Schema validation failed at ${schemaPath}: ${issue}${suggestion ? `. ${suggestion}` : ""}`,
|
|
91
|
+
"SCHEMA_VALIDATION_ERROR",
|
|
92
|
+
{ schemaPath, issue, suggestion },
|
|
93
|
+
);
|
|
94
|
+
this.name = "SchemaValidationError";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Error thrown when schema generation fails
|
|
100
|
+
*/
|
|
101
|
+
export class SchemaGenerationError extends SchmockError {
|
|
102
|
+
constructor(route: string, error: Error, schema?: unknown) {
|
|
103
|
+
super(
|
|
104
|
+
`Schema generation failed for route ${route}: ${error.message}`,
|
|
105
|
+
"SCHEMA_GENERATION_ERROR",
|
|
106
|
+
{ route, originalError: error, schema },
|
|
107
|
+
);
|
|
108
|
+
this.name = "SchemaGenerationError";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Error thrown when resource limits are exceeded
|
|
114
|
+
*/
|
|
115
|
+
export class ResourceLimitError extends SchmockError {
|
|
116
|
+
constructor(resource: string, limit: number, actual?: number) {
|
|
117
|
+
super(
|
|
118
|
+
`Resource limit exceeded for ${resource}: limit=${limit}${actual ? `, actual=${actual}` : ""}`,
|
|
119
|
+
"RESOURCE_LIMIT_ERROR",
|
|
120
|
+
{ resource, limit, actual },
|
|
121
|
+
);
|
|
122
|
+
this.name = "ResourceLimitError";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { schmock } from "./index";
|
|
3
|
+
|
|
4
|
+
describe("schmock factory function", () => {
|
|
5
|
+
describe("factory behavior", () => {
|
|
6
|
+
it("creates callable mock instance with no config", () => {
|
|
7
|
+
const mock = schmock();
|
|
8
|
+
|
|
9
|
+
expect(typeof mock).toBe("function");
|
|
10
|
+
expect(typeof mock.handle).toBe("function");
|
|
11
|
+
expect(typeof mock.pipe).toBe("function");
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("creates callable mock instance with config", () => {
|
|
15
|
+
const config = { debug: true, namespace: "/api" };
|
|
16
|
+
const mock = schmock(config);
|
|
17
|
+
|
|
18
|
+
expect(typeof mock).toBe("function");
|
|
19
|
+
expect(typeof mock.handle).toBe("function");
|
|
20
|
+
expect(typeof mock.pipe).toBe("function");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("supports method chaining from factory call", () => {
|
|
24
|
+
const mock = schmock();
|
|
25
|
+
const result = mock("GET /test", "response");
|
|
26
|
+
|
|
27
|
+
expect(result).toBe(mock); // Should return same instance for chaining
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("supports plugin chaining", () => {
|
|
31
|
+
const mock = schmock();
|
|
32
|
+
const plugin = {
|
|
33
|
+
name: "test",
|
|
34
|
+
process: (ctx: any, res: any) => ({ context: ctx, response: res }),
|
|
35
|
+
};
|
|
36
|
+
const result = mock.pipe(plugin);
|
|
37
|
+
|
|
38
|
+
expect(result).toBe(mock); // Should return same instance for chaining
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
describe("callable instance behavior", () => {
|
|
43
|
+
it("defines routes when called as function", async () => {
|
|
44
|
+
const mock = schmock();
|
|
45
|
+
mock("GET /test", "hello");
|
|
46
|
+
|
|
47
|
+
const response = await mock.handle("GET", "/test");
|
|
48
|
+
expect(response.body).toBe("hello");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("allows method chaining after route definition", async () => {
|
|
52
|
+
const mock = schmock();
|
|
53
|
+
const plugin = {
|
|
54
|
+
name: "test",
|
|
55
|
+
process: (ctx: any, res: any) => ({ context: ctx, response: res }),
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
mock("GET /test", "hello").pipe(plugin);
|
|
59
|
+
|
|
60
|
+
const response = await mock.handle("GET", "/test");
|
|
61
|
+
expect(response.body).toBe("hello");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("passes config to route definition", async () => {
|
|
65
|
+
const mock = schmock();
|
|
66
|
+
mock("GET /test", { data: "test" }, { contentType: "text/plain" });
|
|
67
|
+
|
|
68
|
+
const response = await mock.handle("GET", "/test");
|
|
69
|
+
expect(response.headers["content-type"]).toBe("text/plain");
|
|
70
|
+
expect(response.body).toBe('{"data":"test"}'); // Stringified because contentType is text/plain
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe("binding and method preservation", () => {
|
|
75
|
+
it("preserves handle method binding", async () => {
|
|
76
|
+
const mock = schmock();
|
|
77
|
+
mock("GET /test", "response");
|
|
78
|
+
|
|
79
|
+
const handleMethod = mock.handle;
|
|
80
|
+
const response = await handleMethod("GET", "/test");
|
|
81
|
+
|
|
82
|
+
expect(response.body).toBe("response");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("allows destructuring of methods", async () => {
|
|
86
|
+
const mock = schmock();
|
|
87
|
+
mock("GET /test", "response");
|
|
88
|
+
|
|
89
|
+
const { handle } = mock;
|
|
90
|
+
const response = await handle("GET", "/test");
|
|
91
|
+
|
|
92
|
+
expect(response.body).toBe("response");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("pipes maintain chain references", () => {
|
|
96
|
+
const mock = schmock();
|
|
97
|
+
const plugin1 = {
|
|
98
|
+
name: "plugin1",
|
|
99
|
+
process: (ctx: any, res: any) => ({ context: ctx, response: res }),
|
|
100
|
+
};
|
|
101
|
+
const plugin2 = {
|
|
102
|
+
name: "plugin2",
|
|
103
|
+
process: (ctx: any, res: any) => ({ context: ctx, response: res }),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const chain = mock.pipe(plugin1).pipe(plugin2);
|
|
107
|
+
|
|
108
|
+
expect(chain).toBe(mock);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe("type compatibility", () => {
|
|
113
|
+
it("works with TypeScript function signature", () => {
|
|
114
|
+
// Test that the factory function matches expected TypeScript types
|
|
115
|
+
const mock = schmock({ debug: false });
|
|
116
|
+
mock("GET /users", () => [{ id: 1 }], {
|
|
117
|
+
contentType: "application/json",
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(typeof mock).toBe("function");
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("handles optional config parameter", () => {
|
|
124
|
+
const mock1 = schmock();
|
|
125
|
+
const mock2 = schmock({});
|
|
126
|
+
const mock3 = schmock({ debug: true });
|
|
127
|
+
|
|
128
|
+
expect(typeof mock1).toBe("function");
|
|
129
|
+
expect(typeof mock2).toBe("function");
|
|
130
|
+
expect(typeof mock3).toBe("function");
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,OAAO,IAAI,OAAO,CAEjC;AAGD,YAAY,EACV,OAAO,EACP,aAAa,EACb,UAAU,EACV,YAAY,EACZ,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,QAAQ,EACR,MAAM,GACP,MAAM,SAAS,CAAC;AAGjB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,eAAe,EACf,uBAAuB,EACvB,WAAW,EACX,oBAAoB,EACpB,qBAAqB,EACrB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,UAAU,CAAC"}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { CallableMockInstance } from "./builder";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create a new Schmock mock instance with callable API.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```typescript
|
|
8
|
+
* // New callable API (default)
|
|
9
|
+
* const mock = schmock({ debug: true })
|
|
10
|
+
* mock('GET /users', () => [{ id: 1, name: 'John' }])
|
|
11
|
+
* .pipe(authPlugin())
|
|
12
|
+
*
|
|
13
|
+
* const response = await mock.handle('GET', '/users')
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // Simple usage with defaults
|
|
19
|
+
* const mock = schmock()
|
|
20
|
+
* mock('GET /users', [{ id: 1, name: 'John' }])
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* @param config Optional global configuration
|
|
24
|
+
* @returns A callable mock instance
|
|
25
|
+
*/
|
|
26
|
+
export function schmock(
|
|
27
|
+
config?: Schmock.GlobalConfig,
|
|
28
|
+
): Schmock.CallableMockInstance {
|
|
29
|
+
// Always use new callable API
|
|
30
|
+
const instance = new CallableMockInstance(config || {});
|
|
31
|
+
|
|
32
|
+
// Create a callable function that wraps the instance
|
|
33
|
+
const callableInstance = ((
|
|
34
|
+
route: Schmock.RouteKey,
|
|
35
|
+
generator: Schmock.Generator,
|
|
36
|
+
config: Schmock.RouteConfig = {},
|
|
37
|
+
) => {
|
|
38
|
+
instance.defineRoute(route, generator, config);
|
|
39
|
+
return callableInstance; // Return the callable function for chaining
|
|
40
|
+
}) as any;
|
|
41
|
+
|
|
42
|
+
// Manually bind all instance methods to the callable function with proper return values
|
|
43
|
+
callableInstance.pipe = (plugin: Schmock.Plugin) => {
|
|
44
|
+
instance.pipe(plugin);
|
|
45
|
+
return callableInstance; // Return callable function for chaining
|
|
46
|
+
};
|
|
47
|
+
callableInstance.handle = instance.handle.bind(instance);
|
|
48
|
+
|
|
49
|
+
return callableInstance as Schmock.CallableMockInstance;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Re-export errors
|
|
53
|
+
export {
|
|
54
|
+
PluginError,
|
|
55
|
+
ResourceLimitError,
|
|
56
|
+
ResponseGenerationError,
|
|
57
|
+
RouteDefinitionError,
|
|
58
|
+
RouteNotFoundError,
|
|
59
|
+
RouteParseError,
|
|
60
|
+
SchemaGenerationError,
|
|
61
|
+
SchemaValidationError,
|
|
62
|
+
SchmockError,
|
|
63
|
+
} from "./errors";
|
|
64
|
+
// Re-export types
|
|
65
|
+
export type {
|
|
66
|
+
CallableMockInstance,
|
|
67
|
+
Generator,
|
|
68
|
+
GeneratorFunction,
|
|
69
|
+
GlobalConfig,
|
|
70
|
+
HttpMethod,
|
|
71
|
+
Plugin,
|
|
72
|
+
PluginContext,
|
|
73
|
+
PluginResult,
|
|
74
|
+
RequestContext,
|
|
75
|
+
RequestOptions,
|
|
76
|
+
Response,
|
|
77
|
+
ResponseResult,
|
|
78
|
+
RouteConfig,
|
|
79
|
+
RouteKey,
|
|
80
|
+
} from "./types";
|