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: '路线服务暂时不可用,请稍后重试' }) }) })