forked from zhouruizhe/gmTouringMiniApp
199 lines
5.6 KiB
JavaScript
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',
|
|
)
|
|
})
|