Răsfoiți Sursa

✨ feat(orders): 实现用户端订单创建功能

- 添加订单创建路由及控制器
- 实现订单创建服务逻辑
- 添加订单创建相关数据验证
- 完善订单快照数据结构定义
- 添加订单创建集成测试
- 注册用户端订单API路由

🐛 fix(orders): 修复管理员订单路由导入错误

- 修正管理员订单路由导入路径
- 确保管理员和用户订单路由正确区分

✅ test(orders): 添加订单API集成测试

- 实现订单创建测试用例
- 添加订单权限控制测试
- 实现订单快照机制测试
- 添加订单状态验证测试
- 完善测试工具类,添加订单相关断言方法
yourname 3 luni în urmă
părinte
comite
9d1e69d963

+ 104 - 0
packages/server/src/api/orders/create.ts

@@ -0,0 +1,104 @@
+import { createRoute, OpenAPIHono } from '@hono/zod-openapi';
+import { authMiddleware } from '../../middleware/auth.middleware';
+import { OrderService } from '../../modules/orders/order.service';
+import { OrderCreateSchema, OrderResponseSchema } from '../../modules/orders/order.schema';
+import { RouteService } from '../../modules/routes/route.service';
+import { PassengerService } from '../../modules/passengers/passenger.service';
+import { AuthContext } from '../../types/context';
+import { AppDataSource } from '../../data-source';
+import { ErrorSchema } from '../../utils/errorHandler';
+import { parseWithAwait } from '../../utils/parseWithAwait';
+
+// 订单创建路由定义
+const createOrderRoute = createRoute({
+  method: 'post',
+  path: '/',
+  middleware: [authMiddleware],
+  request: {
+    body: {
+      content: {
+        'application/json': { schema: OrderCreateSchema }
+      }
+    }
+  },
+  responses: {
+    201: {
+      description: '订单创建成功',
+      content: { 'application/json': { schema: OrderResponseSchema } }
+    },
+    400: {
+      description: '参数错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    401: {
+      description: '未授权',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    404: {
+      description: '路线或乘客不存在',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    422: {
+      description: '业务逻辑错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    },
+    500: {
+      description: '服务器错误',
+      content: { 'application/json': { schema: ErrorSchema } }
+    }
+  }
+});
+
+const app = new OpenAPIHono<AuthContext>()
+  .openapi(createOrderRoute, async (c) => {
+    try {
+      const user = c.get('user');
+      const orderData = c.req.valid('json');
+
+      // 验证路线是否存在
+      const routeService = new RouteService(AppDataSource);
+      const route = await routeService.getRouteById(orderData.routeId);
+      if (!route) {
+        return c.json({
+          code: 404,
+          message: '路线不存在'
+        }, 404);
+      }
+
+      // 验证乘客数量不超过路线座位数
+      if (orderData.passengerCount > route.availableSeats) {
+        return c.json({
+          code: 422,
+          message: `乘客数量超过路线座位数,当前路线剩余座位:${route.availableSeats}`
+        }, 422);
+      }
+
+      // 验证乘客快照信息
+      const passengerService = new PassengerService();
+      for (const passengerSnapshot of orderData.passengerSnapshots) {
+        if (passengerSnapshot.id) {
+          const passenger = await passengerService.getPassengerById(passengerSnapshot.id);
+          if (!passenger) {
+            return c.json({
+              code: 404,
+              message: `乘客ID ${passengerSnapshot.id} 不存在`
+            }, 404);
+          }
+        }
+      }
+
+      // 创建订单
+      const orderService = new OrderService();
+      const order = await orderService.createOrder(user.id, orderData);
+
+      return c.json(await parseWithAwait(OrderResponseSchema, order), 201);
+    } catch (error) {
+      console.error('订单创建失败:', error);
+      return c.json({
+        code: 500,
+        message: error instanceof Error ? error.message : '订单创建失败'
+      }, 500);
+    }
+  });
+
+export default app;

+ 7 - 0
packages/server/src/api/orders/index.ts

@@ -0,0 +1,7 @@
+import { OpenAPIHono } from '@hono/zod-openapi';
+import createOrder from './create';
+
+// 注册订单创建路由
+const app = new OpenAPIHono().route('/', createOrder);
+
+export default app;

+ 5 - 2
packages/server/src/index.ts

@@ -10,11 +10,12 @@ import { routesRoutes as adminRoutesRoutes } from './api/admin/routes'
 import areasRoutes from './api/admin/areas'
 import locationsRoutes from './api/admin/locations'
 import { passengersRoutes as adminPassengersRoutes } from './api/admin/passengers'
-import ordersRoutes from './api/admin/orders'
+import adminOrdersRoutes from './api/admin/orders'
 import passengersRoutes from './api/passengers/index'
 import routesRoutes from './api/routes'
 import areasUserRoutes from './api/areas'
 import locationsUserRoutes from './api/locations'
+import ordersRoutes from './api/orders/index'
 import { AuthContext } from './types/context'
 import { AppDataSource } from './data-source'
 import { Hono } from 'hono'
@@ -124,11 +125,12 @@ export const adminRoutesRoutesExport = api.route('/api/v1/admin/routes', adminRo
 export const adminAreasRoutesExport = api.route('/api/v1/admin/areas', areasRoutes)
 export const adminLocationsRoutesExport = api.route('/api/v1/admin/locations', locationsRoutes)
 export const adminPassengersRoutesExport = api.route('/api/v1/admin/passengers', adminPassengersRoutes)
-export const adminOrdersRoutesExport = api.route('/api/v1/admin/orders', ordersRoutes)
+export const adminOrdersRoutesExport = api.route('/api/v1/admin/orders', adminOrdersRoutes)
 export const passengersRoutesExport = api.route('/api/v1/passengers', passengersRoutes)
 export const routesRoutesExport = api.route('/api/v1/routes', routesRoutes)
 export const areasUserRoutesExport = api.route('/api/v1/areas', areasUserRoutes)
 export const locationsUserRoutesExport = api.route('/api/v1/locations', locationsUserRoutes)
+export const ordersRoutesExport = api.route('/api/v1/orders', ordersRoutes)
 
 export type AuthRoutes = typeof authRoutes
 export type UserRoutes = typeof userRoutes
@@ -144,6 +146,7 @@ export type PassengersRoutes = typeof passengersRoutesExport
 export type RoutesRoutes = typeof routesRoutesExport
 export type AreasUserRoutes = typeof areasUserRoutesExport
 export type LocationsUserRoutes = typeof locationsUserRoutesExport
+export type OrdersRoutes = typeof ordersRoutesExport
 
 app.route('/', api)
 export default app

+ 13 - 2
packages/server/src/modules/orders/order.schema.ts

@@ -3,7 +3,6 @@ import { OrderStatus, PaymentStatus } from '../../share/order.types';
 
 // 创建订单Schema
 export const OrderCreateSchema = z.object({
-  userId: z.number().int().positive(),
   routeId: z.number().int().positive(),
   passengerCount: z.number().int().min(1),
   totalAmount: z.number().positive(),
@@ -33,6 +32,18 @@ export const OrderListSchema = z.object({
   search: z.string().optional()
 });
 
+// 路线快照Schema
+export const RouteSnapshotSchema = z.object({
+  id: z.number(),
+  name: z.string(),
+  pickupPoint: z.string(),
+  dropoffPoint: z.string(),
+  departureTime: z.coerce.date(),
+  price: z.number(),
+  vehicleType: z.string(),
+  travelMode: z.string()
+});
+
 // 订单响应Schema
 export const OrderResponseSchema = z.object({
   id: z.number(),
@@ -43,7 +54,7 @@ export const OrderResponseSchema = z.object({
   status: z.nativeEnum(OrderStatus),
   paymentStatus: z.nativeEnum(PaymentStatus),
   passengerSnapshots: z.array(z.any()),
-  routeSnapshot: z.any(),
+  routeSnapshot: RouteSnapshotSchema,
   createdBy: z.number().nullable().optional(),
   updatedBy: z.number().nullable().optional(),
   createdAt: z.date(),

+ 58 - 2
packages/server/src/modules/orders/order.service.ts

@@ -1,11 +1,67 @@
 import { AppDataSource } from '../../data-source';
 import { Order } from './order.entity';
-import { OrderStatus } from '../../share/order.types';
-import { OrderStats } from './order.schema';
+import { OrderStatus, PaymentStatus } from '../../share/order.types';
+import { OrderStats, OrderCreateInput } from './order.schema';
 
 export class OrderService {
   private orderRepository = AppDataSource.getRepository(Order);
 
+  /**
+   * 创建订单
+   */
+  async createOrder(userId: number, orderData: OrderCreateInput) {
+    const order = this.orderRepository.create({
+      userId,
+      routeId: orderData.routeId,
+      passengerCount: orderData.passengerCount,
+      totalAmount: orderData.totalAmount,
+      status: OrderStatus.PENDING_PAYMENT,
+      paymentStatus: PaymentStatus.PENDING,
+      passengerSnapshots: orderData.passengerSnapshots,
+      routeSnapshot: orderData.routeSnapshot,
+      createdBy: orderData.createdBy || userId
+    });
+
+    const savedOrder = await this.orderRepository.save(order);
+
+    // 返回包含关联数据的订单
+    const orderWithRelations = await this.orderRepository.findOne({
+      where: { id: savedOrder.id },
+      relations: ['user', 'route']
+    });
+
+    if (!orderWithRelations) {
+      throw new Error('订单创建后查询失败');
+    }
+
+    // 返回与OrderResponseSchema匹配的数据结构
+    return {
+      id: orderWithRelations.id,
+      userId: orderWithRelations.userId,
+      routeId: orderWithRelations.routeId,
+      passengerCount: orderWithRelations.passengerCount,
+      totalAmount: orderWithRelations.totalAmount,
+      status: orderWithRelations.status,
+      paymentStatus: orderWithRelations.paymentStatus,
+      passengerSnapshots: orderWithRelations.passengerSnapshots,
+      routeSnapshot: orderWithRelations.routeSnapshot,
+      createdBy: orderWithRelations.createdBy,
+      updatedBy: orderWithRelations.updatedBy,
+      createdAt: orderWithRelations.createdAt,
+      updatedAt: orderWithRelations.updatedAt,
+      user: {
+        id: orderWithRelations.user.id,
+        username: orderWithRelations.user.username,
+        phone: orderWithRelations.user.phone
+      },
+      route: {
+        id: orderWithRelations.route.id,
+        name: orderWithRelations.route.name,
+        description: orderWithRelations.route.description
+      }
+    };
+  }
+
   /**
    * 获取订单统计信息
    */

+ 461 - 0
web/tests/integration/server/orders.integration.test.ts

@@ -0,0 +1,461 @@
+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 { ordersRoutesExport } from '@d8d/server/api';
+import { AuthService } from '@d8d/server/modules/auth/auth.service';
+import { UserService } from '@d8d/server/modules/users/user.service';
+import { OrderStatus, PaymentStatus } from '@d8d/server/share/order.types';
+
+// 设置集成测试钩子
+setupIntegrationDatabaseHooks()
+
+describe('用户端订单API集成测试', () => {
+  let client: ReturnType<typeof testClient<typeof ordersRoutesExport>>['api']['v1'];
+  let testToken: string;
+  let testUser: any;
+  let testRoute: any;
+  let testPassenger: any;
+
+  beforeEach(async () => {
+    // 创建测试客户端
+    client = testClient(ordersRoutesExport).api.v1;
+
+    // 创建测试用户并生成token
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+
+    const userService = new UserService(dataSource);
+    const authService = new AuthService(userService);
+
+    // 创建测试用户
+    testUser = await TestDataFactory.createTestUser(dataSource);
+
+    // 生成测试用户的token
+    testToken = authService.generateToken(testUser);
+
+    // 创建测试路线
+    testRoute = await TestDataFactory.createTestRoute(dataSource);
+
+    // 创建测试乘客
+    testPassenger = await TestDataFactory.createTestPassenger(dataSource, {
+      userId: testUser.id,
+      name: '测试乘客'
+    });
+  });
+
+  describe('订单创建测试', () => {
+    it('应该成功创建订单', async () => {
+      const orderData = {
+        routeId: testRoute.id,
+        passengerCount: 1,
+        totalAmount: testRoute.price,
+        passengerSnapshots: [
+          {
+            id: testPassenger.id,
+            name: testPassenger.name,
+            idType: testPassenger.idType,
+            idNumber: testPassenger.idNumber,
+            phone: testPassenger.phone
+          }
+        ],
+        routeSnapshot: {
+          id: testRoute.id,
+          name: testRoute.name,
+          pickupPoint: testRoute.pickupPoint,
+          dropoffPoint: testRoute.dropoffPoint,
+          departureTime: testRoute.departureTime,
+          price: testRoute.price,
+          vehicleType: testRoute.vehicleType,
+          travelMode: testRoute.travelMode
+        }
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      },
+      {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      // 断言响应
+      expect(response.status).toBe(201);
+      if (response.status === 201) {
+        const responseData = await response.json();
+        expect(responseData).toHaveProperty('id');
+        expect(responseData.userId).toBe(testUser.id);
+        expect(responseData.routeId).toBe(orderData.routeId);
+        expect(responseData.passengerCount).toBe(orderData.passengerCount);
+        expect(responseData.totalAmount).toBe(orderData.totalAmount);
+        expect(responseData.status).toBe(OrderStatus.PENDING_PAYMENT);
+        expect(responseData.paymentStatus).toBe(PaymentStatus.PENDING);
+        expect(responseData.passengerSnapshots).toEqual(orderData.passengerSnapshots);
+        // 由于parseWithAwait会将Date转换为字符串,我们需要比较字符串格式
+        const expectedRouteSnapshot = {
+          ...orderData.routeSnapshot,
+          departureTime: orderData.routeSnapshot.departureTime.toISOString()
+        };
+        expect(responseData.routeSnapshot).toEqual(expectedRouteSnapshot);
+
+        // 断言数据库中存在订单
+        await IntegrationTestAssertions.expectOrderToExist(responseData.id);
+      } else {
+        // 调试信息
+        const errorData = await response.json();
+        console.debug('订单创建失败:', errorData);
+      }
+    });
+
+    it('应该拒绝创建不存在的路线订单', async () => {
+      const orderData = {
+        routeId: 999999, // 不存在的路线ID
+        passengerCount: 1,
+        totalAmount: 100,
+        passengerSnapshots: [],
+        routeSnapshot: {}
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      },
+      {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      // 应该返回404错误
+      expect(response.status).toBe(404);
+      if (response.status === 404) {
+        const responseData = await response.json();
+        expect(responseData.message).toContain('路线不存在');
+      }
+    });
+
+    it('应该拒绝创建超过座位数的订单', async () => {
+      const orderData = {
+        routeId: testRoute.id,
+        passengerCount: testRoute.availableSeats + 1, // 超过可用座位数
+        totalAmount: testRoute.price * (testRoute.availableSeats + 1),
+        passengerSnapshots: [],
+        routeSnapshot: {}
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      },
+      {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      // 应该返回422业务逻辑错误
+      expect(response.status).toBe(422);
+      if (response.status === 422) {
+        const responseData = await response.json();
+        expect(responseData.message).toContain('乘客数量超过路线座位数');
+      }
+    });
+
+    it('应该验证乘客快照信息', async () => {
+      const orderData = {
+        routeId: testRoute.id,
+        passengerCount: 1,
+        totalAmount: testRoute.price,
+        passengerSnapshots: [
+          {
+            id: 999999, // 不存在的乘客ID
+            name: '不存在的乘客',
+            idType: 'ID_CARD',
+            idNumber: '110101199001011234',
+            phone: '13812345678'
+          }
+        ],
+        routeSnapshot: {}
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      },
+      {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      // 应该返回404错误
+      expect(response.status).toBe(404);
+      if (response.status === 404) {
+        const responseData = await response.json();
+        expect(responseData.message).toContain('乘客ID 999999 不存在');
+      }
+    });
+
+    it('应该拒绝创建缺少必填字段的订单', async () => {
+      const orderData = {
+        // 缺少routeId
+        routeId: undefined as any, // 故意不提供routeId来测试验证
+        passengerCount: 1,
+        totalAmount: 100,
+        passengerSnapshots: [],
+        routeSnapshot: {}
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      },
+      {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      // 应该返回验证错误
+      expect([400, 500]).toContain(response.status);
+    });
+
+    it('应该正确计算订单金额', async () => {
+      const passengerCount = 2;
+      const expectedTotalAmount = testRoute.price * passengerCount;
+
+      const orderData = {
+        routeId: testRoute.id,
+        passengerCount: passengerCount,
+        totalAmount: expectedTotalAmount,
+        passengerSnapshots: [
+          {
+            id: testPassenger.id,
+            name: testPassenger.name,
+            idType: testPassenger.idType,
+            idNumber: testPassenger.idNumber,
+            phone: testPassenger.phone
+          },
+          {
+            name: '第二个乘客',
+            idType: 'ID_CARD',
+            idNumber: '110101199001012345',
+            phone: '13987654321'
+          }
+        ],
+        routeSnapshot: {
+          id: testRoute.id,
+          name: testRoute.name,
+          pickupPoint: testRoute.pickupPoint,
+          dropoffPoint: testRoute.dropoffPoint,
+          departureTime: testRoute.departureTime,
+          price: testRoute.price,
+          vehicleType: testRoute.vehicleType,
+          travelMode: testRoute.travelMode
+        }
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      },
+      {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(201);
+      if (response.status === 201) {
+        const responseData = await response.json();
+        expect(responseData.totalAmount).toBe(expectedTotalAmount);
+        expect(responseData.passengerCount).toBe(passengerCount);
+      }
+    });
+  });
+
+  describe('权限控制测试', () => {
+    it('应该拒绝未认证用户的订单创建', async () => {
+      const orderData = {
+        routeId: testRoute.id,
+        passengerCount: 1,
+        totalAmount: testRoute.price,
+        passengerSnapshots: [],
+        routeSnapshot: {}
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      });
+
+      expect(response.status).toBe(401);
+    });
+
+    it('应该拒绝无效token的订单创建', async () => {
+      const orderData = {
+        routeId: testRoute.id,
+        passengerCount: 1,
+        totalAmount: testRoute.price,
+        passengerSnapshots: [],
+        routeSnapshot: {}
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      },
+      {
+        headers: {
+          'Authorization': 'Bearer invalid_token'
+        }
+      });
+
+      expect(response.status).toBe(401);
+    });
+  });
+
+  describe('快照机制测试', () => {
+    it('应该正确保存路线快照信息', async () => {
+      const routeSnapshot = {
+        id: testRoute.id,
+        name: testRoute.name,
+        pickupPoint: testRoute.pickupPoint,
+        dropoffPoint: testRoute.dropoffPoint,
+        departureTime: testRoute.departureTime,
+        price: testRoute.price,
+        vehicleType: testRoute.vehicleType,
+        travelMode: testRoute.travelMode
+      };
+
+      const orderData = {
+        routeId: testRoute.id,
+        passengerCount: 1,
+        totalAmount: testRoute.price,
+        passengerSnapshots: [],
+        routeSnapshot: routeSnapshot
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      },
+      {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(201);
+      if (response.status === 201) {
+        const responseData = await response.json();
+        // 由于parseWithAwait会将Date转换为字符串,我们需要比较字符串格式
+        const expectedRouteSnapshot = {
+          ...routeSnapshot,
+          departureTime: routeSnapshot.departureTime.toISOString()
+        };
+        expect(responseData.routeSnapshot).toEqual(expectedRouteSnapshot);
+
+        // 验证数据库中的快照数据
+        const order = await IntegrationTestAssertions.getOrderById(responseData.id);
+        expect(order).not.toBeNull();
+        // 数据库中的routeSnapshot应该包含字符串格式的时间
+        const expectedDbRouteSnapshot = {
+          ...routeSnapshot,
+          departureTime: routeSnapshot.departureTime.toISOString()
+        };
+        expect(order!.routeSnapshot).toEqual(expectedDbRouteSnapshot);
+      }
+    });
+
+    it('应该正确保存乘客快照信息', async () => {
+      const passengerSnapshots = [
+        {
+          id: testPassenger.id,
+          name: testPassenger.name,
+          idType: testPassenger.idType,
+          idNumber: testPassenger.idNumber,
+          phone: testPassenger.phone,
+          isDefault: testPassenger.isDefault
+        },
+        {
+          name: '新乘客',
+          idType: 'PASSPORT',
+          idNumber: 'E12345678',
+          phone: '13987654321',
+          isDefault: false
+        }
+      ];
+
+      const orderData = {
+        routeId: testRoute.id,
+        passengerCount: passengerSnapshots.length,
+        totalAmount: testRoute.price * passengerSnapshots.length,
+        passengerSnapshots: passengerSnapshots,
+        routeSnapshot: {
+          id: testRoute.id,
+          name: testRoute.name,
+          pickupPoint: testRoute.pickupPoint,
+          dropoffPoint: testRoute.dropoffPoint,
+          departureTime: testRoute.departureTime,
+          price: testRoute.price,
+          vehicleType: testRoute.vehicleType,
+          travelMode: testRoute.travelMode
+        }
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      },
+      {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(201);
+      if (response.status === 201) {
+        const responseData = await response.json();
+        expect(responseData.passengerSnapshots).toEqual(passengerSnapshots);
+
+        // 验证数据库中的快照数据
+        const order = await IntegrationTestAssertions.getOrderById(responseData.id);
+        expect(order).not.toBeNull();
+        expect(order!.passengerSnapshots).toEqual(passengerSnapshots);
+      }
+    });
+  });
+
+  describe('订单状态测试', () => {
+    it('新创建的订单应该处于待支付状态', async () => {
+      const orderData = {
+        routeId: testRoute.id,
+        passengerCount: 1,
+        totalAmount: testRoute.price,
+        passengerSnapshots: [],
+        routeSnapshot: {
+          id: testRoute.id,
+          name: testRoute.name,
+          pickupPoint: testRoute.pickupPoint,
+          dropoffPoint: testRoute.dropoffPoint,
+          departureTime: testRoute.departureTime,
+          price: testRoute.price,
+          vehicleType: testRoute.vehicleType,
+          travelMode: testRoute.travelMode
+        }
+      };
+
+      const response = await client.orders.$post({
+        json: orderData,
+      },
+      {
+        headers: {
+          'Authorization': `Bearer ${testToken}`
+        }
+      });
+
+      expect(response.status).toBe(201);
+      if (response.status === 201) {
+        const responseData = await response.json();
+        expect(responseData.status).toBe(OrderStatus.PENDING_PAYMENT);
+        expect(responseData.paymentStatus).toBe(PaymentStatus.PENDING);
+      }
+    });
+  });
+});

+ 2 - 1
web/tests/utils/server/integration-test-db.ts

@@ -7,7 +7,8 @@ import { RouteEntity } from '@d8d/server/modules/routes/route.entity';
 import { LocationEntity } from '@d8d/server/modules/locations/location.entity';
 import { AreaEntity } from '@d8d/server/modules/areas/area.entity';
 import { VehicleType } from '@d8d/server/modules/routes/route.schema';
-import { Passenger, IdType } from '@d8d/server/modules/passengers/passenger.entity';
+import { Passenger } from '@d8d/server/modules/passengers/passenger.entity';
+import { IdType } from '@d8d/server/modules/passengers/passenger.schema';
 import { AppDataSource } from '@d8d/server/data-source';
 
 /**

+ 48 - 0
web/tests/utils/server/integration-test-utils.ts

@@ -5,6 +5,7 @@ import { ActivityEntity } from '@d8d/server/modules/activities/activity.entity';
 import { RouteEntity } from '@d8d/server/modules/routes/route.entity';
 import { LocationEntity } from '@d8d/server/modules/locations/location.entity';
 import { Passenger } from '@d8d/server/modules/passengers/passenger.entity';
+import { Order } from '@d8d/server/modules/orders/order.entity';
 
 
 
@@ -212,4 +213,51 @@ export class IntegrationTestAssertions {
       throw new Error(`Expected passenger ${passengerId} not to exist in database`);
     }
   }
+
+  /**
+   * 断言订单存在于数据库中
+   */
+  static async expectOrderToExist(orderId: number): Promise<void> {
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    if (!dataSource) {
+      throw new Error('Database not initialized');
+    }
+
+    const orderRepository = dataSource.getRepository(Order);
+    const order = await orderRepository.findOne({ where: { id: orderId } });
+
+    if (!order) {
+      throw new Error(`Expected order ${orderId} to exist in database`);
+    }
+  }
+
+  /**
+   * 断言订单不存在于数据库中
+   */
+  static async expectOrderNotToExist(orderId: number): Promise<void> {
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    if (!dataSource) {
+      throw new Error('Database not initialized');
+    }
+
+    const orderRepository = dataSource.getRepository(Order);
+    const order = await orderRepository.findOne({ where: { id: orderId } });
+
+    if (order) {
+      throw new Error(`Expected order ${orderId} not to exist in database`);
+    }
+  }
+
+  /**
+   * 根据ID获取订单
+   */
+  static async getOrderById(orderId: number): Promise<Order | null> {
+    const dataSource = await IntegrationTestDatabase.getDataSource();
+    if (!dataSource) {
+      throw new Error('Database not initialized');
+    }
+
+    const orderRepository = dataSource.getRepository(Order);
+    return await orderRepository.findOne({ where: { id: orderId } });
+  }
 }