← back to Stayclaim
scripts/tests/ingest-la-historic-monuments.test.cjs
152 lines
const assert = require('node:assert/strict');
const { test } = require('node:test');
const { readFileSync } = require('node:fs');
const { stripTypeScriptTypes } = require('node:module');
const vm = require('node:vm');
const path = require('node:path');
// Exercise the actual one-shot entry point. Only external PG/fetch boundaries are
// replaced: no database driver is imported and no network call escapes the VM.
const source = readFileSync(path.join(__dirname, '../ingest-la-historic-monuments.ts'), 'utf8');
assert.equal((source.match(/import \{ Pool \} from 'pg';/g) || []).length, 1);
const executable = stripTypeScriptTypes(source.replace("import { Pool } from 'pg';", 'const Pool = globalThis.TestPool;'));
const valid = { name: 'Historic-Cultural Monuments', type: 'Feature Layer' };
async function run({ metadata = valid, metadataStatus = 200, jsonThrows = false, fetchThrows = false, preflight = false, pages = [{ features: [] }], queryThrows = false } = {}) {
const calls = [], errors = [], logs = [];
let exitCode = 0;
class TestPool {
constructor() { calls.push({ boundary: 'pg-create' }); }
async query(sql, values) {
calls.push({ boundary: 'pg-query', sql, values });
if (queryThrows) throw new Error('simulated DB failure');
return { rows: sql.startsWith('INSERT INTO listing') ? [{ id: 'test-listing-id' }] : [] };
}
async end() { calls.push({ boundary: 'pg-end' }); }
}
const context = {
TestPool, URL, Date,
process: { env: {}, argv: preflight ? ['node', 'importer', '--preflight-only'] : ['node', 'importer'], exit(code) { exitCode = code; } },
console: { log(...args) { logs.push(args.map(String).join(' ')); }, error(...args) { errors.push(args.map(String).join(' ')); } },
async fetch(url) {
const target = String(url);
calls.push({ boundary: 'fetch', url: target });
if (!target.includes('/query?')) {
if (fetchThrows) throw new Error('simulated network failure');
return { ok: metadataStatus === 200, status: metadataStatus, async json() { if (jsonThrows) throw new SyntaxError('invalid JSON'); return metadata; } };
}
assert.match(target, /MapServer\/75\/query\?/);
assert.equal(new URL(target).searchParams.get('orderByFields'), 'OBJECTID');
assert.ok(pages.length, 'unexpected feature page request');
return { ok: true, status: 200, async json() { return pages.shift(); } };
},
};
await vm.runInNewContext(executable, context);
return { calls, errors, logs, exitCode };
}
for (const [name, options] of [
['wrong name (Walk of Fame)', { metadata: { ...valid, name: 'Hollywood Walk of Fame' } }],
['same-name group layer', { metadata: { ...valid, type: 'Group Layer' } }],
['trailing-space group trap', { metadata: { name: 'Historic-Cultural Monuments ', type: 'Group Layer' } }],
['trailing-space feature name', { metadata: { ...valid, name: `${valid.name} ` } }],
['HTTP failure', { metadataStatus: 503 }],
['ArcGIS error JSON', { metadata: { ...valid, error: { code: 499 } } }],
['malformed JSON', { jsonThrows: true }],
['missing fields', { metadata: {} }],
['null metadata', { metadata: null }],
['array metadata', { metadata: [] }],
['network failure', { fetchThrows: true }],
]) {
test(`${name}: aborts before pool construction, DB and feature query`, async () => {
const result = await run(options);
assert.equal(result.exitCode, 1);
assert.equal(result.calls.length, 1);
assert.equal(result.calls[0].url, 'https://maps.lacity.org/arcgis/rest/services/Mapping/NavigateLA/MapServer/75?f=json');
assert.equal(result.errors.length, 1);
});
}
test('valid preflight exits without pool or feature query', async () => {
const result = await run({ preflight: true });
assert.equal(result.exitCode, 0);
assert.equal(result.calls.length, 1);
});
test('valid metadata precedes pool, page query, listing/event writes and pool close', async () => {
const result = await run({ pages: [{ features: [{ attributes: { NAME: 'Test Monument', LOCATION: '200-240 Columbia Avenue', OBJECTID: 123, MNT_NUM: 10 }, geometry: { x: -118.26, y: 34.06 } }] }] });
assert.equal(result.exitCode, 0);
assert.deepEqual(result.calls.map(x => x.boundary), ['fetch', 'pg-create', 'fetch', 'pg-query', 'pg-query', 'pg-query', 'pg-end']);
const event = result.calls[5];
assert.match(event.sql, /INSERT INTO place_event/);
assert.equal(event.values[0], 'test-listing-id');
assert.match(event.values[1], /Test Monument/);
assert.equal(event.values[4], 'hcm:123');
assert.ok(result.logs.includes('✓ HCM: 1 monuments tied to listings'));
assert.equal(result.errors.length, 0);
});
test('DB failure closes pool and reports failure', async () => {
const result = await run({ queryThrows: true, pages: [{ features: [{ attributes: { NAME: 'Test', LOCATION: '200 Columbia Avenue', OBJECTID: 123 } }] }] });
assert.equal(result.exitCode, 1);
assert.equal(result.calls.at(-1).boundary, 'pg-end');
assert.ok(!result.logs.some(line => line.startsWith('✓ HCM:')));
});
test('repeat preflight independently rechecks identity', async () => {
const first = await run({ preflight: true });
const second = await run({ preflight: true, metadata: { ...valid, name: 'Hollywood Walk of Fame' } });
assert.equal(first.exitCode, 0);
assert.equal(second.exitCode, 1);
assert.equal(first.calls.length + second.calls.length, 2);
});
for (const [name, page, message] of [
['ArcGIS error', { error: { code: 499, message: 'Token Required' } }, /ArcGIS error/],
['ArcGIS error with features', { error: { code: 499 }, features: [] }, /ArcGIS error/],
['null container', null, /Invalid HCM feature page/],
['array container', [], /Invalid HCM feature page/],
['string container', 'bad page', /Invalid HCM feature page/],
['missing features', {}, /Invalid HCM feature page/],
['null features', { features: null }, /Invalid HCM feature page/],
['object features', { features: {} }, /Invalid HCM feature page/],
['string features', { features: 'bad features' }, /Invalid HCM feature page/],
]) {
test(`${name}: fails without writes or success and closes pool once`, async () => {
const result = await run({ pages: [page] });
assert.equal(result.exitCode, 1);
assert.deepEqual(result.calls.map(x => x.boundary), ['fetch', 'pg-create', 'fetch', 'pg-end']);
assert.equal(result.errors.length, 1);
assert.match(result.errors[0], message);
assert.match(result.errors[0], /offset 0/);
assert.ok(!result.logs.some(line => line.startsWith('✓ HCM:')));
});
}
test('valid empty features succeeds without writes and closes pool once', async () => {
const result = await run();
assert.equal(result.exitCode, 0);
assert.equal(result.errors.length, 0);
assert.deepEqual(result.calls.map(x => x.boundary), ['fetch', 'pg-create', 'fetch', 'pg-end']);
assert.ok(result.logs.includes('✓ HCM: 0 monuments tied to listings'));
});
for (const [name, secondPage] of [
['ArcGIS error', { error: { code: 499 } }],
['malformed page', { features: null }],
]) {
test(`later ${name}: preserves earlier processing, fails without final success and closes pool`, async () => {
const feature = { attributes: { NAME: 'Test Monument', LOCATION: '200 Columbia Avenue', OBJECTID: 123 } };
const result = await run({ pages: [{ features: Array.from({ length: 1000 }, () => feature) }, secondPage] });
assert.equal(result.exitCode, 1);
const pages = result.calls.filter(x => x.boundary === 'fetch' && x.url.includes('/query?'));
assert.deepEqual(pages.map(x => new URL(x.url).searchParams.get('resultOffset')), ['0', '1000']);
assert.equal(result.calls.filter(x => x.boundary === 'pg-query').length, 3000);
assert.equal(result.calls.filter(x => x.boundary === 'pg-end').length, 1);
assert.equal(result.calls.at(-1).boundary, 'pg-end');
assert.equal(result.errors.length, 1);
assert.match(result.errors[0], /offset 1000/);
assert.ok(!result.logs.some(line => line.startsWith('✓ HCM:')));
});
}