166
tests/unit/GuideRepository.spec.ts
Normal file
166
tests/unit/GuideRepository.spec.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type {
|
||||
SgsSdkApiProvider,
|
||||
SgsSdkManifestPayload
|
||||
} from '@/data/providers/sgsSdkApiProvider'
|
||||
import { SgsSdkGuideRepository } from '@/repositories/GuideRepository'
|
||||
import { toMuseumPoiFromSgs } from '@/data/adapters/sgsSdkGuideAdapter'
|
||||
|
||||
const manifest: SgsSdkManifestPayload = {
|
||||
mapId: 'museum',
|
||||
mapName: '深圳自然博物馆',
|
||||
floors: [
|
||||
{
|
||||
floorId: '101',
|
||||
floorCode: 'L1',
|
||||
floorName: '1F',
|
||||
sortOrder: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const floorPoi = {
|
||||
id: 'poi-toilet',
|
||||
name: '一层卫生间',
|
||||
type: 'toilet',
|
||||
typeName: '卫生间',
|
||||
floorId: '101',
|
||||
position: {
|
||||
x: 4,
|
||||
y: 12,
|
||||
z: 8
|
||||
}
|
||||
}
|
||||
|
||||
const theaterSpace = {
|
||||
id: 'space-theater',
|
||||
name: '球幕影院',
|
||||
type: 'theater',
|
||||
floorId: '101',
|
||||
boundaryWkt: 'POLYGON((0 0, 10 0, 10 10, 0 10, 0 0))'
|
||||
}
|
||||
|
||||
const createProvider = (overrides: Partial<SgsSdkApiProvider> = {}) => ({
|
||||
getManifest: vi.fn().mockResolvedValue(manifest),
|
||||
getFloorPois: vi.fn().mockResolvedValue([floorPoi]),
|
||||
getFloorBusinessPois: vi.fn().mockResolvedValue([]),
|
||||
getFloorSpaces: vi.fn().mockResolvedValue([theaterSpace]),
|
||||
getNavigablePlaces: vi.fn().mockResolvedValue([]),
|
||||
...overrides
|
||||
}) as unknown as SgsSdkApiProvider
|
||||
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks()
|
||||
})
|
||||
|
||||
describe('SgsSdkGuideRepository 点位搜索', () => {
|
||||
it('源数据仅有 floorCode 时回填 manifest 的稳定 floorId', () => {
|
||||
const poi = toMuseumPoiFromSgs({
|
||||
...floorPoi,
|
||||
id: 'poi-floor-code-only',
|
||||
floorId: undefined,
|
||||
floorCode: 'L1'
|
||||
}, manifest.floors)
|
||||
|
||||
expect(poi.floorId).toBe('101')
|
||||
expect(poi.floorLabel).toBe('1F')
|
||||
})
|
||||
|
||||
it('SDK null 坐标不会被误转换为模型原点', () => {
|
||||
const poi = toMuseumPoiFromSgs({
|
||||
...floorPoi,
|
||||
id: 'poi-with-null-position',
|
||||
position: {
|
||||
x: null,
|
||||
y: null,
|
||||
z: null
|
||||
}
|
||||
}, manifest.floors)
|
||||
|
||||
expect(poi.positionGltf).toBeUndefined()
|
||||
})
|
||||
|
||||
it('合并普通点位与空间点位,并保留影院真实分类和楼层高度', async () => {
|
||||
const repository = new SgsSdkGuideRepository(createProvider())
|
||||
|
||||
const results = await repository.searchPois()
|
||||
const theaterResults = results.filter((poi) => poi.name === '球幕影院')
|
||||
|
||||
expect(theaterResults).toHaveLength(1)
|
||||
expect(theaterResults[0]).toMatchObject({
|
||||
id: 'space-space-theater',
|
||||
floorId: '101',
|
||||
floorLabel: '1F',
|
||||
primaryCategory: {
|
||||
id: 'space_theater'
|
||||
},
|
||||
positionGltf: [5, 12, 5]
|
||||
})
|
||||
expect(results.some((poi) => poi.id === 'poi-toilet')).toBe(true)
|
||||
})
|
||||
|
||||
it('部分来源失败时继续返回可用点位,并在后续搜索重试失败来源', async () => {
|
||||
const getFloorSpaces = vi.fn()
|
||||
.mockRejectedValueOnce(new Error('spaces offline'))
|
||||
.mockRejectedValueOnce(new Error('spaces offline'))
|
||||
.mockResolvedValue([theaterSpace])
|
||||
const provider = createProvider({ getFloorSpaces })
|
||||
const repository = new SgsSdkGuideRepository(provider)
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
||||
|
||||
const firstResults = await repository.searchPois()
|
||||
expect(firstResults.map((poi) => poi.id)).toEqual(['poi-toilet'])
|
||||
|
||||
const secondResults = await repository.searchPois()
|
||||
expect(secondResults.some((poi) => poi.id === 'space-space-theater')).toBe(true)
|
||||
expect(getFloorSpaces).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
it('补充来源失败不会阻止核心点位结果缓存', async () => {
|
||||
const getFloorPois = vi.fn().mockResolvedValue([floorPoi])
|
||||
const getFloorSpaces = vi.fn().mockResolvedValue([theaterSpace])
|
||||
const getFloorBusinessPois = vi.fn().mockRejectedValue(new Error('business offline'))
|
||||
const provider = createProvider({
|
||||
getFloorPois,
|
||||
getFloorSpaces,
|
||||
getFloorBusinessPois
|
||||
})
|
||||
const repository = new SgsSdkGuideRepository(provider)
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
||||
|
||||
await repository.listPois()
|
||||
await repository.listPois()
|
||||
|
||||
expect(getFloorPois).toHaveBeenCalledTimes(1)
|
||||
expect(getFloorSpaces).toHaveBeenCalledTimes(1)
|
||||
expect(getFloorBusinessPois).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('楼层高度来源短暂失败时不缓存缺失的空间点位', async () => {
|
||||
const getFloorPois = vi.fn()
|
||||
.mockRejectedValueOnce(new Error('pois offline'))
|
||||
.mockRejectedValueOnce(new Error('pois offline'))
|
||||
.mockResolvedValue([floorPoi])
|
||||
const provider = createProvider({ getFloorPois })
|
||||
const repository = new SgsSdkGuideRepository(provider)
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
||||
|
||||
await expect(repository.searchPois()).rejects.toThrow('点位数据加载失败,请检查网络后重试')
|
||||
|
||||
const recoveredResults = await repository.searchPois()
|
||||
expect(recoveredResults.find((poi) => poi.id === 'space-space-theater')?.positionGltf)
|
||||
.toEqual([5, 12, 5])
|
||||
expect(getFloorPois).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
it('所有核心楼层与空间请求失败时返回明确的可重试错误', async () => {
|
||||
const provider = createProvider({
|
||||
getFloorPois: vi.fn().mockRejectedValue(new Error('pois offline')),
|
||||
getFloorSpaces: vi.fn().mockRejectedValue(new Error('spaces offline'))
|
||||
})
|
||||
const repository = new SgsSdkGuideRepository(provider)
|
||||
vi.spyOn(console, 'warn').mockImplementation(() => undefined)
|
||||
|
||||
await expect(repository.searchPois()).rejects.toThrow('点位数据加载失败,请检查网络后重试')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user