Skip to content

Commit 3a7b180

Browse files
authored
fix(conflict): restore HAPI HDX metadata fallback (#7589)
* test(conflict): reproduce HDX metadata WAF challenge Model the empty 202 AWS WAF response that Railway receives when the metadata request uses a fake browser identity. Use the current official HDX resource label in the fixture. * fix(conflict): identify HDX requests as HAPI client HDX challenges the shared fake Chrome user agent from Railway egress. Load the existing HAPI application identifier and send it on both bounded HDX requests. * test(conflict): assert HDX snapshot request identity
1 parent 30297aa commit 3a7b180

2 files changed

Lines changed: 38 additions & 3 deletions

File tree

scripts/_conflict-hapi.mjs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import Papa from 'papaparse';
22

33
import {
4-
CHROME_UA,
54
loadSharedConfig,
65
} from './_seed-utils.mjs';
76

@@ -17,6 +16,8 @@ export const HAPI_HDX_METADATA_TIMEOUT_MS = 60_000;
1716
export const HAPI_HDX_SNAPSHOT_TIMEOUT_MS = 120_000;
1817

1918
const ISO2_TO_ISO3 = loadSharedConfig('iso2-to-iso3.json');
19+
const HAPI_APP_IDENTIFIER_CONFIG = loadSharedConfig('hapi-app-identifier.json');
20+
const HAPI_HDX_USER_AGENT = `${HAPI_APP_IDENTIFIER_CONFIG.application}/1.0`;
2021
const ISO3_TO_ISO2 = new Map(
2122
Object.entries(ISO2_TO_ISO3).map(([iso2, iso3]) => [String(iso3).toUpperCase(), iso2]),
2223
);
@@ -227,7 +228,7 @@ export async function fetchHapiHdxSnapshotRows({
227228
const requestOptions = (accept, timeoutMs) => ({
228229
headers: {
229230
Accept: accept,
230-
'User-Agent': CHROME_UA,
231+
'User-Agent': HAPI_HDX_USER_AGENT,
231232
},
232233
signal: createTimeoutSignal(timeoutMs),
233234
});

tests/seed-conflict-intel-hapi-circuit-breaker.test.mjs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function hapiHdxResource(year) {
4343
return {
4444
id: `resource-${year}`,
4545
format: 'CSV',
46-
name: `hdx_hapi_conflict_event_global_${year}.csv`,
46+
name: `Global Coordination & Context: Conflict Events (${year})`,
4747
url: `https://data.humdata.org/dataset/example/resource/resource-${year}/download/hdx_hapi_conflict_event_global_${year}.csv`,
4848
};
4949
}
@@ -159,6 +159,40 @@ test('HAPI HDX gives snapshot downloads a longer bounded deadline than metadata'
159159
assert.strictEqual(fetchSignals[1], timeoutSignals[1]);
160160
});
161161

162+
test('HAPI HDX metadata identity avoids the Railway WAF challenge', async () => {
163+
let metadataCalls = 0;
164+
const requestUserAgents = [];
165+
const csv = hapiCsv(
166+
'SDN,,,,,,,,,0,political_violence,12,3,2026-07-01,2026-07-31,dataset,resource,,',
167+
);
168+
const rows = await fetchHapiHdxSnapshotRows({
169+
nowMs: NOW,
170+
countryCodes: ['SD'],
171+
fetchFn: async (input, options) => {
172+
requestUserAgents.push(options.headers['User-Agent']);
173+
if (String(input).includes('/api/3/action/package_show')) {
174+
metadataCalls += 1;
175+
if (options.headers['User-Agent'] !== 'wm-crisis-tracker/1.0') {
176+
return new Response('', {
177+
status: 202,
178+
headers: {
179+
'Content-Type': 'text/html',
180+
'x-amzn-waf-action': 'challenge',
181+
},
182+
});
183+
}
184+
return Response.json(hapiHdxMetadata());
185+
}
186+
return new Response(csv, { headers: { 'Content-Type': 'text/csv' } });
187+
},
188+
});
189+
190+
assert.equal(metadataCalls, 1);
191+
assert.deepEqual(requestUserAgents, ['wm-crisis-tracker/1.0', 'wm-crisis-tracker/1.0']);
192+
assert.equal(rows.length, 1);
193+
assert.equal(rows[0].location_code, 'SDN');
194+
});
195+
162196
test('HAPI bulk rows are grouped by country and only the latest reference period is published', () => {
163197
const rows = [
164198
{

0 commit comments

Comments
 (0)