Repository: wheresrhys/fetch-mock
Pull request #867 (2024-11-08 17:18:25)

Rhys/relative url matching


Patch:
diff --git a/docs/docs/Usage/upgrade-guide.md b/docs/docs/Usage/upgrade-guide.md
index e1507cf..8b1b51a 100644
--- a/docs/docs/Usage/upgrade-guide.md
+++ b/docs/docs/Usage/upgrade-guide.md
@@ -8,19 +8,27 @@ The [@fetch-mock/codemods](https://www.npmjs.com/package/@fetch-mock/codemods) l
 
 ## Summary of changes
 
+### Uses global, native `fetch` implementation in all environments
+
+Previously this was true in browsers, but in node.js the node-fetch library was used.
+
 ### `.mock()` method removed
 
 This combined adding a route with mocking the global instance of `fetch`. These are now split into 2 methods: `.route()` and `.mockGlobal()`.
 
-## Reset methods changed
+### Reset methods changed
 
 `.reset()`, `.restore()`, `.resetBehavior()` and `.resetHistory()` have been removed and replaced with [methods that are more granular and clearly named](/fetch-mock/docs/API/resetting). Note that the [jest](/fetch-mock/docs/wrappers/jest) and [vitest](/fetch-mock/docs/wrappers/vitest) wrappers for fetch-mock still implement `.mockClear()`, `mockReset()` and `mockRestore()`.
 
-## Call history methods changed
+### Call history methods changed
 
 The filtering behaviour has been rewritten around named routes, and methods return CallLog objects that contain far more metadata about the call. [Call history docs](/fetch-mock/docs/API/CallHistory)
 
-## Some convenience routing methods removed
+### Relative URLs not permitted by default in Node.js
+
+A consequence of shifting to use the native implementation of the URL class is that relative URLs are no longer permissable when running tests in node.js, but [this can easily be enabled](https://www.wheresrhys.co.uk/fetch-mock/docs/Usage/configuration#allowrelativeurls).
+
+### Some convenience routing methods removed
 
 `getOnce()` and `getAnyOnce()` have been removed, but the behaviour can still be implemented by the user as follows:
 
@@ -29,13 +37,13 @@ The filtering behaviour has been rewritten around named routes, and methods retu
 
 The same is true for `postOnce()`, `deleteOnce()` etc.
 
-## Options removed
+### Options removed
 
 - `overwriteRoutes` - this reflects that multiple routes using the same underlying matcher but different options no longer throw an error.
 - `warnOnFallback` - given the improved state of node.js debugging tools compared to when fetch-mock was first written, this debugging utilty has been removed.
 - `sendAsJson` - fetch-mock@12 implements streams more robustly than previous options, so the user no longer needs to flag when an object response should be converted to JSON.
 - `fallbackToNetwork` - The [`spyGlobal()` method](/fetch-mock/docs/API/mocking-and-spying#spyglobal) should now be used.
 
-## `sandbox()` method removed
+### `sandbox()` method removed
 
 This was principally used when mocking node-fetch referenced as a local variable. Given that `fetch` is now available as a native global it's less useful and has been removed. If necessary to mock a local instance of node-fetch use [`.fetchHandler`](/fetch-mock/docs/API/mocking-and-spying#fetchhandler)
diff --git a/packages/fetch-mock/src/RequestUtils.ts b/packages/fetch-mock/src/RequestUtils.ts
index bba2dc6..51ef08f 100644
--- a/packages/fetch-mock/src/RequestUtils.ts
+++ b/packages/fetch-mock/src/RequestUtils.ts
@@ -17,7 +17,7 @@ export type NormalizedRequestOptions =
 export function hasCredentialsInUrl(url: string): boolean {
 	const urlObject = new URL(
 		url,
-		protocolRelativeUrlRX.test(url) ? 'http://dummy' : undefined,
+		!absoluteUrlRX.test(url) ? 'http://dummy' : undefined,
 	);
 	return Boolean(urlObject.username || urlObject.password);
 }


Test patch:
diff --git a/packages/fetch-mock/src/__tests__/router-integration.test.js b/packages/fetch-mock/src/__tests__/router-integration.test.js
index 2eb37f4..4e83b12 100644
--- a/packages/fetch-mock/src/__tests__/router-integration.test.js
+++ b/packages/fetch-mock/src/__tests__/router-integration.test.js
@@ -1,6 +1,7 @@
 import { describe, expect, it } from 'vitest';
 import fetchMock from '../FetchMock';
 describe('router integration', () => {
+	const isBrowser = Boolean(globalThis.location);
 	it('matchurls  when called with Request', async () => {
 		const fm = fetchMock.createInstance();
 		fm.post('http://a.com/', 200).catch();
@@ -65,6 +66,14 @@ describe('router integration', () => {
 			),
 		).resolves.not.toThrow();
 	});
+	if (!isBrowser) {
+		it('can match relative urls when allowRelativeUrls option is true', async () => {
+			const fm = fetchMock.createInstance();
+			fm.route('/path', 200, { allowRelativeUrls: true });
+			const res = await fm.fetchHandler('/path');
+			expect(res.status).toEqual(200);
+		});
+	}
 });
 describe('user defined matchers', () => {
 	it('match on sync property', async () => {
diff --git a/packages/fetch-mock/src/__tests__/spec-compliance.test.js b/packages/fetch-mock/src/__tests__/spec-compliance.test.js
index 994b82a..57bb382 100644
--- a/packages/fetch-mock/src/__tests__/spec-compliance.test.js
+++ b/packages/fetch-mock/src/__tests__/spec-compliance.test.js
@@ -23,6 +23,15 @@ describe('Spec compliance', () => {
 				),
 			);
 		});
+		it('rejects on protocol agnostic url containing credentials', async () => {
+			await expect(
+				fetchMock.fetchHandler('//user:password@a.com'),
+			).rejects.toThrow(
+				new TypeError(
+					'Request cannot be constructed from a URL that includes credentials: //user:password@a.com/',
+				),
+			);
+		});
 		it('reject if the request method is GET or HEAD and the body is non-null.', async () => {
 			await expect(
 				fetchMock.fetchHandler('http://a.com', { body: 'a' }),