Files
gmTouringMiniApp/test/fetch-amap-pois.test.mjs
T

199 lines
5.6 KiB
JavaScript

/* 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',
)
})