163 lines
4.9 KiB
JavaScript
163 lines
4.9 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { safeFetch, safeFetchText, getFetchMetrics } from '../apis/utils/fetch.mjs';
|
|
|
|
test('safeFetch reports HTML as degraded JSON response', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
headers: { get: () => 'text/html' },
|
|
text: async () => '<html>not json</html>',
|
|
});
|
|
try {
|
|
const data = await safeFetch('https://example.test/json', { retries: 0, source: 'unit' });
|
|
assert.match(data.error, /Expected JSON/);
|
|
assert.ok(getFetchMetrics().bySource.unit.requests >= 1);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
test('safeFetchText returns text and byte count', async () => {
|
|
const originalFetch = globalThis.fetch;
|
|
globalThis.fetch = async () => ({
|
|
ok: true,
|
|
status: 200,
|
|
text: async () => 'hello',
|
|
});
|
|
try {
|
|
const data = await safeFetchText('https://example.test/rss', { retries: 0, source: 'rss-unit' });
|
|
assert.equal(data.text, 'hello');
|
|
assert.equal(data.bytes, 5);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
function jsonResponse(payload, ok = true, status = 200) {
|
|
return {
|
|
ok,
|
|
status,
|
|
headers: { getSetCookie: () => [], get: () => 'application/json' },
|
|
text: async () => JSON.stringify(payload),
|
|
json: async () => payload,
|
|
};
|
|
}
|
|
|
|
function textResponse(text, ok = false, status = 500) {
|
|
return {
|
|
ok,
|
|
status,
|
|
headers: { getSetCookie: () => [], get: () => 'text/plain' },
|
|
text: async () => text,
|
|
json: async () => JSON.parse(text),
|
|
};
|
|
}
|
|
|
|
async function withAcledEnv(mockFetch, fn) {
|
|
const originalFetch = globalThis.fetch;
|
|
const saved = {
|
|
ACLED_EMAIL: process.env.ACLED_EMAIL,
|
|
ACLED_USER: process.env.ACLED_USER,
|
|
ACLED_USERNAME: process.env.ACLED_USERNAME,
|
|
ACLED_PASSWORD: process.env.ACLED_PASSWORD,
|
|
};
|
|
globalThis.fetch = mockFetch;
|
|
delete process.env.ACLED_EMAIL;
|
|
delete process.env.ACLED_USER;
|
|
delete process.env.ACLED_USERNAME;
|
|
delete process.env.ACLED_PASSWORD;
|
|
const acled = await import('../apis/sources/acled.mjs');
|
|
acled.resetAcledSessionForTests();
|
|
try {
|
|
return await fn(acled);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
for (const [key, value] of Object.entries(saved)) {
|
|
if (value === undefined) delete process.env[key];
|
|
else process.env[key] = value;
|
|
}
|
|
acled.resetAcledSessionForTests();
|
|
}
|
|
}
|
|
|
|
test('ACLED credentialed OAuth success returns live events and supports ACLED_USER', async () => {
|
|
const responses = [
|
|
jsonResponse({ access_token: 'secret-token' }),
|
|
jsonResponse({
|
|
status: 200,
|
|
data: [{
|
|
event_date: '2026-05-17',
|
|
event_type: 'Protests',
|
|
sub_event_type: 'Peaceful protest',
|
|
country: 'Example',
|
|
region: 'Example Region',
|
|
location: 'Example City',
|
|
fatalities: '0',
|
|
latitude: '1.23',
|
|
longitude: '4.56',
|
|
}],
|
|
}),
|
|
];
|
|
|
|
await withAcledEnv(async () => responses.shift(), async ({ briefing }) => {
|
|
process.env.ACLED_USER = 'operator@example.test';
|
|
process.env.ACLED_PASSWORD = 'password';
|
|
const data = await briefing();
|
|
|
|
assert.equal(data.status, 'live');
|
|
assert.equal(data.totalEvents, 1);
|
|
assert.equal(data.topCountries.Example.count, 1);
|
|
});
|
|
});
|
|
|
|
test('ACLED rejected credentials return auth_failed diagnostics', async () => {
|
|
const responses = [
|
|
textResponse('invalid credentials', false, 401),
|
|
textResponse('forbidden', false, 403),
|
|
];
|
|
|
|
await withAcledEnv(async () => responses.shift(), async ({ briefing }) => {
|
|
process.env.ACLED_EMAIL = 'operator@example.test';
|
|
process.env.ACLED_PASSWORD = 'wrong-password';
|
|
const data = await briefing();
|
|
|
|
assert.equal(data.status, 'auth_failed');
|
|
assert.match(data.error, /All ACLED auth methods failed/);
|
|
});
|
|
});
|
|
|
|
test('ACLED token endpoint failure returns api_failed diagnostics', async () => {
|
|
const responses = [
|
|
textResponse('temporary outage', false, 503),
|
|
textResponse('temporary outage', false, 503),
|
|
];
|
|
|
|
await withAcledEnv(async () => responses.shift(), async ({ briefing }) => {
|
|
process.env.ACLED_EMAIL = 'operator@example.test';
|
|
process.env.ACLED_PASSWORD = 'password';
|
|
const data = await briefing();
|
|
|
|
assert.equal(data.status, 'api_failed');
|
|
assert.match(data.error, /All ACLED auth methods failed/);
|
|
});
|
|
});
|
|
|
|
test('ACLED valid empty response is live with zero events', async () => {
|
|
const responses = [
|
|
jsonResponse({ access_token: 'secret-token' }),
|
|
jsonResponse({ status: 200, data: [] }),
|
|
];
|
|
|
|
await withAcledEnv(async () => responses.shift(), async ({ briefing }) => {
|
|
process.env.ACLED_EMAIL = 'operator@example.test';
|
|
process.env.ACLED_PASSWORD = 'password';
|
|
const data = await briefing();
|
|
|
|
assert.equal(data.status, 'live');
|
|
assert.equal(data.totalEvents, 0);
|
|
assert.match(data.message, /valid empty/);
|
|
});
|
|
});
|