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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  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. // API返回格式: { code, message, data: { list, total } }
  84. expect(data).toHaveProperty('data');
  85. expect(data.data).toHaveProperty('list');
  86. expect(Array.isArray(data.data.list)).toBeTruthy();
  87. }
  88. });
  89. it('普通管理员(ID>1)不应该能访问管理员广告列表API', async () => {
  90. const response = await adminClient.$get(undefined, {
  91. headers: {
  92. 'Authorization': `Bearer ${regularUserToken}`,
  93. 'X-Tenant-ID': '1',
  94. 'X-User-ID': '2'
  95. }
  96. });
  97. // 普通管理员应该被拒绝访问
  98. expect([401, 403]).toContain(response.status);
  99. });
  100. it('普通租户用户(tenantId>1)不应该能访问管理员广告列表API', async () => {
  101. const response = await adminClient.$get(undefined, {
  102. headers: {
  103. 'Authorization': `Bearer ${tenantUserToken}`,
  104. 'X-Tenant-ID': '2',
  105. 'X-User-ID': '3'
  106. }
  107. });
  108. // 普通租户用户应该被拒绝访问
  109. expect([401, 403]).toContain(response.status);
  110. });
  111. it('未认证用户不应该能访问管理员广告列表API', async () => {
  112. const response = await adminClient.$get();
  113. // 未认证用户应该被拒绝
  114. expect(response.status).toBe(401);
  115. });
  116. });
  117. describe('管理员广告类型API权限控制', () => {
  118. it('超级管理员(ID=1)应该能访问管理员广告类型列表API', async () => {
  119. const response = await adminTypeClient.$get(undefined, {
  120. headers: {
  121. 'Authorization': `Bearer ${superAdminToken}`,
  122. 'X-Tenant-ID': '1',
  123. 'X-User-ID': '1'
  124. }
  125. });
  126. expect([200, 404]).toContain(response.status);
  127. if (response.status === 200) {
  128. const data = await response.json();
  129. // API返回格式: { code, message, data: { list, total } }
  130. expect(data).toHaveProperty('data');
  131. expect(data.data).toHaveProperty('list');
  132. expect(Array.isArray(data.data.list)).toBeTruthy();
  133. }
  134. });
  135. it('普通管理员(ID>1)不应该能访问管理员广告类型列表API', async () => {
  136. const response = await adminTypeClient.$get(undefined, {
  137. headers: {
  138. 'Authorization': `Bearer ${regularUserToken}`,
  139. 'X-Tenant-ID': '1',
  140. 'X-User-ID': '2'
  141. }
  142. });
  143. expect([401, 403]).toContain(response.status);
  144. });
  145. it('未认证用户不应该能访问管理员广告类型列表API', async () => {
  146. const response = await adminTypeClient.$get();
  147. expect(response.status).toBe(401);
  148. });
  149. });
  150. describe('用户端广告API访问控制', () => {
  151. it('认证用户应该能访问用户端广告列表API', async () => {
  152. const response = await userClient.$get(undefined, {
  153. headers: {
  154. 'Authorization': `Bearer ${tenantUserToken}`,
  155. 'X-Tenant-ID': '2',
  156. 'X-User-ID': '3'
  157. }
  158. });
  159. // 用户端API应该返回数据 (200) 或空列表
  160. expect([200, 404]).toContain(response.status);
  161. if (response.status === 200) {
  162. const data = await response.json();
  163. expect(data).toHaveProperty("data");
  164. expect(data.data).toHaveProperty("list");
  165. expect(Array.isArray(data.data.list)).toBeTruthy();
  166. }
  167. });
  168. it('未认证用户应该能访问用户端广告列表API(公开访问)', async () => {
  169. const response = await userClient.$get();
  170. // 用户端广告API可能是公开的,允许未认证访问
  171. expect([200, 401, 404]).toContain(response.status);
  172. });
  173. it('认证用户应该能访问用户端广告类型列表API', async () => {
  174. const response = await userTypeClient.$get(undefined, {
  175. headers: {
  176. 'Authorization': `Bearer ${tenantUserToken}`,
  177. 'X-Tenant-ID': '2',
  178. 'X-User-ID': '3'
  179. }
  180. });
  181. expect([200, 404]).toContain(response.status);
  182. if (response.status === 200) {
  183. const data = await response.json();
  184. // API返回格式: { code, message, data: { list, total } }
  185. expect(data).toHaveProperty('data');
  186. expect(data.data).toHaveProperty('list');
  187. expect(Array.isArray(data.data.list)).toBeTruthy();
  188. }
  189. });
  190. });
  191. describe('统一广告数据隔离验证', () => {
  192. it('用户端API应该返回统一的广告数据(无tenant_id过滤)', async () => {
  193. const dataSource = await IntegrationTestDatabase.getDataSource();
  194. if (!dataSource) throw new Error('Database not initialized');
  195. // 先创建广告类型
  196. const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType);
  197. const adType = await adTypeRepository.save({
  198. name: 'Test Type',
  199. code: 'test_type_verify',
  200. sort: 0,
  201. status: 1
  202. });
  203. // 创建测试广告数据
  204. const adRepository = dataSource.getRepository(UnifiedAdvertisement);
  205. await adRepository.save({
  206. title: 'Test Ad',
  207. typeId: adType.id,
  208. code: 'test_ad_verify',
  209. url: 'http://example.com',
  210. status: 1
  211. });
  212. const response = await userClient.$get(undefined, {
  213. headers: {
  214. 'Authorization': `Bearer ${tenantUserToken}`,
  215. 'X-Tenant-ID': '2',
  216. 'X-User-ID': '3'
  217. }
  218. });
  219. if (response.status === 200) {
  220. const data = await response.json();
  221. // API返回格式: { code, message, data: { list, total } }
  222. expect(data).toHaveProperty('data');
  223. expect(data.data).toHaveProperty('list');
  224. expect(Array.isArray(data.data.list)).toBeTruthy();
  225. // 验证返回的是统一广告数据,不是按租户隔离的
  226. if (data.data.list.length > 0) {
  227. const ad = data.data.list[0];
  228. expect(ad).not.toHaveProperty('tenantId'); // 统一广告不应该有tenantId字段
  229. }
  230. }
  231. });
  232. });
  233. describe('API路径兼容性验证', () => {
  234. it('用户端广告API路径应该保持兼容', async () => {
  235. const response = await userClient.$get();
  236. // API端点应该可访问
  237. expect([200, 401, 404]).toContain(response.status);
  238. });
  239. it('用户端广告类型API路径应该保持兼容', async () => {
  240. const response = await userTypeClient.$get();
  241. // API端点应该可访问
  242. expect([200, 401, 404]).toContain(response.status);
  243. });
  244. });
  245. describe('管理员操作权限验证', () => {
  246. it('超级管理员应该能创建统一广告', async () => {
  247. const dataSource = await IntegrationTestDatabase.getDataSource();
  248. if (!dataSource) throw new Error('Database not initialized');
  249. // 先创建广告类型
  250. const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType);
  251. const adType = await adTypeRepository.save({
  252. name: 'Test Type',
  253. code: 'test_type',
  254. sort: 0,
  255. status: 1
  256. });
  257. const newAd = {
  258. title: 'New Unified Ad',
  259. typeId: adType.id, // 使用创建的广告类型ID
  260. code: 'test_ad', // 必填
  261. url: 'http://example.com/new',
  262. status: 1
  263. };
  264. const response = await adminClient.$post({
  265. json: newAd
  266. }, {
  267. headers: {
  268. 'Authorization': `Bearer ${superAdminToken}`,
  269. 'X-Tenant-ID': '1',
  270. 'X-User-ID': '1'
  271. }
  272. });
  273. // 超级管理员应该能创建
  274. expect([200, 201]).toContain(response.status);
  275. });
  276. it('普通管理员不应该能创建统一广告', async () => {
  277. const dataSource = await IntegrationTestDatabase.getDataSource();
  278. if (!dataSource) throw new Error('Database not initialized');
  279. // 先创建广告类型
  280. const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType);
  281. const adType = await adTypeRepository.save({
  282. name: 'Test Type 2',
  283. code: 'test_type_2',
  284. sort: 0,
  285. status: 1
  286. });
  287. const newAd = {
  288. title: 'New Unified Ad',
  289. typeId: adType.id,
  290. code: 'test_ad_2',
  291. url: 'http://example.com/new',
  292. status: 1
  293. };
  294. const response = await adminClient.$post({
  295. json: newAd
  296. }, {
  297. headers: {
  298. 'Authorization': `Bearer ${regularUserToken}`,
  299. 'X-Tenant-ID': '1',
  300. 'X-User-ID': '2'
  301. }
  302. });
  303. // 普通管理员应该被拒绝
  304. expect([401, 403]).toContain(response.status);
  305. });
  306. it('超级管理员应该能更新统一广告', async () => {
  307. const dataSource = await IntegrationTestDatabase.getDataSource();
  308. if (!dataSource) throw new Error('Database not initialized');
  309. // 先创建广告类型
  310. const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType);
  311. const adType = await adTypeRepository.save({
  312. name: 'Test Type Update',
  313. code: 'test_type_update',
  314. sort: 0,
  315. status: 1
  316. });
  317. // 创建测试广告
  318. const adRepository = dataSource.getRepository(UnifiedAdvertisement);
  319. const testAd = await adRepository.save({
  320. title: 'Test Ad',
  321. typeId: adType.id,
  322. code: 'test_ad_update',
  323. url: 'http://example.com',
  324. status: 1
  325. });
  326. const updateData = {
  327. title: 'Updated Test Ad'
  328. };
  329. const response = await adminClient[':id'].$put({
  330. param: { id: testAd.id },
  331. json: updateData
  332. }, {
  333. headers: {
  334. 'Authorization': `Bearer ${superAdminToken}`,
  335. 'X-Tenant-ID': '1',
  336. 'X-User-ID': '1'
  337. }
  338. });
  339. // 超级管理员应该能更新
  340. expect([200, 404]).toContain(response.status);
  341. });
  342. it('超级管理员应该能删除统一广告', async () => {
  343. const dataSource = await IntegrationTestDatabase.getDataSource();
  344. if (!dataSource) throw new Error('Database not initialized');
  345. // 先创建广告类型
  346. const adTypeRepository = dataSource.getRepository(UnifiedAdvertisementType);
  347. const adType = await adTypeRepository.save({
  348. name: 'Test Type Delete',
  349. code: 'test_type_delete',
  350. sort: 0,
  351. status: 1
  352. });
  353. // 创建测试广告
  354. const adRepository = dataSource.getRepository(UnifiedAdvertisement);
  355. const testAd = await adRepository.save({
  356. title: 'Test Ad',
  357. typeId: adType.id,
  358. code: 'test_ad_delete',
  359. url: 'http://example.com',
  360. status: 1
  361. });
  362. const response = await adminClient[':id'].$delete({
  363. param: { id: testAd.id }
  364. }, {
  365. headers: {
  366. 'Authorization': `Bearer ${superAdminToken}`,
  367. 'X-Tenant-ID': '1',
  368. 'X-User-ID': '1'
  369. }
  370. });
  371. // 超级管理员应该能删除
  372. expect([200, 204, 404]).toContain(response.status);
  373. });
  374. });
  375. });