import { describe, it, expect, beforeEach, beforeAll, afterAll } from 'vitest'; import { testClient } from 'hono/testing'; import { IntegrationTestDatabase, TestDataFactory } from '../utils/integration-test-db'; import { UserEntityMt, UserServiceMt } from '@d8d/user-module-mt'; // 导入server包的api以确保数据源初始化,同时获取统一广告路由的类型 import { adminUnifiedAdvertisementApiRoutes, adminUnifiedAdvertisementTypeApiRoutes, unifiedAdvertisementApiRoutes } from '../../src/api'; import { AuthService } from '@d8d/auth-module-mt'; import { UnifiedAdvertisement, UnifiedAdvertisementType } from '@d8d/unified-advertisements-module'; describe('统一广告管理员权限集成测试', () => { let adminClient: ReturnType>['api']['v1']['admin']['unified-advertisements']; let adminTypeClient: ReturnType>['api']['v1']['admin']['unified-advertisement-types']; let userClient: ReturnType>['api']['v1']['advertisements']; let userTypeClient: ReturnType>['api']['v1']['advertisement-types']; let authService: AuthService; let userService: UserServiceMt; let superAdminToken: string; let regularUserToken: string; let tenantUserToken: string; // 使用 beforeAll 和 afterAll 而不是 beforeEach/afterEach,避免每次测试都销毁和重新创建数据源 beforeAll(async () => { await IntegrationTestDatabase.getDataSource(); }); afterAll(async () => { await IntegrationTestDatabase.cleanup(); }); beforeEach(async () => { // 创建测试客户端 - 使用server包注册后的路由 adminClient = testClient(adminUnifiedAdvertisementApiRoutes).api.v1.admin['unified-advertisements']; adminTypeClient = testClient(adminUnifiedAdvertisementTypeApiRoutes).api.v1.admin['unified-advertisement-types']; userClient = testClient(unifiedAdvertisementApiRoutes).api.v1.advertisements; userTypeClient = testClient(unifiedAdvertisementApiRoutes).api.v1['advertisement-types']; // 获取数据源 const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 清理测试数据(用户、广告、广告类型) const userRepository = dataSource.getRepository(UserEntityMt); await userRepository.delete({ username: 'superadmin' }); await userRepository.delete({ username: 'regularuser' }); await userRepository.delete({ username: 'tenantuser' }); // 清理广告和广告类型测试数据(使用repository delete方法) const adRepository = dataSource.getRepository(UnifiedAdvertisement); const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType); // 先删除广告(因为它们引用广告类型) const ads = await adRepository.find(); for (const ad of ads) { await adRepository.remove(ad); } // 再删除广告类型 const adTypes = await adTypeRepository.find(); for (const adType of adTypes) { await adTypeRepository.remove(adType); } // 初始化服务 userService = new UserServiceMt(dataSource); authService = new AuthService(userService); // 创建超级管理员 (ID=1, tenantId=1) // 先删除可能存在的超级管理员 await userRepository.delete({ username: 'superadmin' }); const superAdmin = await TestDataFactory.createTestUser(dataSource, { username: 'superadmin', password: 'TestPassword123!', email: 'superadmin@example.com', tenantId: 1 }); // 手动设置ID为1以确保是超级管理员 superAdmin.id = 1; // 使用query builder直接更新ID,避免save时的ID冲突 await dataSource.createQueryBuilder() .update(UserEntityMt) .set({ id: 1 }) .where('username = :username', { username: 'superadmin' }) .execute(); // 重新查询用户以确保ID正确 const superAdminWithId = await userRepository.findOne({ where: { username: 'superadmin' } }); if (!superAdminWithId || superAdminWithId.id !== 1) { throw new Error('超级管理员ID设置失败'); } superAdminToken = authService.generateToken(superAdminWithId); // 创建普通管理员用户 (ID>1, tenantId=1) const regularUser = await TestDataFactory.createTestUser(dataSource, { username: 'regularuser', password: 'TestPassword123!', email: 'regular@example.com', tenantId: 1 }); regularUserToken = authService.generateToken(regularUser); // 创建普通租户用户 (tenantId>1) const tenantUser = await TestDataFactory.createTestUser(dataSource, { username: 'tenantuser', password: 'TestPassword123!', email: 'tenant@example.com', tenantId: 2 }); tenantUserToken = authService.generateToken(tenantUser); }); describe('管理员广告API权限控制', () => { it('超级管理员(ID=1)应该能访问管理员广告列表API', async () => { const response = await adminClient.$get(undefined, { headers: { 'Authorization': `Bearer ${superAdminToken}`, 'X-Tenant-ID': '1', 'X-User-ID': '1' } }); // 超级管理员应该能够访问 (200) 或至少通过认证检查 (不是401/403) expect([200, 404]).toContain(response.status); if (response.status === 200) { const data = await response.json(); // API返回格式: { code, message, data: { list, total } } expect(data).toHaveProperty('data'); expect(data.data).toHaveProperty('list'); expect(Array.isArray(data.data.list)).toBeTruthy(); } }); it('普通管理员(ID>1)不应该能访问管理员广告列表API', async () => { const response = await adminClient.$get(undefined, { headers: { 'Authorization': `Bearer ${regularUserToken}`, 'X-Tenant-ID': '1', 'X-User-ID': '2' } }); // 普通管理员应该被拒绝访问 expect([401, 403]).toContain(response.status); }); it('普通租户用户(tenantId>1)不应该能访问管理员广告列表API', async () => { const response = await adminClient.$get(undefined, { headers: { 'Authorization': `Bearer ${tenantUserToken}`, 'X-Tenant-ID': '2', 'X-User-ID': '3' } }); // 普通租户用户应该被拒绝访问 expect([401, 403]).toContain(response.status); }); it('未认证用户不应该能访问管理员广告列表API', async () => { const response = await adminClient.$get(); // 未认证用户应该被拒绝 expect(response.status).toBe(401); }); }); describe('管理员广告类型API权限控制', () => { it('超级管理员(ID=1)应该能访问管理员广告类型列表API', async () => { const response = await adminTypeClient.$get(undefined, { headers: { 'Authorization': `Bearer ${superAdminToken}`, 'X-Tenant-ID': '1', 'X-User-ID': '1' } }); expect([200, 404]).toContain(response.status); if (response.status === 200) { const data = await response.json(); // API返回格式: { code, message, data: { list, total } } expect(data).toHaveProperty('data'); expect(data.data).toHaveProperty('list'); expect(Array.isArray(data.data.list)).toBeTruthy(); } }); it('普通管理员(ID>1)不应该能访问管理员广告类型列表API', async () => { const response = await adminTypeClient.$get(undefined, { headers: { 'Authorization': `Bearer ${regularUserToken}`, 'X-Tenant-ID': '1', 'X-User-ID': '2' } }); expect([401, 403]).toContain(response.status); }); it('未认证用户不应该能访问管理员广告类型列表API', async () => { const response = await adminTypeClient.$get(); expect(response.status).toBe(401); }); }); describe('用户端广告API访问控制', () => { it('认证用户应该能访问用户端广告列表API', async () => { const response = await userClient.$get(undefined, { headers: { 'Authorization': `Bearer ${tenantUserToken}`, 'X-Tenant-ID': '2', 'X-User-ID': '3' } }); // 用户端API应该返回数据 (200) 或空列表 expect([200, 404]).toContain(response.status); if (response.status === 200) { const data = await response.json(); expect(data).toHaveProperty("data"); expect(data.data).toHaveProperty("list"); expect(Array.isArray(data.data.list)).toBeTruthy(); } }); it('未认证用户应该能访问用户端广告列表API(公开访问)', async () => { const response = await userClient.$get(); // 用户端广告API可能是公开的,允许未认证访问 expect([200, 401, 404]).toContain(response.status); }); it('认证用户应该能访问用户端广告类型列表API', async () => { const response = await userTypeClient.$get(undefined, { headers: { 'Authorization': `Bearer ${tenantUserToken}`, 'X-Tenant-ID': '2', 'X-User-ID': '3' } }); expect([200, 404]).toContain(response.status); if (response.status === 200) { const data = await response.json(); // API返回格式: { code, message, data: { list, total } } expect(data).toHaveProperty('data'); expect(data.data).toHaveProperty('list'); expect(Array.isArray(data.data.list)).toBeTruthy(); } }); }); describe('统一广告数据隔离验证', () => { it('用户端API应该返回统一的广告数据(无tenant_id过滤)', async () => { const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 先创建广告类型 const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType); const adType = await adTypeRepository.save({ name: 'Test Type', code: 'test_type_verify', sort: 0, status: 1 }); // 创建测试广告数据 const adRepository = dataSource.getRepository(UnifiedAdvertisement); await adRepository.save({ title: 'Test Ad', typeId: adType.id, code: 'test_ad_verify', url: 'http://example.com', status: 1 }); const response = await userClient.$get(undefined, { headers: { 'Authorization': `Bearer ${tenantUserToken}`, 'X-Tenant-ID': '2', 'X-User-ID': '3' } }); if (response.status === 200) { const data = await response.json(); // API返回格式: { code, message, data: { list, total } } expect(data).toHaveProperty('data'); expect(data.data).toHaveProperty('list'); expect(Array.isArray(data.data.list)).toBeTruthy(); // 验证返回的是统一广告数据,不是按租户隔离的 if (data.data.list.length > 0) { const ad = data.data.list[0]; expect(ad).not.toHaveProperty('tenantId'); // 统一广告不应该有tenantId字段 } } }); }); describe('API路径兼容性验证', () => { it('用户端广告API路径应该保持兼容', async () => { const response = await userClient.$get(); // API端点应该可访问 expect([200, 401, 404]).toContain(response.status); }); it('用户端广告类型API路径应该保持兼容', async () => { const response = await userTypeClient.$get(); // API端点应该可访问 expect([200, 401, 404]).toContain(response.status); }); }); describe('管理员操作权限验证', () => { it('超级管理员应该能创建统一广告', async () => { const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 先创建广告类型 const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType); const adType = await adTypeRepository.save({ name: 'Test Type', code: 'test_type', sort: 0, status: 1 }); const newAd = { title: 'New Unified Ad', typeId: adType.id, // 使用创建的广告类型ID code: 'test_ad', // 必填 url: 'http://example.com/new', status: 1 }; const response = await adminClient.$post({ json: newAd }, { headers: { 'Authorization': `Bearer ${superAdminToken}`, 'X-Tenant-ID': '1', 'X-User-ID': '1' } }); // 超级管理员应该能创建 expect([200, 201]).toContain(response.status); }); it('普通管理员不应该能创建统一广告', async () => { const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 先创建广告类型 const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType); const adType = await adTypeRepository.save({ name: 'Test Type 2', code: 'test_type_2', sort: 0, status: 1 }); const newAd = { title: 'New Unified Ad', typeId: adType.id, code: 'test_ad_2', url: 'http://example.com/new', status: 1 }; const response = await adminClient.$post({ json: newAd }, { headers: { 'Authorization': `Bearer ${regularUserToken}`, 'X-Tenant-ID': '1', 'X-User-ID': '2' } }); // 普通管理员应该被拒绝 expect([401, 403]).toContain(response.status); }); it('超级管理员应该能更新统一广告', async () => { const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 先创建广告类型 const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType); const adType = await adTypeRepository.save({ name: 'Test Type Update', code: 'test_type_update', sort: 0, status: 1 }); // 创建测试广告 const adRepository = dataSource.getRepository(UnifiedAdvertisement); const testAd = await adRepository.save({ title: 'Test Ad', typeId: adType.id, code: 'test_ad_update', url: 'http://example.com', status: 1 }); const updateData = { title: 'Updated Test Ad' }; const response = await adminClient[':id'].$put({ param: { id: testAd.id }, json: updateData }, { headers: { 'Authorization': `Bearer ${superAdminToken}`, 'X-Tenant-ID': '1', 'X-User-ID': '1' } }); // 超级管理员应该能更新 expect([200, 404]).toContain(response.status); }); it('超级管理员应该能删除统一广告', async () => { const dataSource = await IntegrationTestDatabase.getDataSource(); if (!dataSource) throw new Error('Database not initialized'); // 先创建广告类型 const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType); const adType = await adTypeRepository.save({ name: 'Test Type Delete', code: 'test_type_delete', sort: 0, status: 1 }); // 创建测试广告 const adRepository = dataSource.getRepository(UnifiedAdvertisement); const testAd = await adRepository.save({ title: 'Test Ad', typeId: adType.id, code: 'test_ad_delete', url: 'http://example.com', status: 1 }); const response = await adminClient[':id'].$delete({ param: { id: testAd.id } }, { headers: { 'Authorization': `Bearer ${superAdminToken}`, 'X-Tenant-ID': '1', 'X-User-ID': '1' } }); // 超级管理员应该能删除 expect([200, 204, 404]).toContain(response.status); }); }); });