unified-advertisement-auth.integration.test.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import { describe, it, expect, beforeEach } from 'vitest';
  2. import { testClient } from 'hono/testing';
  3. import {
  4. IntegrationTestDatabase,
  5. setupIntegrationDatabaseHooks,
  6. TestDataFactory
  7. } from '../utils/integration-test-db';
  8. import { UserEntityMt, UserServiceMt } from '@d8d/user-module-mt';
  9. // 导入server包的api以确保数据源初始化,同时获取统一广告路由的类型
  10. import { adminUnifiedAdvertisementApiRoutes, adminUnifiedAdvertisementTypeApiRoutes, advertisementApiRoutes, advertisementTypeApiRoutes } from '../../src/api';
  11. import { AuthService } from '@d8d/auth-module-mt';
  12. import { UnifiedAdvertisement, UnifiedAdvertisementType } from '@d8d/unified-advertisements-module';
  13. // 设置集成测试钩子
  14. setupIntegrationDatabaseHooks()
  15. describe('统一广告管理员权限集成测试', () => {
  16. let adminClient: ReturnType<typeof testClient<typeof adminUnifiedAdvertisementApiRoutes>>['api']['v1']['admin']['unified-advertisements'];
  17. let adminTypeClient: ReturnType<typeof testClient<typeof adminUnifiedAdvertisementTypeApiRoutes>>['api']['v1']['admin']['unified-advertisement-types'];
  18. let userClient: ReturnType<typeof testClient<typeof advertisementApiRoutes>>['api']['v1']['advertisements'];
  19. let userTypeClient: ReturnType<typeof testClient<typeof advertisementTypeApiRoutes>>['api']['v1']['advertisement-types'];
  20. let authService: AuthService;
  21. let userService: UserServiceMt;
  22. let superAdminToken: string;
  23. let regularUserToken: string;
  24. let tenantUserToken: string;
  25. beforeEach(async () => {
  26. // 创建测试客户端 - 使用server包注册后的路由
  27. adminClient = testClient(adminUnifiedAdvertisementApiRoutes).api.v1.admin['unified-advertisements'];
  28. adminTypeClient = testClient(adminUnifiedAdvertisementTypeApiRoutes).api.v1.admin['unified-advertisement-types'];
  29. userClient = testClient(advertisementApiRoutes).api.v1.advertisements;
  30. userTypeClient = testClient(advertisementTypeApiRoutes).api.v1['advertisement-types'];
  31. // 获取数据源
  32. const dataSource = await IntegrationTestDatabase.getDataSource();
  33. if (!dataSource) throw new Error('Database not initialized');
  34. // 初始化服务
  35. userService = new UserServiceMt(dataSource);
  36. authService = new AuthService(userService);
  37. // 清理测试用户
  38. const userRepository = dataSource.getRepository(UserEntityMt);
  39. await userRepository.delete({ username: 'superadmin' });
  40. await userRepository.delete({ username: 'regularuser' });
  41. await userRepository.delete({ username: 'tenantuser' });
  42. // 创建超级管理员 (ID=1, tenantId=1)
  43. const superAdmin = await TestDataFactory.createTestUser(dataSource, {
  44. username: 'superadmin',
  45. password: 'TestPassword123!',
  46. email: 'superadmin@example.com',
  47. tenantId: 1
  48. });
  49. // 手动设置ID为1以确保是超级管理员
  50. superAdmin.id = 1;
  51. await userRepository.save(superAdmin);
  52. superAdminToken = authService.generateToken(superAdmin);
  53. // 创建普通管理员用户 (ID>1, tenantId=1)
  54. const regularUser = await TestDataFactory.createTestUser(dataSource, {
  55. username: 'regularuser',
  56. password: 'TestPassword123!',
  57. email: 'regular@example.com',
  58. tenantId: 1
  59. });
  60. regularUserToken = authService.generateToken(regularUser);
  61. // 创建普通租户用户 (tenantId>1)
  62. const tenantUser = await TestDataFactory.createTestUser(dataSource, {
  63. username: 'tenantuser',
  64. password: 'TestPassword123!',
  65. email: 'tenant@example.com',
  66. tenantId: 2
  67. });
  68. tenantUserToken = authService.generateToken(tenantUser);
  69. });
  70. describe('管理员广告API权限控制', () => {
  71. it('超级管理员(ID=1)应该能访问管理员广告列表API', async () => {
  72. const response = await adminClient.$get(undefined, {
  73. headers: {
  74. 'Authorization': `Bearer ${superAdminToken}`,
  75. 'X-Tenant-ID': '1',
  76. 'X-User-ID': '1'
  77. }
  78. });
  79. // 超级管理员应该能够访问 (200) 或至少通过认证检查 (不是401/403)
  80. expect([200, 404]).toContain(response.status);
  81. if (response.status === 200) {
  82. const data = await response.json();
  83. expect(Array.isArray(data)).toBeTruthy();
  84. }
  85. });
  86. it('普通管理员(ID>1)不应该能访问管理员广告列表API', async () => {
  87. const response = await adminClient.$get(undefined, {
  88. headers: {
  89. 'Authorization': `Bearer ${regularUserToken}`,
  90. 'X-Tenant-ID': '1',
  91. 'X-User-ID': '2'
  92. }
  93. });
  94. // 普通管理员应该被拒绝访问
  95. expect([401, 403]).toContain(response.status);
  96. });
  97. it('普通租户用户(tenantId>1)不应该能访问管理员广告列表API', async () => {
  98. const response = await adminClient.$get(undefined, {
  99. headers: {
  100. 'Authorization': `Bearer ${tenantUserToken}`,
  101. 'X-Tenant-ID': '2',
  102. 'X-User-ID': '3'
  103. }
  104. });
  105. // 普通租户用户应该被拒绝访问
  106. expect([401, 403]).toContain(response.status);
  107. });
  108. it('未认证用户不应该能访问管理员广告列表API', async () => {
  109. const response = await adminClient.$get();
  110. // 未认证用户应该被拒绝
  111. expect(response.status).toBe(401);
  112. });
  113. });
  114. describe('管理员广告类型API权限控制', () => {
  115. it('超级管理员(ID=1)应该能访问管理员广告类型列表API', async () => {
  116. const response = await adminTypeClient.$get(undefined, {
  117. headers: {
  118. 'Authorization': `Bearer ${superAdminToken}`,
  119. 'X-Tenant-ID': '1',
  120. 'X-User-ID': '1'
  121. }
  122. });
  123. expect([200, 404]).toContain(response.status);
  124. if (response.status === 200) {
  125. const data = await response.json();
  126. expect(Array.isArray(data)).toBeTruthy();
  127. }
  128. });
  129. it('普通管理员(ID>1)不应该能访问管理员广告类型列表API', async () => {
  130. const response = await adminTypeClient.$get(undefined, {
  131. headers: {
  132. 'Authorization': `Bearer ${regularUserToken}`,
  133. 'X-Tenant-ID': '1',
  134. 'X-User-ID': '2'
  135. }
  136. });
  137. expect([401, 403]).toContain(response.status);
  138. });
  139. it('未认证用户不应该能访问管理员广告类型列表API', async () => {
  140. const response = await adminTypeClient.$get();
  141. expect(response.status).toBe(401);
  142. });
  143. });
  144. describe('用户端广告API访问控制', () => {
  145. it('认证用户应该能访问用户端广告列表API', async () => {
  146. const response = await userClient.$get(undefined, {
  147. headers: {
  148. 'Authorization': `Bearer ${tenantUserToken}`,
  149. 'X-Tenant-ID': '2',
  150. 'X-User-ID': '3'
  151. }
  152. });
  153. // 用户端API应该返回数据 (200) 或空列表
  154. expect([200, 404]).toContain(response.status);
  155. if (response.status === 200) {
  156. const data = await response.json();
  157. expect(Array.isArray(data)).toBeTruthy();
  158. }
  159. });
  160. it('未认证用户应该能访问用户端广告列表API(公开访问)', async () => {
  161. const response = await userClient.$get();
  162. // 用户端广告API可能是公开的,允许未认证访问
  163. expect([200, 401, 404]).toContain(response.status);
  164. });
  165. it('认证用户应该能访问用户端广告类型列表API', async () => {
  166. const response = await userTypeClient.$get(undefined, {
  167. headers: {
  168. 'Authorization': `Bearer ${tenantUserToken}`,
  169. 'X-Tenant-ID': '2',
  170. 'X-User-ID': '3'
  171. }
  172. });
  173. expect([200, 404]).toContain(response.status);
  174. if (response.status === 200) {
  175. const data = await response.json();
  176. expect(Array.isArray(data)).toBeTruthy();
  177. }
  178. });
  179. });
  180. describe('统一广告数据隔离验证', () => {
  181. it('用户端API应该返回统一的广告数据(无tenant_id过滤)', async () => {
  182. const dataSource = await IntegrationTestDatabase.getDataSource();
  183. if (!dataSource) throw new Error('Database not initialized');
  184. // 创建测试广告数据
  185. const adRepository = dataSource.getRepository(UnifiedAdvertisement);
  186. await adRepository.save({
  187. title: 'Test Ad',
  188. imageUrl: 'http://example.com/ad.jpg',
  189. linkUrl: 'http://example.com',
  190. position: 'home',
  191. status: 1
  192. });
  193. const response = await userClient.$get(undefined, {
  194. headers: {
  195. 'Authorization': `Bearer ${tenantUserToken}`,
  196. 'X-Tenant-ID': '2',
  197. 'X-User-ID': '3'
  198. }
  199. });
  200. if (response.status === 200) {
  201. const data = await response.json();
  202. expect(Array.isArray(data)).toBeTruthy();
  203. // 验证返回的是统一广告数据,不是按租户隔离的
  204. if (data.length > 0) {
  205. const ad = data[0];
  206. expect(ad).not.toHaveProperty('tenantId'); // 统一广告不应该有tenantId字段
  207. }
  208. }
  209. });
  210. });
  211. describe('API路径兼容性验证', () => {
  212. it('用户端广告API路径应该保持兼容', async () => {
  213. const response = await userClient.$get();
  214. // API端点应该可访问
  215. expect([200, 401, 404]).toContain(response.status);
  216. });
  217. it('用户端广告类型API路径应该保持兼容', async () => {
  218. const response = await userTypeClient.$get();
  219. // API端点应该可访问
  220. expect([200, 401, 404]).toContain(response.status);
  221. });
  222. });
  223. describe('管理员操作权限验证', () => {
  224. it('超级管理员应该能创建统一广告', async () => {
  225. const newAd = {
  226. title: 'New Unified Ad',
  227. imageUrl: 'http://example.com/new-ad.jpg',
  228. linkUrl: 'http://example.com/new',
  229. position: 'home',
  230. status: 1
  231. };
  232. const response = await adminClient.$post({
  233. json: newAd
  234. }, {
  235. headers: {
  236. 'Authorization': `Bearer ${superAdminToken}`,
  237. 'X-Tenant-ID': '1',
  238. 'X-User-ID': '1'
  239. }
  240. });
  241. // 超级管理员应该能创建
  242. expect([200, 201]).toContain(response.status);
  243. });
  244. it('普通管理员不应该能创建统一广告', async () => {
  245. const newAd = {
  246. title: 'New Unified Ad',
  247. imageUrl: 'http://example.com/new-ad.jpg',
  248. linkUrl: 'http://example.com/new',
  249. position: 'home',
  250. status: 1
  251. };
  252. const response = await adminClient.$post({
  253. json: newAd
  254. }, {
  255. headers: {
  256. 'Authorization': `Bearer ${regularUserToken}`,
  257. 'X-Tenant-ID': '1',
  258. 'X-User-ID': '2'
  259. }
  260. });
  261. // 普通管理员应该被拒绝
  262. expect([401, 403]).toContain(response.status);
  263. });
  264. it('超级管理员应该能更新统一广告', async () => {
  265. const dataSource = await IntegrationTestDatabase.getDataSource();
  266. if (!dataSource) throw new Error('Database not initialized');
  267. // 创建测试广告
  268. const adRepository = dataSource.getRepository(UnifiedAdvertisement);
  269. const testAd = await adRepository.save({
  270. title: 'Test Ad',
  271. imageUrl: 'http://example.com/ad.jpg',
  272. linkUrl: 'http://example.com',
  273. position: 'home',
  274. status: 1
  275. });
  276. const updateData = {
  277. title: 'Updated Test Ad'
  278. };
  279. const response = await adminClient[':id'].$put({
  280. param: { id: testAd.id },
  281. json: updateData
  282. }, {
  283. headers: {
  284. 'Authorization': `Bearer ${superAdminToken}`,
  285. 'X-Tenant-ID': '1',
  286. 'X-User-ID': '1'
  287. }
  288. });
  289. // 超级管理员应该能更新
  290. expect([200, 404]).toContain(response.status);
  291. });
  292. it('超级管理员应该能删除统一广告', async () => {
  293. const dataSource = await IntegrationTestDatabase.getDataSource();
  294. if (!dataSource) throw new Error('Database not initialized');
  295. // 创建测试广告
  296. const adRepository = dataSource.getRepository(UnifiedAdvertisement);
  297. const testAd = await adRepository.save({
  298. title: 'Test Ad',
  299. imageUrl: 'http://example.com/ad.jpg',
  300. linkUrl: 'http://example.com',
  301. position: 'home',
  302. status: 1
  303. });
  304. const response = await adminClient[':id'].$delete({
  305. param: { id: testAd.id }
  306. }, {
  307. headers: {
  308. 'Authorization': `Bearer ${superAdminToken}`,
  309. 'X-Tenant-ID': '1',
  310. 'X-User-ID': '1'
  311. }
  312. });
  313. // 超级管理员应该能删除
  314. expect([200, 204, 404]).toContain(response.status);
  315. });
  316. });
  317. });