diff --git a/.changeset/neat-wings-bow.md b/.changeset/neat-wings-bow.md new file mode 100644 index 000000000..a4f700cb0 --- /dev/null +++ b/.changeset/neat-wings-bow.md @@ -0,0 +1,5 @@ +--- +"@commercetools/ts-client": patch +--- + +[Feat][DEVX-793] Allow Passing Custom Headers to Http Requests diff --git a/packages/sdk-client-v3/src/types/types.d.ts b/packages/sdk-client-v3/src/types/types.d.ts index 27215f43e..c1c5c0994 100644 --- a/packages/sdk-client-v3/src/types/types.d.ts +++ b/packages/sdk-client-v3/src/types/types.d.ts @@ -305,6 +305,7 @@ export type IClientOptions = { retryConfig?: RetryOptions maskSensitiveHeaderData?: boolean httpClientOptions?: object + request?: MiddlewareRequest } export type HttpClientOptions = IClientOptions & Optional diff --git a/packages/sdk-client-v3/src/utils/executor.ts b/packages/sdk-client-v3/src/utils/executor.ts index eacb158ac..a0f666c32 100644 --- a/packages/sdk-client-v3/src/utils/executor.ts +++ b/packages/sdk-client-v3/src/utils/executor.ts @@ -57,7 +57,7 @@ export default async function executor(request: HttpClientConfig) { ...options, ...rest, headers: { - ...rest.headers, + ...Object.assign({}, rest.headers, rest.request?.headers ?? {}), }, // for axios diff --git a/packages/sdk-client-v3/tests/utils.test/executor.test.ts b/packages/sdk-client-v3/tests/utils.test/executor.test.ts new file mode 100644 index 000000000..95bc3d4e5 --- /dev/null +++ b/packages/sdk-client-v3/tests/utils.test/executor.test.ts @@ -0,0 +1,129 @@ +import executor from '../../src/utils/executor' +import { HttpClientConfig } from '../../src/types/types' + +const mockResponse = { + data: { ok: true }, + status: 200, + statusCode: 200, + headers: {}, +} + +function createExecutorConfig( + overrides: Partial & { + httpClient?: jest.Mock + } = {} +): HttpClientConfig { + const httpClient = + overrides.httpClient ?? jest.fn(() => Promise.resolve(mockResponse)) + + return { + url: 'https://api.example.com/test', + method: 'GET', + headers: {}, + request: { + uri: '/test', + method: 'GET', + body: null, + headers: {}, + }, + httpClient, + ...overrides, + } +} + +describe('executor', () => { + describe('request headers', () => { + test('passes rest.headers to httpClient', async () => { + const httpClient = jest.fn(() => Promise.resolve(mockResponse)) + + await executor( + createExecutorConfig({ + httpClient, + headers: { 'X-Client-Header': 'client-value' }, + }) + ) + + expect(httpClient).toHaveBeenCalledWith( + 'https://api.example.com/test', + expect.objectContaining({ + headers: { 'X-Client-Header': 'client-value' }, + }) + ) + }) + + test('merges rest.request.headers with rest.headers', async () => { + const httpClient = jest.fn(() => Promise.resolve(mockResponse)) + + await executor( + createExecutorConfig({ + httpClient, + headers: { 'X-Client-Header': 'client-value' }, + request: { + uri: '/test', + method: 'GET', + body: null, + headers: { 'X-Request-Header': 'request-value' }, + }, + }) + ) + + expect(httpClient).toHaveBeenCalledWith( + 'https://api.example.com/test', + expect.objectContaining({ + headers: { + 'X-Client-Header': 'client-value', + 'X-Request-Header': 'request-value', + }, + }) + ) + }) + + test('rest.request.headers override rest.headers on conflict', async () => { + const httpClient = jest.fn(() => Promise.resolve(mockResponse)) + + await executor( + createExecutorConfig({ + httpClient, + headers: { 'X-Shared-Header': 'client-value' }, + request: { + uri: '/test', + method: 'GET', + body: null, + headers: { 'X-Shared-Header': 'request-value' }, + }, + }) + ) + + expect(httpClient).toHaveBeenCalledWith( + 'https://api.example.com/test', + expect.objectContaining({ + headers: { 'X-Shared-Header': 'request-value' }, + }) + ) + }) + + test('uses only rest.headers when rest.request.headers is undefined', async () => { + const httpClient = jest.fn(() => Promise.resolve(mockResponse)) + + await executor( + createExecutorConfig({ + httpClient, + headers: { Authorization: 'Bearer token' }, + request: { + uri: '/test', + method: 'GET', + body: null, + headers: undefined as unknown as Record, + }, + }) + ) + + expect(httpClient).toHaveBeenCalledWith( + 'https://api.example.com/test', + expect.objectContaining({ + headers: { Authorization: 'Bearer token' }, + }) + ) + }) + }) +})