| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468 |
- 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<typeof testClient<typeof adminUnifiedAdvertisementApiRoutes>>['api']['v1']['admin']['unified-advertisements'];
- let adminTypeClient: ReturnType<typeof testClient<typeof adminUnifiedAdvertisementTypeApiRoutes>>['api']['v1']['admin']['unified-advertisement-types'];
- let userClient: ReturnType<typeof testClient<typeof unifiedAdvertisementApiRoutes>>['api']['v1']['advertisements'];
- let userTypeClient: ReturnType<typeof testClient<typeof unifiedAdvertisementApiRoutes>>['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);
- });
- });
- });
|