feat: add refresh retry/timeout and tests
This commit is contained in:
112
tests/refresh-client.test.js
Normal file
112
tests/refresh-client.test.js
Normal file
@@ -0,0 +1,112 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { requestRefreshToken } from '../refresh-client.js';
|
||||
|
||||
function mockResponse({ ok, status, jsonData, textData, headers }) {
|
||||
const normalizedHeaders = {};
|
||||
if (headers) {
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
normalizedHeaders[key.toLowerCase()] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok,
|
||||
status,
|
||||
json: async () => jsonData ?? {},
|
||||
text: async () => textData ?? '',
|
||||
headers: {
|
||||
get: (name) => normalizedHeaders[name.toLowerCase()] ?? null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
test('refresh retries on 500 then succeeds', async () => {
|
||||
let calls = 0;
|
||||
const fetchImpl = async () => {
|
||||
calls += 1;
|
||||
if (calls === 1) {
|
||||
return mockResponse({ ok: false, status: 500, textData: 'boom' });
|
||||
}
|
||||
return mockResponse({
|
||||
ok: true,
|
||||
status: 200,
|
||||
jsonData: { access_token: 'access', refresh_token: 'refresh' }
|
||||
});
|
||||
};
|
||||
|
||||
const data = await requestRefreshToken({
|
||||
refreshUrl: 'https://example.com',
|
||||
refreshToken: 'refresh_token',
|
||||
clientId: 'client',
|
||||
timeoutMs: 20,
|
||||
maxRetries: 1,
|
||||
retryDelayMs: 1,
|
||||
fetchImpl
|
||||
});
|
||||
|
||||
assert.equal(data.access_token, 'access');
|
||||
assert.equal(calls, 2);
|
||||
});
|
||||
|
||||
test('refresh does not retry on 400', async () => {
|
||||
let calls = 0;
|
||||
const fetchImpl = async () => {
|
||||
calls += 1;
|
||||
return mockResponse({ ok: false, status: 400, textData: 'bad request' });
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
() => requestRefreshToken({
|
||||
refreshUrl: 'https://example.com',
|
||||
refreshToken: 'refresh_token',
|
||||
clientId: 'client',
|
||||
timeoutMs: 20,
|
||||
maxRetries: 2,
|
||||
retryDelayMs: 1,
|
||||
fetchImpl
|
||||
}),
|
||||
(err) => err?.status === 400
|
||||
);
|
||||
|
||||
assert.equal(calls, 1);
|
||||
});
|
||||
|
||||
test('refresh retries on timeout abort', async () => {
|
||||
let calls = 0;
|
||||
const fetchImpl = async (url, options) => {
|
||||
calls += 1;
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!options?.signal) {
|
||||
resolve(mockResponse({ ok: true, status: 200, jsonData: {} }));
|
||||
return;
|
||||
}
|
||||
if (options.signal.aborted) {
|
||||
const error = new Error('Aborted');
|
||||
error.name = 'AbortError';
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
options.signal.addEventListener('abort', () => {
|
||||
const error = new Error('Aborted');
|
||||
error.name = 'AbortError';
|
||||
reject(error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
await assert.rejects(
|
||||
() => requestRefreshToken({
|
||||
refreshUrl: 'https://example.com',
|
||||
refreshToken: 'refresh_token',
|
||||
clientId: 'client',
|
||||
timeoutMs: 5,
|
||||
maxRetries: 1,
|
||||
retryDelayMs: 1,
|
||||
fetchImpl
|
||||
}),
|
||||
(err) => err?.name === 'AbortError'
|
||||
);
|
||||
|
||||
assert.equal(calls, 2);
|
||||
});
|
||||
Reference in New Issue
Block a user