import * as fc from 'fast-check'; /** * **Feature: backend-frontend-integration, Property 7: Dashboard Stats Accuracy** * **Validates: Requirements 6.1** * * *For any* dashboard stats query, the returned todayOrderCount SHALL equal the count * of orders created today, and todayGMV SHALL equal the sum of totalAmount for * completed orders today. */ // 模拟订单数据结构 interface MockOrder { id: string; orderNo: string; sellerId: string; totalAmount: number; status: 'PendingPayment' | 'PendingShipment' | 'Shipped' | 'Completed' | 'Cancelled'; createdAt: Date; } // 模拟今日统计结果 interface TodayStats { todayOrderCount: number; todayGMV: number; pendingShipmentCount: number; } /** * 纯函数:计算今日统计数据 * 用于属性测试验证业务逻辑的正确性 */ function calculateTodayStats(orders: MockOrder[], sellerId: string, today: Date): TodayStats { // 设置今日开始时间 const todayStart = new Date(today); todayStart.setHours(0, 0, 0, 0); // 过滤该商家的订单 const sellerOrders = orders.filter(order => order.sellerId === sellerId); // 今日订单:创建时间在今日的订单 const todayOrders = sellerOrders.filter(order => order.createdAt >= todayStart); const todayOrderCount = todayOrders.length; // 今日GMV:今日已完成订单的总额 const todayCompletedOrders = todayOrders.filter(order => order.status === 'Completed'); const todayGMV = todayCompletedOrders.reduce((sum, order) => sum + order.totalAmount, 0); // 待发货订单数:所有待发货订单(不限于今日) const pendingShipmentCount = sellerOrders.filter( order => order.status === 'PendingShipment' ).length; return { todayOrderCount, todayGMV, pendingShipmentCount }; } /** * 辅助函数:生成指定日期范围内的随机日期 */ function randomDateInRange(start: Date, end: Date): Date { const startTime = start.getTime(); const endTime = end.getTime(); const randomTime = startTime + Math.random() * (endTime - startTime); return new Date(randomTime); } describe('DashboardService Property Tests', () => { // 生成订单状态的arbitrary const orderStatusArb = fc.constantFrom( 'PendingPayment' as const, 'PendingShipment' as const, 'Shipped' as const, 'Completed' as const, 'Cancelled' as const ); describe('Property 7: Dashboard Stats Accuracy', () => { it('今日订单数等于今日创建的订单数量', () => { fc.assert( fc.property( fc.uuid(), // sellerId fc.integer({ min: 0, max: 50 }), // 订单数量 (sellerId, orderCount) => { const today = new Date(); const todayStart = new Date(today); todayStart.setHours(0, 0, 0, 0); // 生成订单:一半今日,一半昨日 const orders: MockOrder[] = []; const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); for (let i = 0; i < orderCount; i++) { const isToday = i % 2 === 0; const createdAt = isToday ? randomDateInRange(todayStart, today) : randomDateInRange(yesterday, todayStart); orders.push({ id: `order-${i}`, orderNo: `ORDER${String(i).padStart(14, '0')}`, sellerId: i % 3 === 0 ? 'other-seller' : sellerId, // 1/3是其他商家 totalAmount: Math.random() * 1000, status: 'Completed', createdAt }); } const stats = calculateTodayStats(orders, sellerId, today); // 手动计算预期的今日订单数 const expectedTodayCount = orders.filter( o => o.sellerId === sellerId && o.createdAt >= todayStart ).length; return stats.todayOrderCount === expectedTodayCount; } ), { numRuns: 100 } ); }); it('今日GMV等于今日已完成订单的总额', () => { fc.assert( fc.property( fc.uuid(), // sellerId fc.array( fc.record({ totalAmount: fc.integer({ min: 100, max: 100000 }).map(cents => cents / 100), status: orderStatusArb, isToday: fc.boolean(), isSeller: fc.boolean() }), { minLength: 0, maxLength: 30 } ), (sellerId, orderData) => { const today = new Date(); const todayStart = new Date(today); todayStart.setHours(0, 0, 0, 0); const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); // 根据生成的数据创建订单 const orders: MockOrder[] = orderData.map((data, i) => ({ id: `order-${i}`, orderNo: `ORDER${String(i).padStart(14, '0')}`, sellerId: data.isSeller ? sellerId : 'other-seller', totalAmount: data.totalAmount, status: data.status, createdAt: data.isToday ? randomDateInRange(todayStart, today) : randomDateInRange(yesterday, todayStart) })); const stats = calculateTodayStats(orders, sellerId, today); // 手动计算预期的今日GMV const expectedGMV = orders .filter(o => o.sellerId === sellerId && o.createdAt >= todayStart && o.status === 'Completed' ) .reduce((sum, o) => sum + o.totalAmount, 0); // 使用近似比较处理浮点数精度问题 return Math.abs(stats.todayGMV - expectedGMV) < 0.01; } ), { numRuns: 100 } ); }); it('待发货订单数等于状态为PendingShipment的订单数量', () => { fc.assert( fc.property( fc.uuid(), // sellerId fc.array( fc.record({ status: orderStatusArb, isSeller: fc.boolean() }), { minLength: 0, maxLength: 30 } ), (sellerId, orderData) => { const today = new Date(); const orders: MockOrder[] = orderData.map((data, i) => ({ id: `order-${i}`, orderNo: `ORDER${String(i).padStart(14, '0')}`, sellerId: data.isSeller ? sellerId : 'other-seller', totalAmount: Math.random() * 1000, status: data.status, createdAt: today })); const stats = calculateTodayStats(orders, sellerId, today); // 手动计算预期的待发货订单数 const expectedPending = orders.filter( o => o.sellerId === sellerId && o.status === 'PendingShipment' ).length; return stats.pendingShipmentCount === expectedPending; } ), { numRuns: 100 } ); }); it('空订单列表返回零统计', () => { fc.assert( fc.property( fc.uuid(), (sellerId) => { const today = new Date(); const stats = calculateTodayStats([], sellerId, today); return ( stats.todayOrderCount === 0 && stats.todayGMV === 0 && stats.pendingShipmentCount === 0 ); } ), { numRuns: 100 } ); }); it('不同商家的统计数据相互独立', () => { fc.assert( fc.property( fc.uuid(), fc.uuid(), fc.array( fc.record({ totalAmount: fc.integer({ min: 100, max: 100000 }).map(cents => cents / 100), status: orderStatusArb }), { minLength: 1, maxLength: 20 } ), (sellerId1, sellerId2, orderData) => { // 确保两个商家ID不同 if (sellerId1 === sellerId2) return true; const today = new Date(); const todayStart = new Date(today); todayStart.setHours(0, 0, 0, 0); // 创建订单,分配给两个商家 const orders: MockOrder[] = orderData.map((data, i) => ({ id: `order-${i}`, orderNo: `ORDER${String(i).padStart(14, '0')}`, sellerId: i % 2 === 0 ? sellerId1 : sellerId2, totalAmount: data.totalAmount, status: data.status, createdAt: randomDateInRange(todayStart, today) })); const stats1 = calculateTodayStats(orders, sellerId1, today); const stats2 = calculateTodayStats(orders, sellerId2, today); // 验证:两个商家的订单数之和等于总订单数 const totalOrders = orders.filter(o => o.createdAt >= todayStart).length; return stats1.todayOrderCount + stats2.todayOrderCount === totalOrders; } ), { numRuns: 100 } ); }); it('GMV只计算已完成订单,不包括其他状态', () => { fc.assert( fc.property( fc.uuid(), fc.integer({ min: 100, max: 10000 }).map(cents => cents / 100), (sellerId, amount) => { const today = new Date(); const todayStart = new Date(today); todayStart.setHours(0, 0, 0, 0); // 创建不同状态的订单,金额相同 const statuses: Array<'PendingPayment' | 'PendingShipment' | 'Shipped' | 'Completed' | 'Cancelled'> = [ 'PendingPayment', 'PendingShipment', 'Shipped', 'Completed', 'Cancelled' ]; const orders: MockOrder[] = statuses.map((status, i) => ({ id: `order-${i}`, orderNo: `ORDER${String(i).padStart(14, '0')}`, sellerId, totalAmount: amount, status, createdAt: randomDateInRange(todayStart, today) })); const stats = calculateTodayStats(orders, sellerId, today); // GMV应该只等于一个已完成订单的金额 return Math.abs(stats.todayGMV - amount) < 0.01; } ), { numRuns: 100 } ); }); it('昨日订单不计入今日统计', () => { fc.assert( fc.property( fc.uuid(), fc.integer({ min: 1, max: 20 }), (sellerId, orderCount) => { const today = new Date(); const todayStart = new Date(today); todayStart.setHours(0, 0, 0, 0); const yesterday = new Date(today); yesterday.setDate(yesterday.getDate() - 1); const yesterdayStart = new Date(yesterday); yesterdayStart.setHours(0, 0, 0, 0); // 只创建昨日的订单 const orders: MockOrder[] = []; for (let i = 0; i < orderCount; i++) { orders.push({ id: `order-${i}`, orderNo: `ORDER${String(i).padStart(14, '0')}`, sellerId, totalAmount: Math.random() * 1000, status: 'Completed', createdAt: randomDateInRange(yesterdayStart, todayStart) }); } const stats = calculateTodayStats(orders, sellerId, today); // 今日订单数和GMV应该都是0 return stats.todayOrderCount === 0 && stats.todayGMV === 0; } ), { numRuns: 100 } ); }); }); });