|
|
@@ -0,0 +1,364 @@
|
|
|
+import { describe, it, expect, beforeEach } from 'vitest';
|
|
|
+import { testClient } from 'hono/testing';
|
|
|
+import {
|
|
|
+ IntegrationTestDatabase,
|
|
|
+ setupIntegrationDatabaseHooks,
|
|
|
+ TestDataFactory
|
|
|
+} from '../utils/integration-test-db';
|
|
|
+import { UserEntityMt, UserServiceMt } from '@d8d/user-module-mt';
|
|
|
+import { unifiedAdvertisementAdminRoutes, unifiedAdvertisementTypeAdminRoutes, unifiedAdvertisementRoutes, unifiedAdvertisementTypeRoutes } from '@d8d/unified-advertisements-module';
|
|
|
+import { AuthService } from '@d8d/auth-module-mt';
|
|
|
+import { UnifiedAdvertisement, UnifiedAdvertisementType } from '@d8d/unified-advertisements-module';
|
|
|
+
|
|
|
+// 设置集成测试钩子
|
|
|
+setupIntegrationDatabaseHooks()
|
|
|
+
|
|
|
+describe('统一广告管理员权限集成测试', () => {
|
|
|
+ let adminClient: ReturnType<typeof testClient<typeof unifiedAdvertisementAdminRoutes>>;
|
|
|
+ let adminTypeClient: ReturnType<typeof testClient<typeof unifiedAdvertisementTypeAdminRoutes>>;
|
|
|
+ let userClient: ReturnType<typeof testClient<typeof unifiedAdvertisementRoutes>>;
|
|
|
+ let userTypeClient: ReturnType<typeof testClient<typeof unifiedAdvertisementTypeRoutes>>;
|
|
|
+ let authService: AuthService;
|
|
|
+ let userService: UserServiceMt;
|
|
|
+ let superAdminToken: string;
|
|
|
+ let regularUserToken: string;
|
|
|
+ let tenantUserToken: string;
|
|
|
+
|
|
|
+ beforeEach(async () => {
|
|
|
+ // 创建测试客户端
|
|
|
+ adminClient = testClient(unifiedAdvertisementAdminRoutes);
|
|
|
+ adminTypeClient = testClient(unifiedAdvertisementTypeAdminRoutes);
|
|
|
+ userClient = testClient(unifiedAdvertisementRoutes);
|
|
|
+ userTypeClient = testClient(unifiedAdvertisementTypeRoutes);
|
|
|
+
|
|
|
+ // 获取数据源
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ if (!dataSource) throw new Error('Database not initialized');
|
|
|
+
|
|
|
+ // 初始化服务
|
|
|
+ userService = new UserServiceMt(dataSource);
|
|
|
+ authService = new AuthService(userService);
|
|
|
+
|
|
|
+ // 清理测试用户
|
|
|
+ const userRepository = dataSource.getRepository(UserEntityMt);
|
|
|
+ await userRepository.delete({ username: 'superadmin' });
|
|
|
+ await userRepository.delete({ username: 'regularuser' });
|
|
|
+ await userRepository.delete({ username: 'tenantuser' });
|
|
|
+
|
|
|
+ // 创建超级管理员 (ID=1, tenantId=1)
|
|
|
+ const superAdmin = await TestDataFactory.createTestUser(dataSource, {
|
|
|
+ username: 'superadmin',
|
|
|
+ password: 'TestPassword123!',
|
|
|
+ email: 'superadmin@example.com',
|
|
|
+ tenantId: 1
|
|
|
+ });
|
|
|
+ // 手动设置ID为1以确保是超级管理员
|
|
|
+ superAdmin.id = 1;
|
|
|
+ await userRepository.save(superAdmin);
|
|
|
+ superAdminToken = authService.generateToken(superAdmin);
|
|
|
+
|
|
|
+ // 创建普通管理员用户 (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({
|
|
|
+ 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();
|
|
|
+ expect(Array.isArray(data)).toBeTruthy();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('普通管理员(ID>1)不应该能访问管理员广告列表API', async () => {
|
|
|
+ const response = await adminClient.$get({
|
|
|
+ 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({
|
|
|
+ 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({
|
|
|
+ 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();
|
|
|
+ expect(Array.isArray(data)).toBeTruthy();
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ it('普通管理员(ID>1)不应该能访问管理员广告类型列表API', async () => {
|
|
|
+ const response = await adminTypeClient.$get({
|
|
|
+ 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({
|
|
|
+ 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(Array.isArray(data)).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({
|
|
|
+ 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();
|
|
|
+ expect(Array.isArray(data)).toBeTruthy();
|
|
|
+ }
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('统一广告数据隔离验证', () => {
|
|
|
+ it('用户端API应该返回统一的广告数据(无tenant_id过滤)', async () => {
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ if (!dataSource) throw new Error('Database not initialized');
|
|
|
+
|
|
|
+ // 创建测试广告数据
|
|
|
+ const adRepository = dataSource.getRepository(UnifiedAdvertisement);
|
|
|
+ await adRepository.save({
|
|
|
+ title: 'Test Ad',
|
|
|
+ imageUrl: 'http://example.com/ad.jpg',
|
|
|
+ linkUrl: 'http://example.com',
|
|
|
+ position: 'home',
|
|
|
+ status: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ const response = await userClient.$get({
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${tenantUserToken}`,
|
|
|
+ 'X-Tenant-ID': '2',
|
|
|
+ 'X-User-ID': '3'
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.status === 200) {
|
|
|
+ const data = await response.json();
|
|
|
+ expect(Array.isArray(data)).toBeTruthy();
|
|
|
+ // 验证返回的是统一广告数据,不是按租户隔离的
|
|
|
+ if (data.length > 0) {
|
|
|
+ const ad = data[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 newAd = {
|
|
|
+ title: 'New Unified Ad',
|
|
|
+ imageUrl: 'http://example.com/new-ad.jpg',
|
|
|
+ linkUrl: 'http://example.com/new',
|
|
|
+ position: 'home',
|
|
|
+ status: 1
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await adminClient.$post({
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${superAdminToken}`,
|
|
|
+ 'X-Tenant-ID': '1',
|
|
|
+ 'X-User-ID': '1'
|
|
|
+ },
|
|
|
+ json: newAd
|
|
|
+ });
|
|
|
+
|
|
|
+ // 超级管理员应该能创建
|
|
|
+ expect([200, 201]).toContain(response.status);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('普通管理员不应该能创建统一广告', async () => {
|
|
|
+ const newAd = {
|
|
|
+ title: 'New Unified Ad',
|
|
|
+ imageUrl: 'http://example.com/new-ad.jpg',
|
|
|
+ linkUrl: 'http://example.com/new',
|
|
|
+ position: 'home',
|
|
|
+ status: 1
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await adminClient.$post({
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${regularUserToken}`,
|
|
|
+ 'X-Tenant-ID': '1',
|
|
|
+ 'X-User-ID': '2'
|
|
|
+ },
|
|
|
+ json: newAd
|
|
|
+ });
|
|
|
+
|
|
|
+ // 普通管理员应该被拒绝
|
|
|
+ expect([401, 403]).toContain(response.status);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('超级管理员应该能更新统一广告', async () => {
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ if (!dataSource) throw new Error('Database not initialized');
|
|
|
+
|
|
|
+ // 创建测试广告
|
|
|
+ const adRepository = dataSource.getRepository(UnifiedAdvertisement);
|
|
|
+ const testAd = await adRepository.save({
|
|
|
+ title: 'Test Ad',
|
|
|
+ imageUrl: 'http://example.com/ad.jpg',
|
|
|
+ linkUrl: 'http://example.com',
|
|
|
+ position: 'home',
|
|
|
+ status: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ const updateData = {
|
|
|
+ title: 'Updated Test Ad'
|
|
|
+ };
|
|
|
+
|
|
|
+ const response = await adminClient[':id'].$put({
|
|
|
+ param: { id: String(testAd.id) },
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${superAdminToken}`,
|
|
|
+ 'X-Tenant-ID': '1',
|
|
|
+ 'X-User-ID': '1'
|
|
|
+ },
|
|
|
+ json: updateData
|
|
|
+ });
|
|
|
+
|
|
|
+ // 超级管理员应该能更新
|
|
|
+ expect([200, 404]).toContain(response.status);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('超级管理员应该能删除统一广告', async () => {
|
|
|
+ const dataSource = await IntegrationTestDatabase.getDataSource();
|
|
|
+ if (!dataSource) throw new Error('Database not initialized');
|
|
|
+
|
|
|
+ // 创建测试广告
|
|
|
+ const adRepository = dataSource.getRepository(UnifiedAdvertisement);
|
|
|
+ const testAd = await adRepository.save({
|
|
|
+ title: 'Test Ad',
|
|
|
+ imageUrl: 'http://example.com/ad.jpg',
|
|
|
+ linkUrl: 'http://example.com',
|
|
|
+ position: 'home',
|
|
|
+ status: 1
|
|
|
+ });
|
|
|
+
|
|
|
+ const response = await adminClient[':id'].$delete({
|
|
|
+ param: { id: String(testAd.id) },
|
|
|
+ headers: {
|
|
|
+ 'Authorization': `Bearer ${superAdminToken}`,
|
|
|
+ 'X-Tenant-ID': '1',
|
|
|
+ 'X-User-ID': '1'
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 超级管理员应该能删除
|
|
|
+ expect([200, 204, 404]).toContain(response.status);
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|