diff --git a/src/hydra/fetchHydra.test.ts b/src/hydra/fetchHydra.test.ts index d1f9e033..5124e02b 100644 --- a/src/hydra/fetchHydra.test.ts +++ b/src/hydra/fetchHydra.test.ts @@ -120,3 +120,129 @@ test.each([ expect(violations).toStrictEqual(expected); }, ); + +describe('fetchHydra header handling', () => { + beforeEach(() => { + fetchMock.resetMocks(); + }); + + test('should preserve headers when using function headers with authentication', async () => { + fetchMock.mockResponse( + JSON.stringify({ + '@id': '/users/1', + '@type': 'User', + id: 1, + }), + { + status: 200, + headers: { + 'Content-Type': 'application/ld+json', + Link: '; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"', + }, + }, + ); + + const getHeaders = () => ({ + 'Content-Type': 'application/merge-patch+json', + }); + + await fetchHydra(new URL('http://localhost/users/1'), { + headers: getHeaders, + user: { + authenticated: true, + token: 'Bearer test-token', + }, + method: 'PATCH', + }); + + expect(fetchMock).toHaveBeenCalledTimes(1); + const call = fetchMock.mock.calls[0]; + expect(call).toBeDefined(); + const requestHeaders = call![1]?.headers as Headers; + + expect(requestHeaders).toBeInstanceOf(Headers); + expect(requestHeaders.get('Content-Type')).toBe( + 'application/merge-patch+json', + ); + expect(requestHeaders.get('Authorization')).toBe('Bearer test-token'); + }); + + test('should preserve headers when using object headers with authentication', async () => { + fetchMock.mockResponse( + JSON.stringify({ + '@id': '/users/1', + '@type': 'User', + id: 1, + }), + { + status: 200, + headers: { + 'Content-Type': 'application/ld+json', + Link: '; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"', + }, + }, + ); + + await fetchHydra(new URL('http://localhost/users/1'), { + headers: { + 'Content-Type': 'application/merge-patch+json', + }, + user: { + authenticated: true, + token: 'Bearer test-token', + }, + method: 'PATCH', + }); + + expect(fetchMock).toHaveBeenCalledTimes(1); + const call = fetchMock.mock.calls[0]; + expect(call).toBeDefined(); + const requestHeaders = call![1]?.headers as Headers; + + expect(requestHeaders).toBeInstanceOf(Headers); + expect(requestHeaders.get('Content-Type')).toBe( + 'application/merge-patch+json', + ); + expect(requestHeaders.get('Authorization')).toBe('Bearer test-token'); + }); + + test('should add authentication header without overriding existing headers', async () => { + fetchMock.mockResponse( + JSON.stringify({ + '@id': '/users/1', + '@type': 'User', + id: 1, + }), + { + status: 200, + headers: { + 'Content-Type': 'application/ld+json', + Link: '; rel="http://www.w3.org/ns/hydra/core#apiDocumentation"', + }, + }, + ); + + await fetchHydra(new URL('http://localhost/users/1'), { + headers: { + 'Content-Type': 'application/merge-patch+json', + 'X-Custom-Header': 'custom-value', + }, + user: { + authenticated: true, + token: 'Bearer test-token', + }, + }); + + expect(fetchMock).toHaveBeenCalledTimes(1); + const call = fetchMock.mock.calls[0]; + expect(call).toBeDefined(); + const requestHeaders = call![1]?.headers as Headers; + + expect(requestHeaders).toBeInstanceOf(Headers); + expect(requestHeaders.get('Content-Type')).toBe( + 'application/merge-patch+json', + ); + expect(requestHeaders.get('X-Custom-Header')).toBe('custom-value'); + expect(requestHeaders.get('Authorization')).toBe('Bearer test-token'); + }); +}); diff --git a/src/hydra/fetchHydra.ts b/src/hydra/fetchHydra.ts index 282f5d1a..bf4c938f 100644 --- a/src/hydra/fetchHydra.ts +++ b/src/hydra/fetchHydra.ts @@ -17,13 +17,14 @@ function fetchHydra( ): Promise { let requestHeaders = options.headers ?? new Headers(); - if ( - typeof requestHeaders !== 'function' && - options.user && - options.user.authenticated && - options.user.token - ) { - requestHeaders = new Headers(requestHeaders); + // Resolve headers if it's a function + if (typeof requestHeaders === 'function') { + requestHeaders = requestHeaders(); + } + + // Convert to Headers object and add authentication if needed + requestHeaders = new Headers(requestHeaders); + if (options.user && options.user.authenticated && options.user.token) { requestHeaders.set('Authorization', options.user.token); }