@scenarist/msw-adapter 0.3.1 → 0.3.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/conversion/response-builder.d.ts.map +1 -1
- package/dist/conversion/response-builder.js +1 -0
- package/dist/handlers/dynamic-handler.d.ts.map +1 -1
- package/dist/handlers/dynamic-handler.js +49 -22
- package/dist/matching/url-matcher.d.ts.map +1 -1
- package/dist/matching/url-matcher.js +12 -4
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"response-builder.d.ts","sourceRoot":"","sources":["../../src/conversion/response-builder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,eAAO,MAAM,aAAa,GACxB,UAAU,iBAAiB,KAC1B,OAAO,CAAC,QAAQ,
|
|
1
|
+
{"version":3,"file":"response-builder.d.ts","sourceRoot":"","sources":["../../src/conversion/response-builder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,eAAO,MAAM,aAAa,GACxB,UAAU,iBAAiB,KAC1B,OAAO,CAAC,QAAQ,CAelB,CAAC"}
|
|
@@ -8,6 +8,7 @@ export const buildResponse = async (response) => {
|
|
|
8
8
|
// framework-agnostic serialization in core package. The body is guaranteed
|
|
9
9
|
// to be JSON-serializable by ScenaristResponse design contract. We do not
|
|
10
10
|
// perform runtime validation for performance reasons (garbage in, garbage out).
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- MSW API boundary requires cast from unknown to JsonBodyType
|
|
11
12
|
return HttpResponse.json(response.body, {
|
|
12
13
|
status: response.status,
|
|
13
14
|
headers: response.headers,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dynamic-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/dynamic-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EAIjB,gBAAgB,EAChB,cAAc,EACd,MAAM,EACP,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;IAC3E,QAAQ,CAAC,qBAAqB,EAAE,CAC9B,UAAU,EAAE,MAAM,KACf,iBAAiB,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;
|
|
1
|
+
{"version":3,"file":"dynamic-handler.d.ts","sourceRoot":"","sources":["../../src/handlers/dynamic-handler.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,KAAK,CAAC;AACvC,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EAIjB,gBAAgB,EAChB,cAAc,EACd,MAAM,EACP,MAAM,iBAAiB,CAAC;AAKzB,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,MAAM,CAAC;IACjD,QAAQ,CAAC,iBAAiB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,cAAc,GAAG,SAAS,CAAC;IAC3E,QAAQ,CAAC,qBAAqB,EAAE,CAC9B,UAAU,EAAE,MAAM,KACf,iBAAiB,GAAG,SAAS,CAAC;IACnC,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,gBAAgB,CAAC;IAC5C,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAoIF,eAAO,MAAM,oBAAoB,GAC/B,SAAS,qBAAqB,KAC7B,WAiIF,CAAC"}
|
|
@@ -33,49 +33,76 @@ const extractHttpRequestContext = async (request) => {
|
|
|
33
33
|
url.searchParams.forEach((value, key) => {
|
|
34
34
|
query[key] = value;
|
|
35
35
|
});
|
|
36
|
+
// HTTP methods from Request.method are uppercase strings matching HttpMethod type
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Request.method is a string, HttpMethod is a string literal union; values align at runtime
|
|
38
|
+
const method = request.method;
|
|
36
39
|
return {
|
|
37
|
-
method
|
|
40
|
+
method,
|
|
38
41
|
url: request.url,
|
|
39
42
|
body,
|
|
40
43
|
headers,
|
|
41
44
|
query,
|
|
42
45
|
};
|
|
43
46
|
};
|
|
47
|
+
/**
|
|
48
|
+
* Check if a mock matches the request's method and URL.
|
|
49
|
+
* Returns match result with extracted URL params if matching.
|
|
50
|
+
*/
|
|
51
|
+
const mockMatchesRequest = (mock, method, url) => {
|
|
52
|
+
const methodMatches = mock.method.toUpperCase() === method.toUpperCase();
|
|
53
|
+
if (!methodMatches) {
|
|
54
|
+
return { matches: false, params: {} };
|
|
55
|
+
}
|
|
56
|
+
const urlMatch = matchesUrl(mock.url, url);
|
|
57
|
+
return { matches: urlMatch.matches, params: urlMatch.params ?? {} };
|
|
58
|
+
};
|
|
44
59
|
/**
|
|
45
60
|
* Get mocks from active scenario, with default scenario mocks as fallback.
|
|
46
61
|
* Returns URL-matching mocks with their extracted params for ResponseSelector to evaluate.
|
|
47
62
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
63
|
+
* Mock selection priority (Issue #335):
|
|
64
|
+
* 1. If no active scenario → use default scenario mocks
|
|
65
|
+
* 2. If active scenario has a fallback mock (no match criteria) → use ONLY active mocks
|
|
66
|
+
* 3. If active scenario has only conditional mocks → include default as backup
|
|
67
|
+
*
|
|
68
|
+
* A "fallback mock" is one without match criteria - it always matches if URL+method match.
|
|
69
|
+
* When active scenario explicitly covers an endpoint with a fallback mock, we don't
|
|
70
|
+
* include default's mock for that endpoint, preventing specificity-based conflicts.
|
|
51
71
|
*
|
|
52
72
|
* Each mock is paired with params extracted from its URL pattern.
|
|
53
73
|
* After ResponseSelector chooses a mock, we use THAT mock's params.
|
|
54
74
|
*/
|
|
55
75
|
const getMocksFromScenarios = (activeScenario, getScenarioDefinition, method, url) => {
|
|
56
76
|
const mocksWithParams = [];
|
|
57
|
-
// Step 1:
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
if (defaultScenario) {
|
|
61
|
-
defaultScenario.mocks.forEach((mock) => {
|
|
62
|
-
const methodMatches = mock.method.toUpperCase() === method.toUpperCase();
|
|
63
|
-
const urlMatch = matchesUrl(mock.url, url);
|
|
64
|
-
if (methodMatches && urlMatch.matches) {
|
|
65
|
-
mocksWithParams.push({ mock, params: urlMatch.params });
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
// Step 2: Add active scenario mocks (if any)
|
|
70
|
-
// These override defaults based on specificity (via ResponseSelector)
|
|
77
|
+
// Step 1: Check active scenario first
|
|
78
|
+
// Track if active has a fallback mock (no match criteria) for this URL+method
|
|
79
|
+
let activeHasFallbackMock = false;
|
|
71
80
|
if (activeScenario) {
|
|
72
81
|
const scenarioDefinition = getScenarioDefinition(activeScenario.scenarioId);
|
|
73
82
|
if (scenarioDefinition) {
|
|
74
83
|
scenarioDefinition.mocks.forEach((mock) => {
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
84
|
+
const match = mockMatchesRequest(mock, method, url);
|
|
85
|
+
if (match.matches) {
|
|
86
|
+
mocksWithParams.push({ mock, params: match.params });
|
|
87
|
+
// A mock without match criteria is a "fallback" - it always matches
|
|
88
|
+
if (!mock.match) {
|
|
89
|
+
activeHasFallbackMock = true;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// Step 2: Include default scenario mocks only if:
|
|
96
|
+
// - No active scenario is set, OR
|
|
97
|
+
// - Active scenario doesn't have a fallback mock for this URL+method
|
|
98
|
+
// (i.e., active only has conditional mocks, so default is needed as backup)
|
|
99
|
+
if (!activeScenario || !activeHasFallbackMock) {
|
|
100
|
+
const defaultScenario = getScenarioDefinition("default");
|
|
101
|
+
if (defaultScenario) {
|
|
102
|
+
defaultScenario.mocks.forEach((mock) => {
|
|
103
|
+
const match = mockMatchesRequest(mock, method, url);
|
|
104
|
+
if (match.matches) {
|
|
105
|
+
mocksWithParams.push({ mock, params: match.params });
|
|
79
106
|
}
|
|
80
107
|
});
|
|
81
108
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"url-matcher.d.ts","sourceRoot":"","sources":["../../src/matching/url-matcher.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;CAC5E,CAAC;
|
|
1
|
+
{"version":3,"file":"url-matcher.d.ts","sourceRoot":"","sources":["../../src/matching/url-matcher.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;CAC5E,CAAC;AAqEF;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,UAAU,GACrB,SAAS,MAAM,GAAG,MAAM,EACxB,YAAY,MAAM,KACjB,cAuEF,CAAC"}
|
|
@@ -13,6 +13,7 @@ import { match } from "path-to-regexp";
|
|
|
13
13
|
const extractPathnameOrReturnAsIs = (url) => {
|
|
14
14
|
// Match protocol://host pattern to manually extract pathname
|
|
15
15
|
// This preserves path-to-regexp syntax that URL constructor would corrupt
|
|
16
|
+
// eslint-disable-next-line security/detect-unsafe-regex -- This regex is safe: no nested quantifiers or overlapping alternatives
|
|
16
17
|
const urlPattern = /^https?:\/\/[^/]+(\/.*)?$/;
|
|
17
18
|
const match = urlPattern.exec(url);
|
|
18
19
|
if (match) {
|
|
@@ -33,14 +34,21 @@ const extractPathnameOrReturnAsIs = (url) => {
|
|
|
33
34
|
* Returns Record<string, string | string[]> matching MSW's documented types.
|
|
34
35
|
*/
|
|
35
36
|
const extractParams = (params) => {
|
|
36
|
-
|
|
37
|
+
const result = {};
|
|
38
|
+
for (const [key, value] of Object.entries(params)) {
|
|
37
39
|
// Filter out unnamed groups (numeric keys like '0', '1', '2', etc.)
|
|
38
40
|
if (/^\d+$/.test(key)) {
|
|
39
|
-
|
|
41
|
+
continue;
|
|
40
42
|
}
|
|
41
43
|
// Keep strings and arrays (MSW documented: string | string[])
|
|
42
|
-
|
|
43
|
-
|
|
44
|
+
if (typeof value === "string") {
|
|
45
|
+
result[key] = value;
|
|
46
|
+
}
|
|
47
|
+
else if (Array.isArray(value)) {
|
|
48
|
+
result[key] = value;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return result;
|
|
44
52
|
};
|
|
45
53
|
/**
|
|
46
54
|
* Extract hostname from URL, or return undefined if not a full URL.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scenarist/msw-adapter",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Internal: MSW integration layer for Scenarist framework adapters",
|
|
5
5
|
"author": "Paul Hammond (citypaul) <paul@packsoftware.co.uk>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
],
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"path-to-regexp": "^6.3.0",
|
|
45
|
-
"@scenarist/core": "0.3.
|
|
45
|
+
"@scenarist/core": "0.3.3"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
48
|
"msw": "^2.0.0"
|