user-refunds.integration.test.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  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 { Merchant } from '@d8d/merchant-module';
  8. import { DeliveryAddress } from '@d8d/delivery-address-module';
  9. import { AreaEntity } from '@d8d/geo-areas';
  10. import { Supplier } from '@d8d/supplier-module';
  11. import { userRefundsRoutes } from '../../src/routes/user/refunds';
  12. import { Order, OrderRefund } from '../../src/entities';
  13. // 设置集成测试钩子
  14. setupIntegrationDatabaseHooksWithEntities([
  15. UserEntity, Role, Order, OrderRefund, File, Merchant, DeliveryAddress, AreaEntity, Supplier
  16. ])
  17. describe('用户退款管理API集成测试', () => {
  18. let client: ReturnType<typeof testClient<typeof userRefundsRoutes>>;
  19. let userToken: string;
  20. let otherUserToken: string;
  21. let testUser: UserEntity;
  22. let otherUser: UserEntity;
  23. let testOrder: Order;
  24. let otherUserOrder: Order;
  25. beforeEach(async () => {
  26. // 创建测试客户端
  27. client = testClient(userRefundsRoutes);
  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 orderRepository = dataSource.getRepository(Order);
  61. testOrder = orderRepository.create({
  62. orderNo: `ORDER_${Math.floor(Math.random() * 100000)}`,
  63. userId: testUser.id,
  64. amount: 100.00,
  65. costAmount: 80.00,
  66. payAmount: 100.00,
  67. orderType: 1,
  68. payType: 1,
  69. payState: 2,
  70. state: 0,
  71. createdBy: testUser.id
  72. });
  73. await orderRepository.save(testOrder);
  74. // 创建其他用户的订单
  75. otherUserOrder = orderRepository.create({
  76. orderNo: `ORDER_${Math.floor(Math.random() * 100000)}`,
  77. userId: otherUser.id,
  78. amount: 200.00,
  79. costAmount: 160.00,
  80. payAmount: 200.00,
  81. orderType: 1,
  82. payType: 1,
  83. payState: 2,
  84. state: 0,
  85. createdBy: otherUser.id
  86. });
  87. await orderRepository.save(otherUserOrder);
  88. });
  89. describe('GET /refunds', () => {
  90. it('应该返回当前用户订单的退款列表', async () => {
  91. // 为测试用户的订单创建一些退款
  92. const dataSource = await IntegrationTestDatabase.getDataSource();
  93. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  94. const userRefund1 = orderRefundRepository.create({
  95. orderNo: testOrder.orderNo,
  96. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  97. refundAmount: 50.00,
  98. state: 0,
  99. createdBy: testUser.id
  100. });
  101. await orderRefundRepository.save(userRefund1);
  102. const userRefund2 = orderRefundRepository.create({
  103. orderNo: testOrder.orderNo,
  104. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  105. refundAmount: 25.00,
  106. state: 1,
  107. createdBy: testUser.id
  108. });
  109. await orderRefundRepository.save(userRefund2);
  110. // 为其他用户的订单创建一个退款,确保不会返回
  111. const otherUserRefund = orderRefundRepository.create({
  112. orderNo: otherUserOrder.orderNo,
  113. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  114. refundAmount: 100.00,
  115. state: 0,
  116. createdBy: otherUser.id
  117. });
  118. await orderRefundRepository.save(otherUserRefund);
  119. const response = await client.index.$get({
  120. query: {
  121. page: 1,
  122. pageSize: 10
  123. }
  124. }, {
  125. headers: {
  126. 'Authorization': `Bearer ${userToken}`
  127. }
  128. });
  129. console.debug('用户退款列表响应状态:', response.status);
  130. if (response.status !== 200) {
  131. const errorData = await response.json();
  132. console.debug('用户退款列表错误响应:', errorData);
  133. }
  134. expect(response.status).toBe(200);
  135. if (response.status === 200) {
  136. const data = await response.json();
  137. expect(data).toHaveProperty('data');
  138. expect(Array.isArray(data.data)).toBe(true);
  139. // 验证只返回当前用户订单的退款
  140. data.data.forEach((refund: any) => {
  141. expect(refund.order.userId).toBe(testUser.id);
  142. });
  143. // 验证不包含其他用户订单的退款
  144. const otherUserRefundInResponse = data.data.find((refund: any) =>
  145. refund.order && refund.order.userId === otherUser.id
  146. );
  147. expect(otherUserRefundInResponse).toBeUndefined();
  148. }
  149. });
  150. it('应该拒绝未认证用户的访问', async () => {
  151. const response = await client.index.$get({
  152. query: {
  153. page: 1,
  154. pageSize: 10
  155. }
  156. });
  157. expect(response.status).toBe(401);
  158. });
  159. });
  160. describe('POST /refunds', () => {
  161. it('应该成功创建退款申请并自动设置当前用户权限', async () => {
  162. const createData = {
  163. orderNo: testOrder.orderNo,
  164. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  165. refundAmount: 75.00,
  166. state: 0
  167. };
  168. const response = await client.index.$post({
  169. json: createData
  170. }, {
  171. headers: {
  172. 'Authorization': `Bearer ${userToken}`
  173. }
  174. });
  175. console.debug('用户创建退款申请响应状态:', response.status);
  176. if (response.status !== 201) {
  177. const errorData = await response.json();
  178. console.debug('用户创建退款申请错误响应:', errorData);
  179. }
  180. expect(response.status).toBe(201);
  181. if (response.status === 201) {
  182. const data = await response.json();
  183. expect(data).toHaveProperty('id');
  184. expect(data.refundOrderNo).toBe(createData.refundOrderNo);
  185. expect(parseFloat(data.refundAmount)).toBe(createData.refundAmount);
  186. expect(data.state).toBe(createData.state);
  187. expect(data.createdBy).toBe(testUser.id); // 验证自动设置创建用户
  188. }
  189. });
  190. it('应该验证创建退款申请的必填字段', async () => {
  191. const invalidData = {
  192. // 缺少必填字段
  193. refundAmount: -1
  194. };
  195. const response = await client.index.$post({
  196. json: invalidData
  197. }, {
  198. headers: {
  199. 'Authorization': `Bearer ${userToken}`
  200. }
  201. });
  202. expect(response.status).toBe(400);
  203. });
  204. it('应该拒绝为其他用户的订单创建退款申请', async () => {
  205. const createData = {
  206. orderNo: otherUserOrder.orderNo,
  207. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  208. refundAmount: 75.00,
  209. state: 0
  210. };
  211. const response = await client.index.$post({
  212. json: createData
  213. }, {
  214. headers: {
  215. 'Authorization': `Bearer ${userToken}`
  216. }
  217. });
  218. expect(response.status).toBe(403); // 数据权限控制返回403
  219. });
  220. });
  221. describe('GET /refunds/:id', () => {
  222. it('应该返回当前用户订单的退款详情', async () => {
  223. // 先为测试用户的订单创建一个退款
  224. const dataSource = await IntegrationTestDatabase.getDataSource();
  225. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  226. const testRefund = orderRefundRepository.create({
  227. orderNo: testOrder.orderNo,
  228. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  229. refundAmount: 50.00,
  230. state: 0,
  231. createdBy: testUser.id
  232. });
  233. await orderRefundRepository.save(testRefund);
  234. const response = await client[':id'].$get({
  235. param: { id: testRefund.id }
  236. }, {
  237. headers: {
  238. 'Authorization': `Bearer ${userToken}`
  239. }
  240. });
  241. console.debug('用户退款详情响应状态:', response.status);
  242. if (response.status !== 200) {
  243. const errorData = await response.json();
  244. console.debug('用户退款详情错误响应:', errorData);
  245. }
  246. expect(response.status).toBe(200);
  247. if (response.status === 200) {
  248. const data = await response.json();
  249. expect(data.id).toBe(testRefund.id);
  250. expect(data.refundOrderNo).toBe(testRefund.refundOrderNo);
  251. expect(data.order.userId).toBe(testUser.id); // 验证订单属于当前用户
  252. }
  253. });
  254. it('应该拒绝访问其他用户订单的退款', async () => {
  255. // 为其他用户的订单创建一个退款
  256. const dataSource = await IntegrationTestDatabase.getDataSource();
  257. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  258. const otherUserRefund = orderRefundRepository.create({
  259. orderNo: otherUserOrder.orderNo,
  260. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  261. refundAmount: 100.00,
  262. state: 0,
  263. createdBy: otherUser.id
  264. });
  265. await orderRefundRepository.save(otherUserRefund);
  266. const response = await client[':id'].$get({
  267. param: { id: otherUserRefund.id }
  268. }, {
  269. headers: {
  270. 'Authorization': `Bearer ${userToken}`
  271. }
  272. });
  273. expect(response.status).toBe(403); // 数据权限控制返回403(权限不足)
  274. });
  275. it('应该处理不存在的退款', async () => {
  276. const response = await client[':id'].$get({
  277. param: { id: 999999 }
  278. }, {
  279. headers: {
  280. 'Authorization': `Bearer ${userToken}`
  281. }
  282. });
  283. expect(response.status).toBe(404);
  284. });
  285. });
  286. describe('PUT /refunds/:id', () => {
  287. it('应该成功更新当前用户订单的退款', async () => {
  288. // 先为测试用户的订单创建一个退款
  289. const dataSource = await IntegrationTestDatabase.getDataSource();
  290. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  291. const testRefund = orderRefundRepository.create({
  292. orderNo: testOrder.orderNo,
  293. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  294. refundAmount: 50.00,
  295. state: 0,
  296. createdBy: testUser.id
  297. });
  298. await orderRefundRepository.save(testRefund);
  299. const updateData = {
  300. state: 1
  301. };
  302. const response = await client[':id'].$put({
  303. param: { id: testRefund.id },
  304. json: updateData
  305. }, {
  306. headers: {
  307. 'Authorization': `Bearer ${userToken}`
  308. }
  309. });
  310. console.debug('用户更新退款响应状态:', response.status);
  311. expect(response.status).toBe(200);
  312. if (response.status === 200) {
  313. const data = await response.json();
  314. expect(data.state).toBe(updateData.state);
  315. expect(data.updatedBy).toBe(testUser.id); // 验证自动设置更新用户
  316. }
  317. });
  318. it('应该拒绝更新其他用户订单的退款', async () => {
  319. // 为其他用户的订单创建一个退款
  320. const dataSource = await IntegrationTestDatabase.getDataSource();
  321. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  322. const otherUserRefund = orderRefundRepository.create({
  323. orderNo: otherUserOrder.orderNo,
  324. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  325. refundAmount: 100.00,
  326. state: 0,
  327. createdBy: otherUser.id
  328. });
  329. await orderRefundRepository.save(otherUserRefund);
  330. const updateData = {
  331. state: 1
  332. };
  333. const response = await client[':id'].$put({
  334. param: { id: otherUserRefund.id },
  335. json: updateData
  336. }, {
  337. headers: {
  338. 'Authorization': `Bearer ${userToken}`
  339. }
  340. });
  341. expect(response.status).toBe(403); // 数据权限控制返回403
  342. });
  343. });
  344. describe('DELETE /refunds/:id', () => {
  345. it('应该成功删除当前用户订单的退款', async () => {
  346. // 先为测试用户的订单创建一个退款
  347. const dataSource = await IntegrationTestDatabase.getDataSource();
  348. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  349. const testRefund = orderRefundRepository.create({
  350. orderNo: testOrder.orderNo,
  351. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  352. refundAmount: 50.00,
  353. state: 0,
  354. createdBy: testUser.id
  355. });
  356. await orderRefundRepository.save(testRefund);
  357. const response = await client[':id'].$delete({
  358. param: { id: testRefund.id }
  359. }, {
  360. headers: {
  361. 'Authorization': `Bearer ${userToken}`
  362. }
  363. });
  364. console.debug('用户删除退款响应状态:', response.status);
  365. expect(response.status).toBe(204);
  366. });
  367. it('应该拒绝删除其他用户订单的退款', async () => {
  368. // 为其他用户的订单创建一个退款
  369. const dataSource = await IntegrationTestDatabase.getDataSource();
  370. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  371. const otherUserRefund = orderRefundRepository.create({
  372. orderNo: otherUserOrder.orderNo,
  373. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  374. refundAmount: 100.00,
  375. state: 0,
  376. createdBy: otherUser.id
  377. });
  378. await orderRefundRepository.save(otherUserRefund);
  379. const response = await client[':id'].$delete({
  380. param: { id: otherUserRefund.id }
  381. }, {
  382. headers: {
  383. 'Authorization': `Bearer ${userToken}`
  384. }
  385. });
  386. expect(response.status).toBe(403); // 数据权限控制返回403
  387. });
  388. });
  389. describe('数据权限配置测试', () => {
  390. it('应该验证dataPermission配置正确工作', async () => {
  391. // 这个测试验证数据权限配置是否正常工作
  392. // 用户只能访问自己订单的退款
  393. const dataSource = await IntegrationTestDatabase.getDataSource();
  394. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  395. // 创建测试用户和其他用户订单的退款
  396. const userRefund = orderRefundRepository.create({
  397. orderNo: testOrder.orderNo,
  398. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  399. refundAmount: 50.00,
  400. state: 0,
  401. createdBy: testUser.id
  402. });
  403. await orderRefundRepository.save(userRefund);
  404. const otherUserRefund = orderRefundRepository.create({
  405. orderNo: otherUserOrder.orderNo,
  406. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  407. refundAmount: 100.00,
  408. state: 0,
  409. createdBy: otherUser.id
  410. });
  411. await orderRefundRepository.save(otherUserRefund);
  412. // 使用测试用户token获取列表
  413. const response = await client.index.$get({
  414. query: {
  415. page: 1,
  416. pageSize: 10
  417. }
  418. }, {
  419. headers: {
  420. 'Authorization': `Bearer ${userToken}`
  421. }
  422. });
  423. if (response.status !== 200) {
  424. const errorData = await response.json();
  425. console.debug('数据权限配置测试错误响应:', errorData);
  426. }
  427. expect(response.status).toBe(200);
  428. const data = await response.json();
  429. // 类型检查确保data属性存在
  430. if ('data' in data && Array.isArray(data.data)) {
  431. // 验证只返回测试用户订单的退款
  432. const userRefundInResponse = data.data.filter((refund: any) =>
  433. refund.order && refund.order.userId === testUser.id
  434. );
  435. const otherUserRefundInResponse = data.data.filter((refund: any) =>
  436. refund.order && refund.order.userId === otherUser.id
  437. );
  438. expect(userRefundInResponse.length).toBeGreaterThan(0);
  439. expect(otherUserRefundInResponse.length).toBe(0);
  440. } else {
  441. // 如果响应是错误格式,应该失败
  442. expect(data).toHaveProperty('data');
  443. }
  444. });
  445. });
  446. describe('退款状态管理测试', () => {
  447. it('应该正确处理退款状态变更', async () => {
  448. const dataSource = await IntegrationTestDatabase.getDataSource();
  449. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  450. // 创建不同状态的退款
  451. const pendingRefund = orderRefundRepository.create({
  452. orderNo: testOrder.orderNo,
  453. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  454. refundAmount: 50.00,
  455. state: 0, // 未退款
  456. createdBy: testUser.id
  457. });
  458. await orderRefundRepository.save(pendingRefund);
  459. const processingRefund = orderRefundRepository.create({
  460. orderNo: testOrder.orderNo,
  461. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  462. refundAmount: 25.00,
  463. state: 1, // 退款中
  464. createdBy: testUser.id
  465. });
  466. await orderRefundRepository.save(processingRefund);
  467. const completedRefund = orderRefundRepository.create({
  468. orderNo: testOrder.orderNo,
  469. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  470. refundAmount: 75.00,
  471. state: 2, // 退款成功
  472. createdBy: testUser.id
  473. });
  474. await orderRefundRepository.save(completedRefund);
  475. // 验证状态过滤
  476. const response = await client.index.$get({
  477. query: { filters: JSON.stringify({ state: 0 }) }
  478. }, {
  479. headers: {
  480. 'Authorization': `Bearer ${userToken}`
  481. }
  482. });
  483. expect(response.status).toBe(200);
  484. const data = await response.json();
  485. // 类型检查确保data属性存在
  486. if ('data' in data && Array.isArray(data.data)) {
  487. // 应该只返回未退款状态的退款
  488. const pendingRefundsInResponse = data.data.filter((refund: any) => refund.state === 0);
  489. const processingRefundsInResponse = data.data.filter((refund: any) => refund.state === 1);
  490. const completedRefundsInResponse = data.data.filter((refund: any) => refund.state === 2);
  491. expect(pendingRefundsInResponse.length).toBeGreaterThan(0);
  492. expect(processingRefundsInResponse.length).toBe(0);
  493. expect(completedRefundsInResponse.length).toBe(0);
  494. } else {
  495. // 如果响应是错误格式,应该失败
  496. expect(data).toHaveProperty('data');
  497. }
  498. });
  499. });
  500. });