176 lines
3.8 KiB
JavaScript
176 lines
3.8 KiB
JavaScript
const { createRequester, RequestError } = require('../services/request')
|
|
|
|
describe('createRequester', () => {
|
|
test('normalizes successful responses', async () => {
|
|
const requester = createRequester({
|
|
requestAdapter: jest.fn().mockResolvedValue({
|
|
statusCode: 200,
|
|
data: {
|
|
code: 0,
|
|
data: { id: 1, name: 'Xuanzhi' },
|
|
message: 'ok'
|
|
}
|
|
})
|
|
})
|
|
|
|
await expect(
|
|
requester({
|
|
url: '/users/me',
|
|
method: 'GET'
|
|
})
|
|
).resolves.toEqual({
|
|
code: 0,
|
|
data: { id: 1, name: 'Xuanzhi' },
|
|
message: 'ok'
|
|
})
|
|
})
|
|
|
|
test('retries retryable transport failures once', async () => {
|
|
const requestAdapter = jest
|
|
.fn()
|
|
.mockRejectedValueOnce(new Error('timeout'))
|
|
.mockResolvedValueOnce({
|
|
statusCode: 200,
|
|
data: {
|
|
code: 0,
|
|
data: { ok: true },
|
|
message: 'ok'
|
|
}
|
|
})
|
|
|
|
const requester = createRequester({
|
|
requestAdapter,
|
|
shouldRetry: error => error.message === 'timeout'
|
|
})
|
|
|
|
await expect(
|
|
requester({
|
|
url: '/reports/slow-task',
|
|
retry: 1
|
|
})
|
|
).resolves.toEqual({
|
|
code: 0,
|
|
data: { ok: true },
|
|
message: 'ok'
|
|
})
|
|
|
|
expect(requestAdapter).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
test('rejects business errors with a normalized error instance', async () => {
|
|
const requester = createRequester({
|
|
requestAdapter: jest.fn().mockResolvedValue({
|
|
statusCode: 200,
|
|
data: {
|
|
code: 50001,
|
|
data: null,
|
|
message: 'invalid payload'
|
|
}
|
|
})
|
|
})
|
|
|
|
await expect(
|
|
requester({
|
|
url: '/orders',
|
|
method: 'POST',
|
|
data: { name: '' }
|
|
})
|
|
).rejects.toEqual(
|
|
expect.objectContaining({
|
|
name: 'RequestError',
|
|
code: 50001,
|
|
message: 'invalid payload'
|
|
})
|
|
)
|
|
|
|
expect(RequestError).toBeDefined()
|
|
})
|
|
|
|
test('clears auth state and notifies unauthorized handler when token expires', async () => {
|
|
const onUnauthorized = jest.fn()
|
|
const sessionStore = {
|
|
clearSession: jest.fn()
|
|
}
|
|
|
|
const requester = createRequester({
|
|
requestAdapter: jest.fn().mockResolvedValue({
|
|
statusCode: 200,
|
|
data: {
|
|
code: 401,
|
|
data: null,
|
|
message: 'token expired'
|
|
}
|
|
}),
|
|
authExpiredCodes: [401],
|
|
onUnauthorized,
|
|
sessionStore
|
|
})
|
|
|
|
await expect(requester({ url: '/users/me' })).rejects.toEqual(
|
|
expect.objectContaining({
|
|
code: 401,
|
|
message: 'token expired'
|
|
})
|
|
)
|
|
|
|
expect(sessionStore.clearSession).toHaveBeenCalledTimes(1)
|
|
expect(onUnauthorized).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
code: 401,
|
|
message: 'token expired'
|
|
})
|
|
)
|
|
})
|
|
|
|
test('deduplicates in-flight requests when dedupe is enabled', async () => {
|
|
let resolveRequest
|
|
const requestAdapter = jest.fn(
|
|
() =>
|
|
new Promise(resolve => {
|
|
resolveRequest = resolve
|
|
})
|
|
)
|
|
|
|
const requester = createRequester({
|
|
requestAdapter
|
|
})
|
|
|
|
const firstPromise = requester({
|
|
url: '/orders/submit',
|
|
method: 'POST',
|
|
data: { skuId: 1 },
|
|
dedupe: true
|
|
})
|
|
const secondPromise = requester({
|
|
url: '/orders/submit',
|
|
method: 'POST',
|
|
data: { skuId: 1 },
|
|
dedupe: true
|
|
})
|
|
|
|
resolveRequest({
|
|
statusCode: 200,
|
|
data: {
|
|
code: 0,
|
|
data: { orderId: 'A1001' },
|
|
message: 'ok'
|
|
}
|
|
})
|
|
|
|
await expect(Promise.all([firstPromise, secondPromise])).resolves.toEqual([
|
|
{
|
|
code: 0,
|
|
data: { orderId: 'A1001' },
|
|
message: 'ok'
|
|
},
|
|
{
|
|
code: 0,
|
|
data: { orderId: 'A1001' },
|
|
message: 'ok'
|
|
}
|
|
])
|
|
|
|
expect(requestAdapter).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|