From 2870c6cc802431c5599caaf25ed175db5b0d41dd Mon Sep 17 00:00:00 2001 From: Noam Okman Date: Wed, 9 Jul 2025 17:07:10 +0300 Subject: [PATCH 1/2] fix withHeaders not respecting headers class and withToken tests --- package.json | 4 +-- src/enhancers/withHeaders.ts | 24 ++++++++++--- src/enhancers/withToken.ts | 21 ++++------- tests/specs/withHeaders.spec.ts | 63 +++++++++++++++++++++++++++++++++ tests/specs/withToken.spec.ts | 41 +++++++++++++++++++++ 5 files changed, 132 insertions(+), 21 deletions(-) create mode 100644 tests/specs/withToken.spec.ts diff --git a/package.json b/package.json index 9bbfd91..ea731c2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@osskit/fetch-enhancers", - "version": "4.6.0", + "version": "4.7.0", "repository": { "url": "https://github.com/osskit/fetch-enhancers" }, @@ -49,4 +49,4 @@ "*.{ts,js,json}": "prettier --write" }, "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" -} +} \ No newline at end of file diff --git a/src/enhancers/withHeaders.ts b/src/enhancers/withHeaders.ts index eb59e94..b388d36 100644 --- a/src/enhancers/withHeaders.ts +++ b/src/enhancers/withHeaders.ts @@ -1,12 +1,28 @@ import type { Fetch } from '../types.js'; +const headersToObject = (headers?: HeadersInit): Record | undefined => { + if (!headers) { + return undefined; + } + if (headers instanceof Headers) { + return Object.fromEntries(headers.entries()); + } + if (Array.isArray(headers)) { + return Object.fromEntries(headers); + } + if (typeof headers === 'object') { + return { ...headers }; + } + return undefined; +}; + export const withHeaders = - (fetch: Fetch, getHeaders: (headers?: HeadersInit) => HeadersInit): Fetch => - (url, init) => + (fetch: Fetch, getHeaders: (headers?: HeadersInit) => HeadersInit | Promise): Fetch => + async (url, init) => fetch(url, { ...init, headers: { - ...init?.headers, - ...getHeaders(init?.headers), + ...headersToObject(init?.headers), + ...headersToObject(await getHeaders(init?.headers)), }, }); diff --git a/src/enhancers/withToken.ts b/src/enhancers/withToken.ts index f35413b..80f490c 100644 --- a/src/enhancers/withToken.ts +++ b/src/enhancers/withToken.ts @@ -1,17 +1,8 @@ import type { Fetch } from '../types.js'; +import { withHeaders } from './withHeaders.js'; -export const withToken = - (getToken: () => Promise | string, fetch: Fetch): Fetch => - async (url, innerInit) => { - const innerFetch = fetch; - const token = await getToken(); - - return innerFetch(url, { - ...innerInit, - headers: { - ...innerInit?.headers, - // eslint-disable-next-line @typescript-eslint/naming-convention - Authorization: `Bearer ${token}`, - }, - }); - }; +export const withToken = (getToken: () => Promise | string, fetch: Fetch): Fetch => + withHeaders(fetch, async () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + Authorization: `Bearer ${await getToken()}`, + })); diff --git a/tests/specs/withHeaders.spec.ts b/tests/specs/withHeaders.spec.ts index 97d2b46..1e4a10b 100644 --- a/tests/specs/withHeaders.spec.ts +++ b/tests/specs/withHeaders.spec.ts @@ -40,4 +40,67 @@ describe('withHeaders', () => { await expect(res.text()).resolves.toMatchInlineSnapshot(`"{"id":"override","bar":"bar","foo":"foo"}"`); server.close(); }); + + it('add headers when getHeaders returns a promise', async () => { + const fetchWithAsyncHeaders = withHeaders(fetch, async () => ({ foo: 'foo', id: 'override' })); + const server = createServer((req, res) => { + const response = JSON.stringify({ foo: req.headers.foo, bar: req.headers.bar }); + res.writeHead(200); + res.end(response); + }); + + await waitForServer(server); + const { port } = server.address() as AddressInfo; + const res = await fetchWithAsyncHeaders(`http://127.0.0.1:${port}`, { headers: { id: 'id', bar: 'bar' } }); + await expect(res.text()).resolves.toMatchInlineSnapshot(`"{"id":"override","bar":"bar","foo":"foo"}"`); + server.close(); + }); + + it('merges headers when init.headers is a Headers instance', async () => { + const server = createServer((req, res) => { + const response = JSON.stringify({ foo: req.headers.foo, bar: req.headers.bar }); + res.writeHead(200); + res.end(response); + }); + + await waitForServer(server); + const { port } = server.address() as AddressInfo; + const headers = new Headers({ bar: 'baz' }); + const fetchWithHeadersInstance = withHeaders(fetch, () => ({ foo: 'bar' })); + const res = await fetchWithHeadersInstance(`http://127.0.0.1:${port}`, { headers }); + await expect(res.text()).resolves.toBe('{"foo":"bar","bar":"baz"}'); + server.close(); + }); + + it('merges headers when init.headers is an object and getHeaders returns a Headers instance', async () => { + const server = createServer((req, res) => { + const response = JSON.stringify({ foo: req.headers.foo, bar: req.headers.bar }); + res.writeHead(200); + res.end(response); + }); + + await waitForServer(server); + const { port } = server.address() as AddressInfo; + const headers = new Headers({ bar: 'baz' }); + const fetchWithHeadersInstance = withHeaders(fetch, () => new Headers({ foo: 'bar' })); + const res = await fetchWithHeadersInstance(`http://127.0.0.1:${port}`, { headers }); + await expect(res.text()).resolves.toBe('{"foo":"bar","bar":"baz"}'); + server.close(); + }); + + it('merges headers when init.headers is a Headers instance and getHeaders returns a Headers instance', async () => { + const server = createServer((req, res) => { + const response = JSON.stringify({ foo: req.headers.foo, bar: req.headers.bar }); + res.writeHead(200); + res.end(response); + }); + + await waitForServer(server); + const { port } = server.address() as AddressInfo; + const headers = new Headers({ bar: 'baz' }); + const fetchWithHeadersInstance = withHeaders(fetch, () => new Headers({ foo: 'bar' })); + const res = await fetchWithHeadersInstance(`http://127.0.0.1:${port}`, { headers }); + await expect(res.text()).resolves.toBe('{"foo":"bar","bar":"baz"}'); + server.close(); + }); }); diff --git a/tests/specs/withToken.spec.ts b/tests/specs/withToken.spec.ts new file mode 100644 index 0000000..1f4f9a8 --- /dev/null +++ b/tests/specs/withToken.spec.ts @@ -0,0 +1,41 @@ +import { createServer } from 'node:http'; +import type { AddressInfo } from 'node:net'; +import { describe, it, expect } from 'vitest'; +import { withToken } from '../../src/index.js'; +import { waitForServer } from '../services/waitForServer.js'; + +describe('withToken', () => { + it('adds token to headers', async () => { + const server = createServer((req, res) => { + const response = JSON.stringify({ + authorization: req.headers.authorization, + }); + res.writeHead(200); + res.end(response); + }); + + await waitForServer(server); + const { port } = server.address() as AddressInfo; + const fetchWithToken = withToken(() => 'mytoken', fetch); + const res = await fetchWithToken(`http://127.0.0.1:${port}`); + await expect(res.text()).resolves.toBe('{"authorization":"Bearer mytoken"}'); + }); + + it('merges headers when init.headers is a Headers instance', async () => { + const server = createServer((req, res) => { + const response = JSON.stringify({ + authorization: req.headers.authorization, + foo: req.headers.foo, + }); + res.writeHead(200); + res.end(response); + }); + await waitForServer(server); + const { port } = server.address() as AddressInfo; + const headers = new Headers({ foo: 'bar' }); + const fetchWithToken = withToken(() => 'mytoken', fetch); + const res = await fetchWithToken(`http://127.0.0.1:${port}`, { headers }); + await expect(res.text()).resolves.toBe('{"authorization":"Bearer mytoken","foo":"bar"}'); + server.close(); + }); +}); From c37ed1f21e09a79031cb4b46e9f936705b29229d Mon Sep 17 00:00:00 2001 From: Noam Okman Date: Wed, 9 Jul 2025 17:27:15 +0300 Subject: [PATCH 2/2] . --- tests/specs/withHeaders.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/specs/withHeaders.spec.ts b/tests/specs/withHeaders.spec.ts index 1e4a10b..3f01450 100644 --- a/tests/specs/withHeaders.spec.ts +++ b/tests/specs/withHeaders.spec.ts @@ -37,14 +37,14 @@ describe('withHeaders', () => { const { port } = server.address() as AddressInfo; const res = await fetchWithExtraHeaders(`http://127.0.0.1:${port}`, { headers: { id: 'id', bar: 'bar' } }); - await expect(res.text()).resolves.toMatchInlineSnapshot(`"{"id":"override","bar":"bar","foo":"foo"}"`); + await expect(res.json()).resolves.toStrictEqual({ id: 'override', bar: 'bar', foo: 'foo' }); server.close(); }); it('add headers when getHeaders returns a promise', async () => { const fetchWithAsyncHeaders = withHeaders(fetch, async () => ({ foo: 'foo', id: 'override' })); const server = createServer((req, res) => { - const response = JSON.stringify({ foo: req.headers.foo, bar: req.headers.bar }); + const response = JSON.stringify({ id: req.headers.id, bar: req.headers.bar, foo: req.headers.foo }); res.writeHead(200); res.end(response); }); @@ -52,7 +52,7 @@ describe('withHeaders', () => { await waitForServer(server); const { port } = server.address() as AddressInfo; const res = await fetchWithAsyncHeaders(`http://127.0.0.1:${port}`, { headers: { id: 'id', bar: 'bar' } }); - await expect(res.text()).resolves.toMatchInlineSnapshot(`"{"id":"override","bar":"bar","foo":"foo"}"`); + await expect(res.json()).resolves.toStrictEqual({ id: 'override', bar: 'bar', foo: 'foo' }); server.close(); }); @@ -68,7 +68,7 @@ describe('withHeaders', () => { const headers = new Headers({ bar: 'baz' }); const fetchWithHeadersInstance = withHeaders(fetch, () => ({ foo: 'bar' })); const res = await fetchWithHeadersInstance(`http://127.0.0.1:${port}`, { headers }); - await expect(res.text()).resolves.toBe('{"foo":"bar","bar":"baz"}'); + await expect(res.json()).resolves.toStrictEqual({ foo: 'bar', bar: 'baz' }); server.close(); }); @@ -84,7 +84,7 @@ describe('withHeaders', () => { const headers = new Headers({ bar: 'baz' }); const fetchWithHeadersInstance = withHeaders(fetch, () => new Headers({ foo: 'bar' })); const res = await fetchWithHeadersInstance(`http://127.0.0.1:${port}`, { headers }); - await expect(res.text()).resolves.toBe('{"foo":"bar","bar":"baz"}'); + await expect(res.json()).resolves.toStrictEqual({ foo: 'bar', bar: 'baz' }); server.close(); }); @@ -100,7 +100,7 @@ describe('withHeaders', () => { const headers = new Headers({ bar: 'baz' }); const fetchWithHeadersInstance = withHeaders(fetch, () => new Headers({ foo: 'bar' })); const res = await fetchWithHeadersInstance(`http://127.0.0.1:${port}`, { headers }); - await expect(res.text()).resolves.toBe('{"foo":"bar","bar":"baz"}'); + await expect(res.json()).resolves.toStrictEqual({ foo: 'bar', bar: 'baz' }); server.close(); }); });