import { describe, it, expect, beforeEach } from 'vitest'; import { testClient } from 'hono/testing'; import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util'; import { UserEntityMt, RoleMt } from '@d8d/core-module-mt/user-module-mt/entities'; import { FileMt } from '@d8d/core-module-mt/file-module-mt/entities'; import { OrderMt, OrderGoodsMt } from '@d8d/orders-module-mt'; import { MerchantMt } from '@d8d/merchant-module-mt'; import { SupplierMt } from '@d8d/supplier-module-mt'; import { DeliveryAddressMt } from '@d8d/delivery-address-module-mt'; import { AreaEntityMt } from '@d8d/geo-areas-mt'; import { GoodsMt, GoodsCategoryMt } from '@d8d/goods-module-mt'; import dataOverviewRoutes from '../../src/routes'; import { DataOverviewTestDataFactory } from '../utils/test-data-factory'; // 设置集成测试钩子 - 需要User、Role、File、Order及相关实体 setupIntegrationDatabaseHooksWithEntities([ UserEntityMt, RoleMt, FileMt, OrderMt, OrderGoodsMt, MerchantMt, SupplierMt, DeliveryAddressMt, AreaEntityMt, GoodsMt, GoodsCategoryMt ]) describe('多租户数据概览API集成测试', () => { let client: ReturnType>; let userToken: string; let adminToken: string; let testUser: UserEntityMt; beforeEach(async () => { // 创建测试客户端 client = testClient(dataOverviewRoutes); // 获取数据源并创建测试用户 const dataSource = await IntegrationTestDatabase.getDataSource(); // 创建租户1的测试用户 testUser = await DataOverviewTestDataFactory.createTestUser(dataSource, 1); // 生成JWT令牌 userToken = DataOverviewTestDataFactory.generateUserToken(testUser); adminToken = DataOverviewTestDataFactory.generateAdminToken(1); }); describe('租户数据隔离验证', () => { it('应该确保订单数据的租户隔离', async () => { const dataSource = await IntegrationTestDatabase.getDataSource(); const orderRepository = dataSource.getRepository(OrderMt); // 创建租户1的订单数据 await DataOverviewTestDataFactory.createTestOrders(dataSource, 1, 2); // 创建租户2的订单数据 await DataOverviewTestDataFactory.createTestOrders(dataSource, 2, 3); // 验证租户1只能看到租户1的订单 const tenant1Orders = await orderRepository.find({ where: { tenantId: 1 } }); // 验证租户2只能看到租户2的订单 const tenant2Orders = await orderRepository.find({ where: { tenantId: 2 } }); expect(tenant1Orders).toHaveLength(2); expect(tenant1Orders[0].tenantId).toBe(1); expect(tenant2Orders).toHaveLength(3); expect(tenant2Orders[0].tenantId).toBe(2); }); it('应该防止跨租户数据访问', async () => { const dataSource = await IntegrationTestDatabase.getDataSource(); const orderRepository = dataSource.getRepository(OrderMt); // 创建租户1的订单 const tenant1Orders = await DataOverviewTestDataFactory.createTestOrders(dataSource, 1, 1); const tenant1Order = tenant1Orders[0]; // 尝试使用租户2的ID查询租户1的订单 const crossTenantOrder = await orderRepository.findOne({ where: { orderNo: tenant1Order.orderNo, tenantId: 2 // 错误的租户ID } }); expect(crossTenantOrder).toBeNull(); }); it('应该在创建数据时正确设置租户ID', async () => { const dataSource = await IntegrationTestDatabase.getDataSource(); const orderRepository = dataSource.getRepository(OrderMt); const tenantId = 5; const orders = await DataOverviewTestDataFactory.createTestOrders(dataSource, tenantId, 1); const order = orders[0]; expect(order.tenantId).toBe(tenantId); expect(order.createdBy).toBeDefined(); }); }); describe('GET /api/data-overview/summary', () => { it('应该返回今日数据概览统计(默认时间范围)', async () => { // 创建测试订单数据 const dataSource = await IntegrationTestDatabase.getDataSource(); await DataOverviewTestDataFactory.createTestOrders(dataSource, testUser.tenantId, 5); const response = await client.summary.$get({ query: { year: undefined } }, { headers: { 'Authorization': `Bearer ${userToken}` } }); expect(response.status).toBe(200); if (response.status === 200) { const data = await response.json(); expect(data.success).toBe(true); expect(data.data).toBeDefined(); expect(typeof data.data.totalSales).toBe('number'); expect(typeof data.data.totalOrders).toBe('number'); expect(typeof data.data.wechatSales).toBe('number'); expect(typeof data.data.wechatOrders).toBe('number'); expect(typeof data.data.creditSales).toBe('number'); expect(typeof data.data.creditOrders).toBe('number'); } }); it('应该支持自定义时间范围参数', async () => { const startDate = '2025-01-01T00:00:00Z'; const endDate = '2025-01-31T23:59:59Z'; const response = await client.summary.$get({ query: { timeRange: 'custom', startDate, endDate, year: undefined } }, { headers: { 'Authorization': `Bearer ${userToken}` } }); expect(response.status).toBe(200); if (response.status === 200) { const data = await response.json(); expect(data.success).toBe(true); } }); it('当时间范围参数无效时应该返回400错误', async () => { // 提供自定义时间范围但不提供startDate和endDate const response = await client.summary.$get({ query: { timeRange: 'custom', year: undefined // 缺少startDate和endDate } }, { headers: { 'Authorization': `Bearer ${userToken}` } }); expect(response.status).toBe(400); }); it('当startDate晚于endDate时应该返回400错误', async () => { const response = await client.summary.$get({ query: { timeRange: 'custom', startDate: '2025-01-31T00:00:00Z', endDate: '2025-01-01T00:00:00Z', year: undefined } }, { headers: { 'Authorization': `Bearer ${userToken}` } }); expect(response.status).toBe(400); }); it('应该验证多租户数据隔离', async () => { // 创建租户100的订单数据 const dataSource = await IntegrationTestDatabase.getDataSource(); const tenant100User = await DataOverviewTestDataFactory.createTestUser(dataSource, 100); const tenant100Token = DataOverviewTestDataFactory.generateUserToken(tenant100User); await DataOverviewTestDataFactory.createTestOrders(dataSource, 100, 3); // 创建租户101的用户和订单 const tenant101User = await DataOverviewTestDataFactory.createTestUser(dataSource, 101); const tenant101Token = DataOverviewTestDataFactory.generateUserToken(tenant101User); await DataOverviewTestDataFactory.createTestOrders(dataSource, 101, 2); // 租户100查询应该只看到租户100的数据 const response1 = await client.summary.$get({ query: { year: undefined } }, { headers: { 'Authorization': `Bearer ${tenant100Token}` } }); // 租户101查询应该只看到租户101的数据 const response2 = await client.summary.$get({ query: { year: undefined } }, { headers: { 'Authorization': `Bearer ${tenant101Token}` } }); expect(response1.status).toBe(200); expect(response2.status).toBe(200); if (response1.status === 200 && response2.status === 200) { const data1 = await response1.json(); const data2 = await response2.json(); console.debug('租户100统计数据:', data1.data); console.debug('租户101统计数据:', data2.data); // 两个租户的统计数据应该独立 expect(data1.data.totalOrders).toBe(3); expect(data2.data.totalOrders).toBe(2); } }); it('应该支持缓存机制', async () => { // 第一次查询应该从数据库获取 const dataSource = await IntegrationTestDatabase.getDataSource(); await DataOverviewTestDataFactory.createTestOrders(dataSource, testUser.tenantId, 2); const response1 = await client.summary.$get({ query: { year: undefined } }, { headers: { 'Authorization': `Bearer ${userToken}` } }); expect(response1.status).toBe(200); // 第二次查询(短时间内)应该从缓存获取相同结果 const response2 = await client.summary.$get({ query: { year: undefined } }, { headers: { 'Authorization': `Bearer ${userToken}` } }); expect(response2.status).toBe(200); if (response1.status === 200 && response2.status === 200) { const data1 = await response1.json(); const data2 = await response2.json(); expect(data1.data.totalOrders).toBe(data2.data.totalOrders); } }); it('应该排除已取消的订单', async () => { // 创建新租户的用户和token const dataSource = await IntegrationTestDatabase.getDataSource(); const tenant105User = await DataOverviewTestDataFactory.createTestUser(dataSource, 105); const tenant105Token = DataOverviewTestDataFactory.generateUserToken(tenant105User); const orderRepository = dataSource.getRepository(OrderMt); // 创建3个正常订单(支付成功,未取消) const normalOrders = await DataOverviewTestDataFactory.createTestOrders(dataSource, 105, 3); // 创建2个已取消的订单(设置cancelTime) const cancelledOrders = await DataOverviewTestDataFactory.createTestOrders(dataSource, 105, 2); for (const order of cancelledOrders) { order.cancelTime = new Date(); order.cancelReason = '测试取消'; await orderRepository.save(order); } const response = await client.summary.$get({ query: { year: undefined } }, { headers: { 'Authorization': `Bearer ${tenant105Token}` } }); expect(response.status).toBe(200); if (response.status === 200) { const data = await response.json(); // 应该只统计3个正常订单,排除2个取消订单 expect(data.data.totalOrders).toBe(3); expect(data.data.totalSales).toBeGreaterThan(0); // 验证支付方式分类统计也正确 const totalFromPaymentTypes = data.data.wechatOrders + data.data.creditOrders; expect(totalFromPaymentTypes).toBe(3); // 3个正常订单 } }); }); describe('GET /api/data-overview/today', () => { it('应该返回今日实时统计数据', async () => { // 创建新租户的用户和token const dataSource = await IntegrationTestDatabase.getDataSource(); const tenant103User = await DataOverviewTestDataFactory.createTestUser(dataSource, 103); const tenant103Token = DataOverviewTestDataFactory.generateUserToken(tenant103User); // 创建今日订单数据 await DataOverviewTestDataFactory.createTodayTestOrders(dataSource, 103, 3); const response = await client.today.$get({ query: {} }, { headers: { 'Authorization': `Bearer ${tenant103Token}` } }); expect(response.status).toBe(200); if (response.status === 200) { const data = await response.json(); expect(data.success).toBe(true); expect(data.data).toBeDefined(); expect(typeof data.data.todaySales).toBe('number'); expect(typeof data.data.todayOrders).toBe('number'); expect(data.data.todayOrders).toBe(3); } }); it('当没有今日订单时应该返回零值', async () => { // 创建新租户的用户和token(确保没有订单) const dataSource = await IntegrationTestDatabase.getDataSource(); const tenant104User = await DataOverviewTestDataFactory.createTestUser(dataSource, 104); const tenant104Token = DataOverviewTestDataFactory.generateUserToken(tenant104User); const response = await client.today.$get({ query: {} }, { headers: { 'Authorization': `Bearer ${tenant104Token}` } }); expect(response.status).toBe(200); if (response.status === 200) { const data = await response.json(); expect(data.data.todaySales).toBe(0); expect(data.data.todayOrders).toBe(0); } }); }); describe('认证和授权', () => { it('当缺少认证头时应该返回401错误', async () => { const response = await client.summary.$get({ query: { year: undefined } }); // 没有Authorization头 expect(response.status).toBe(401); }); it('当令牌无效时应该返回401错误', async () => { const response = await client.summary.$get({ query: { year: undefined } }, { headers: { 'Authorization': 'Bearer invalid-token' } }); expect(response.status).toBe(401); }); }); });