import { describe, it, expect, beforeEach, vi } from 'vitest'; import { DataSource, Repository } from 'typeorm'; import { OrderMt } from '@d8d/orders-module-mt'; import { DataOverviewServiceMt, TimeFilterParams, SummaryStatistics } from '../../src/services/data-overview.service'; // Mock redisUtil with importOriginal to preserve other exports vi.mock('@d8d/shared-utils', async (importOriginal) => { const actual = await importOriginal(); return { ...actual, redisUtil: { get: vi.fn(), set: vi.fn(), keys: vi.fn(), del: vi.fn() }, AppDataSource: {} }; }); describe('DataOverviewServiceMt', () => { let service: DataOverviewServiceMt; let mockDataSource: DataSource; let mockOrderRepository: Repository; let mockRedisUtil: any; beforeEach(() => { // Mock Order Repository mockOrderRepository = { createQueryBuilder: vi.fn(() => ({ select: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), andWhere: vi.fn().mockReturnThis(), setParameters: vi.fn().mockReturnThis(), getRawOne: vi.fn() })) } as any; // Mock DataSource mockDataSource = { getRepository: vi.fn((entity) => { if (entity === OrderMt) { return mockOrderRepository; } return {} as any; }) } as any; // Get mocked redisUtil const { redisUtil } = require('@d8d/shared-utils'); mockRedisUtil = redisUtil; vi.mocked(mockRedisUtil.get).mockReset(); vi.mocked(mockRedisUtil.set).mockReset(); vi.mocked(mockRedisUtil.keys).mockReset(); vi.mocked(mockRedisUtil.del).mockReset(); service = new DataOverviewServiceMt(mockDataSource); }); describe('getDateRange', () => { it('应该返回今天的时间范围(默认)', () => { const now = new Date('2025-12-26T10:00:00Z'); vi.setSystemTime(now); const params: TimeFilterParams = {}; const result = service['getDateRange'](params); const expectedStart = new Date('2025-12-26T00:00:00Z'); const expectedEnd = new Date('2025-12-26T23:59:59.999Z'); expect(result.startDate.toISOString()).toBe(expectedStart.toISOString()); expect(result.endDate.toISOString()).toBe(expectedEnd.toISOString()); }); it('应该返回昨天的时间范围', () => { const now = new Date('2025-12-26T10:00:00Z'); vi.setSystemTime(now); const params: TimeFilterParams = { timeRange: 'yesterday' }; const result = service['getDateRange'](params); const expectedStart = new Date('2025-12-25T00:00:00Z'); const expectedEnd = new Date('2025-12-25T23:59:59.999Z'); expect(result.startDate.toISOString()).toBe(expectedStart.toISOString()); expect(result.endDate.toISOString()).toBe(expectedEnd.toISOString()); }); it('应该返回最近7天的时间范围', () => { const now = new Date('2025-12-26T10:00:00Z'); vi.setSystemTime(now); const params: TimeFilterParams = { timeRange: 'last7days' }; const result = service['getDateRange'](params); const expectedStart = new Date('2025-12-19T10:00:00Z'); // 7天前 const expectedEnd = now; expect(result.startDate.toISOString()).toBe(expectedStart.toISOString()); expect(result.endDate.toISOString()).toBe(expectedEnd.toISOString()); }); it('应该返回最近30天的时间范围', () => { const now = new Date('2025-12-26T10:00:00Z'); vi.setSystemTime(now); const params: TimeFilterParams = { timeRange: 'last30days' }; const result = service['getDateRange'](params); const expectedStart = new Date('2025-11-26T10:00:00Z'); // 30天前 const expectedEnd = now; expect(result.startDate.toISOString()).toBe(expectedStart.toISOString()); expect(result.endDate.toISOString()).toBe(expectedEnd.toISOString()); }); it('应该返回自定义时间范围', () => { const params: TimeFilterParams = { timeRange: 'custom', startDate: '2025-01-01T00:00:00Z', endDate: '2025-01-31T23:59:59Z' }; const result = service['getDateRange'](params); expect(result.startDate.toISOString()).toBe('2025-01-01T00:00:00.000Z'); expect(result.endDate.toISOString()).toBe('2025-01-31T23:59:59.000Z'); }); it('当自定义时间范围缺少参数时应该使用默认值', () => { const now = new Date('2025-12-26T10:00:00Z'); vi.setSystemTime(now); const params: TimeFilterParams = { timeRange: 'custom' }; // 缺少startDate和endDate const result = service['getDateRange'](params); const expectedStart = new Date('2025-12-26T00:00:00Z'); const expectedEnd = new Date('2025-12-26T23:59:59.999Z'); expect(result.startDate.toISOString()).toBe(expectedStart.toISOString()); expect(result.endDate.toISOString()).toBe(expectedEnd.toISOString()); }); }); describe('getSummaryStatistics', () => { it('应该从缓存返回统计数据', async () => { const tenantId = 1; const params: TimeFilterParams = { timeRange: 'today' }; const cacheKey = `data_overview:summary:${tenantId}:today::`; const cachedStats: SummaryStatistics = { totalSales: 10000, totalOrders: 50, wechatSales: 6000, wechatOrders: 30, creditSales: 4000, creditOrders: 20, todaySales: 500, todayOrders: 5 }; mockRedisUtil.get.mockResolvedValue(JSON.stringify(cachedStats)); const result = await service.getSummaryStatistics(tenantId, params); expect(result).toEqual(cachedStats); expect(mockRedisUtil.get).toHaveBeenCalledWith(cacheKey); expect(mockRedisUtil.set).not.toHaveBeenCalled(); }); it('当缓存未命中时应该查询数据库并设置缓存', async () => { const tenantId = 1; const params: TimeFilterParams = { timeRange: 'today' }; const cacheKey = `data_overview:summary:${tenantId}:today::`; mockRedisUtil.get.mockResolvedValue(null); // Mock database query result const mockQueryBuilder = { select: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), andWhere: vi.fn().mockReturnThis(), setParameters: vi.fn().mockReturnThis(), getRawOne: vi.fn().mockResolvedValue({ total_orders: '50', total_sales: '10000.50', wechat_sales: '6000.00', credit_sales: '4000.50', wechat_orders: '30', credit_orders: '20' }) }; const mockTodayQueryBuilder = { select: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), andWhere: vi.fn().mockReturnThis(), getRawOne: vi.fn().mockResolvedValue({ today_orders: '5', today_sales: '500.00' }) }; vi.mocked(mockOrderRepository.createQueryBuilder) .mockReturnValueOnce(mockQueryBuilder as any) .mockReturnValueOnce(mockTodayQueryBuilder as any); const result = await service.getSummaryStatistics(tenantId, params); expect(result.totalSales).toBe(10000.50); expect(result.totalOrders).toBe(50); expect(result.wechatSales).toBe(6000); expect(result.creditSales).toBe(4000.50); expect(result.wechatOrders).toBe(30); expect(result.creditOrders).toBe(20); expect(result.todaySales).toBe(500); expect(result.todayOrders).toBe(5); expect(mockRedisUtil.set).toHaveBeenCalledWith( cacheKey, expect.any(String), 5 * 60 // 5分钟TTL(今日数据) ); }); it('应该为历史数据设置30分钟缓存', async () => { const tenantId = 1; const params: TimeFilterParams = { timeRange: 'last7days' }; const cacheKey = `data_overview:summary:${tenantId}:last7days::`; mockRedisUtil.get.mockResolvedValue(null); const mockQueryBuilder = { select: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), andWhere: vi.fn().mockReturnThis(), setParameters: vi.fn().mockReturnThis(), getRawOne: vi.fn().mockResolvedValue({ total_orders: '100', total_sales: '20000.00', wechat_sales: '12000.00', credit_sales: '8000.00', wechat_orders: '60', credit_orders: '40' }) }; const mockTodayQueryBuilder = { select: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), andWhere: vi.fn().mockReturnThis(), getRawOne: vi.fn().mockResolvedValue({ today_orders: '10', today_sales: '1000.00' }) }; vi.mocked(mockOrderRepository.createQueryBuilder) .mockReturnValueOnce(mockQueryBuilder as any) .mockReturnValueOnce(mockTodayQueryBuilder as any); await service.getSummaryStatistics(tenantId, params); expect(mockRedisUtil.set).toHaveBeenCalledWith( cacheKey, expect.any(String), 30 * 60 // 30分钟TTL(历史数据) ); }); }); describe('getTodayStatistics', () => { it('应该从缓存返回今日统计数据', async () => { const tenantId = 1; const today = new Date().toISOString().split('T')[0]; const cacheKey = `data_overview:today:${tenantId}:${today}`; const cachedStats = { todaySales: 500, todayOrders: 5 }; mockRedisUtil.get.mockResolvedValue(JSON.stringify(cachedStats)); const result = await service.getTodayStatistics(tenantId); expect(result).toEqual(cachedStats); expect(mockRedisUtil.get).toHaveBeenCalledWith(cacheKey); expect(mockRedisUtil.set).not.toHaveBeenCalled(); }); it('当缓存未命中时应该查询数据库并设置缓存', async () => { const tenantId = 1; const today = new Date().toISOString().split('T')[0]; const cacheKey = `data_overview:today:${tenantId}:${today}`; mockRedisUtil.get.mockResolvedValue(null); const mockQueryBuilder = { select: vi.fn().mockReturnThis(), where: vi.fn().mockReturnThis(), andWhere: vi.fn().mockReturnThis(), getRawOne: vi.fn().mockResolvedValue({ today_orders: '5', today_sales: '500.00' }) }; vi.mocked(mockOrderRepository.createQueryBuilder).mockReturnValue(mockQueryBuilder as any); const result = await service.getTodayStatistics(tenantId); expect(result.todaySales).toBe(500); expect(result.todayOrders).toBe(5); expect(mockRedisUtil.set).toHaveBeenCalledWith(cacheKey, expect.any(String), 5 * 60); }); }); describe('clearCache', () => { it('应该清理指定租户的所有缓存', async () => { const tenantId = 1; const cacheKeys = [ 'data_overview:summary:1:today::', 'data_overview:today:1:2025-12-26' ]; mockRedisUtil.keys.mockResolvedValue(cacheKeys); await service.clearCache(tenantId); expect(mockRedisUtil.keys).toHaveBeenCalledWith('data_overview:*:1:*'); expect(mockRedisUtil.del).toHaveBeenCalledWith(...cacheKeys); }); it('当没有缓存键时不应该调用del', async () => { const tenantId = 1; mockRedisUtil.keys.mockResolvedValue([]); await service.clearCache(tenantId); expect(mockRedisUtil.keys).toHaveBeenCalledWith('data_overview:*:1:*'); expect(mockRedisUtil.del).not.toHaveBeenCalled(); }); }); });