@schmock/core 2.0.3 → 2.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/interceptor.d.ts.map +1 -1
- package/dist/interceptor.js +55 -8
- package/package.json +1 -1
- package/src/interceptor.ts +57 -9
- package/src/steps/interceptor.steps.ts +54 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AA2JA;;GAEG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,CACN,MAAM,EAAE,OAAO,CAAC,UAAU,EAC1B,IAAI,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,OAAO,CAAC,cAAc,KACpC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,EAC9B,OAAO,GAAE,OAAO,CAAC,gBAAqB,GACrC,OAAO,CAAC,eAAe,CA0JzB"}
|
package/dist/interceptor.js
CHANGED
|
@@ -19,6 +19,43 @@ function extractPathname(url) {
|
|
|
19
19
|
}
|
|
20
20
|
return urlWithoutQuery;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Extract origin (scheme + host + port) from a URL string, or null for
|
|
24
|
+
* relative URLs that don't carry one.
|
|
25
|
+
*/
|
|
26
|
+
function extractOrigin(url) {
|
|
27
|
+
if (!url.includes("://"))
|
|
28
|
+
return null;
|
|
29
|
+
try {
|
|
30
|
+
return new URL(url).origin;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parse the user-supplied baseUrl option into its origin and path parts.
|
|
38
|
+
* - "/api" → { origin: null, path: "/api" }
|
|
39
|
+
* - "https://x.com/api/v1" → { origin: "https://x.com", path: "/api/v1" }
|
|
40
|
+
* - "https://x.com" → { origin: "https://x.com", path: "" }
|
|
41
|
+
*
|
|
42
|
+
* Trailing slash is stripped from the path so the segment-boundary check
|
|
43
|
+
* works the same way for "/api" and "/api/".
|
|
44
|
+
*/
|
|
45
|
+
function parseBaseUrl(baseUrl) {
|
|
46
|
+
if (baseUrl.includes("://")) {
|
|
47
|
+
try {
|
|
48
|
+
const parsed = new URL(baseUrl);
|
|
49
|
+
const rawPath = parsed.pathname;
|
|
50
|
+
const path = rawPath === "/" ? "" : rawPath.replace(/\/$/, "");
|
|
51
|
+
return { origin: parsed.origin, path };
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// Fall through to path-only handling
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return { origin: null, path: baseUrl.replace(/\/$/, "") };
|
|
58
|
+
}
|
|
22
59
|
/**
|
|
23
60
|
* Extract query parameters from a URL string.
|
|
24
61
|
*/
|
|
@@ -114,15 +151,25 @@ export function createFetchInterceptor(handle, options = {}) {
|
|
|
114
151
|
? input.href
|
|
115
152
|
: input;
|
|
116
153
|
const path = extractPathname(urlString);
|
|
117
|
-
// BaseUrl filter — non-matching requests go straight to real fetch
|
|
118
|
-
//
|
|
154
|
+
// BaseUrl filter — non-matching requests go straight to real fetch.
|
|
155
|
+
// Two modes:
|
|
156
|
+
// - origin form ("https://api.example.com/v1"): require matching
|
|
157
|
+
// origin AND matching path prefix.
|
|
158
|
+
// - path form ("/api"): match pathname prefix only.
|
|
159
|
+
// Both enforce a segment boundary so "/api" doesn't match "/apiv2".
|
|
119
160
|
if (baseUrl) {
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
161
|
+
const { origin: baseOrigin, path: basePath } = parseBaseUrl(baseUrl);
|
|
162
|
+
if (baseOrigin) {
|
|
163
|
+
const reqOrigin = extractOrigin(urlString);
|
|
164
|
+
if (reqOrigin !== baseOrigin) {
|
|
165
|
+
return originalFetch(input, init);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (basePath) {
|
|
169
|
+
const isMatch = path === basePath || path.startsWith(`${basePath}/`);
|
|
170
|
+
if (!isMatch) {
|
|
171
|
+
return originalFetch(input, init);
|
|
172
|
+
}
|
|
126
173
|
}
|
|
127
174
|
}
|
|
128
175
|
// Build adapter request
|
package/package.json
CHANGED
package/src/interceptor.ts
CHANGED
|
@@ -24,6 +24,45 @@ function extractPathname(url: string): string {
|
|
|
24
24
|
return urlWithoutQuery;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Extract origin (scheme + host + port) from a URL string, or null for
|
|
29
|
+
* relative URLs that don't carry one.
|
|
30
|
+
*/
|
|
31
|
+
function extractOrigin(url: string): string | null {
|
|
32
|
+
if (!url.includes("://")) return null;
|
|
33
|
+
try {
|
|
34
|
+
return new URL(url).origin;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Parse the user-supplied baseUrl option into its origin and path parts.
|
|
42
|
+
* - "/api" → { origin: null, path: "/api" }
|
|
43
|
+
* - "https://x.com/api/v1" → { origin: "https://x.com", path: "/api/v1" }
|
|
44
|
+
* - "https://x.com" → { origin: "https://x.com", path: "" }
|
|
45
|
+
*
|
|
46
|
+
* Trailing slash is stripped from the path so the segment-boundary check
|
|
47
|
+
* works the same way for "/api" and "/api/".
|
|
48
|
+
*/
|
|
49
|
+
function parseBaseUrl(baseUrl: string): {
|
|
50
|
+
origin: string | null;
|
|
51
|
+
path: string;
|
|
52
|
+
} {
|
|
53
|
+
if (baseUrl.includes("://")) {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = new URL(baseUrl);
|
|
56
|
+
const rawPath = parsed.pathname;
|
|
57
|
+
const path = rawPath === "/" ? "" : rawPath.replace(/\/$/, "");
|
|
58
|
+
return { origin: parsed.origin, path };
|
|
59
|
+
} catch {
|
|
60
|
+
// Fall through to path-only handling
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return { origin: null, path: baseUrl.replace(/\/$/, "") };
|
|
64
|
+
}
|
|
65
|
+
|
|
27
66
|
/**
|
|
28
67
|
* Extract query parameters from a URL string.
|
|
29
68
|
*/
|
|
@@ -150,16 +189,25 @@ export function createFetchInterceptor(
|
|
|
150
189
|
|
|
151
190
|
const path = extractPathname(urlString);
|
|
152
191
|
|
|
153
|
-
// BaseUrl filter — non-matching requests go straight to real fetch
|
|
154
|
-
//
|
|
192
|
+
// BaseUrl filter — non-matching requests go straight to real fetch.
|
|
193
|
+
// Two modes:
|
|
194
|
+
// - origin form ("https://api.example.com/v1"): require matching
|
|
195
|
+
// origin AND matching path prefix.
|
|
196
|
+
// - path form ("/api"): match pathname prefix only.
|
|
197
|
+
// Both enforce a segment boundary so "/api" doesn't match "/apiv2".
|
|
155
198
|
if (baseUrl) {
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
199
|
+
const { origin: baseOrigin, path: basePath } = parseBaseUrl(baseUrl);
|
|
200
|
+
if (baseOrigin) {
|
|
201
|
+
const reqOrigin = extractOrigin(urlString);
|
|
202
|
+
if (reqOrigin !== baseOrigin) {
|
|
203
|
+
return originalFetch(input, init);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (basePath) {
|
|
207
|
+
const isMatch = path === basePath || path.startsWith(`${basePath}/`);
|
|
208
|
+
if (!isMatch) {
|
|
209
|
+
return originalFetch(input, init);
|
|
210
|
+
}
|
|
163
211
|
}
|
|
164
212
|
}
|
|
165
213
|
|
|
@@ -130,6 +130,60 @@ describeFeature(feature, ({ Scenario, AfterEachScenario }) => {
|
|
|
130
130
|
});
|
|
131
131
|
});
|
|
132
132
|
|
|
133
|
+
Scenario("Origin-form baseUrl intercepts matching origin only", ({ Given, When, Then, And }) => {
|
|
134
|
+
Given("a Schmock instance with route \"GET /api/users\" returning users", () => {
|
|
135
|
+
setup();
|
|
136
|
+
mock("GET /api/users", [{ id: 1 }]);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
And("fetch is intercepted with baseUrl \"https://api.example.com\"", () => {
|
|
140
|
+
handle = mock.intercept({ baseUrl: "https://api.example.com" });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
When("I fetch \"https://api.example.com/api/users\"", async () => {
|
|
144
|
+
fetchResponse = await fetch("https://api.example.com/api/users");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
Then("the response status should be {int}", (_, status: number) => {
|
|
148
|
+
expect(fetchResponse?.status).toBe(status);
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
When("I fetch \"https://other.example.com/api/users\"", async () => {
|
|
152
|
+
await fetch("https://other.example.com/api/users");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
Then("the original fetch should have been called", () => {
|
|
156
|
+
expect(savedFetch).toHaveBeenCalled();
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
Scenario("Origin-form baseUrl with a path prefix requires both to match", ({ Given, When, Then, And }) => {
|
|
161
|
+
Given("a Schmock instance with route \"GET /v1/users\" returning users", () => {
|
|
162
|
+
setup();
|
|
163
|
+
mock("GET /v1/users", [{ id: 1 }]);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
And("fetch is intercepted with baseUrl \"https://api.example.com/v1\"", () => {
|
|
167
|
+
handle = mock.intercept({ baseUrl: "https://api.example.com/v1" });
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
When("I fetch \"https://api.example.com/v1/users\"", async () => {
|
|
171
|
+
fetchResponse = await fetch("https://api.example.com/v1/users");
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
Then("the response status should be {int}", (_, status: number) => {
|
|
175
|
+
expect(fetchResponse?.status).toBe(status);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
When("I fetch \"https://api.example.com/users\"", async () => {
|
|
179
|
+
await fetch("https://api.example.com/users");
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
Then("the original fetch should have been called", () => {
|
|
183
|
+
expect(savedFetch).toHaveBeenCalled();
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
133
187
|
Scenario("beforeRequest hook modifies the request", ({ Given, When, Then, And }) => {
|
|
134
188
|
Given("a Schmock instance with route \"GET /api/users\" that reads headers", () => {
|
|
135
189
|
setup();
|