Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
290 changes: 287 additions & 3 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

520 changes: 520 additions & 0 deletions src/test/api/CommercetoolsApi.test.ts

Large diffs are not rendered by default.

15 changes: 15 additions & 0 deletions src/test/auth/CommercetoolsAuth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,21 @@ describe('CommercetoolsAuth', () => {
})
})

describe('revokeToken', () => {
it('should delegate to the underlying api.revokeToken method', async () => {
// Covers line 275 of CommercetoolsAuth.ts.
const auth = new CommercetoolsAuth(defaultConfig)
const scope = nock('https://auth.us-east-2.aws.commercetools.com')
.post('/oauth/token/revoke', `token=my-access-token&token_type_hint=access_token`)
.reply(200, {})

await expect(
auth.revokeToken({ tokenValue: 'my-access-token', tokenType: 'access_token' }),
).resolves.toBeUndefined()
expect(scope.isDone()).toBe(true)
})
})

describe('multiple simultaneous requests', () => {
it('should make all requests wait for the pending client credentials', async () => {
const scope = nock('https://auth.us-east-2.aws.commercetools.com', {
Expand Down
45 changes: 45 additions & 0 deletions src/test/auth/CommercetoolsAuthApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -595,4 +595,49 @@ describe('CommercetoolsAuthApi', () => {
}
})
})

describe('getRequestOptions', () => {
it('uses a provided correlationId when passed in', () => {
// Covers lines 177-178 of CommercetoolsAuthApi.ts — the explicit
// `correlationId` branch in `getRequestOptions`.
const api = new CommercetoolsAuthApi(defaultConfig)

const result = api.getRequestOptions({
path: '/token',
correlationId: 'my-correlation-id',
body: { grant_type: 'client_credentials' },
})

expect(result.headers?.['X-Correlation-ID']).toBe('my-correlation-id')
expect(result.method).toBe('POST')
expect(result.url).toBe('https://auth.us-east-2.aws.commercetools.com/oauth/token')
})

it('generates a UUID correlationId when none is provided', () => {
const api = new CommercetoolsAuthApi(defaultConfig)

const result = api.getRequestOptions({
path: '/token',
body: { grant_type: 'client_credentials' },
})

expect(result.headers?.['X-Correlation-ID']).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
)
})

it('treats an empty string correlationId as missing and generates a UUID', () => {
const api = new CommercetoolsAuthApi(defaultConfig)

const result = api.getRequestOptions({
path: '/token',
correlationId: '',
body: { grant_type: 'client_credentials' },
})

expect(result.headers?.['X-Correlation-ID']).toMatch(
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/,
)
})
})
})
8 changes: 8 additions & 0 deletions src/test/utils/axios/convert-error.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ describe('convertAxiosError', () => {
expect(result?.response.tlsVersion).toBeUndefined()
})

it('falls back to an empty string when the request config has no url', () => {
// Covers the `error.config?.url ?? ''` fallback branch on line 15.
const error = buildAxiosError({ config: undefined as unknown as AxiosError['config'] })
const result = convertAxiosError(error, stats)
expect(result?.request.url).toBe('')
expect(result?.request.method).toBeUndefined()
})

it('produces the expected full shape including tlsVersion', () => {
const error = buildAxiosError({
response: {
Expand Down
10 changes: 10 additions & 0 deletions src/test/utils/axios/convert-response.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ describe('convertAxiosResponse', () => {
expect(result.response.tlsVersion).toBeUndefined()
})

it('falls back to an empty string when the response config has no url', () => {
// Covers the `response.config?.url ?? ''` fallback branch on line 12.
const result = convertAxiosResponse(
buildResponse({ config: undefined as unknown as AxiosResponse['config'] }),
stats,
)
expect(result.request.url).toBe('')
expect(result.request.method).toBeUndefined()
})

it('includes the other response fields alongside tlsVersion', () => {
const result = convertAxiosResponse(buildResponse(), stats)
expect(result).toEqual({
Expand Down
36 changes: 36 additions & 0 deletions src/test/utils/axios/extract-headers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { AxiosHeaders } from 'axios'
import { extractAxiosHeaders } from '../../../lib/utils/axios/extract-headers.js'

describe('extractAxiosHeaders', () => {
it('returns undefined when given non-AxiosHeaders input', () => {
expect(extractAxiosHeaders(undefined)).toBeUndefined()
expect(extractAxiosHeaders(null)).toBeUndefined()
expect(extractAxiosHeaders({ accept: 'application/json' })).toBeUndefined()
})

it('returns undefined when given an empty AxiosHeaders instance', () => {
expect(extractAxiosHeaders(new AxiosHeaders())).toBeUndefined()
})

it('lowercases header names and stringifies scalar values', () => {
const headers = new AxiosHeaders()
headers.set('Accept', 'application/json')
headers.set('X-Number', 42)
headers.set('X-Boolean', true)

expect(extractAxiosHeaders(headers)).toEqual({
accept: 'application/json',
'x-number': '42',
'x-boolean': 'true',
})
})

it('joins array header values with a comma and space', () => {
const headers = new AxiosHeaders()
headers.set('Set-Cookie', ['a=1', 'b=2'])

expect(extractAxiosHeaders(headers)).toEqual({
'set-cookie': 'a=1, b=2',
})
})
})
26 changes: 26 additions & 0 deletions src/test/utils/get-socket-stats.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,30 @@ describe('getSocketStats', () => {
queuedRequests: 0,
})
})

it('treats entries with nullish values as zero', () => {
// Covers the `?.length ?? 0` fallback branches on lines 12/17/22 where
// an enumerated key has an explicitly undefined value.
const mockAgent = {
sockets: {
'host-a:443': undefined,
'host-b:443': [1, 2],
},
freeSockets: {
'host-a:443': undefined,
'host-b:443': [3],
},
requests: {
'host-a:443': undefined,
'host-b:443': [4, 5, 6],
},
} as unknown as https.Agent

const stats = getSocketStats(mockAgent)
expect(stats).toEqual({
activeSockets: 2,
freeSocketCount: 1,
queuedRequests: 3,
})
})
})
19 changes: 18 additions & 1 deletion src/test/utils/mask.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { maskSensitiveData } from '../../lib/utils/mask.js'
import { AxiosHeaders } from 'axios'
import { maskSensitiveData, maskSensitiveHeaders } from '../../lib/utils/mask.js'

describe('maskSensitiveData', () => {
it('should return null if null is passed in', () => {
Expand Down Expand Up @@ -37,6 +38,22 @@ describe('maskSensitiveData', () => {
})
})

describe('AxiosHeaders input', () => {
it('should serialize an AxiosHeaders instance via toJSON before masking', () => {
// Covers the `data instanceof AxiosHeaders` branch on line 43 of mask.ts.
const headers = new AxiosHeaders()
headers.set('Authorization', 'Bearer secret-token')
headers.set('Accept', 'application/json')

const result = maskSensitiveHeaders(headers)

expect(result).toEqual({
Authorization: '********',
Accept: 'application/json',
})
})
})

describe('deeply nested object', () => {
it('should mask all matching field names, regardless of nesting level', () => {
const result = maskSensitiveData(
Expand Down
13 changes: 13 additions & 0 deletions src/test/utils/plain-clone.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,17 @@ describe('plainClone', function () {
},
})
})

it('should ignore inherited (prototype-chain) properties', () => {
// Covers the `hasOwnProperty` false branch on line 20 — properties that
// come from the prototype chain must not be copied across.
const proto = { inherited: 'should-not-be-copied' }
const source = Object.create(proto)
source.own = 'should-be-copied'

const result = plainClone(source)

expect(result).toEqual({ own: 'should-be-copied' })
expect(result.inherited).toBeUndefined()
})
})