mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
test(circuit-breakers): harden regression tests with try/finally and existence guards (#911)
- Wrap all 4 behavioral it() blocks in try/finally so clearAllCircuitBreakers() always runs on assertion failure (P2 — leaked breaker state between tests) - Add assert.ok(fnStart !== -1) guards for fetchHapiSummary, fetchPositiveGdeltArticles, and fetchGdeltArticles so renames produce a clear diagnostic (P2 — silent false-positives) - Fix misleading comment in seed-wb-indicators.mjs: WLD/EAS are 3-char codes and aren't filtered by iso3.length !== 3 (P3) - Add timeout-minutes: 10 and permissions: contents: read to seed GHA workflow (P3)
This commit is contained in:
3
.github/workflows/seed-wb-indicators.yml
vendored
3
.github/workflows/seed-wb-indicators.yml
vendored
@@ -9,6 +9,9 @@ on:
|
||||
jobs:
|
||||
seed:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
|
||||
@@ -185,7 +185,7 @@ async function fetchWbIndicator(indicatorId, dateRange) {
|
||||
for (const entry of allEntries) {
|
||||
if (entry.value === null || entry.value === undefined) continue;
|
||||
const iso3 = entry.countryiso3code;
|
||||
if (!iso3 || iso3.length !== 3) continue; // skip aggregates (WLD, EAS, etc.)
|
||||
if (!iso3 || iso3.length !== 3) continue; // skip entries with missing or malformed country codes
|
||||
|
||||
const year = parseInt(entry.date, 10);
|
||||
if (!latestByCountry[iso3] || year > latestByCountry[iso3].year) {
|
||||
|
||||
@@ -37,6 +37,7 @@ describe('conflict/index.ts — per-country HAPI circuit breakers', () => {
|
||||
// Scoped slices to avoid false positives from comments or unrelated code
|
||||
const breakerSection = src.slice(src.indexOf('hapiBreakers'), src.indexOf('hapiBreakers') + 400);
|
||||
const fnStart = src.indexOf('export async function fetchHapiSummary');
|
||||
assert.ok(fnStart !== -1, 'fetchHapiSummary not found in conflict/index.ts — was it renamed?');
|
||||
const fnBody = src.slice(fnStart, src.indexOf('\nexport ', fnStart + 1));
|
||||
|
||||
it('does NOT have a single shared hapiBreaker', () => {
|
||||
@@ -89,8 +90,10 @@ describe('gdelt-intel.ts — dedicated circuit breakers per GDELT query type', (
|
||||
|
||||
// Scoped function body slices
|
||||
const posStart = src.indexOf('export async function fetchPositiveGdeltArticles');
|
||||
assert.ok(posStart !== -1, 'fetchPositiveGdeltArticles not found in gdelt-intel.ts — was it renamed?');
|
||||
const posBody = src.slice(posStart, src.indexOf('\nexport ', posStart + 1));
|
||||
const regStart = src.indexOf('export async function fetchGdeltArticles');
|
||||
assert.ok(regStart !== -1, 'fetchGdeltArticles not found in gdelt-intel.ts — was it renamed?');
|
||||
const regBody = src.slice(regStart, src.indexOf('\nexport ', regStart + 1));
|
||||
|
||||
it('has a dedicated positiveGdeltBreaker separate from gdeltBreaker', () => {
|
||||
@@ -158,6 +161,7 @@ describe('CircuitBreaker isolation — HAPI per-country independence', () => {
|
||||
|
||||
clearAllCircuitBreakers();
|
||||
|
||||
try {
|
||||
const breakerUS = createCircuitBreaker({ name: 'HDX HAPI:US', cacheTtlMs: 30 * 60 * 1000 });
|
||||
const breakerRU = createCircuitBreaker({ name: 'HDX HAPI:RU', cacheTtlMs: 30 * 60 * 1000 });
|
||||
|
||||
@@ -176,8 +180,9 @@ describe('CircuitBreaker isolation — HAPI per-country independence', () => {
|
||||
const goodData = { summary: { countryCode: 'RU', conflictEvents: 12, displacedPersons: 5000 } };
|
||||
const result = await breakerRU.execute(async () => goodData, fallback);
|
||||
assert.deepEqual(result, goodData, 'breakerRU should return live data unaffected by breakerUS cooldown');
|
||||
|
||||
} finally {
|
||||
clearAllCircuitBreakers();
|
||||
}
|
||||
});
|
||||
|
||||
it('HAPI: different countries cache independently (no cross-country poisoning)', async () => {
|
||||
@@ -187,6 +192,7 @@ describe('CircuitBreaker isolation — HAPI per-country independence', () => {
|
||||
|
||||
clearAllCircuitBreakers();
|
||||
|
||||
try {
|
||||
const breakerUS = createCircuitBreaker({ name: 'HDX HAPI:US', cacheTtlMs: 30 * 60 * 1000 });
|
||||
const breakerRU = createCircuitBreaker({ name: 'HDX HAPI:RU', cacheTtlMs: 30 * 60 * 1000 });
|
||||
|
||||
@@ -208,8 +214,9 @@ describe('CircuitBreaker isolation — HAPI per-country independence', () => {
|
||||
'breakerRU cache must return RU data, not US data');
|
||||
assert.notEqual(cachedUS.summary?.conflictEvents, cachedRU.summary?.conflictEvents,
|
||||
'Cached conflict event counts must be independent per country');
|
||||
|
||||
} finally {
|
||||
clearAllCircuitBreakers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -225,6 +232,7 @@ describe('CircuitBreaker isolation — GDELT split breaker independence', () =>
|
||||
|
||||
clearAllCircuitBreakers();
|
||||
|
||||
try {
|
||||
const gdelt = createCircuitBreaker({ name: 'GDELT Intelligence', cacheTtlMs: 10 * 60 * 1000 });
|
||||
const positive = createCircuitBreaker({ name: 'GDELT Positive', cacheTtlMs: 10 * 60 * 1000 });
|
||||
|
||||
@@ -243,8 +251,9 @@ describe('CircuitBreaker isolation — GDELT split breaker independence', () =>
|
||||
const realArticles = { articles: [{ url: 'https://news.example/military', title: 'Conflict update' }], totalArticles: 1 };
|
||||
const result = await gdelt.execute(async () => realArticles, fallback);
|
||||
assert.deepEqual(result, realArticles, 'gdelt breaker should return live data unaffected by positive cooldown');
|
||||
|
||||
} finally {
|
||||
clearAllCircuitBreakers();
|
||||
}
|
||||
});
|
||||
|
||||
it('GDELT: regular and positive breakers cache different data independently', async () => {
|
||||
@@ -254,6 +263,7 @@ describe('CircuitBreaker isolation — GDELT split breaker independence', () =>
|
||||
|
||||
clearAllCircuitBreakers();
|
||||
|
||||
try {
|
||||
const gdelt = createCircuitBreaker({ name: 'GDELT Intelligence', cacheTtlMs: 10 * 60 * 1000 });
|
||||
const positive = createCircuitBreaker({ name: 'GDELT Positive', cacheTtlMs: 10 * 60 * 1000 });
|
||||
|
||||
@@ -282,7 +292,8 @@ describe('CircuitBreaker isolation — GDELT split breaker independence', () =>
|
||||
cachedPositive.articles[0]?.url,
|
||||
'Cached article URLs must be distinct per breaker (no cross-contamination)',
|
||||
);
|
||||
|
||||
} finally {
|
||||
clearAllCircuitBreakers();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user