138 lines
5.5 KiB
JavaScript
138 lines
5.5 KiB
JavaScript
const test = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
|
|
const { fetchApiSourceResponse, buildApiSourcePayload } = require('../src/data/api-sources');
|
|
|
|
function jsonResponse(status, body) {
|
|
return {
|
|
status: status,
|
|
ok: status >= 200 && status < 300,
|
|
headers: { get: () => 'application/json' },
|
|
text: async () => JSON.stringify(body)
|
|
};
|
|
}
|
|
|
|
test('API source payload accepts POST request and token-login settings', () => {
|
|
const payload = buildApiSourcePayload({
|
|
body: {
|
|
name: 'Protected report',
|
|
api_url: 'https://example.com/report',
|
|
request_method: 'POST',
|
|
request_body_json: '{"site_id":42}',
|
|
auth_method: 'token_login',
|
|
token_url: 'https://example.com/login',
|
|
token_request_body_json: '{"username":"demo","password":"secret"}',
|
|
token_response_path: 'data.accessToken',
|
|
token_refresh_url: 'https://example.com/refresh',
|
|
token_refresh_request_body_json: '{"grant_type":"refresh_token","refresh_token":"{{refresh_token}}"}',
|
|
token_refresh_response_path: 'data.refreshToken',
|
|
token_header_name: 'Authorization',
|
|
token_header_prefix: 'Bearer',
|
|
update_interval_value: '5',
|
|
update_interval_unit: 'minutes'
|
|
}
|
|
});
|
|
|
|
assert.equal(payload.requestMethod, 'POST');
|
|
assert.equal(payload.requestBodyJson, '{"site_id":42}');
|
|
assert.equal(payload.authMethod, 'token_login');
|
|
assert.equal(payload.tokenUrl, 'https://example.com/login');
|
|
assert.equal(payload.tokenResponsePath, 'data.accessToken');
|
|
assert.equal(payload.tokenRefreshUrl, 'https://example.com/refresh');
|
|
assert.equal(payload.tokenRefreshResponsePath, 'data.refreshToken');
|
|
});
|
|
|
|
test('API source sends login POST, uses token, and refreshes once after 401', async () => {
|
|
const originalFetch = global.fetch;
|
|
const calls = [];
|
|
let loginCount = 0;
|
|
let apiCount = 0;
|
|
global.fetch = async function (url, options) {
|
|
calls.push({ url, options });
|
|
if (url === 'https://example.com/login') {
|
|
loginCount += 1;
|
|
return jsonResponse(200, { accessToken: 'token-' + loginCount, expires_in: 300 });
|
|
}
|
|
apiCount += 1;
|
|
return apiCount === 1
|
|
? jsonResponse(401, { error: 'expired' })
|
|
: jsonResponse(200, { items: [{ id: 1 }] });
|
|
};
|
|
|
|
try {
|
|
const result = await fetchApiSourceResponse({
|
|
id: 987654,
|
|
api_url: 'https://example.com/report',
|
|
request_method: 'POST',
|
|
request_body_json: JSON.stringify({ site_id: 42 }),
|
|
auth_method: 'token_login',
|
|
token_url: 'https://example.com/login',
|
|
token_request_body_json: JSON.stringify({ username: 'demo', password: 'secret' }),
|
|
token_response_path: 'accessToken',
|
|
token_header_name: 'Authorization',
|
|
token_header_prefix: 'Bearer'
|
|
});
|
|
|
|
assert.deepEqual(JSON.parse(result.responseJson), { items: [{ id: 1 }] });
|
|
assert.equal(loginCount, 2);
|
|
assert.equal(apiCount, 2);
|
|
assert.equal(calls[0].options.method, 'POST');
|
|
assert.equal(calls[0].options.headers['Content-Type'], 'application/json');
|
|
assert.deepEqual(JSON.parse(calls[0].options.body), { username: 'demo', password: 'secret' });
|
|
assert.equal(calls[1].options.headers.Authorization, 'Bearer token-1');
|
|
assert.equal(calls[3].options.headers.Authorization, 'Bearer token-2');
|
|
assert.deepEqual(JSON.parse(calls[3].options.body), { site_id: 42 });
|
|
} finally {
|
|
global.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
test('API source reuses an unexpired token and refreshes it after expiry', async () => {
|
|
const originalFetch = global.fetch;
|
|
const originalDateNow = Date.now;
|
|
const calls = [];
|
|
let currentTime = 1000000;
|
|
Date.now = () => currentTime;
|
|
global.fetch = async function (url, options) {
|
|
calls.push({ url, options });
|
|
if (url === 'https://example.com/login') {
|
|
return jsonResponse(200, { access_token: 'initial-token', refresh_token: 'initial-refresh', expires_in: 120 });
|
|
}
|
|
if (url === 'https://example.com/refresh') {
|
|
return jsonResponse(200, { access_token: 'refreshed-token', refresh_token: 'rotated-refresh', expires_in: 300 });
|
|
}
|
|
return jsonResponse(200, { items: [] });
|
|
};
|
|
|
|
const source = {
|
|
id: 987655,
|
|
api_url: 'https://example.com/report',
|
|
auth_method: 'token_login',
|
|
token_url: 'https://example.com/login',
|
|
token_request_body_json: JSON.stringify({ username: 'demo', password: 'secret' }),
|
|
token_refresh_url: 'https://example.com/refresh',
|
|
token_refresh_request_body_json: JSON.stringify({ grant_type: 'refresh_token', refresh_token: '{{refresh_token}}' })
|
|
};
|
|
|
|
try {
|
|
await fetchApiSourceResponse(source);
|
|
await fetchApiSourceResponse(source);
|
|
assert.equal(calls.filter(call => call.url === 'https://example.com/login').length, 1);
|
|
assert.equal(calls.filter(call => call.url === 'https://example.com/report').length, 2);
|
|
|
|
currentTime += 120000;
|
|
await fetchApiSourceResponse(source);
|
|
assert.equal(calls.filter(call => call.url === 'https://example.com/refresh').length, 1);
|
|
assert.deepEqual(JSON.parse(calls[3].options.body), { grant_type: 'refresh_token', refresh_token: 'initial-refresh' });
|
|
assert.equal(calls[4].options.headers.Authorization, 'Bearer refreshed-token');
|
|
|
|
currentTime += 300000;
|
|
await fetchApiSourceResponse(source);
|
|
assert.equal(calls.filter(call => call.url === 'https://example.com/refresh').length, 2);
|
|
assert.deepEqual(JSON.parse(calls[5].options.body), { grant_type: 'refresh_token', refresh_token: 'rotated-refresh' });
|
|
} finally {
|
|
Date.now = originalDateNow;
|
|
global.fetch = originalFetch;
|
|
}
|
|
});
|