← back to La Socrata Ingester
test/run-policy.test.mjs
122 lines
// TK-10955: zero-network, zero-database proof of aggregate exit semantics.
import assert from 'node:assert/strict';
import { spawnSync } from 'node:child_process';
import { classifyRunResults } from '../src/run-policy.js';
import { SOURCES } from '../src/sources.js';
const sources = {
gis_zoning: {
allowFailure: {
since: '2026-08-11',
scope: 'aggregate-all-only',
reason: 'mock known upstream failure',
},
},
healthy_source: {},
};
const knownFailure = [{ name: 'gis_zoning', error: 'mock HTTP 502 after retries' }];
const unexpectedFailure = [{ name: 'healthy_source', error: 'mock schema regression' }];
const tolerated = classifyRunResults(knownFailure, sources, 'all');
assert.equal(tolerated.exitCode, 0, 'known gis_zoning failure must not fail aggregate all');
assert.deepEqual(tolerated.tolerated.map((r) => r.name), ['gis_zoning']);
assert.equal(tolerated.unexpected.length, 0);
const unexpected = classifyRunResults(unexpectedFailure, sources, 'all');
assert.equal(unexpected.exitCode, 1, 'unexpected source failure must fail aggregate all');
assert.deepEqual(unexpected.unexpected.map((r) => r.name), ['healthy_source']);
const directKnown = classifyRunResults(knownFailure, sources, 'gis_zoning');
assert.equal(directKnown.exitCode, 1, 'direct source runs must remain strict');
assert.equal(directKnown.tolerated.length, 0);
const mixed = classifyRunResults([...knownFailure, ...unexpectedFailure], sources, 'all');
assert.equal(mixed.exitCode, 1, 'a tolerated failure must never mask an unexpected failure');
assert.deepEqual(mixed.tolerated.map((r) => r.name), ['gis_zoning']);
assert.deepEqual(mixed.unexpected.map((r) => r.name), ['healthy_source']);
console.log('PASS run policy: known aggregate failure exits0; direct/unexpected/mixed failures exit1');
// Exercise the OS process boundary without importing cli.js (which would open PG).
// The child receives only mocked results plus real audited source metadata, then exits
// with the exact code the CLI consumes.
const policyUrl = new URL('../src/run-policy.js', import.meta.url).href;
function mockedProcessStatus(results, targetToken) {
const child = spawnSync(process.execPath, [
'--input-type=module',
'--eval',
`import { classifyRunResults } from ${JSON.stringify(policyUrl)};
const sources = ${JSON.stringify(SOURCES)};
const results = ${JSON.stringify(results)};
process.exit(classifyRunResults(results, sources, ${JSON.stringify(targetToken)}).exitCode);`,
], { encoding: 'utf8' });
assert.equal(child.signal, null, child.stderr);
return child.status;
}
assert.equal(mockedProcessStatus(knownFailure, 'all'), 0, 'known aggregate failure OS exit must be 0');
assert.equal(mockedProcessStatus(unexpectedFailure, 'all'), 1, 'unexpected aggregate failure OS exit must be 1');
assert.equal(mockedProcessStatus(knownFailure, 'gis_zoning'), 1, 'direct known-source failure OS exit must be 1');
console.log('PASS process boundary: mocked no-DB child exits 0/1/1 as required');
// ── TK-10955 (contrarian hole #2): an allowFailure exemption must be scoped to the
// AUDITED failure, not to the source name. Otherwise the one source carrying an
// exemption is the one source whose layer-identity drift is silently swallowed.
const zoningLike = {
gis_zoning: {
allowFailure: { scope: 'aggregate-all-only', match: ['HTTP 502', 'HTTP 503', 'HTTP 504'] },
},
};
const audited502 = classifyRunResults(
[{ name: 'gis_zoning', error: 'HTTP 502 after 5 tries: https://maps.lacity.org/...' }],
zoningLike, 'all'
);
assert.equal(audited502.exitCode, 0, 'the audited 502 must stay tolerated');
const drift = classifyRunResults(
[{ name: 'gis_zoning', error: 'ArcGIS layer identity drift: .../MapServer/72 is now "Foo" but this source expects "Generalized Zoning".' }],
zoningLike, 'all'
);
assert.equal(drift.exitCode, 1, 'layer-identity drift must NOT be swallowed by the 502 exemption');
assert.deepEqual(drift.unexpected.map((r) => r.name), ['gis_zoning']);
assert.equal(drift.tolerated.length, 0);
// Real config must actually carry the scoping (not just the mock).
assert.ok(Array.isArray(SOURCES.gis_zoning.allowFailure.match), 'gis_zoning must scope its exemption');
assert.equal(
classifyRunResults([{ name: 'gis_zoning', error: 'ArcGIS layer identity drift: ...' }], SOURCES, 'all').exitCode,
1, 'live SOURCES config must also refuse to tolerate identity drift'
);
console.log('PASS allowFailure is scoped to the audited failure; identity drift still exits 1');
// ── TK-10955 (2026-09-12 recurrence): assessor_parcels' audited page-1 400 must be
// tolerated in aggregate `all`, but a genuinely different assessor_parcels failure (a
// real bad query, or an upstream schema change) must still fail loud — same discipline
// as gis_zoning's exemption above, now proven on a second source.
const auditedAssessor400 = classifyRunResults(
[{ name: 'assessor_parcels', error: 'ArcGIS error: {"code":400,"message":"Cannot perform query. Invalid query parameters.","details":["Unable to perform query. Please check your parameters."]}' }],
SOURCES, 'all'
);
assert.equal(auditedAssessor400.exitCode, 0, 'the audited assessor_parcels page-1 400 must stay tolerated in aggregate all');
assert.deepEqual(auditedAssessor400.tolerated.map((r) => r.name), ['assessor_parcels']);
const unrelatedAssessorError = classifyRunResults(
[{ name: 'assessor_parcels', error: 'ArcGIS error: {"code":400,"message":"Invalid field: RollYear2"}' }],
SOURCES, 'all'
);
assert.equal(unrelatedAssessorError.exitCode, 1, 'a different assessor_parcels 400 must NOT be swallowed by the audited exemption');
assert.deepEqual(unrelatedAssessorError.unexpected.map((r) => r.name), ['assessor_parcels']);
assert.equal(unrelatedAssessorError.tolerated.length, 0);
const directAssessor400 = classifyRunResults(
[{ name: 'assessor_parcels', error: 'ArcGIS error: {"code":400,"message":"Cannot perform query. Invalid query parameters.","details":["Unable to perform query. Please check your parameters."]}' }],
SOURCES, 'assessor_parcels'
);
assert.equal(directAssessor400.exitCode, 1, 'a direct assessor_parcels run must remain strict (aggregate-all-only scope)');
assert.ok(Array.isArray(SOURCES.assessor_parcels.allowFailure.match), 'assessor_parcels must scope its exemption');
console.log('PASS assessor_parcels audited page-1 400 tolerated in aggregate all; unrelated/direct failures still exit 1');