@schmock/core 1.0.3 → 1.1.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 +13 -5
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +147 -60
- package/dist/constants.d.ts +6 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +20 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +3 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -11
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +2 -17
- package/dist/types.d.ts +17 -210
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/package.json +4 -4
- package/src/builder.test.ts +2 -2
- package/src/builder.ts +232 -108
- package/src/constants.test.ts +59 -0
- package/src/constants.ts +25 -0
- package/src/errors.ts +3 -1
- package/src/index.ts +41 -29
- package/src/namespace.test.ts +3 -2
- package/src/parser.property.test.ts +495 -0
- package/src/parser.ts +2 -20
- package/src/route-matching.test.ts +1 -1
- package/src/steps/async-support.steps.ts +101 -91
- package/src/steps/basic-usage.steps.ts +49 -36
- package/src/steps/developer-experience.steps.ts +110 -94
- package/src/steps/error-handling.steps.ts +90 -66
- package/src/steps/fluent-api.steps.ts +75 -72
- package/src/steps/http-methods.steps.ts +33 -33
- package/src/steps/performance-reliability.steps.ts +52 -88
- package/src/steps/plugin-integration.steps.ts +176 -176
- package/src/steps/request-history.steps.ts +333 -0
- package/src/steps/state-concurrency.steps.ts +418 -316
- package/src/steps/stateful-workflows.steps.ts +138 -136
- package/src/types.ts +20 -259
|
@@ -1,12 +1,31 @@
|
|
|
1
1
|
import { describeFeature, loadFeature } from "@amiceli/vitest-cucumber";
|
|
2
2
|
import { expect } from "vitest";
|
|
3
3
|
import { schmock } from "../index";
|
|
4
|
-
import type {
|
|
4
|
+
import type { CallableMockInstance } from "../types";
|
|
5
|
+
|
|
6
|
+
interface CartItem {
|
|
7
|
+
id: number;
|
|
8
|
+
name: string;
|
|
9
|
+
price: number;
|
|
10
|
+
quantity: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface UserRecord {
|
|
14
|
+
username: string;
|
|
15
|
+
password: string;
|
|
16
|
+
profile: { name: string; role: string };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface SessionRecord {
|
|
20
|
+
user: string;
|
|
21
|
+
profile: { name: string; role: string };
|
|
22
|
+
loginTime: string;
|
|
23
|
+
}
|
|
5
24
|
|
|
6
25
|
const feature = await loadFeature("../../features/stateful-workflows.feature");
|
|
7
26
|
|
|
8
27
|
describeFeature(feature, ({ Scenario }) => {
|
|
9
|
-
let mock:
|
|
28
|
+
let mock: CallableMockInstance;
|
|
10
29
|
let response: any;
|
|
11
30
|
let sessionToken: string;
|
|
12
31
|
let addedItemIds: number[] = [];
|
|
@@ -14,73 +33,65 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
14
33
|
Scenario("Shopping cart workflow", ({ Given, When, Then, And }) => {
|
|
15
34
|
addedItemIds = [];
|
|
16
35
|
|
|
17
|
-
Given("I create a stateful shopping cart mock
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
mock(
|
|
23
|
-
items
|
|
24
|
-
total
|
|
25
|
-
count:
|
|
36
|
+
Given("I create a stateful shopping cart mock", () => {
|
|
37
|
+
const items: CartItem[] = [];
|
|
38
|
+
let total = 0;
|
|
39
|
+
|
|
40
|
+
mock = schmock();
|
|
41
|
+
mock("GET /cart", () => ({
|
|
42
|
+
items,
|
|
43
|
+
total,
|
|
44
|
+
count: items.length,
|
|
26
45
|
}));
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const item = {
|
|
30
|
-
id: Date.now(),
|
|
31
|
-
name:
|
|
32
|
-
price:
|
|
33
|
-
quantity:
|
|
46
|
+
mock("POST /cart/add", ({ body }) => {
|
|
47
|
+
const b = body as Record<string, unknown>;
|
|
48
|
+
const item: CartItem = {
|
|
49
|
+
id: Date.now(),
|
|
50
|
+
name: b.name as string,
|
|
51
|
+
price: b.price as number,
|
|
52
|
+
quantity: (b.quantity as number) || 1,
|
|
34
53
|
};
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
state.total += item.price * item.quantity;
|
|
38
|
-
|
|
54
|
+
items.push(item);
|
|
55
|
+
total += item.price * item.quantity;
|
|
39
56
|
return {
|
|
40
|
-
message:
|
|
57
|
+
message: "Item added to cart",
|
|
41
58
|
added: item,
|
|
42
59
|
cart: {
|
|
43
|
-
items
|
|
44
|
-
total
|
|
45
|
-
count:
|
|
46
|
-
}
|
|
60
|
+
items,
|
|
61
|
+
total,
|
|
62
|
+
count: items.length,
|
|
63
|
+
},
|
|
47
64
|
};
|
|
48
65
|
});
|
|
49
|
-
|
|
50
|
-
mock('DELETE /cart/:id', ({ state, params }) => {
|
|
66
|
+
mock("DELETE /cart/:id", ({ params }) => {
|
|
51
67
|
const itemId = parseInt(params.id);
|
|
52
|
-
const itemIndex =
|
|
53
|
-
|
|
68
|
+
const itemIndex = items.findIndex((item) => item.id === itemId);
|
|
54
69
|
if (itemIndex === -1) {
|
|
55
|
-
return [404, { error:
|
|
70
|
+
return [404, { error: "Item not found in cart" }];
|
|
56
71
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
state.items.splice(itemIndex, 1);
|
|
61
|
-
|
|
72
|
+
const removedItem = items[itemIndex];
|
|
73
|
+
total -= removedItem.price * removedItem.quantity;
|
|
74
|
+
items.splice(itemIndex, 1);
|
|
62
75
|
return {
|
|
63
|
-
message:
|
|
76
|
+
message: "Item removed from cart",
|
|
64
77
|
item: removedItem,
|
|
65
78
|
cart: {
|
|
66
|
-
items
|
|
67
|
-
total
|
|
68
|
-
count:
|
|
69
|
-
}
|
|
79
|
+
items,
|
|
80
|
+
total,
|
|
81
|
+
count: items.length,
|
|
82
|
+
},
|
|
70
83
|
};
|
|
71
84
|
});
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
state.total = 0;
|
|
76
|
-
|
|
85
|
+
mock("POST /cart/clear", () => {
|
|
86
|
+
items.length = 0;
|
|
87
|
+
total = 0;
|
|
77
88
|
return {
|
|
78
|
-
message:
|
|
89
|
+
message: "Cart cleared",
|
|
79
90
|
cart: {
|
|
80
|
-
items
|
|
81
|
-
total
|
|
82
|
-
count:
|
|
83
|
-
}
|
|
91
|
+
items,
|
|
92
|
+
total,
|
|
93
|
+
count: items.length,
|
|
94
|
+
},
|
|
84
95
|
};
|
|
85
96
|
});
|
|
86
97
|
});
|
|
@@ -114,29 +125,34 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
114
125
|
|
|
115
126
|
Then("the cart should contain {int} items", async (_, expectedCount: number) => {
|
|
116
127
|
const cartResponse = await mock.handle("GET", "/cart");
|
|
117
|
-
|
|
128
|
+
const body = cartResponse.body as { items: CartItem[]; total: number; count: number };
|
|
129
|
+
expect(body.count).toBe(expectedCount);
|
|
118
130
|
});
|
|
119
131
|
|
|
120
132
|
Then("the cart should contain {int} item", async (_, expectedCount: number) => {
|
|
121
133
|
const cartResponse = await mock.handle("GET", "/cart");
|
|
122
|
-
|
|
134
|
+
const body = cartResponse.body as { items: CartItem[]; total: number; count: number };
|
|
135
|
+
expect(body.count).toBe(expectedCount);
|
|
123
136
|
});
|
|
124
137
|
|
|
125
138
|
And("the initial cart total should be {string}", async (_, expectedTotalStr: string) => {
|
|
126
139
|
const expectedTotal = parseFloat(expectedTotalStr);
|
|
127
140
|
const cartResponse = await mock.handle("GET", "/cart");
|
|
128
|
-
|
|
141
|
+
const body = cartResponse.body as { items: CartItem[]; total: number; count: number };
|
|
142
|
+
expect(body.total).toBe(expectedTotal);
|
|
129
143
|
});
|
|
130
144
|
|
|
131
145
|
And("the final cart total should be {string}", async (_, expectedTotalStr: string) => {
|
|
132
146
|
const expectedTotal = parseFloat(expectedTotalStr);
|
|
133
147
|
const cartResponse = await mock.handle("GET", "/cart");
|
|
134
|
-
|
|
148
|
+
const body = cartResponse.body as { items: CartItem[]; total: number; count: number };
|
|
149
|
+
expect(body.total).toBe(expectedTotal);
|
|
135
150
|
});
|
|
136
151
|
|
|
137
152
|
When("I remove the first item from the cart", async () => {
|
|
138
153
|
const cartResponse = await mock.handle("GET", "/cart");
|
|
139
|
-
const
|
|
154
|
+
const body = cartResponse.body as { items: CartItem[]; total: number; count: number };
|
|
155
|
+
const firstItemId = body.items[0].id;
|
|
140
156
|
response = await mock.handle("DELETE", `/cart/${firstItemId}`);
|
|
141
157
|
});
|
|
142
158
|
});
|
|
@@ -144,66 +160,54 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
144
160
|
Scenario("User session simulation", ({ Given, When, Then, And }) => {
|
|
145
161
|
sessionToken = "";
|
|
146
162
|
|
|
147
|
-
Given("I create a session-based mock
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
mock('POST /auth/login', ({ state, body }) => {
|
|
160
|
-
const user = state.users.find(u => u.username === body.username && u.password === body.password);
|
|
161
|
-
|
|
163
|
+
Given("I create a session-based authentication mock", () => {
|
|
164
|
+
const users: UserRecord[] = [
|
|
165
|
+
{ username: "admin", password: "secret", profile: { name: "Admin User", role: "administrator" } },
|
|
166
|
+
{ username: "user1", password: "pass123", profile: { name: "Regular User", role: "user" } },
|
|
167
|
+
];
|
|
168
|
+
const sessions: Record<string, SessionRecord> = {};
|
|
169
|
+
|
|
170
|
+
mock = schmock();
|
|
171
|
+
mock("POST /auth/login", ({ body }) => {
|
|
172
|
+
const b = body as Record<string, unknown>;
|
|
173
|
+
const user = users.find((u) => u.username === b.username && u.password === b.password);
|
|
162
174
|
if (!user) {
|
|
163
|
-
return [401, { error:
|
|
175
|
+
return [401, { error: "Invalid credentials" }];
|
|
164
176
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
state.sessions[token] = {
|
|
177
|
+
const token = "token-" + user.username + "-" + Date.now();
|
|
178
|
+
sessions[token] = {
|
|
168
179
|
user: user.username,
|
|
169
180
|
profile: user.profile,
|
|
170
|
-
loginTime: new Date().toISOString()
|
|
181
|
+
loginTime: new Date().toISOString(),
|
|
171
182
|
};
|
|
172
|
-
|
|
173
183
|
return {
|
|
174
|
-
message:
|
|
175
|
-
token
|
|
176
|
-
user: user.profile
|
|
184
|
+
message: "Login successful",
|
|
185
|
+
token,
|
|
186
|
+
user: user.profile,
|
|
177
187
|
};
|
|
178
188
|
});
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (!token || !state.sessions[token]) {
|
|
184
|
-
return [401, { error: 'Unauthorized' }];
|
|
189
|
+
mock("GET /profile", ({ headers }) => {
|
|
190
|
+
const token = headers.authorization ? headers.authorization.replace("Bearer ", "") : "";
|
|
191
|
+
if (!token || !sessions[token]) {
|
|
192
|
+
return [401, { error: "Unauthorized" }];
|
|
185
193
|
}
|
|
186
|
-
|
|
187
|
-
const session = state.sessions[token];
|
|
194
|
+
const session = sessions[token];
|
|
188
195
|
return {
|
|
189
196
|
user: session.user,
|
|
190
197
|
profile: session.profile,
|
|
191
198
|
loginTime: session.loginTime,
|
|
192
199
|
session: {
|
|
193
|
-
active: true
|
|
194
|
-
}
|
|
200
|
+
active: true,
|
|
201
|
+
},
|
|
195
202
|
};
|
|
196
203
|
});
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (!token || !state.sessions[token]) {
|
|
202
|
-
return [401, { error: 'Unauthorized' }];
|
|
204
|
+
mock("POST /auth/logout", ({ headers }) => {
|
|
205
|
+
const token = headers.authorization ? headers.authorization.replace("Bearer ", "") : "";
|
|
206
|
+
if (!token || !sessions[token]) {
|
|
207
|
+
return [401, { error: "Unauthorized" }];
|
|
203
208
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return { message: 'Logged out successfully' };
|
|
209
|
+
delete sessions[token];
|
|
210
|
+
return { message: "Logged out successfully" };
|
|
207
211
|
});
|
|
208
212
|
});
|
|
209
213
|
|
|
@@ -264,45 +268,37 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
264
268
|
});
|
|
265
269
|
|
|
266
270
|
Scenario("Multi-user state isolation", ({ Given, When, Then, And }) => {
|
|
267
|
-
Given("I create a multi-user counter mock
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
mock = schmock(
|
|
271
|
-
|
|
272
|
-
mock('POST /counter/:userId/increment', ({ state, params }) => {
|
|
271
|
+
Given("I create a multi-user counter mock", () => {
|
|
272
|
+
const counters: Record<string, number> = {};
|
|
273
|
+
|
|
274
|
+
mock = schmock();
|
|
275
|
+
mock("POST /counter/:userId/increment", ({ params }) => {
|
|
273
276
|
const userId = params.userId;
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
state.counters[userId] = 0;
|
|
277
|
+
if (!counters[userId]) {
|
|
278
|
+
counters[userId] = 0;
|
|
277
279
|
}
|
|
278
|
-
|
|
279
|
-
state.counters[userId]++;
|
|
280
|
-
|
|
280
|
+
counters[userId]++;
|
|
281
281
|
return {
|
|
282
|
-
userId
|
|
283
|
-
count:
|
|
284
|
-
message:
|
|
282
|
+
userId,
|
|
283
|
+
count: counters[userId],
|
|
284
|
+
message: "Counter incremented for user " + userId,
|
|
285
285
|
};
|
|
286
286
|
});
|
|
287
|
-
|
|
288
|
-
mock('GET /counter/:userId', ({ state, params }) => {
|
|
287
|
+
mock("GET /counter/:userId", ({ params }) => {
|
|
289
288
|
const userId = params.userId;
|
|
290
|
-
const count =
|
|
291
|
-
|
|
289
|
+
const count = counters[userId] || 0;
|
|
292
290
|
return {
|
|
293
|
-
userId
|
|
294
|
-
count
|
|
291
|
+
userId,
|
|
292
|
+
count,
|
|
295
293
|
};
|
|
296
294
|
});
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
const userCount = Object.keys(state.counters).length;
|
|
301
|
-
|
|
295
|
+
mock("GET /counters/summary", () => {
|
|
296
|
+
const totalCount = Object.values(counters).reduce((sum, count) => sum + count, 0);
|
|
297
|
+
const userCount = Object.keys(counters).length;
|
|
302
298
|
return {
|
|
303
299
|
totalCounts: totalCount,
|
|
304
300
|
totalUsers: userCount,
|
|
305
|
-
counters
|
|
301
|
+
counters,
|
|
306
302
|
};
|
|
307
303
|
});
|
|
308
304
|
});
|
|
@@ -321,31 +317,37 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
321
317
|
|
|
322
318
|
Then("{string}'s counter should be {int}", async (_, userId: string, expectedCount: number) => {
|
|
323
319
|
const response = await mock.handle("GET", `/counter/${userId}`);
|
|
324
|
-
|
|
320
|
+
const body = response.body as { userId: string; count: number };
|
|
321
|
+
expect(body.count).toBe(expectedCount);
|
|
325
322
|
});
|
|
326
323
|
|
|
327
324
|
And("{string}'s counter should be {int}", async (_, userId: string, expectedCount: number) => {
|
|
328
325
|
const response = await mock.handle("GET", `/counter/${userId}`);
|
|
329
|
-
|
|
326
|
+
const body = response.body as { userId: string; count: number };
|
|
327
|
+
expect(body.count).toBe(expectedCount);
|
|
330
328
|
});
|
|
331
329
|
|
|
332
330
|
And("the summary should show {int} total users", async (_, expectedUsers: number) => {
|
|
333
331
|
const response = await mock.handle("GET", "/counters/summary");
|
|
334
|
-
|
|
332
|
+
const body = response.body as { totalCounts: number; totalUsers: number; counters: Record<string, number> };
|
|
333
|
+
expect(body.totalUsers).toBe(expectedUsers);
|
|
335
334
|
});
|
|
336
335
|
|
|
337
336
|
And("the summary should show total counts of {int}", async (_, expectedTotal: number) => {
|
|
338
337
|
const response = await mock.handle("GET", "/counters/summary");
|
|
339
|
-
|
|
338
|
+
const body = response.body as { totalCounts: number; totalUsers: number; counters: Record<string, number> };
|
|
339
|
+
expect(body.totalCounts).toBe(expectedTotal);
|
|
340
340
|
});
|
|
341
341
|
|
|
342
342
|
And("each user's state should be independent", async () => {
|
|
343
343
|
const aliceResponse = await mock.handle("GET", "/counter/alice");
|
|
344
344
|
const bobResponse = await mock.handle("GET", "/counter/bob");
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
expect(
|
|
345
|
+
const aliceBody = aliceResponse.body as { userId: string; count: number };
|
|
346
|
+
const bobBody = bobResponse.body as { userId: string; count: number };
|
|
347
|
+
|
|
348
|
+
expect(aliceBody.count).toBe(3);
|
|
349
|
+
expect(bobBody.count).toBe(2);
|
|
350
|
+
expect(aliceBody.count).not.toBe(bobBody.count);
|
|
349
351
|
});
|
|
350
352
|
});
|
|
351
|
-
});
|
|
353
|
+
});
|
package/src/types.ts
CHANGED
|
@@ -1,259 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
export
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
export type
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
| "PUT"
|
|
22
|
-
| "DELETE"
|
|
23
|
-
| "PATCH"
|
|
24
|
-
| "HEAD"
|
|
25
|
-
| "OPTIONS";
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Route key format: 'METHOD /path'
|
|
29
|
-
*
|
|
30
|
-
* @example
|
|
31
|
-
* 'GET /users'
|
|
32
|
-
* 'POST /users/:id'
|
|
33
|
-
* 'DELETE /api/posts/:postId/comments/:commentId'
|
|
34
|
-
*/
|
|
35
|
-
export type RouteKey = `${HttpMethod} ${string}`;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Plugin interface for extending Schmock functionality
|
|
39
|
-
*/
|
|
40
|
-
export interface Plugin {
|
|
41
|
-
/** Unique plugin identifier */
|
|
42
|
-
name: string;
|
|
43
|
-
/** Plugin version (semver) */
|
|
44
|
-
version?: string;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Process the request through this plugin
|
|
48
|
-
* First plugin to set response becomes the generator, others transform
|
|
49
|
-
* @param context - Plugin context with request details
|
|
50
|
-
* @param response - Response from previous plugin (if any)
|
|
51
|
-
* @returns Updated context and response
|
|
52
|
-
*/
|
|
53
|
-
process(
|
|
54
|
-
context: PluginContext,
|
|
55
|
-
response?: any,
|
|
56
|
-
): PluginResult | Promise<PluginResult>;
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Called when an error occurs
|
|
60
|
-
* Can handle, transform, or suppress errors
|
|
61
|
-
* @param error - The error that occurred
|
|
62
|
-
* @param context - Plugin context
|
|
63
|
-
* @returns Modified error, response data, or void to continue error propagation
|
|
64
|
-
*/
|
|
65
|
-
onError?(
|
|
66
|
-
error: Error,
|
|
67
|
-
context: PluginContext,
|
|
68
|
-
):
|
|
69
|
-
| Error
|
|
70
|
-
| ResponseResult
|
|
71
|
-
| undefined
|
|
72
|
-
| Promise<Error | ResponseResult | undefined>;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Result returned by plugin process method
|
|
77
|
-
*/
|
|
78
|
-
export interface PluginResult {
|
|
79
|
-
/** Updated context */
|
|
80
|
-
context: PluginContext;
|
|
81
|
-
/** Response data (if generated/modified) */
|
|
82
|
-
response?: any;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Context passed through plugin pipeline
|
|
87
|
-
*/
|
|
88
|
-
export interface PluginContext {
|
|
89
|
-
/** Request path */
|
|
90
|
-
path: string;
|
|
91
|
-
/** Matched route configuration */
|
|
92
|
-
route: any;
|
|
93
|
-
/** HTTP method */
|
|
94
|
-
method: HttpMethod;
|
|
95
|
-
/** Route parameters */
|
|
96
|
-
params: Record<string, string>;
|
|
97
|
-
/** Query parameters */
|
|
98
|
-
query: Record<string, string>;
|
|
99
|
-
/** Request headers */
|
|
100
|
-
headers: Record<string, string>;
|
|
101
|
-
/** Request body */
|
|
102
|
-
body?: any;
|
|
103
|
-
/** Shared state between plugins for this request */
|
|
104
|
-
state: Map<string, any>;
|
|
105
|
-
/** Route-specific state */
|
|
106
|
-
routeState?: any;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Global configuration options for the mock instance
|
|
111
|
-
*/
|
|
112
|
-
export interface GlobalConfig {
|
|
113
|
-
/** Base path prefix for all routes */
|
|
114
|
-
namespace?: string;
|
|
115
|
-
/** Response delay in ms, or [min, max] for random delay */
|
|
116
|
-
delay?: number | [number, number];
|
|
117
|
-
/** Enable debug mode for detailed logging */
|
|
118
|
-
debug?: boolean;
|
|
119
|
-
/** Initial shared state object */
|
|
120
|
-
state?: any;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Route-specific configuration options
|
|
125
|
-
*/
|
|
126
|
-
export interface RouteConfig {
|
|
127
|
-
/** MIME type for content type validation (auto-detected if not provided) */
|
|
128
|
-
contentType?: string;
|
|
129
|
-
/** Additional route-specific options */
|
|
130
|
-
[key: string]: any;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Generator types that can be passed to route definitions
|
|
135
|
-
*/
|
|
136
|
-
export type Generator = GeneratorFunction | StaticData | JSONSchema;
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Function that generates responses
|
|
140
|
-
*/
|
|
141
|
-
export type GeneratorFunction = (
|
|
142
|
-
context: RequestContext,
|
|
143
|
-
) => ResponseResult | Promise<ResponseResult>;
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Static data (non-function) that gets returned as-is
|
|
147
|
-
*/
|
|
148
|
-
export type StaticData = any;
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Context passed to generator functions
|
|
152
|
-
*/
|
|
153
|
-
export interface RequestContext {
|
|
154
|
-
/** HTTP method */
|
|
155
|
-
method: HttpMethod;
|
|
156
|
-
/** Request path */
|
|
157
|
-
path: string;
|
|
158
|
-
/** Route parameters (e.g., :id) */
|
|
159
|
-
params: Record<string, string>;
|
|
160
|
-
/** Query string parameters */
|
|
161
|
-
query: Record<string, string>;
|
|
162
|
-
/** Request headers */
|
|
163
|
-
headers: Record<string, string>;
|
|
164
|
-
/** Request body (for POST, PUT, PATCH) */
|
|
165
|
-
body?: any;
|
|
166
|
-
/** Shared mutable state */
|
|
167
|
-
state: any;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Response result types:
|
|
172
|
-
* - Any value: returns as 200 OK
|
|
173
|
-
* - [status, body]: custom status with body
|
|
174
|
-
* - [status, body, headers]: custom status, body, and headers
|
|
175
|
-
*/
|
|
176
|
-
export type ResponseResult =
|
|
177
|
-
| any
|
|
178
|
-
| [number, any]
|
|
179
|
-
| [number, any, Record<string, string>];
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Response object returned by handle method
|
|
183
|
-
*/
|
|
184
|
-
export interface Response {
|
|
185
|
-
status: number;
|
|
186
|
-
body: any;
|
|
187
|
-
headers: Record<string, string>;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Options for handle method
|
|
192
|
-
*/
|
|
193
|
-
export interface RequestOptions {
|
|
194
|
-
headers?: Record<string, string>;
|
|
195
|
-
body?: any;
|
|
196
|
-
query?: Record<string, string>;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
/**
|
|
200
|
-
* Main callable mock instance interface
|
|
201
|
-
*/
|
|
202
|
-
export interface CallableMockInstance {
|
|
203
|
-
/**
|
|
204
|
-
* Define a route by calling the instance directly
|
|
205
|
-
*
|
|
206
|
-
* @param route - Route pattern in format 'METHOD /path'
|
|
207
|
-
* @param generator - Response generator (function, static data, or schema)
|
|
208
|
-
* @param config - Route-specific configuration
|
|
209
|
-
* @returns The same instance for method chaining
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* ```typescript
|
|
213
|
-
* const mock = schmock()
|
|
214
|
-
* mock('GET /users', () => [...users], { contentType: 'application/json' })
|
|
215
|
-
* mock('POST /users', userData, { contentType: 'application/json' })
|
|
216
|
-
* ```
|
|
217
|
-
*/
|
|
218
|
-
(
|
|
219
|
-
route: RouteKey,
|
|
220
|
-
generator: Generator,
|
|
221
|
-
config?: RouteConfig,
|
|
222
|
-
): CallableMockInstance;
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* Add a plugin to the pipeline
|
|
226
|
-
*
|
|
227
|
-
* @param plugin - Plugin to add to the pipeline
|
|
228
|
-
* @returns The same instance for method chaining
|
|
229
|
-
*
|
|
230
|
-
* @example
|
|
231
|
-
* ```typescript
|
|
232
|
-
* mock('GET /users', generator, config)
|
|
233
|
-
* .pipe(authPlugin())
|
|
234
|
-
* .pipe(corsPlugin())
|
|
235
|
-
* ```
|
|
236
|
-
*/
|
|
237
|
-
pipe(plugin: Plugin): CallableMockInstance;
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Handle a request and return a response
|
|
241
|
-
*
|
|
242
|
-
* @param method - HTTP method
|
|
243
|
-
* @param path - Request path
|
|
244
|
-
* @param options - Request options (headers, body, query)
|
|
245
|
-
* @returns Promise resolving to response object
|
|
246
|
-
*
|
|
247
|
-
* @example
|
|
248
|
-
* ```typescript
|
|
249
|
-
* const response = await mock.handle('GET', '/users', {
|
|
250
|
-
* headers: { 'Authorization': 'Bearer token' }
|
|
251
|
-
* })
|
|
252
|
-
* ```
|
|
253
|
-
*/
|
|
254
|
-
handle(
|
|
255
|
-
method: HttpMethod,
|
|
256
|
-
path: string,
|
|
257
|
-
options?: RequestOptions,
|
|
258
|
-
): Promise<Response>;
|
|
259
|
-
}
|
|
1
|
+
/// <reference path="../../../types/schmock.d.ts" />
|
|
2
|
+
|
|
3
|
+
// Re-export types for internal use
|
|
4
|
+
export type HttpMethod = Schmock.HttpMethod;
|
|
5
|
+
export type RouteKey = Schmock.RouteKey;
|
|
6
|
+
export type ResponseBody = Schmock.ResponseBody;
|
|
7
|
+
export type ResponseResult = Schmock.ResponseResult;
|
|
8
|
+
export type RequestContext = Schmock.RequestContext;
|
|
9
|
+
export type Response = Schmock.Response;
|
|
10
|
+
export type RequestOptions = Schmock.RequestOptions;
|
|
11
|
+
export type GlobalConfig = Schmock.GlobalConfig;
|
|
12
|
+
export type RouteConfig = Schmock.RouteConfig;
|
|
13
|
+
export type Generator = Schmock.Generator;
|
|
14
|
+
export type GeneratorFunction = Schmock.GeneratorFunction;
|
|
15
|
+
export type CallableMockInstance = Schmock.CallableMockInstance;
|
|
16
|
+
export type Plugin = Schmock.Plugin;
|
|
17
|
+
export type PluginContext = Schmock.PluginContext;
|
|
18
|
+
export type PluginResult = Schmock.PluginResult;
|
|
19
|
+
export type StaticData = Schmock.StaticData;
|
|
20
|
+
export type RequestRecord = Schmock.RequestRecord;
|