137 lines
3.9 KiB
TypeScript
137 lines
3.9 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type { GuideRouteReadiness, GuideRouteResult, GuideRouteTarget } from '@/domain/museum'
|
|
import type { GuideRouteRepository } from '@/repositories/GuideRouteRepository'
|
|
import { GuideRouteUseCase } from '@/usecases/guideRouteUseCase'
|
|
import {
|
|
clearGuidePerformanceRecords,
|
|
getGuidePerformanceRecords
|
|
} from '@/services/performance/guidePerformance'
|
|
|
|
const startTarget: GuideRouteTarget = {
|
|
routeTargetId: 'start-anchor',
|
|
poiId: 'start-poi',
|
|
name: '起点',
|
|
floorId: 'L1',
|
|
floorLabel: '1F',
|
|
positionGltf: [1, 0, 1],
|
|
routeNodeId: 'start-node'
|
|
}
|
|
|
|
const startEndpoint = {
|
|
poiId: startTarget.poiId,
|
|
name: startTarget.name,
|
|
floorId: startTarget.floorId,
|
|
floorLabel: startTarget.floorLabel,
|
|
routeNodeId: startTarget.routeNodeId,
|
|
position: [1, 0, 1] as [number, number, number]
|
|
}
|
|
|
|
const endTarget: GuideRouteTarget = {
|
|
routeTargetId: 'end-anchor',
|
|
poiId: 'end-poi',
|
|
name: '终点',
|
|
floorId: 'L2',
|
|
floorLabel: '2F',
|
|
positionGltf: [2, 0, 2],
|
|
routeNodeId: 'end-node'
|
|
}
|
|
|
|
const endEndpoint = {
|
|
poiId: endTarget.poiId,
|
|
name: endTarget.name,
|
|
floorId: endTarget.floorId,
|
|
floorLabel: endTarget.floorLabel,
|
|
routeNodeId: endTarget.routeNodeId,
|
|
position: [2, 0, 2] as [number, number, number]
|
|
}
|
|
|
|
const route: GuideRouteResult = {
|
|
id: 'route-1',
|
|
start: startEndpoint,
|
|
end: endEndpoint,
|
|
distanceMeters: 18.6,
|
|
floorSegments: [],
|
|
nodeIds: [],
|
|
points: [],
|
|
connectorPoints: [],
|
|
transitions: []
|
|
}
|
|
|
|
const ready: GuideRouteReadiness = {
|
|
ready: true,
|
|
message: '',
|
|
requiredData: []
|
|
}
|
|
|
|
describe('GuideRouteUseCase performance', () => {
|
|
beforeEach(() => {
|
|
clearGuidePerformanceRecords()
|
|
})
|
|
|
|
it('records a successful cross-floor route calculation without location names', async () => {
|
|
const repository = {
|
|
getRouteReadiness: vi.fn().mockResolvedValue(ready),
|
|
listRouteTargets: vi.fn().mockResolvedValue([startTarget, endTarget]),
|
|
findRoute: vi.fn().mockResolvedValue(route)
|
|
} satisfies GuideRouteRepository
|
|
const useCase = new GuideRouteUseCase(repository)
|
|
|
|
await expect(useCase.planRoute({
|
|
startPoiId: startTarget.poiId,
|
|
startTarget,
|
|
endTarget
|
|
})).resolves.toMatchObject({ route, error: '' })
|
|
|
|
expect(getGuidePerformanceRecords()).toEqual(expect.arrayContaining([
|
|
expect.objectContaining({
|
|
kind: 'interaction',
|
|
name: 'route-plan',
|
|
outcome: 'success',
|
|
detail: {
|
|
startFloorId: 'L1',
|
|
endFloorId: 'L2',
|
|
coordinateFallback: false,
|
|
distanceMeters: 19,
|
|
floorSegmentCount: 0
|
|
}
|
|
})
|
|
]))
|
|
})
|
|
|
|
it('hides repository URLs and response bodies from route error messages', async () => {
|
|
const repository = {
|
|
getRouteReadiness: vi.fn().mockResolvedValue(ready),
|
|
listRouteTargets: vi.fn().mockResolvedValue([startTarget, endTarget]),
|
|
findRoute: vi.fn().mockRejectedValue(new Error(
|
|
'SGS 数据接口请求失败: 500 /app-api/gis/sdk/routes/plan body="internal response"'
|
|
))
|
|
} satisfies GuideRouteRepository
|
|
const useCase = new GuideRouteUseCase(repository)
|
|
|
|
await expect(useCase.planRoute({
|
|
startPoiId: startTarget.poiId,
|
|
startTarget,
|
|
endTarget
|
|
})).resolves.toEqual({
|
|
route: null,
|
|
error: '路线服务暂时不可用,请稍后重试'
|
|
})
|
|
})
|
|
|
|
it('hides repository errors while reading route readiness', async () => {
|
|
const repository = {
|
|
getRouteReadiness: vi.fn().mockRejectedValue(new Error(
|
|
'SGS 数据接口请求失败: 500 /app-api/gis/sdk/maps/1/manifest body="internal response"'
|
|
)),
|
|
listRouteTargets: vi.fn().mockResolvedValue([]),
|
|
findRoute: vi.fn()
|
|
} satisfies GuideRouteRepository
|
|
const useCase = new GuideRouteUseCase(repository)
|
|
|
|
await expect(useCase.getRouteReadiness()).resolves.toMatchObject({
|
|
ready: false,
|
|
message: '路线服务暂时不可用,请稍后重试'
|
|
})
|
|
})
|
|
})
|