/* eslint-disable test/no-import-node-test */ import assert from 'node:assert/strict' import test from 'node:test' import { assertSafeOutputPath, buildAmapSearchParams, buildQueryPlan, dedupePois, normalizeAmapPage, normalizeAmapPoi, parseAmapLocation, parseCliArgs, } from '../scripts/fetch-amap-pois.mjs' const typeQuery = { id: 'types:parks', kind: 'types', label: 'parks', types: ['110101'], } const keywordQuery = { id: 'keyword:1', kind: 'keyword', label: 'greenway', keyword: 'greenway', } function rawPoi(overrides = {}) { return { id: 'B001', name: 'Test Park', parent: [], location: '113.935,22.748', type: 'Tourist Attraction;Park', typecode: '110101', pname: 'Guangdong Province', pcode: '440000', cityname: 'Shenzhen', citycode: '0755', adname: 'Guangming District', adcode: '440311', address: ['Park Road'], business: { alias: [], tel: '0755-12345678', rating: '4.7', opentime_today: '06:00-22:00', opentime_week: 'Mon-Sun 06:00-22:00', }, photos: [ { title: 'Gate', url: 'https://example.invalid/gate.jpg' }, { title: 'Duplicate', url: 'https://example.invalid/gate.jpg' }, ], ...overrides, } } test('parseAmapLocation accepts valid coordinates and rejects malformed values', () => { assert.deepEqual(parseAmapLocation('113.935,22.748'), { longitude: 113.935, latitude: 22.748, }) assert.equal(parseAmapLocation('113.935'), null) assert.equal(parseAmapLocation('181,22.748'), null) }) test('normalizeAmapPoi maps only reviewed candidate fields without raw response data', () => { const normalized = normalizeAmapPoi(rawPoi(), typeQuery) assert.ok(normalized) assert.equal(normalized.id, 'B001') assert.equal(normalized.coordinateSystem, 'GCJ02') assert.equal(normalized.address, 'Park Road') assert.equal(normalized.parentId, null) assert.deepEqual(normalized.business, { alias: null, telephone: '0755-12345678', sourceRating: 4.7, openingHoursToday: '06:00-22:00', openingHoursWeek: 'Mon-Sun 06:00-22:00', }) assert.deepEqual(normalized.photos, [ { title: 'Gate', url: 'https://example.invalid/gate.jpg' }, ]) assert.deepEqual(normalized.matchedQueries, [{ id: 'types:parks', kind: 'types', label: 'parks', }]) assert.equal('raw' in normalized, false) }) test('normalizeAmapPage applies exact adcode filtering and rejects unusable records', () => { const page = normalizeAmapPage([ rawPoi(), rawPoi({ id: 'B002', adcode: '440309' }), rawPoi({ id: 'B003', location: [] }), ], typeQuery) assert.deepEqual(page.pois.map(poi => poi.id), ['B001']) assert.equal(page.filteredOutByAdcode, 1) assert.equal(page.rejectedInvalid, 1) }) test('dedupePois merges matches and unique photos by AMap ID without mutating inputs', () => { const first = normalizeAmapPoi(rawPoi(), typeQuery) const second = normalizeAmapPoi(rawPoi({ address: [], photos: [ { title: [], url: 'https://example.invalid/gate.jpg' }, { title: 'Lake', url: 'https://example.invalid/lake.jpg' }, ], }), keywordQuery) assert.ok(first) assert.ok(second) const firstSnapshot = JSON.stringify(first) const secondSnapshot = JSON.stringify(second) const result = dedupePois([first, second]) assert.equal(result.length, 1) assert.equal(result[0].id, 'B001') assert.equal(result[0].address, 'Park Road') assert.equal(result[0].sourceOccurrences, 2) assert.deepEqual(result[0].matchedQueries.map(query => query.id), [ 'types:parks', 'keyword:1', ]) assert.deepEqual(result[0].photos, [ { title: 'Gate', url: 'https://example.invalid/gate.jpg' }, { title: 'Lake', url: 'https://example.invalid/lake.jpg' }, ]) assert.equal(JSON.stringify(first), firstSnapshot) assert.equal(JSON.stringify(second), secondSnapshot) }) test('buildQueryPlan keeps type scans and keyword recall as separate requests', () => { const plan = buildQueryPlan( [{ name: 'parks', types: ['110101', '110103'] }], ['greenway'], ) assert.deepEqual(plan, [ { id: 'types:parks', kind: 'types', label: 'parks', types: ['110101', '110103'], }, { id: 'keyword:1', kind: 'keyword', label: 'greenway', keyword: 'greenway', }, ]) }) test('AMap request parameters always enforce Guangming District and strict city filtering', () => { const params = buildAmapSearchParams(typeQuery, 25, 3) assert.equal(params.get('region'), '440311') assert.equal(params.get('city_limit'), 'true') assert.equal(params.get('page_size'), '25') assert.equal(params.get('page_num'), '3') assert.equal(params.get('types'), '110101') assert.equal(params.get('show_fields'), 'business,photos') assert.equal(params.has('key'), false) }) test('CLI rejects key arguments without echoing their value', () => { const secret = 'must-not-appear' assert.throws( () => parseCliArgs([`--key=${secret}`]), error => !error.message.includes(secret), ) }) test('CLI limits pagination to the AMap 200-result window', () => { assert.equal(parseCliArgs(['--no-keywords']).maxPages, 8) assert.throws( () => parseCliArgs(['--page-size', '25', '--max-pages', '9']), /cannot exceed 8/, ) }) test('generated output cannot be directed into source or test directories', () => { const projectRoot = '/workspace/map-app' assert.throws( () => assertSafeOutputPath('src/data/candidates.json', projectRoot), /Refusing to write/, ) assert.equal( assertSafeOutputPath('artifacts/candidates.json', projectRoot), '/workspace/map-app/artifacts/candidates.json', ) })