| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352 |
- 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 }
- );
- });
- });
- });
|