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; } });