user-goods-routes.integration.test.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. import { describe, it, expect, beforeEach } from 'vitest';
  2. import { testClient } from 'hono/testing';
  3. import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
  4. import { JWTUtil } from '@d8d/shared-utils';
  5. import { UserEntity, Role } from '@d8d/user-module';
  6. import { File } from '@d8d/file-module';
  7. import { Supplier } from '@d8d/supplier-module';
  8. import { Merchant } from '@d8d/merchant-module';
  9. import { userGoodsRoutes } from '../../src/routes';
  10. import { Goods, GoodsCategory } from '../../src/entities';
  11. // 设置集成测试钩子
  12. setupIntegrationDatabaseHooksWithEntities([
  13. UserEntity, Role, Goods, GoodsCategory, File, Supplier, Merchant
  14. ])
  15. describe('用户商品管理API集成测试', () => {
  16. let client: ReturnType<typeof testClient<typeof userGoodsRoutes>>;
  17. let userToken: string;
  18. let otherUserToken: string;
  19. let testUser: UserEntity;
  20. let otherUser: UserEntity;
  21. let testCategory: GoodsCategory;
  22. let testSupplier: Supplier;
  23. let testMerchant: Merchant;
  24. let testFile: File;
  25. beforeEach(async () => {
  26. // 创建测试客户端
  27. client = testClient(userGoodsRoutes);
  28. // 获取数据源
  29. const dataSource = await IntegrationTestDatabase.getDataSource();
  30. // 创建测试用户
  31. const userRepository = dataSource.getRepository(UserEntity);
  32. testUser = userRepository.create({
  33. username: `test_user_${Math.floor(Math.random() * 100000)}`,
  34. password: 'test_password',
  35. nickname: '测试用户',
  36. registrationSource: 'web'
  37. });
  38. await userRepository.save(testUser);
  39. // 创建其他用户
  40. otherUser = userRepository.create({
  41. username: `other_user_${Math.floor(Math.random() * 100000)}`,
  42. password: 'other_password',
  43. nickname: '其他用户',
  44. registrationSource: 'web'
  45. });
  46. await userRepository.save(otherUser);
  47. // 生成测试用户的token
  48. userToken = JWTUtil.generateToken({
  49. id: testUser.id,
  50. username: testUser.username,
  51. roles: [{name:'user'}]
  52. });
  53. // 生成其他用户的token
  54. otherUserToken = JWTUtil.generateToken({
  55. id: otherUser.id,
  56. username: otherUser.username,
  57. roles: [{name:'user'}]
  58. });
  59. // 创建测试商品分类
  60. const categoryRepository = dataSource.getRepository(GoodsCategory);
  61. testCategory = categoryRepository.create({
  62. name: '测试分类',
  63. parentId: 0,
  64. level: 1,
  65. state: 1,
  66. createdBy: testUser.id
  67. });
  68. await categoryRepository.save(testCategory);
  69. // 创建测试供应商
  70. const supplierRepository = dataSource.getRepository(Supplier);
  71. testSupplier = supplierRepository.create({
  72. name: '测试供应商',
  73. username: `test_supplier_${Math.floor(Math.random() * 100000)}`,
  74. password: 'password123',
  75. phone: '13800138000',
  76. realname: '测试供应商',
  77. state: 1,
  78. createdBy: testUser.id
  79. });
  80. await supplierRepository.save(testSupplier);
  81. // 创建测试商户
  82. const merchantRepository = dataSource.getRepository(Merchant);
  83. testMerchant = merchantRepository.create({
  84. name: '测试商户',
  85. username: `test_merchant_${Math.floor(Math.random() * 100000)}`,
  86. password: 'password123',
  87. phone: '13800138001',
  88. realname: '测试商户',
  89. state: 1,
  90. createdBy: testUser.id
  91. });
  92. await merchantRepository.save(testMerchant);
  93. // 创建测试文件
  94. const fileRepository = dataSource.getRepository(File);
  95. testFile = fileRepository.create({
  96. name: 'test_image.jpg',
  97. type: 'image/jpeg',
  98. size: 102400,
  99. path: 'images/test_image.jpg',
  100. uploadUserId: testUser.id,
  101. uploadTime: new Date(),
  102. createdAt: new Date(),
  103. updatedAt: new Date()
  104. });
  105. await fileRepository.save(testFile);
  106. });
  107. describe('GET /goods', () => {
  108. it('应该返回当前用户的商品列表', async () => {
  109. // 为测试用户创建一些商品
  110. const dataSource = await IntegrationTestDatabase.getDataSource();
  111. const goodsRepository = dataSource.getRepository(Goods);
  112. const userGoods1 = goodsRepository.create({
  113. name: '用户商品1',
  114. price: 100.00,
  115. costPrice: 80.00,
  116. categoryId1: testCategory.id,
  117. categoryId2: testCategory.id,
  118. categoryId3: testCategory.id,
  119. goodsType: 1,
  120. supplierId: testSupplier.id,
  121. merchantId: testMerchant.id,
  122. imageFileId: testFile.id,
  123. state: 1,
  124. stock: 100,
  125. lowestBuy: 1,
  126. createdBy: testUser.id
  127. });
  128. await goodsRepository.save(userGoods1);
  129. const userGoods2 = goodsRepository.create({
  130. name: '用户商品2',
  131. price: 200.00,
  132. costPrice: 160.00,
  133. categoryId1: testCategory.id,
  134. categoryId2: testCategory.id,
  135. categoryId3: testCategory.id,
  136. goodsType: 1,
  137. supplierId: testSupplier.id,
  138. merchantId: testMerchant.id,
  139. imageFileId: testFile.id,
  140. state: 1,
  141. stock: 50,
  142. lowestBuy: 1,
  143. createdBy: testUser.id
  144. });
  145. await goodsRepository.save(userGoods2);
  146. // 为其他用户创建一个商品,确保不会返回
  147. const otherUserGoods = goodsRepository.create({
  148. name: '其他用户商品',
  149. price: 300.00,
  150. costPrice: 240.00,
  151. categoryId1: testCategory.id,
  152. categoryId2: testCategory.id,
  153. categoryId3: testCategory.id,
  154. goodsType: 1,
  155. supplierId: testSupplier.id,
  156. merchantId: testMerchant.id,
  157. imageFileId: testFile.id,
  158. state: 1,
  159. stock: 30,
  160. lowestBuy: 1,
  161. createdBy: otherUser.id
  162. });
  163. await goodsRepository.save(otherUserGoods);
  164. const response = await client.index.$get({
  165. query: {
  166. page: 1,
  167. pageSize: 10
  168. }
  169. }, {
  170. headers: {
  171. 'Authorization': `Bearer ${userToken}`
  172. }
  173. });
  174. console.debug('用户商品列表响应状态:', response.status);
  175. if (response.status !== 200) {
  176. const errorData = await response.json();
  177. console.debug('用户商品列表错误响应:', errorData);
  178. }
  179. expect(response.status).toBe(200);
  180. if (response.status === 200) {
  181. const data = await response.json();
  182. expect(data).toHaveProperty('data');
  183. expect(Array.isArray(data.data)).toBe(true);
  184. // 验证只返回当前用户的商品
  185. data.data.forEach((goods: any) => {
  186. expect(goods.createdBy).toBe(testUser.id);
  187. });
  188. // 验证不包含其他用户的商品
  189. const otherUserGoodsInResponse = data.data.find((goods: any) => goods.createdBy === otherUser.id);
  190. expect(otherUserGoodsInResponse).toBeUndefined();
  191. }
  192. });
  193. it('应该拒绝未认证用户的访问', async () => {
  194. const response = await client.index.$get({
  195. query: {
  196. page: 1,
  197. pageSize: 10
  198. }
  199. });
  200. expect(response.status).toBe(401);
  201. });
  202. });
  203. describe('POST /goods', () => {
  204. it('应该成功创建商品并自动设置当前用户权限', async () => {
  205. const createData = {
  206. name: '用户创建商品',
  207. price: 150.00,
  208. costPrice: 120.00,
  209. categoryId1: testCategory.id,
  210. categoryId2: testCategory.id,
  211. categoryId3: testCategory.id,
  212. goodsType: 1,
  213. supplierId: testSupplier.id,
  214. merchantId: testMerchant.id,
  215. state: 1,
  216. stock: 80,
  217. lowestBuy: 1
  218. };
  219. const response = await client.index.$post({
  220. json: createData
  221. }, {
  222. headers: {
  223. 'Authorization': `Bearer ${userToken}`
  224. }
  225. });
  226. console.debug('用户创建商品响应状态:', response.status);
  227. if (response.status !== 201) {
  228. const errorData = await response.json();
  229. console.debug('用户创建商品错误响应:', errorData);
  230. }
  231. expect(response.status).toBe(201);
  232. if (response.status === 201) {
  233. const data = await response.json();
  234. expect(data).toHaveProperty('id');
  235. expect(data.name).toBe(createData.name);
  236. expect(data.price).toBe(Number(createData.price));
  237. expect(data.createdBy).toBe(testUser.id); // 验证自动设置当前用户权限
  238. }
  239. });
  240. it('应该验证创建商品的必填字段', async () => {
  241. const invalidData = {
  242. // 缺少必填字段
  243. name: '',
  244. price: -1,
  245. categoryId1: -1
  246. };
  247. const response = await client.index.$post({
  248. json: invalidData
  249. }, {
  250. headers: {
  251. 'Authorization': `Bearer ${userToken}`
  252. }
  253. });
  254. expect(response.status).toBe(400);
  255. });
  256. });
  257. describe('GET /goods/:id', () => {
  258. it('应该返回当前用户的商品详情', async () => {
  259. // 先为测试用户创建一个商品
  260. const dataSource = await IntegrationTestDatabase.getDataSource();
  261. const goodsRepository = dataSource.getRepository(Goods);
  262. const testGoods = goodsRepository.create({
  263. name: '测试用户商品详情',
  264. price: 100.00,
  265. costPrice: 80.00,
  266. categoryId1: testCategory.id,
  267. categoryId2: testCategory.id,
  268. categoryId3: testCategory.id,
  269. goodsType: 1,
  270. supplierId: testSupplier.id,
  271. merchantId: testMerchant.id,
  272. state: 1,
  273. stock: 100,
  274. lowestBuy: 1,
  275. createdBy: testUser.id
  276. });
  277. await goodsRepository.save(testGoods);
  278. const response = await client[':id'].$get({
  279. param: { id: testGoods.id }
  280. }, {
  281. headers: {
  282. 'Authorization': `Bearer ${userToken}`
  283. }
  284. });
  285. console.debug('用户商品详情响应状态:', response.status);
  286. if (response.status !== 200) {
  287. const errorData = await response.json();
  288. console.debug('用户商品详情错误响应:', errorData);
  289. }
  290. expect(response.status).toBe(200);
  291. if (response.status === 200) {
  292. const data = await response.json();
  293. expect(data.id).toBe(testGoods.id);
  294. expect(data.name).toBe(testGoods.name);
  295. expect(data.createdBy).toBe(testUser.id);
  296. }
  297. });
  298. it('应该拒绝访问其他用户的商品', async () => {
  299. // 为其他用户创建一个商品
  300. const dataSource = await IntegrationTestDatabase.getDataSource();
  301. const goodsRepository = dataSource.getRepository(Goods);
  302. const otherUserGoods = goodsRepository.create({
  303. name: '其他用户商品',
  304. price: 100.00,
  305. costPrice: 80.00,
  306. categoryId1: testCategory.id,
  307. categoryId2: testCategory.id,
  308. categoryId3: testCategory.id,
  309. goodsType: 1,
  310. supplierId: testSupplier.id,
  311. merchantId: testMerchant.id,
  312. state: 1,
  313. stock: 100,
  314. lowestBuy: 1,
  315. createdBy: otherUser.id
  316. });
  317. await goodsRepository.save(otherUserGoods);
  318. const response = await client[':id'].$get({
  319. param: { id: otherUserGoods.id }
  320. }, {
  321. headers: {
  322. 'Authorization': `Bearer ${userToken}`
  323. }
  324. });
  325. expect(response.status).toBe(403); // 数据权限控制返回403(权限不足)
  326. });
  327. it('应该处理不存在的商品', async () => {
  328. const response = await client[':id'].$get({
  329. param: { id: 999999 }
  330. }, {
  331. headers: {
  332. 'Authorization': `Bearer ${userToken}`
  333. }
  334. });
  335. expect(response.status).toBe(404);
  336. });
  337. });
  338. describe('PUT /goods/:id', () => {
  339. it('应该成功更新当前用户的商品', async () => {
  340. // 先为测试用户创建一个商品
  341. const dataSource = await IntegrationTestDatabase.getDataSource();
  342. const goodsRepository = dataSource.getRepository(Goods);
  343. const testGoods = goodsRepository.create({
  344. name: '测试更新商品',
  345. price: 100.00,
  346. costPrice: 80.00,
  347. categoryId1: testCategory.id,
  348. categoryId2: testCategory.id,
  349. categoryId3: testCategory.id,
  350. goodsType: 1,
  351. supplierId: testSupplier.id,
  352. merchantId: testMerchant.id,
  353. state: 1,
  354. stock: 100,
  355. lowestBuy: 1,
  356. createdBy: testUser.id
  357. });
  358. await goodsRepository.save(testGoods);
  359. const updateData = {
  360. name: '更新后的商品名称',
  361. price: 120.00,
  362. state: 2
  363. };
  364. const response = await client[':id'].$put({
  365. param: { id: testGoods.id },
  366. json: updateData
  367. }, {
  368. headers: {
  369. 'Authorization': `Bearer ${userToken}`
  370. }
  371. });
  372. console.debug('用户更新商品响应状态:', response.status);
  373. expect(response.status).toBe(200);
  374. if (response.status === 200) {
  375. const data = await response.json();
  376. expect(data.name).toBe(updateData.name);
  377. expect(data.price).toBe(Number(updateData.price));
  378. expect(data.state).toBe(updateData.state);
  379. expect(data.updatedBy).toBe(testUser.id); // 验证自动设置更新用户
  380. }
  381. });
  382. it('应该拒绝更新其他用户的商品', async () => {
  383. // 为其他用户创建一个商品
  384. const dataSource = await IntegrationTestDatabase.getDataSource();
  385. const goodsRepository = dataSource.getRepository(Goods);
  386. const otherUserGoods = goodsRepository.create({
  387. name: '其他用户商品',
  388. price: 100.00,
  389. costPrice: 80.00,
  390. categoryId1: testCategory.id,
  391. categoryId2: testCategory.id,
  392. categoryId3: testCategory.id,
  393. goodsType: 1,
  394. supplierId: testSupplier.id,
  395. merchantId: testMerchant.id,
  396. state: 1,
  397. stock: 100,
  398. lowestBuy: 1,
  399. createdBy: otherUser.id
  400. });
  401. await goodsRepository.save(otherUserGoods);
  402. const updateData = {
  403. name: '尝试更新其他用户商品'
  404. };
  405. const response = await client[':id'].$put({
  406. param: { id: otherUserGoods.id },
  407. json: updateData
  408. }, {
  409. headers: {
  410. 'Authorization': `Bearer ${userToken}`
  411. }
  412. });
  413. expect(response.status).toBe(403); // 数据权限控制返回403
  414. });
  415. });
  416. describe('DELETE /goods/:id', () => {
  417. it('应该成功删除当前用户的商品', async () => {
  418. // 先为测试用户创建一个商品
  419. const dataSource = await IntegrationTestDatabase.getDataSource();
  420. const goodsRepository = dataSource.getRepository(Goods);
  421. const testGoods = goodsRepository.create({
  422. name: '测试删除商品',
  423. price: 100.00,
  424. costPrice: 80.00,
  425. categoryId1: testCategory.id,
  426. categoryId2: testCategory.id,
  427. categoryId3: testCategory.id,
  428. goodsType: 1,
  429. supplierId: testSupplier.id,
  430. merchantId: testMerchant.id,
  431. state: 1,
  432. stock: 100,
  433. lowestBuy: 1,
  434. createdBy: testUser.id
  435. });
  436. await goodsRepository.save(testGoods);
  437. const response = await client[':id'].$delete({
  438. param: { id: testGoods.id }
  439. }, {
  440. headers: {
  441. 'Authorization': `Bearer ${userToken}`
  442. }
  443. });
  444. console.debug('用户删除商品响应状态:', response.status);
  445. expect(response.status).toBe(204);
  446. });
  447. it('应该拒绝删除其他用户的商品', async () => {
  448. // 为其他用户创建一个商品
  449. const dataSource = await IntegrationTestDatabase.getDataSource();
  450. const goodsRepository = dataSource.getRepository(Goods);
  451. const otherUserGoods = goodsRepository.create({
  452. name: '其他用户商品',
  453. price: 100.00,
  454. costPrice: 80.00,
  455. categoryId1: testCategory.id,
  456. categoryId2: testCategory.id,
  457. categoryId3: testCategory.id,
  458. goodsType: 1,
  459. supplierId: testSupplier.id,
  460. merchantId: testMerchant.id,
  461. state: 1,
  462. stock: 100,
  463. lowestBuy: 1,
  464. createdBy: otherUser.id
  465. });
  466. await goodsRepository.save(otherUserGoods);
  467. const response = await client[':id'].$delete({
  468. param: { id: otherUserGoods.id }
  469. }, {
  470. headers: {
  471. 'Authorization': `Bearer ${userToken}`
  472. }
  473. });
  474. expect(response.status).toBe(403); // 数据权限控制返回403
  475. });
  476. });
  477. describe('数据权限配置测试', () => {
  478. it('应该验证dataPermission配置正确工作', async () => {
  479. // 这个测试验证数据权限配置是否正常工作
  480. // 用户只能访问自己创建的商品
  481. const dataSource = await IntegrationTestDatabase.getDataSource();
  482. const goodsRepository = dataSource.getRepository(Goods);
  483. // 创建测试用户和其他用户的商品
  484. const userGoods = goodsRepository.create({
  485. name: '用户商品',
  486. price: 100.00,
  487. costPrice: 80.00,
  488. categoryId1: testCategory.id,
  489. categoryId2: testCategory.id,
  490. categoryId3: testCategory.id,
  491. goodsType: 1,
  492. supplierId: testSupplier.id,
  493. merchantId: testMerchant.id,
  494. state: 1,
  495. stock: 100,
  496. lowestBuy: 1,
  497. createdBy: testUser.id
  498. });
  499. await goodsRepository.save(userGoods);
  500. const otherUserGoods = goodsRepository.create({
  501. name: '其他用户商品',
  502. price: 200.00,
  503. costPrice: 160.00,
  504. categoryId1: testCategory.id,
  505. categoryId2: testCategory.id,
  506. categoryId3: testCategory.id,
  507. goodsType: 1,
  508. supplierId: testSupplier.id,
  509. merchantId: testMerchant.id,
  510. state: 1,
  511. stock: 50,
  512. lowestBuy: 1,
  513. createdBy: otherUser.id
  514. });
  515. await goodsRepository.save(otherUserGoods);
  516. // 使用测试用户token获取列表
  517. const response = await client.index.$get({
  518. query: {
  519. page: 1,
  520. pageSize: 10
  521. }
  522. }, {
  523. headers: {
  524. 'Authorization': `Bearer ${userToken}`
  525. }
  526. });
  527. if (response.status !== 200) {
  528. const errorData = await response.json();
  529. console.debug('数据权限配置测试错误响应:', errorData);
  530. }
  531. expect(response.status).toBe(200);
  532. const data = await response.json();
  533. // 类型检查确保data属性存在
  534. if ('data' in data && Array.isArray(data.data)) {
  535. // 验证只返回测试用户的商品
  536. const userGoodsInResponse = data.data.filter((goods: any) => goods.createdBy === testUser.id);
  537. const otherUserGoodsInResponse = data.data.filter((goods: any) => goods.createdBy === otherUser.id);
  538. expect(userGoodsInResponse.length).toBeGreaterThan(0);
  539. expect(otherUserGoodsInResponse.length).toBe(0);
  540. } else {
  541. // 如果响应是错误格式,应该失败
  542. expect(data).toHaveProperty('data');
  543. }
  544. });
  545. });
  546. });