@schmock/core 1.0.4 → 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 +139 -59
- package/dist/index.d.ts +2 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -11
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +2 -17
- package/dist/types.d.ts +17 -214
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/package.json +1 -1
- package/src/builder.test.ts +2 -2
- package/src/builder.ts +216 -107
- package/src/constants.ts +1 -1
- package/src/index.ts +32 -29
- package/src/namespace.test.ts +3 -2
- package/src/parser.property.test.ts +495 -0
- package/src/parser.ts +2 -20
- 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 +95 -97
- package/src/steps/error-handling.steps.ts +71 -72
- 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 -271
|
@@ -1,100 +1,100 @@
|
|
|
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
5
|
|
|
6
6
|
const feature = await loadFeature("../../features/plugin-integration.feature");
|
|
7
7
|
|
|
8
8
|
describeFeature(feature, ({ Scenario }) => {
|
|
9
|
-
let mock:
|
|
9
|
+
let mock: CallableMockInstance;
|
|
10
10
|
let requestResponses: any[] = [];
|
|
11
11
|
|
|
12
12
|
Scenario("Plugin state sharing with pipeline", ({ Given, When, Then, And }) => {
|
|
13
13
|
requestResponses = [];
|
|
14
14
|
|
|
15
|
-
Given("I create a mock with
|
|
16
|
-
|
|
17
|
-
mock
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
response: {
|
|
34
|
-
request_number: routeState.request_count,
|
|
35
|
-
path: ctx.path,
|
|
36
|
-
processed_at: new Date().toISOString()
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// Pass through existing response
|
|
42
|
-
return { context: ctx, response };
|
|
15
|
+
Given("I create a mock with a counter plugin using route state", () => {
|
|
16
|
+
mock = schmock({ state: {} });
|
|
17
|
+
mock("GET /counter", null, { contentType: "application/json" }).pipe({
|
|
18
|
+
name: "counter-plugin",
|
|
19
|
+
process: (ctx, response) => {
|
|
20
|
+
const routeState = ctx.routeState!;
|
|
21
|
+
routeState.request_count =
|
|
22
|
+
((routeState.request_count as number) || 0) + 1;
|
|
23
|
+
|
|
24
|
+
if (!response) {
|
|
25
|
+
return {
|
|
26
|
+
context: ctx,
|
|
27
|
+
response: {
|
|
28
|
+
request_number: routeState.request_count,
|
|
29
|
+
path: ctx.path,
|
|
30
|
+
processed_at: new Date().toISOString(),
|
|
31
|
+
},
|
|
32
|
+
};
|
|
43
33
|
}
|
|
44
|
-
|
|
34
|
+
|
|
35
|
+
return { context: ctx, response };
|
|
36
|
+
},
|
|
37
|
+
});
|
|
45
38
|
});
|
|
46
39
|
|
|
47
40
|
When("I request {string} three times", async (_, request: string) => {
|
|
48
41
|
const [method, path] = request.split(" ");
|
|
49
42
|
requestResponses = [];
|
|
50
|
-
|
|
43
|
+
|
|
51
44
|
for (let i = 0; i < 3; i++) {
|
|
52
45
|
const response = await mock.handle(method as any, path);
|
|
53
46
|
requestResponses.push(response);
|
|
54
47
|
}
|
|
55
48
|
});
|
|
56
49
|
|
|
57
|
-
Then(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
expect(requestResponses[i].body[property]).toBe(i + 1);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
50
|
+
Then(
|
|
51
|
+
"each response should have incrementing {string} values",
|
|
52
|
+
(_, property: string) => {
|
|
53
|
+
expect(requestResponses).toHaveLength(3);
|
|
64
54
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
55
|
+
for (let i = 0; i < requestResponses.length; i++) {
|
|
56
|
+
expect(requestResponses[i].body[property]).toBe(i + 1);
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
And(
|
|
62
|
+
"each response should have a {string} timestamp",
|
|
63
|
+
(_, property: string) => {
|
|
64
|
+
for (const response of requestResponses) {
|
|
65
|
+
expect(response.body[property]).toBeDefined();
|
|
66
|
+
expect(typeof response.body[property]).toBe("string");
|
|
67
|
+
expect(new Date(response.body[property]).getTime()).toBeGreaterThan(0);
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
);
|
|
72
71
|
|
|
73
72
|
And("the route state should persist across requests", () => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
const requestNumbers = requestResponses.map(
|
|
74
|
+
(r) => r.body.request_number,
|
|
75
|
+
);
|
|
77
76
|
expect(requestNumbers).toEqual([1, 2, 3]);
|
|
78
77
|
});
|
|
79
78
|
});
|
|
80
79
|
|
|
81
80
|
Scenario("Multiple plugins in pipeline", ({ Given, When, Then }) => {
|
|
82
|
-
Given("I create a mock with
|
|
83
|
-
// Create mock with multiple plugins in pipeline
|
|
81
|
+
Given("I create a mock with auth and wrapper plugins", () => {
|
|
84
82
|
mock = schmock({});
|
|
85
|
-
mock(
|
|
83
|
+
mock("GET /users", () => [{ id: 1, name: "John" }], {
|
|
84
|
+
contentType: "application/json",
|
|
85
|
+
})
|
|
86
86
|
.pipe({
|
|
87
87
|
name: "auth-plugin",
|
|
88
88
|
process: (ctx, response) => {
|
|
89
89
|
if (!ctx.headers.authorization) {
|
|
90
90
|
throw new Error("Missing authorization");
|
|
91
91
|
}
|
|
92
|
-
ctx.state.set(
|
|
92
|
+
ctx.state.set("user", { id: 1, name: "Admin" });
|
|
93
93
|
return { context: ctx, response };
|
|
94
|
-
}
|
|
94
|
+
},
|
|
95
95
|
})
|
|
96
96
|
.pipe({
|
|
97
|
-
name: "wrapper-plugin",
|
|
97
|
+
name: "wrapper-plugin",
|
|
98
98
|
process: (ctx, response) => {
|
|
99
99
|
if (response) {
|
|
100
100
|
return {
|
|
@@ -102,22 +102,27 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
102
102
|
response: {
|
|
103
103
|
data: response,
|
|
104
104
|
meta: {
|
|
105
|
-
user: ctx.state.get(
|
|
106
|
-
timestamp: "2025-01-31T10:15:30.123Z"
|
|
107
|
-
}
|
|
108
|
-
}
|
|
105
|
+
user: ctx.state.get("user"),
|
|
106
|
+
timestamp: "2025-01-31T10:15:30.123Z",
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
109
|
};
|
|
110
110
|
}
|
|
111
111
|
return { context: ctx, response };
|
|
112
|
-
}
|
|
112
|
+
},
|
|
113
113
|
});
|
|
114
114
|
});
|
|
115
115
|
|
|
116
|
-
When(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
116
|
+
When(
|
|
117
|
+
"I request {string} with headers:",
|
|
118
|
+
async (_, request: string, docString: string) => {
|
|
119
|
+
const [method, path] = request.split(" ");
|
|
120
|
+
const headers = JSON.parse(docString);
|
|
121
|
+
requestResponses = [
|
|
122
|
+
await mock.handle(method as any, path, { headers }),
|
|
123
|
+
];
|
|
124
|
+
},
|
|
125
|
+
);
|
|
121
126
|
|
|
122
127
|
Then("I should receive:", (_, docString: string) => {
|
|
123
128
|
const expected = JSON.parse(docString);
|
|
@@ -126,29 +131,31 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
126
131
|
});
|
|
127
132
|
|
|
128
133
|
Scenario("Plugin error handling", ({ Given, When, Then, And }) => {
|
|
129
|
-
Given("I create a mock with
|
|
130
|
-
// Create mock with error handling plugin
|
|
134
|
+
Given("I create a mock with an auth guard plugin", () => {
|
|
131
135
|
mock = schmock({});
|
|
132
|
-
mock(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
return { context: ctx, response };
|
|
136
|
+
mock("GET /protected", () => ({ secret: "data" }), {
|
|
137
|
+
contentType: "application/json",
|
|
138
|
+
}).pipe({
|
|
139
|
+
name: "auth-plugin",
|
|
140
|
+
process: (ctx, response) => {
|
|
141
|
+
if (!ctx.headers.authorization) {
|
|
142
|
+
return {
|
|
143
|
+
context: ctx,
|
|
144
|
+
response: [401, { error: "Unauthorized", code: "AUTH_REQUIRED" }],
|
|
145
|
+
};
|
|
144
146
|
}
|
|
145
|
-
|
|
147
|
+
return { context: ctx, response };
|
|
148
|
+
},
|
|
149
|
+
});
|
|
146
150
|
});
|
|
147
151
|
|
|
148
|
-
When(
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
When(
|
|
153
|
+
"I request {string} without authorization",
|
|
154
|
+
async (_, request: string) => {
|
|
155
|
+
const [method, path] = request.split(" ");
|
|
156
|
+
requestResponses = [await mock.handle(method as any, path)];
|
|
157
|
+
},
|
|
158
|
+
);
|
|
152
159
|
|
|
153
160
|
Then("the status should be {int}", (_, status: number) => {
|
|
154
161
|
expect(requestResponses[0].status).toBe(status);
|
|
@@ -160,101 +167,91 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
160
167
|
});
|
|
161
168
|
});
|
|
162
169
|
|
|
163
|
-
Scenario(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
mock
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
process: (ctx, response) => {
|
|
171
|
-
ctx.state.set('step1', 'processed');
|
|
172
|
-
// Transform the response by adding step1 property
|
|
173
|
-
if (response) {
|
|
174
|
-
return {
|
|
175
|
-
context: ctx,
|
|
176
|
-
response: { ...response, step1: 'processed' }
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
return { context: ctx, response };
|
|
180
|
-
}
|
|
181
|
-
})
|
|
182
|
-
.pipe({
|
|
183
|
-
name: "step-2",
|
|
184
|
-
process: (ctx, response) => {
|
|
185
|
-
if (response) {
|
|
186
|
-
return {
|
|
187
|
-
context: ctx,
|
|
188
|
-
response: { ...response, step2: 'processed' }
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
return { context: ctx, response };
|
|
192
|
-
}
|
|
170
|
+
Scenario(
|
|
171
|
+
"Pipeline order and response transformation",
|
|
172
|
+
({ Given, When, Then }) => {
|
|
173
|
+
Given("I create a mock with three ordered step plugins", () => {
|
|
174
|
+
mock = schmock({});
|
|
175
|
+
mock("GET /data", () => ({ value: 42 }), {
|
|
176
|
+
contentType: "application/json",
|
|
193
177
|
})
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
178
|
+
.pipe({
|
|
179
|
+
name: "step-1",
|
|
180
|
+
process: (ctx, response) => {
|
|
181
|
+
ctx.state.set("step1", "processed");
|
|
182
|
+
if (response) {
|
|
183
|
+
return {
|
|
184
|
+
context: ctx,
|
|
185
|
+
response: { ...response, step1: "processed" },
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return { context: ctx, response };
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
.pipe({
|
|
192
|
+
name: "step-2",
|
|
193
|
+
process: (ctx, response) => {
|
|
194
|
+
if (response) {
|
|
195
|
+
return {
|
|
196
|
+
context: ctx,
|
|
197
|
+
response: { ...response, step2: "processed" },
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
return { context: ctx, response };
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
.pipe({
|
|
204
|
+
name: "step-3",
|
|
205
|
+
process: (ctx, response) => {
|
|
206
|
+
if (response) {
|
|
207
|
+
return {
|
|
208
|
+
context: ctx,
|
|
209
|
+
response: { ...response, step3: "processed" },
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
return { context: ctx, response };
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
});
|
|
207
216
|
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
217
|
+
When("I request {string}", async (_, request: string) => {
|
|
218
|
+
const [method, path] = request.split(" ");
|
|
219
|
+
requestResponses = [await mock.handle(method as any, path)];
|
|
220
|
+
});
|
|
212
221
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
222
|
+
Then("I should receive:", (_, docString: string) => {
|
|
223
|
+
const expected = JSON.parse(docString);
|
|
224
|
+
expect(requestResponses[0].body).toEqual(expected);
|
|
225
|
+
});
|
|
226
|
+
},
|
|
227
|
+
);
|
|
218
228
|
|
|
219
229
|
Scenario("Schema plugin in pipeline", ({ Given, When, Then, And }) => {
|
|
220
|
-
Given("I create a mock with
|
|
221
|
-
// Create mock with schema plugin that generates data
|
|
230
|
+
Given("I create a mock with a metadata wrapper plugin", () => {
|
|
222
231
|
mock = schmock({});
|
|
223
|
-
mock(
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
type: 'array',
|
|
227
|
-
items: {
|
|
228
|
-
type: 'object',
|
|
229
|
-
properties: {
|
|
230
|
-
id: { type: 'integer' },
|
|
231
|
-
name: { type: 'string', faker: 'person.fullName' },
|
|
232
|
-
email: { type: 'string', format: 'email' }
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
// Generate mock data from schema (simplified)
|
|
237
|
-
return [
|
|
232
|
+
mock(
|
|
233
|
+
"GET /users",
|
|
234
|
+
() => [
|
|
238
235
|
{ id: 1, name: "John Doe", email: "john@example.com" },
|
|
239
|
-
{ id: 2, name: "Jane Smith", email: "jane@example.com" }
|
|
240
|
-
]
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
255
|
-
return { context: ctx, response };
|
|
236
|
+
{ id: 2, name: "Jane Smith", email: "jane@example.com" },
|
|
237
|
+
],
|
|
238
|
+
{ contentType: "application/json" },
|
|
239
|
+
).pipe({
|
|
240
|
+
name: "add-metadata",
|
|
241
|
+
process: (ctx, response) => {
|
|
242
|
+
if (response && Array.isArray(response)) {
|
|
243
|
+
return {
|
|
244
|
+
context: ctx,
|
|
245
|
+
response: {
|
|
246
|
+
users: response,
|
|
247
|
+
count: response.length,
|
|
248
|
+
generated_at: new Date().toISOString(),
|
|
249
|
+
},
|
|
250
|
+
};
|
|
256
251
|
}
|
|
257
|
-
|
|
252
|
+
return { context: ctx, response };
|
|
253
|
+
},
|
|
254
|
+
});
|
|
258
255
|
});
|
|
259
256
|
|
|
260
257
|
When("I request {string}", async (_, request: string) => {
|
|
@@ -271,9 +268,12 @@ describeFeature(feature, ({ Scenario }) => {
|
|
|
271
268
|
expect(requestResponses[0].body).toHaveProperty(property);
|
|
272
269
|
});
|
|
273
270
|
|
|
274
|
-
And(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
271
|
+
And(
|
|
272
|
+
"the response should have a {string} timestamp",
|
|
273
|
+
(_, property: string) => {
|
|
274
|
+
expect(requestResponses[0].body).toHaveProperty(property);
|
|
275
|
+
expect(typeof requestResponses[0].body[property]).toBe("string");
|
|
276
|
+
},
|
|
277
|
+
);
|
|
278
278
|
});
|
|
279
|
-
});
|
|
279
|
+
});
|