Initial commit: gmTouringMiniApp project
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
/* 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',
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user