@schmock/openapi 1.2.1 → 1.4.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/content-negotiation.d.ts +7 -0
- package/dist/content-negotiation.d.ts.map +1 -0
- package/dist/content-negotiation.js +46 -0
- package/dist/crud-detector.d.ts +2 -0
- package/dist/crud-detector.d.ts.map +1 -1
- package/dist/crud-detector.js +55 -8
- package/dist/generators.d.ts +27 -7
- package/dist/generators.d.ts.map +1 -1
- package/dist/generators.js +190 -18
- package/dist/index.js +159 -159
- package/dist/normalizer.js +1 -1
- package/dist/parser.d.ts +31 -4
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +208 -6
- package/dist/plugin.d.ts +9 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +506 -47
- package/dist/prefer.d.ts +12 -0
- package/dist/prefer.d.ts.map +1 -0
- package/dist/prefer.js +25 -0
- package/dist/seed.js +1 -1
- package/package.json +5 -3
- package/src/content-negotiation.ts +53 -0
- package/src/crud-detector.ts +65 -8
- package/src/generators.test.ts +270 -0
- package/src/generators.ts +237 -11
- package/src/index.ts +1 -1
- package/src/normalizer.ts +1 -1
- package/src/parser.ts +292 -12
- package/src/plugin.ts +655 -51
- package/src/prefer.ts +37 -0
- package/src/seed.ts +1 -1
- package/src/steps/callback-mocking.steps.ts +164 -0
- package/src/steps/content-negotiation.steps.ts +107 -0
- package/src/steps/errors-mode.steps.ts +95 -0
- package/src/steps/openapi-compliance.steps.ts +427 -0
- package/src/steps/prefer-header.steps.ts +140 -0
- package/src/steps/security-validation.steps.ts +183 -0
- package/src/stress.test.ts +92 -35
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { describeFeature, loadFeature } from "@amiceli/vitest-cucumber";
|
|
2
|
+
import { schmock } from "@schmock/core";
|
|
3
|
+
import { expect } from "vitest";
|
|
4
|
+
import { openapi } from "../plugin";
|
|
5
|
+
|
|
6
|
+
const feature = await loadFeature("../../features/security-validation.feature");
|
|
7
|
+
|
|
8
|
+
const bearerSpec = {
|
|
9
|
+
openapi: "3.0.3",
|
|
10
|
+
info: { title: "Test", version: "1.0.0" },
|
|
11
|
+
components: {
|
|
12
|
+
securitySchemes: {
|
|
13
|
+
bearerAuth: { type: "http", scheme: "bearer" },
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
security: [{ bearerAuth: [] }],
|
|
17
|
+
paths: {
|
|
18
|
+
"/items": {
|
|
19
|
+
get: { responses: { "200": { description: "OK" } } },
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const apiKeySpec = {
|
|
25
|
+
openapi: "3.0.3",
|
|
26
|
+
info: { title: "Test", version: "1.0.0" },
|
|
27
|
+
components: {
|
|
28
|
+
securitySchemes: {
|
|
29
|
+
apiKey: { type: "apiKey", in: "header", name: "x-api-key" },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
security: [{ apiKey: [] }],
|
|
33
|
+
paths: {
|
|
34
|
+
"/items": {
|
|
35
|
+
get: { responses: { "200": { description: "OK" } } },
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const basicSpec = {
|
|
41
|
+
openapi: "3.0.3",
|
|
42
|
+
info: { title: "Test", version: "1.0.0" },
|
|
43
|
+
components: {
|
|
44
|
+
securitySchemes: {
|
|
45
|
+
basicAuth: { type: "http", scheme: "basic" },
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
security: [{ basicAuth: [] }],
|
|
49
|
+
paths: {
|
|
50
|
+
"/items": {
|
|
51
|
+
get: { responses: { "200": { description: "OK" } } },
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const mixedSpec = {
|
|
57
|
+
openapi: "3.0.3",
|
|
58
|
+
info: { title: "Test", version: "1.0.0" },
|
|
59
|
+
components: {
|
|
60
|
+
securitySchemes: {
|
|
61
|
+
bearerAuth: { type: "http", scheme: "bearer" },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
security: [{ bearerAuth: [] }],
|
|
65
|
+
paths: {
|
|
66
|
+
"/items": {
|
|
67
|
+
get: { responses: { "200": { description: "OK" } } },
|
|
68
|
+
},
|
|
69
|
+
"/health": {
|
|
70
|
+
get: {
|
|
71
|
+
security: [{}],
|
|
72
|
+
responses: { "200": { description: "OK" } },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
describeFeature(feature, ({ Scenario }) => {
|
|
79
|
+
let mock: Schmock.CallableMockInstance;
|
|
80
|
+
let response: Schmock.Response;
|
|
81
|
+
|
|
82
|
+
Scenario("Missing Bearer token returns 401", ({ Given, When, Then, And }) => {
|
|
83
|
+
Given("a mock with a spec requiring Bearer auth", async () => {
|
|
84
|
+
mock = schmock({ state: {} });
|
|
85
|
+
mock.pipe(await openapi({ spec: bearerSpec, security: true }));
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
When("I request without an Authorization header", async () => {
|
|
89
|
+
response = await mock.handle("GET", "/items", { headers: {} });
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
Then("the response status is 401", () => {
|
|
93
|
+
expect(response.status).toBe(401);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
And('the response has a WWW-Authenticate header with "Bearer"', () => {
|
|
97
|
+
expect(response.headers["www-authenticate"]).toContain("Bearer");
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
Scenario("Valid Bearer token returns 200", ({ Given, When, Then }) => {
|
|
102
|
+
Given("a mock with a spec requiring Bearer auth", async () => {
|
|
103
|
+
mock = schmock({ state: {} });
|
|
104
|
+
mock.pipe(await openapi({ spec: bearerSpec, security: true }));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
When('I request with Authorization header "Bearer my-token"', async () => {
|
|
108
|
+
response = await mock.handle("GET", "/items", {
|
|
109
|
+
headers: { authorization: "Bearer my-token" },
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
Then("the response status is 200", () => {
|
|
114
|
+
expect(response.status).toBe(200);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
Scenario("API key in header is validated", ({ Given, When, Then }) => {
|
|
119
|
+
Given("a mock with a spec requiring an API key header", async () => {
|
|
120
|
+
mock = schmock({ state: {} });
|
|
121
|
+
mock.pipe(await openapi({ spec: apiKeySpec, security: true }));
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
When("I request without the API key header", async () => {
|
|
125
|
+
response = await mock.handle("GET", "/items", { headers: {} });
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
Then("the response status is 401", () => {
|
|
129
|
+
expect(response.status).toBe(401);
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
Scenario("Valid API key passes through", ({ Given, When, Then }) => {
|
|
134
|
+
Given("a mock with a spec requiring an API key header", async () => {
|
|
135
|
+
mock = schmock({ state: {} });
|
|
136
|
+
mock.pipe(await openapi({ spec: apiKeySpec, security: true }));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
When("I request with the API key header present", async () => {
|
|
140
|
+
response = await mock.handle("GET", "/items", {
|
|
141
|
+
headers: { "x-api-key": "my-key-123" },
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
Then("the response status is 200", () => {
|
|
146
|
+
expect(response.status).toBe(200);
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
Scenario("Basic auth is validated", ({ Given, When, Then, And }) => {
|
|
151
|
+
Given("a mock with a spec requiring Basic auth", async () => {
|
|
152
|
+
mock = schmock({ state: {} });
|
|
153
|
+
mock.pipe(await openapi({ spec: basicSpec, security: true }));
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
When("I request without an Authorization header", async () => {
|
|
157
|
+
response = await mock.handle("GET", "/items", { headers: {} });
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
Then("the response status is 401", () => {
|
|
161
|
+
expect(response.status).toBe(401);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
And('the response has a WWW-Authenticate header with "Basic"', () => {
|
|
165
|
+
expect(response.headers["www-authenticate"]).toContain("Basic");
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
Scenario("Public endpoint skips validation", ({ Given, When, Then }) => {
|
|
170
|
+
Given("a mock with a spec where one endpoint is public", async () => {
|
|
171
|
+
mock = schmock({ state: {} });
|
|
172
|
+
mock.pipe(await openapi({ spec: mixedSpec, security: true }));
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
When("I request the public endpoint without auth", async () => {
|
|
176
|
+
response = await mock.handle("GET", "/health", { headers: {} });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
Then("the response status is 200", () => {
|
|
180
|
+
expect(response.status).toBe(200);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
});
|
package/src/stress.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/// <reference path="
|
|
1
|
+
/// <reference path="../../core/schmock.d.ts" />
|
|
2
2
|
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
4
|
import { resolve } from "node:path";
|
|
@@ -197,10 +197,11 @@ describe("stress: integration — train-travel.yaml", () => {
|
|
|
197
197
|
const trips = await mock.handle("GET", "/trips");
|
|
198
198
|
expect(trips.status).toBe(200);
|
|
199
199
|
|
|
200
|
-
// Bookings: CRUD resource
|
|
200
|
+
// Bookings: CRUD resource (wrapped list — allOf with data array)
|
|
201
201
|
const emptyList = await mock.handle("GET", "/bookings");
|
|
202
202
|
expect(emptyList.status).toBe(200);
|
|
203
|
-
|
|
203
|
+
const emptyBody = emptyList.body as Record<string, unknown>;
|
|
204
|
+
expect(emptyBody.data).toEqual([]);
|
|
204
205
|
});
|
|
205
206
|
|
|
206
207
|
it("bookings CRUD lifecycle", async () => {
|
|
@@ -226,10 +227,11 @@ describe("stress: integration — train-travel.yaml", () => {
|
|
|
226
227
|
expect(read.status).toBe(200);
|
|
227
228
|
expect(read.body).toMatchObject({ passenger_name: "John Doe" });
|
|
228
229
|
|
|
229
|
-
// List
|
|
230
|
+
// List (wrapped)
|
|
230
231
|
const list = await mock.handle("GET", "/bookings");
|
|
231
232
|
expect(list.status).toBe(200);
|
|
232
|
-
|
|
233
|
+
const listBody = list.body as Record<string, unknown>;
|
|
234
|
+
expect(listBody.data).toHaveLength(1);
|
|
233
235
|
|
|
234
236
|
// Delete
|
|
235
237
|
const deleted = await mock.handle("DELETE", "/bookings/1");
|
|
@@ -270,7 +272,8 @@ describe("stress: integration — train-travel.yaml", () => {
|
|
|
270
272
|
}
|
|
271
273
|
|
|
272
274
|
const list = await mock.handle("GET", "/bookings");
|
|
273
|
-
|
|
275
|
+
const listBody = list.body as Record<string, unknown>;
|
|
276
|
+
expect(listBody.data).toHaveLength(5);
|
|
274
277
|
});
|
|
275
278
|
});
|
|
276
279
|
|
|
@@ -1149,10 +1152,11 @@ describe("stress: scalar-galaxy.yaml — BREAD operations", () => {
|
|
|
1149
1152
|
}),
|
|
1150
1153
|
);
|
|
1151
1154
|
|
|
1152
|
-
// BROWSE — list all 8 planets
|
|
1155
|
+
// BROWSE — list all 8 planets (wrapped: { data: [...], meta: {...} })
|
|
1153
1156
|
const allPlanets = await mock.handle("GET", "/planets");
|
|
1154
1157
|
expect(allPlanets.status).toBe(200);
|
|
1155
|
-
|
|
1158
|
+
const allPlanetsBody = allPlanets.body as Record<string, unknown>;
|
|
1159
|
+
expect(allPlanetsBody.data).toHaveLength(8);
|
|
1156
1160
|
|
|
1157
1161
|
// READ — each planet is coherent
|
|
1158
1162
|
for (let i = 1; i <= 8; i++) {
|
|
@@ -1186,9 +1190,9 @@ describe("stress: scalar-galaxy.yaml — BREAD operations", () => {
|
|
|
1186
1190
|
expect(newPlanet.name).toBe("Kepler-442b");
|
|
1187
1191
|
expect(newPlanet.planetId).toBe(9); // auto-incremented past seed max (8)
|
|
1188
1192
|
|
|
1189
|
-
// BROWSE after ADD — 9 planets
|
|
1193
|
+
// BROWSE after ADD — 9 planets (wrapped)
|
|
1190
1194
|
const afterAdd = await mock.handle("GET", "/planets");
|
|
1191
|
-
expect(afterAdd.body).toHaveLength(9);
|
|
1195
|
+
expect((afterAdd.body as Record<string, unknown>).data).toHaveLength(9);
|
|
1192
1196
|
|
|
1193
1197
|
// EDIT — update Mars terraforming progress
|
|
1194
1198
|
const edited = await mock.handle("PUT", "/planets/4", {
|
|
@@ -1203,9 +1207,9 @@ describe("stress: scalar-galaxy.yaml — BREAD operations", () => {
|
|
|
1203
1207
|
const deleted = await mock.handle("DELETE", "/planets/9");
|
|
1204
1208
|
expect(deleted.status).toBe(204);
|
|
1205
1209
|
|
|
1206
|
-
// BROWSE after DELETE — back to 8
|
|
1210
|
+
// BROWSE after DELETE — back to 8 (wrapped)
|
|
1207
1211
|
const afterDelete = await mock.handle("GET", "/planets");
|
|
1208
|
-
expect(afterDelete.body).toHaveLength(8);
|
|
1212
|
+
expect((afterDelete.body as Record<string, unknown>).data).toHaveLength(8);
|
|
1209
1213
|
|
|
1210
1214
|
// READ deleted — 404
|
|
1211
1215
|
const gone = await mock.handle("GET", "/planets/9");
|
|
@@ -1495,13 +1499,16 @@ describe("stress: lifecycle and multi-instance flows", () => {
|
|
|
1495
1499
|
}),
|
|
1496
1500
|
);
|
|
1497
1501
|
|
|
1498
|
-
expect(
|
|
1502
|
+
expect(
|
|
1503
|
+
((await mock.handle("GET", "/planets")).body as Record<string, unknown>)
|
|
1504
|
+
.data,
|
|
1505
|
+
).toHaveLength(8);
|
|
1499
1506
|
|
|
1500
1507
|
mock.resetState();
|
|
1501
1508
|
|
|
1502
1509
|
// After reset, collection is empty — seeder runs again on next request
|
|
1503
1510
|
const list = await mock.handle("GET", "/planets");
|
|
1504
|
-
expect(list.body).toHaveLength(8); // re-seeded on access
|
|
1511
|
+
expect((list.body as Record<string, unknown>).data).toHaveLength(8); // re-seeded on access
|
|
1505
1512
|
|
|
1506
1513
|
// Can still create new items
|
|
1507
1514
|
const created = await mock.handle("POST", "/planets", {
|
|
@@ -1572,7 +1579,8 @@ describe("stress: lifecycle and multi-instance flows", () => {
|
|
|
1572
1579
|
await mock.handle("POST", "/planets", { body: { name: "Exo-2" } });
|
|
1573
1580
|
|
|
1574
1581
|
const list = await mock.handle("GET", "/planets");
|
|
1575
|
-
const
|
|
1582
|
+
const listBody = list.body as Record<string, unknown>;
|
|
1583
|
+
const items = listBody.data as Record<string, unknown>[];
|
|
1576
1584
|
expect(items).toHaveLength(4);
|
|
1577
1585
|
|
|
1578
1586
|
// Seed items at IDs 1-2, runtime items at IDs 3-4
|
|
@@ -1595,10 +1603,16 @@ describe("stress: lifecycle and multi-instance flows", () => {
|
|
|
1595
1603
|
await mock.handle("POST", "/pets", { body: { name: "Buddy" } });
|
|
1596
1604
|
expect((await mock.handle("GET", "/pets")).body).toHaveLength(1);
|
|
1597
1605
|
|
|
1598
|
-
// Galaxy CRUD — completely independent
|
|
1599
|
-
expect(
|
|
1606
|
+
// Galaxy CRUD — completely independent (wrapped format)
|
|
1607
|
+
expect(
|
|
1608
|
+
((await mock.handle("GET", "/planets")).body as Record<string, unknown>)
|
|
1609
|
+
.data,
|
|
1610
|
+
).toHaveLength(8);
|
|
1600
1611
|
await mock.handle("POST", "/planets", { body: { name: "Nibiru" } });
|
|
1601
|
-
expect(
|
|
1612
|
+
expect(
|
|
1613
|
+
((await mock.handle("GET", "/planets")).body as Record<string, unknown>)
|
|
1614
|
+
.data,
|
|
1615
|
+
).toHaveLength(9);
|
|
1602
1616
|
|
|
1603
1617
|
// Petstore still has 1
|
|
1604
1618
|
expect((await mock.handle("GET", "/pets")).body).toHaveLength(1);
|
|
@@ -1858,9 +1872,9 @@ describe("stress: realistic E2E flows", () => {
|
|
|
1858
1872
|
const me = await mock.handle("GET", "/me");
|
|
1859
1873
|
expect(me.status).toBe(200);
|
|
1860
1874
|
|
|
1861
|
-
// 6. List planets
|
|
1875
|
+
// 6. List planets (wrapped)
|
|
1862
1876
|
const list = await mock.handle("GET", "/planets");
|
|
1863
|
-
expect(list.body).toHaveLength(1);
|
|
1877
|
+
expect((list.body as Record<string, unknown>).data).toHaveLength(1);
|
|
1864
1878
|
});
|
|
1865
1879
|
|
|
1866
1880
|
it("galaxy: populate solar system then explore", async () => {
|
|
@@ -1885,9 +1899,9 @@ describe("stress: realistic E2E flows", () => {
|
|
|
1885
1899
|
expect(res.status).toBe(201);
|
|
1886
1900
|
}
|
|
1887
1901
|
|
|
1888
|
-
// Verify count
|
|
1902
|
+
// Verify count (wrapped)
|
|
1889
1903
|
const list = await mock.handle("GET", "/planets");
|
|
1890
|
-
expect(list.body).toHaveLength(8);
|
|
1904
|
+
expect((list.body as Record<string, unknown>).data).toHaveLength(8);
|
|
1891
1905
|
|
|
1892
1906
|
// Read each by ID
|
|
1893
1907
|
for (let i = 1; i <= 8; i++) {
|
|
@@ -2609,10 +2623,11 @@ describe("stress: stripe spec — CRUD lifecycle with fixtures", () => {
|
|
|
2609
2623
|
custFixture.email,
|
|
2610
2624
|
);
|
|
2611
2625
|
|
|
2612
|
-
// List
|
|
2626
|
+
// List (wrapped: { data: [...], has_more, object, url })
|
|
2613
2627
|
const list = await mock.handle("GET", "/v1/customers");
|
|
2614
2628
|
expect(list.status).toBe(200);
|
|
2615
|
-
|
|
2629
|
+
const listBody = list.body as Record<string, unknown>;
|
|
2630
|
+
expect(listBody.data).toHaveLength(1);
|
|
2616
2631
|
|
|
2617
2632
|
// Delete
|
|
2618
2633
|
const deleted = await mock.handle("DELETE", "/v1/customers/1");
|
|
@@ -2641,9 +2656,9 @@ describe("stress: stripe spec — CRUD lifecycle with fixtures", () => {
|
|
|
2641
2656
|
expect(read.status).toBe(200);
|
|
2642
2657
|
expect((read.body as Record<string, unknown>).name).toBe("Premium Plan");
|
|
2643
2658
|
|
|
2644
|
-
// List
|
|
2659
|
+
// List (wrapped)
|
|
2645
2660
|
const list = await mock.handle("GET", "/v1/products");
|
|
2646
|
-
expect(list.body).toHaveLength(1);
|
|
2661
|
+
expect((list.body as Record<string, unknown>).data).toHaveLength(1);
|
|
2647
2662
|
|
|
2648
2663
|
// Delete
|
|
2649
2664
|
const del = await mock.handle("DELETE", "/v1/products/1");
|
|
@@ -2695,15 +2710,50 @@ describe("stress: stripe spec — CRUD lifecycle with fixtures", () => {
|
|
|
2695
2710
|
body: { percent_off: 25 },
|
|
2696
2711
|
});
|
|
2697
2712
|
|
|
2698
|
-
// Each resource is independent
|
|
2699
|
-
expect(
|
|
2700
|
-
|
|
2701
|
-
|
|
2713
|
+
// Each resource is independent (all wrapped)
|
|
2714
|
+
expect(
|
|
2715
|
+
(
|
|
2716
|
+
(await mock.handle("GET", "/v1/customers")).body as Record<
|
|
2717
|
+
string,
|
|
2718
|
+
unknown
|
|
2719
|
+
>
|
|
2720
|
+
).data,
|
|
2721
|
+
).toHaveLength(2);
|
|
2722
|
+
expect(
|
|
2723
|
+
(
|
|
2724
|
+
(await mock.handle("GET", "/v1/products")).body as Record<
|
|
2725
|
+
string,
|
|
2726
|
+
unknown
|
|
2727
|
+
>
|
|
2728
|
+
).data,
|
|
2729
|
+
).toHaveLength(1);
|
|
2730
|
+
expect(
|
|
2731
|
+
(
|
|
2732
|
+
(await mock.handle("GET", "/v1/coupons")).body as Record<
|
|
2733
|
+
string,
|
|
2734
|
+
unknown
|
|
2735
|
+
>
|
|
2736
|
+
).data,
|
|
2737
|
+
).toHaveLength(1);
|
|
2702
2738
|
|
|
2703
2739
|
// Delete a customer doesn't affect products
|
|
2704
2740
|
await mock.handle("DELETE", "/v1/customers/1");
|
|
2705
|
-
expect(
|
|
2706
|
-
|
|
2741
|
+
expect(
|
|
2742
|
+
(
|
|
2743
|
+
(await mock.handle("GET", "/v1/customers")).body as Record<
|
|
2744
|
+
string,
|
|
2745
|
+
unknown
|
|
2746
|
+
>
|
|
2747
|
+
).data,
|
|
2748
|
+
).toHaveLength(1);
|
|
2749
|
+
expect(
|
|
2750
|
+
(
|
|
2751
|
+
(await mock.handle("GET", "/v1/products")).body as Record<
|
|
2752
|
+
string,
|
|
2753
|
+
unknown
|
|
2754
|
+
>
|
|
2755
|
+
).data,
|
|
2756
|
+
).toHaveLength(1);
|
|
2707
2757
|
}, 120_000);
|
|
2708
2758
|
|
|
2709
2759
|
it("non-CRUD endpoints respond alongside CRUD", async () => {
|
|
@@ -2728,12 +2778,19 @@ describe("stress: stripe spec — CRUD lifecycle with fixtures", () => {
|
|
|
2728
2778
|
await mock.handle("POST", "/v1/customers", {
|
|
2729
2779
|
body: { email: "temp@stripe.com" },
|
|
2730
2780
|
});
|
|
2731
|
-
expect(
|
|
2781
|
+
expect(
|
|
2782
|
+
(
|
|
2783
|
+
(await mock.handle("GET", "/v1/customers")).body as Record<
|
|
2784
|
+
string,
|
|
2785
|
+
unknown
|
|
2786
|
+
>
|
|
2787
|
+
).data,
|
|
2788
|
+
).toHaveLength(1);
|
|
2732
2789
|
|
|
2733
2790
|
mock.resetState();
|
|
2734
2791
|
|
|
2735
2792
|
const list = await mock.handle("GET", "/v1/customers");
|
|
2736
|
-
expect(list.body).toEqual([]);
|
|
2793
|
+
expect((list.body as Record<string, unknown>).data).toEqual([]);
|
|
2737
2794
|
}, 120_000);
|
|
2738
2795
|
});
|
|
2739
2796
|
|
|
@@ -2770,7 +2827,7 @@ describe("stress: stripe spec — the confused Stripe developer", () => {
|
|
|
2770
2827
|
}
|
|
2771
2828
|
|
|
2772
2829
|
const list = await mock.handle("GET", "/v1/customers");
|
|
2773
|
-
expect(list.body).toHaveLength(50);
|
|
2830
|
+
expect((list.body as Record<string, unknown>).data).toHaveLength(50);
|
|
2774
2831
|
}, 120_000);
|
|
2775
2832
|
|
|
2776
2833
|
it("interleaved operations across Stripe resources", async () => {
|