@schmock/core 1.0.2 → 1.0.3
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 +2 -2
- package/dist/builder.d.ts.map +1 -1
- package/dist/builder.js +5 -7
- package/package.json +2 -2
- package/src/builder.ts +5 -7
- package/src/route-matching.test.ts +51 -8
package/dist/builder.d.ts
CHANGED
|
@@ -42,8 +42,8 @@ export declare class CallableMockInstance {
|
|
|
42
42
|
private runPluginPipeline;
|
|
43
43
|
/**
|
|
44
44
|
* Find a route that matches the given method and path
|
|
45
|
-
* Uses two-pass matching:
|
|
46
|
-
*
|
|
45
|
+
* Uses two-pass matching: static routes first, then parameterized routes
|
|
46
|
+
* Matches routes in registration order (first registered wins)
|
|
47
47
|
* @param method - HTTP method to match
|
|
48
48
|
* @param path - Request path to match
|
|
49
49
|
* @returns Matched compiled route or undefined if no match
|
package/dist/builder.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,SAAS,EAET,YAAY,EACZ,UAAU,EACV,MAAM,EAGN,cAAc,EACd,QAAQ,EACR,WAAW,EACX,QAAQ,EACT,MAAM,YAAY,CAAC;AA4CpB;;;;GAIG;AACH,qBAAa,oBAAoB;IAKnB,OAAO,CAAC,YAAY;IAJhC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAc;gBAER,YAAY,GAAE,YAAiB;IAanD,WAAW,CACT,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,WAAW,GAClB,IAAI;IA4DP,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAepB,MAAM,CACV,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC;IAiLpB;;;;OAIG;YACW,UAAU;IAcxB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IA0DrB;;;;;;;;;;OAUG;YACW,iBAAiB;IAmF/B;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS;
|
|
1
|
+
{"version":3,"file":"builder.d.ts","sourceRoot":"","sources":["../src/builder.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,SAAS,EAET,YAAY,EACZ,UAAU,EACV,MAAM,EAGN,cAAc,EACd,QAAQ,EACR,WAAW,EACX,QAAQ,EACT,MAAM,YAAY,CAAC;AA4CpB;;;;GAIG;AACH,qBAAa,oBAAoB;IAKnB,OAAO,CAAC,YAAY;IAJhC,OAAO,CAAC,MAAM,CAA+B;IAC7C,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,MAAM,CAAc;gBAER,YAAY,GAAE,YAAiB;IAanD,WAAW,CACT,KAAK,EAAE,QAAQ,EACf,SAAS,EAAE,SAAS,EACpB,MAAM,EAAE,WAAW,GAClB,IAAI;IA4DP,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAepB,MAAM,CACV,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,cAAc,GACvB,OAAO,CAAC,QAAQ,CAAC;IAiLpB;;;;OAIG;YACW,UAAU;IAcxB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;IA0DrB;;;;;;;;;;OAUG;YACW,iBAAiB;IAmF/B;;;;;;;;OAQG;IACH,OAAO,CAAC,SAAS;IA6BjB;;;;;;;OAOG;IACH,OAAO,CAAC,aAAa;CActB"}
|
package/dist/builder.js
CHANGED
|
@@ -383,17 +383,16 @@ export class CallableMockInstance {
|
|
|
383
383
|
}
|
|
384
384
|
/**
|
|
385
385
|
* Find a route that matches the given method and path
|
|
386
|
-
* Uses two-pass matching:
|
|
387
|
-
*
|
|
386
|
+
* Uses two-pass matching: static routes first, then parameterized routes
|
|
387
|
+
* Matches routes in registration order (first registered wins)
|
|
388
388
|
* @param method - HTTP method to match
|
|
389
389
|
* @param path - Request path to match
|
|
390
390
|
* @returns Matched compiled route or undefined if no match
|
|
391
391
|
* @private
|
|
392
392
|
*/
|
|
393
393
|
findRoute(method, path) {
|
|
394
|
-
// First pass: Look for
|
|
395
|
-
for (
|
|
396
|
-
const route = this.routes[i];
|
|
394
|
+
// First pass: Look for static routes (routes without parameters)
|
|
395
|
+
for (const route of this.routes) {
|
|
397
396
|
if (route.method === method &&
|
|
398
397
|
route.params.length === 0 &&
|
|
399
398
|
route.pattern.test(path)) {
|
|
@@ -401,8 +400,7 @@ export class CallableMockInstance {
|
|
|
401
400
|
}
|
|
402
401
|
}
|
|
403
402
|
// Second pass: Look for parameterized routes
|
|
404
|
-
for (
|
|
405
|
-
const route = this.routes[i];
|
|
403
|
+
for (const route of this.routes) {
|
|
406
404
|
if (route.method === method &&
|
|
407
405
|
route.params.length > 0 &&
|
|
408
406
|
route.pattern.test(path)) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@schmock/core",
|
|
3
3
|
"description": "Core functionality for Schmock",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.3",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@amiceli/vitest-cucumber": "^6.1.0",
|
|
35
|
-
"@types/node": "^
|
|
35
|
+
"@types/node": "^25.0.0",
|
|
36
36
|
"@vitest/ui": "^4.0.15",
|
|
37
37
|
"vitest": "^4.0.15"
|
|
38
38
|
}
|
package/src/builder.ts
CHANGED
|
@@ -525,8 +525,8 @@ export class CallableMockInstance {
|
|
|
525
525
|
|
|
526
526
|
/**
|
|
527
527
|
* Find a route that matches the given method and path
|
|
528
|
-
* Uses two-pass matching:
|
|
529
|
-
*
|
|
528
|
+
* Uses two-pass matching: static routes first, then parameterized routes
|
|
529
|
+
* Matches routes in registration order (first registered wins)
|
|
530
530
|
* @param method - HTTP method to match
|
|
531
531
|
* @param path - Request path to match
|
|
532
532
|
* @returns Matched compiled route or undefined if no match
|
|
@@ -536,9 +536,8 @@ export class CallableMockInstance {
|
|
|
536
536
|
method: HttpMethod,
|
|
537
537
|
path: string,
|
|
538
538
|
): CompiledCallableRoute | undefined {
|
|
539
|
-
// First pass: Look for
|
|
540
|
-
for (
|
|
541
|
-
const route = this.routes[i];
|
|
539
|
+
// First pass: Look for static routes (routes without parameters)
|
|
540
|
+
for (const route of this.routes) {
|
|
542
541
|
if (
|
|
543
542
|
route.method === method &&
|
|
544
543
|
route.params.length === 0 &&
|
|
@@ -549,8 +548,7 @@ export class CallableMockInstance {
|
|
|
549
548
|
}
|
|
550
549
|
|
|
551
550
|
// Second pass: Look for parameterized routes
|
|
552
|
-
for (
|
|
553
|
-
const route = this.routes[i];
|
|
551
|
+
for (const route of this.routes) {
|
|
554
552
|
if (
|
|
555
553
|
route.method === method &&
|
|
556
554
|
route.params.length > 0 &&
|
|
@@ -128,7 +128,7 @@ describe("route matching", () => {
|
|
|
128
128
|
});
|
|
129
129
|
|
|
130
130
|
describe("route precedence and conflicts", () => {
|
|
131
|
-
it("
|
|
131
|
+
it("prioritizes static routes over parameterized routes", async () => {
|
|
132
132
|
const mock = schmock();
|
|
133
133
|
mock("GET /users/:id", "parameterized");
|
|
134
134
|
mock("GET /users/special", "static");
|
|
@@ -136,7 +136,7 @@ describe("route matching", () => {
|
|
|
136
136
|
const paramResponse = await mock.handle("GET", "/users/123");
|
|
137
137
|
const staticResponse = await mock.handle("GET", "/users/special");
|
|
138
138
|
|
|
139
|
-
//
|
|
139
|
+
// Static routes should always be checked before parameterized routes
|
|
140
140
|
expect(paramResponse.body).toBe("parameterized");
|
|
141
141
|
expect(staticResponse.body).toBe("static");
|
|
142
142
|
});
|
|
@@ -153,18 +153,61 @@ describe("route matching", () => {
|
|
|
153
153
|
expect(v1Response.body).toBe("v1-specific");
|
|
154
154
|
});
|
|
155
155
|
|
|
156
|
-
it("matches
|
|
156
|
+
it("matches routes in registration order (first registered wins)", async () => {
|
|
157
157
|
const mock = schmock();
|
|
158
158
|
mock("GET /:type/items", "first");
|
|
159
159
|
mock("GET /shop/:category", "second");
|
|
160
160
|
|
|
161
161
|
const response = await mock.handle("GET", "/shop/items");
|
|
162
162
|
|
|
163
|
-
// Both routes match, but
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
// Both routes match, but the first registered route should win
|
|
164
|
+
// This matches the behavior of Express, Hono, Fastify, etc.
|
|
165
|
+
expect(response.body).toBe("first");
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("matches specific routes before wildcard when registered in natural order", async () => {
|
|
169
|
+
// Bug report reproduction: natural order (specific before wildcard)
|
|
170
|
+
const mock = schmock();
|
|
171
|
+
mock("GET /api/items/special", () => ({ type: "special" }));
|
|
172
|
+
mock("GET /api/items/:id", () => ({ type: "generic" }));
|
|
173
|
+
|
|
174
|
+
const specialResult = await mock.handle("GET", "/api/items/special");
|
|
175
|
+
const genericResult = await mock.handle("GET", "/api/items/123");
|
|
176
|
+
|
|
177
|
+
// Static route should match for /api/items/special
|
|
178
|
+
expect(specialResult.body).toEqual({ type: "special" });
|
|
179
|
+
// Parameterized route should match for /api/items/123
|
|
180
|
+
expect(genericResult.body).toEqual({ type: "generic" });
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
it("matches multiple specific routes before wildcard", async () => {
|
|
184
|
+
// Bug report scenario with multiple specific routes
|
|
185
|
+
const mock = schmock();
|
|
186
|
+
mock("GET /api/vulns/aggregated", "aggregated");
|
|
187
|
+
mock("GET /api/vulns/count", "count");
|
|
188
|
+
mock("GET /api/vulns/familyList", "familyList");
|
|
189
|
+
mock("GET /api/vulns/:vulnId", "byId");
|
|
190
|
+
|
|
191
|
+
const aggregatedRes = await mock.handle("GET", "/api/vulns/aggregated");
|
|
192
|
+
const countRes = await mock.handle("GET", "/api/vulns/count");
|
|
193
|
+
const familyListRes = await mock.handle("GET", "/api/vulns/familyList");
|
|
194
|
+
const byIdRes = await mock.handle("GET", "/api/vulns/CVE-2024-1234");
|
|
195
|
+
|
|
196
|
+
expect(aggregatedRes.body).toBe("aggregated");
|
|
197
|
+
expect(countRes.body).toBe("count");
|
|
198
|
+
expect(familyListRes.body).toBe("familyList");
|
|
199
|
+
expect(byIdRes.body).toBe("byId");
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("matches overlapping parameterized routes in registration order", async () => {
|
|
203
|
+
const mock = schmock();
|
|
204
|
+
mock("GET /api/:org/users/:id", "first-pattern");
|
|
205
|
+
mock("GET /api/:version/users/:userId", "second-pattern");
|
|
206
|
+
|
|
207
|
+
const response = await mock.handle("GET", "/api/acme/users/123");
|
|
208
|
+
|
|
209
|
+
// When both routes are parameterized and match, first registered wins
|
|
210
|
+
expect(response.body).toBe("first-pattern");
|
|
168
211
|
});
|
|
169
212
|
});
|
|
170
213
|
|