import { describe, it, expect, beforeEach } from 'vitest'; import { testClient } from 'hono/testing'; import { IntegrationTestDatabase, setupIntegrationDatabaseHooks, TestDataFactory } from '~/utils/server/integration-test-db'; import { IntegrationTestAssertions } from '~/utils/server/integration-test-utils'; import { routesRoutesExport } from '@/server/api'; import { VehicleType, TravelMode } from '@/server/modules/routes/route.schema'; // 设置集成测试钩子 setupIntegrationDatabaseHooks() describe('用户端路线搜索API集成测试', () => { let client: ReturnType>['api']['v1']; beforeEach(async () => { // 创建测试客户端 client = testClient(routesRoutesExport).api.v1; // 创建测试数据 const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 创建测试活动 const activity1 = await TestDataFactory.createTestActivity(dataSource, { name: '测试活动1' }); const activity2 = await TestDataFactory.createTestActivity(dataSource, { name: '测试活动2' }); // 创建测试路线,覆盖所有组合查询场景 // 大巴拼车路线 await TestDataFactory.createTestRoute(dataSource, { name: '大巴拼车路线1', vehicleType: VehicleType.BUS, travelMode: TravelMode.CARPOOL, activityId: activity1.id, price: 100, availableSeats: 30 }); await TestDataFactory.createTestRoute(dataSource, { name: '大巴拼车路线2', vehicleType: VehicleType.BUS, travelMode: TravelMode.CARPOOL, activityId: activity2.id, price: 120, availableSeats: 25 }); // 商务车路线(拼车) await TestDataFactory.createTestRoute(dataSource, { name: '商务车拼车路线', vehicleType: VehicleType.BUSINESS, travelMode: TravelMode.CARPOOL, activityId: activity1.id, price: 200, availableSeats: 4 }); // 商务车路线(包车) await TestDataFactory.createTestRoute(dataSource, { name: '商务车包车路线', vehicleType: VehicleType.BUSINESS, travelMode: TravelMode.CHARTER, activityId: activity2.id, price: 500, availableSeats: 4 }); // 大巴包车路线 await TestDataFactory.createTestRoute(dataSource, { name: '大巴包车路线', vehicleType: VehicleType.BUS, travelMode: TravelMode.CHARTER, activityId: activity1.id, price: 800, availableSeats: 40 }); // 中巴拼车路线(用于验证排除逻辑) await TestDataFactory.createTestRoute(dataSource, { name: '中巴拼车路线', vehicleType: VehicleType.MINIBUS, travelMode: TravelMode.CARPOOL, activityId: activity1.id, price: 150, availableSeats: 15 }); // 小车包车路线(用于验证排除逻辑) await TestDataFactory.createTestRoute(dataSource, { name: '小车包车路线', vehicleType: VehicleType.CAR, travelMode: TravelMode.CHARTER, activityId: activity2.id, price: 300, availableSeats: 3 }); }); describe('组合查询逻辑测试', () => { it('应该正确查询大巴拼车组合', async () => { const response = await client.routes.search.$get({ query: { vehicleType: 'bus', travelMode: 'carpool' } }); IntegrationTestAssertions.expectStatus(response, 200); if (response.status === 200) { const responseData = await response.json(); expect(responseData.success).toBe(true); expect(responseData.data.routes).toHaveLength(2); // 验证所有返回路线都是大巴拼车 responseData.data.routes.forEach((route: any) => { expect(route.vehicleType).toBe(VehicleType.BUS); expect(route.travelMode).toBe(TravelMode.CARPOOL); }); // 验证包含正确的路线 const routeNames = responseData.data.routes.map((route: any) => route.name); expect(routeNames).toContain('大巴拼车路线1'); expect(routeNames).toContain('大巴拼车路线2'); // 验证不包含其他类型的路线 expect(routeNames).not.toContain('商务车拼车路线'); expect(routeNames).not.toContain('大巴包车路线'); } }); it('应该正确查询商务车组合(支持拼车和包车)', async () => { const response = await client.routes.search.$get({ query: { vehicleType: 'business', travelMode: 'carpool,charter' } }); IntegrationTestAssertions.expectStatus(response, 200); if (response.status === 200) { const responseData = await response.json(); expect(responseData.success).toBe(true); expect(responseData.data.routes).toHaveLength(2); // 验证所有返回路线都是商务车 responseData.data.routes.forEach((route: any) => { expect(route.vehicleType).toBe(VehicleType.BUSINESS); }); // 验证包含商务车拼车和包车路线 const routeNames = responseData.data.routes.map((route: any) => route.name); expect(routeNames).toContain('商务车拼车路线'); expect(routeNames).toContain('商务车包车路线'); // 验证不包含其他车型的路线 expect(routeNames).not.toContain('大巴拼车路线1'); expect(routeNames).not.toContain('中巴拼车路线'); } }); it('应该正确查询包车组合(支持大巴和商务车)', async () => { const response = await client.routes.search.$get({ query: { vehicleType: 'bus,business', travelMode: 'charter' } }); IntegrationTestAssertions.expectStatus(response, 200); if (response.status === 200) { const responseData = await response.json(); expect(responseData.success).toBe(true); expect(responseData.data.routes).toHaveLength(2); // 验证所有返回路线都是包车 responseData.data.routes.forEach((route: any) => { expect(route.travelMode).toBe(TravelMode.CHARTER); }); // 验证包含大巴包车和商务车包车路线 const routeNames = responseData.data.routes.map((route: any) => route.name); expect(routeNames).toContain('大巴包车路线'); expect(routeNames).toContain('商务车包车路线'); // 验证不包含拼车路线 expect(routeNames).not.toContain('大巴拼车路线1'); expect(routeNames).not.toContain('商务车拼车路线'); } }); it('应该支持多值车型参数查询', async () => { const response = await client.routes.search.$get({ query: { vehicleType: 'bus,minibus', travelMode: 'carpool' } }); IntegrationTestAssertions.expectStatus(response, 200); if (response.status === 200) { const responseData = await response.json(); expect(responseData.success).toBe(true); // 验证包含大巴拼车和中巴拼车路线 const routeNames = responseData.data.routes.map((route: any) => route.name); expect(routeNames).toContain('大巴拼车路线1'); expect(routeNames).toContain('大巴拼车路线2'); expect(routeNames).toContain('中巴拼车路线'); // 验证不包含商务车和小车路线 expect(routeNames).not.toContain('商务车拼车路线'); expect(routeNames).not.toContain('小车包车路线'); } }); it('应该支持多值出行方式参数查询', async () => { const response = await client.routes.search.$get({ query: { vehicleType: 'business', travelMode: 'carpool,charter' } }); IntegrationTestAssertions.expectStatus(response, 200); if (response.status === 200) { const responseData = await response.json(); expect(responseData.success).toBe(true); // 验证包含商务车拼车和包车路线 const routeNames = responseData.data.routes.map((route: any) => route.name); expect(routeNames).toContain('商务车拼车路线'); expect(routeNames).toContain('商务车包车路线'); } }); it('应该正确处理单个参数查询', async () => { // 只查询车型 const response1 = await client.routes.search.$get({ query: { vehicleType: 'bus' } }); IntegrationTestAssertions.expectStatus(response1, 200); if (response1.status === 200) { const responseData = await response1.json(); expect(responseData.success).toBe(true); // 验证包含所有大巴路线(拼车和包车) const routeNames = responseData.data.routes.map((route: any) => route.name); expect(routeNames).toContain('大巴拼车路线1'); expect(routeNames).toContain('大巴拼车路线2'); expect(routeNames).toContain('大巴包车路线'); } // 只查询出行方式 const response2 = await client.routes.search.$get({ query: { travelMode: 'carpool' } }); IntegrationTestAssertions.expectStatus(response2, 200); if (response2.status === 200) { const responseData = await response2.json(); expect(responseData.success).toBe(true); // 验证包含所有拼车路线 const routeNames = responseData.data.routes.map((route: any) => route.name); expect(routeNames).toContain('大巴拼车路线1'); expect(routeNames).toContain('大巴拼车路线2'); expect(routeNames).toContain('商务车拼车路线'); expect(routeNames).toContain('中巴拼车路线'); } }); it('应该正确处理空参数查询', async () => { const response = await client.routes.search.$get({ query: {} }); IntegrationTestAssertions.expectStatus(response, 200); if (response.status === 200) { const responseData = await response.json(); expect(responseData.success).toBe(true); // 验证返回所有路线 expect(responseData.data.routes.length).toBeGreaterThan(0); } }); it('应该正确处理无效参数', async () => { // 无效车型参数 const response1 = await client.routes.search.$get({ query: { vehicleType: 'invalid_vehicle' } }); // 无效参数应该返回400错误 IntegrationTestAssertions.expectStatus(response1, 400); if (response1.status === 400) { const responseData = await response1.json(); console.debug('Invalid vehicle type response:', responseData); expect(responseData.success).toBe(false); expect(responseData.error.message).toContain('车型必须是有效的类型'); } // 无效出行方式参数 const response2 = await client.routes.search.$get({ query: { travelMode: 'invalid_mode' } }); // 无效参数应该返回400错误 IntegrationTestAssertions.expectStatus(response2, 400); if (response2.status === 400) { const responseData = await response2.json(); expect(responseData.success).toBe(false); expect(responseData.error.message).toContain('出行方式必须是有效的类型'); } }); }); describe('组合查询与其他筛选条件结合测试', () => { it('应该支持组合查询与价格筛选结合', async () => { const response = await client.routes.search.$get({ query: { vehicleType: 'bus', travelMode: 'carpool', minPrice: '110', maxPrice: '130' } }); IntegrationTestAssertions.expectStatus(response, 200); if (response.status === 200) { const responseData = await response.json(); expect(responseData.success).toBe(true); // 验证只返回价格在110-130之间的大巴拼车路线 const routeNames = responseData.data.routes.map((route: any) => route.name); expect(routeNames).toContain('大巴拼车路线2'); // 价格120 expect(routeNames).not.toContain('大巴拼车路线1'); // 价格100 // 验证价格范围 responseData.data.routes.forEach((route: any) => { expect(Number(route.price)).toBeGreaterThanOrEqual(110); expect(Number(route.price)).toBeLessThanOrEqual(130); }); } }); it('应该支持组合查询与活动筛选结合', async () => { const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); const activity = await TestDataFactory.createTestActivity(dataSource, { name: '特定活动' }); // 为特定活动创建路线 await TestDataFactory.createTestRoute(dataSource, { name: '特定活动大巴拼车路线', vehicleType: VehicleType.BUS, travelMode: TravelMode.CARPOOL, activityId: activity.id, price: 150 }); const response = await client.routes.search.$get({ query: { vehicleType: 'bus', travelMode: 'carpool' } }); IntegrationTestAssertions.expectStatus(response, 200); if (response.status === 200) { const responseData = await response.json(); expect(responseData.success).toBe(true); // 验证包含特定活动的路线 const routeNames = responseData.data.routes.map((route: any) => route.name); expect(routeNames).toContain('特定活动大巴拼车路线'); } }); }); describe('分页和排序测试', () => { it('应该支持组合查询的分页', async () => { const response = await client.routes.search.$get({ query: { vehicleType: 'bus', travelMode: 'carpool', page: '1', pageSize: '1' } }); IntegrationTestAssertions.expectStatus(response, 200); if (response.status === 200) { const responseData = await response.json(); expect(responseData.success).toBe(true); // 验证分页信息 expect(responseData.data.pagination.page).toBe(1); expect(responseData.data.pagination.pageSize).toBe(1); expect(responseData.data.pagination.total).toBe(2); expect(responseData.data.pagination.totalPages).toBe(2); // 验证只返回1条路线 expect(responseData.data.routes).toHaveLength(1); } }); it('应该支持组合查询的价格排序', async () => { const response = await client.routes.search.$get({ query: { vehicleType: 'bus', travelMode: 'carpool', sortBy: 'price', sortOrder: 'ASC' } }); IntegrationTestAssertions.expectStatus(response, 200); if (response.status === 200) { const responseData = await response.json(); expect(responseData.success).toBe(true); // 验证路线按价格升序排列 const prices = responseData.data.routes.map((route: any) => Number(route.price)); expect(prices[0]).toBeLessThanOrEqual(prices[1]); } }); }); });