admin-refunds.integration.test.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  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 adminRefundsRoutes from '../../src/routes/admin/refunds';
  12. import { Order, OrderRefund } from '../../src/entities';
  13. import { OrdersTestDataFactory } from '../utils/test-data-factory';
  14. // 设置集成测试钩子
  15. setupIntegrationDatabaseHooksWithEntities([
  16. UserEntity, Role, Order, OrderRefund, File, Merchant, DeliveryAddress, AreaEntity, Supplier
  17. ])
  18. describe('管理员退款管理API集成测试', () => {
  19. let client: ReturnType<typeof testClient<typeof adminRefundsRoutes>>;
  20. let adminToken: string;
  21. let testUser: UserEntity;
  22. let testAdmin: UserEntity;
  23. let testOrder: Order;
  24. let otherUserOrder: Order;
  25. beforeEach(async () => {
  26. // 创建测试客户端
  27. client = testClient(adminRefundsRoutes);
  28. // 获取数据源
  29. const dataSource = await IntegrationTestDatabase.getDataSource();
  30. // 创建测试用户
  31. testUser = await OrdersTestDataFactory.createTestUser(dataSource);
  32. // 创建测试管理员用户
  33. testAdmin = await OrdersTestDataFactory.createTestUser(dataSource, {
  34. username: `test_admin_${Math.floor(Math.random() * 100000)}`,
  35. nickname: '测试管理员'
  36. });
  37. // 生成测试管理员的token
  38. adminToken = JWTUtil.generateToken({
  39. id: testAdmin.id,
  40. username: testAdmin.username,
  41. roles: [{name:'admin'}]
  42. });
  43. // 为测试用户创建完整的订单环境
  44. const testUserEnvironment = await OrdersTestDataFactory.createCompleteOrderEnvironment(dataSource, testUser.id);
  45. testOrder = testUserEnvironment.order;
  46. // 为管理员用户创建完整的订单环境
  47. const adminEnvironment = await OrdersTestDataFactory.createCompleteOrderEnvironment(dataSource, testAdmin.id);
  48. otherUserOrder = adminEnvironment.order;
  49. });
  50. describe('GET /refunds', () => {
  51. it('应该返回所有订单的退款列表', async () => {
  52. // 为不同用户的订单创建退款
  53. const dataSource = await IntegrationTestDatabase.getDataSource();
  54. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  55. const userRefund = orderRefundRepository.create({
  56. orderNo: testOrder.orderNo,
  57. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  58. refundAmount: 50.00,
  59. state: 0,
  60. createdBy: testUser.id,
  61. order: testOrder // 设置关联的订单对象
  62. });
  63. await orderRefundRepository.save(userRefund);
  64. const adminRefund = orderRefundRepository.create({
  65. orderNo: otherUserOrder.orderNo,
  66. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  67. refundAmount: 100.00,
  68. state: 1,
  69. createdBy: testAdmin.id,
  70. order: otherUserOrder // 设置关联的订单对象
  71. });
  72. await orderRefundRepository.save(adminRefund);
  73. const response = await client.index.$get({
  74. query: {}
  75. }, {
  76. headers: {
  77. 'Authorization': `Bearer ${adminToken}`
  78. }
  79. });
  80. console.debug('管理员退款列表响应状态:', response.status);
  81. if (response.status !== 200) {
  82. const errorData = await response.json();
  83. console.debug('管理员退款列表错误详情:', JSON.stringify(errorData, null, 2));
  84. }
  85. expect(response.status).toBe(200);
  86. if (response.status === 200) {
  87. const data = await response.json();
  88. expect(data).toHaveProperty('data');
  89. expect(Array.isArray(data.data)).toBe(true);
  90. // 验证返回所有用户的退款(管理员可以访问所有数据)
  91. const userRefundCount = data.data.filter((refund: any) =>
  92. refund.order && refund.order.userId === testUser.id
  93. ).length;
  94. const adminRefundCount = data.data.filter((refund: any) =>
  95. refund.order && refund.order.userId === testAdmin.id
  96. ).length;
  97. expect(userRefundCount).toBeGreaterThan(0);
  98. expect(adminRefundCount).toBeGreaterThan(0);
  99. }
  100. });
  101. it('应该拒绝未认证用户的访问', async () => {
  102. const response = await client.index.$get({
  103. query: {}
  104. });
  105. expect(response.status).toBe(401);
  106. });
  107. });
  108. describe('POST /refunds', () => {
  109. it('应该成功创建退款申请并可以指定权限', async () => {
  110. const createData = {
  111. orderNo: testOrder.orderNo,
  112. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  113. refundAmount: 75.00,
  114. state: 0,
  115. createdBy: testAdmin.id // 管理员可以指定创建人
  116. };
  117. const response = await client.index.$post({
  118. json: createData
  119. }, {
  120. headers: {
  121. 'Authorization': `Bearer ${adminToken}`
  122. }
  123. });
  124. console.debug('管理员创建退款申请响应状态:', response.status);
  125. if (response.status !== 201) {
  126. const errorData = await response.json();
  127. console.debug('管理员创建退款申请错误响应:', errorData);
  128. }
  129. expect(response.status).toBe(201);
  130. if (response.status === 201) {
  131. const data = await response.json();
  132. expect(data).toHaveProperty('id');
  133. expect(data.refundOrderNo).toBe(createData.refundOrderNo);
  134. expect(parseFloat(data.refundAmount)).toBe(createData.refundAmount);
  135. expect(data.state).toBe(createData.state);
  136. expect(data.createdBy).toBe(testAdmin.id); // 验证可以指定创建人
  137. }
  138. });
  139. it('应该验证创建退款申请的必填字段', async () => {
  140. const invalidData = {
  141. // 缺少必填字段
  142. refundAmount: -1
  143. };
  144. const response = await client.index.$post({
  145. json: invalidData
  146. }, {
  147. headers: {
  148. 'Authorization': `Bearer ${adminToken}`
  149. }
  150. });
  151. expect(response.status).toBe(400);
  152. });
  153. });
  154. describe('GET /refunds/:id', () => {
  155. it('应该返回指定退款的详情', async () => {
  156. // 先为测试用户的订单创建一个退款
  157. const dataSource = await IntegrationTestDatabase.getDataSource();
  158. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  159. const testRefund = orderRefundRepository.create({
  160. orderNo: testOrder.orderNo,
  161. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  162. refundAmount: 50.00,
  163. state: 0,
  164. createdBy: testUser.id,
  165. order: testOrder // 设置关联的订单对象
  166. });
  167. await orderRefundRepository.save(testRefund);
  168. const response = await client[':id'].$get({
  169. param: { id: testRefund.id }
  170. }, {
  171. headers: {
  172. 'Authorization': `Bearer ${adminToken}`
  173. }
  174. });
  175. console.debug('管理员退款详情响应状态:', response.status);
  176. expect(response.status).toBe(200);
  177. if (response.status === 200) {
  178. const data = await response.json();
  179. expect(data.id).toBe(testRefund.id);
  180. expect(data.refundOrderNo).toBe(testRefund.refundOrderNo);
  181. expect(data.order.userId).toBe(testUser.id); // 验证可以访问其他用户的退款
  182. }
  183. });
  184. it('应该处理不存在的退款', async () => {
  185. const response = await client[':id'].$get({
  186. param: { id: 999999 }
  187. }, {
  188. headers: {
  189. 'Authorization': `Bearer ${adminToken}`
  190. }
  191. });
  192. expect(response.status).toBe(404);
  193. });
  194. });
  195. describe('PUT /refunds/:id', () => {
  196. it('应该成功更新任何退款', async () => {
  197. // 先为测试用户的订单创建一个退款
  198. const dataSource = await IntegrationTestDatabase.getDataSource();
  199. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  200. const testRefund = orderRefundRepository.create({
  201. orderNo: testOrder.orderNo,
  202. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  203. refundAmount: 50.00,
  204. state: 0,
  205. createdBy: testUser.id,
  206. order: testOrder // 设置关联的订单对象
  207. });
  208. await orderRefundRepository.save(testRefund);
  209. const updateData = {
  210. state: 1,
  211. updatedBy: testAdmin.id // 管理员可以指定更新人
  212. };
  213. const response = await client[':id'].$put({
  214. param: { id: testRefund.id },
  215. json: updateData
  216. }, {
  217. headers: {
  218. 'Authorization': `Bearer ${adminToken}`
  219. }
  220. });
  221. console.debug('管理员更新退款响应状态:', response.status);
  222. expect(response.status).toBe(200);
  223. if (response.status === 200) {
  224. const data = await response.json();
  225. expect(data.state).toBe(updateData.state);
  226. expect(data.updatedBy).toBe(testAdmin.id); // 验证可以指定更新人
  227. }
  228. });
  229. });
  230. describe('DELETE /refunds/:id', () => {
  231. it('应该成功删除任何退款', async () => {
  232. // 先为测试用户的订单创建一个退款
  233. const dataSource = await IntegrationTestDatabase.getDataSource();
  234. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  235. const testRefund = orderRefundRepository.create({
  236. orderNo: testOrder.orderNo,
  237. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  238. refundAmount: 50.00,
  239. state: 0,
  240. createdBy: testUser.id,
  241. order: testOrder // 设置关联的订单对象
  242. });
  243. await orderRefundRepository.save(testRefund);
  244. const response = await client[':id'].$delete({
  245. param: { id: testRefund.id }
  246. }, {
  247. headers: {
  248. 'Authorization': `Bearer ${adminToken}`
  249. }
  250. });
  251. console.debug('管理员删除退款响应状态:', response.status);
  252. expect(response.status).toBe(204);
  253. });
  254. });
  255. describe('管理员权限验证测试', () => {
  256. it('应该验证管理员可以访问所有数据', async () => {
  257. const dataSource = await IntegrationTestDatabase.getDataSource();
  258. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  259. // 创建不同用户的退款
  260. const userRefund = orderRefundRepository.create({
  261. orderNo: testOrder.orderNo,
  262. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  263. refundAmount: 50.00,
  264. state: 0,
  265. createdBy: testUser.id,
  266. order: testOrder // 设置关联的订单对象
  267. });
  268. await orderRefundRepository.save(userRefund);
  269. const adminRefund = orderRefundRepository.create({
  270. orderNo: otherUserOrder.orderNo,
  271. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  272. refundAmount: 100.00,
  273. state: 0,
  274. createdBy: testAdmin.id,
  275. order: otherUserOrder // 设置关联的订单对象
  276. });
  277. await orderRefundRepository.save(adminRefund);
  278. // 使用管理员token获取列表
  279. const response = await client.index.$get({
  280. query: {}
  281. }, {
  282. headers: {
  283. 'Authorization': `Bearer ${adminToken}`
  284. }
  285. });
  286. expect(response.status).toBe(200);
  287. const data = await response.json();
  288. // 类型检查确保data属性存在
  289. if ('data' in data && Array.isArray(data.data)) {
  290. // 验证返回所有用户的退款
  291. const userRefundInResponse = data.data.filter((refund: any) =>
  292. refund.order && refund.order.userId === testUser.id
  293. );
  294. const adminRefundInResponse = data.data.filter((refund: any) =>
  295. refund.order && refund.order.userId === testAdmin.id
  296. );
  297. expect(userRefundInResponse.length).toBeGreaterThan(0);
  298. expect(adminRefundInResponse.length).toBeGreaterThan(0);
  299. } else {
  300. // 如果响应是错误格式,应该失败
  301. expect(data).toHaveProperty('data');
  302. }
  303. });
  304. });
  305. describe('退款状态管理测试', () => {
  306. it('应该正确处理退款状态变更', async () => {
  307. const dataSource = await IntegrationTestDatabase.getDataSource();
  308. const orderRefundRepository = dataSource.getRepository(OrderRefund);
  309. // 创建不同状态的退款
  310. const pendingRefund = orderRefundRepository.create({
  311. orderNo: testOrder.orderNo,
  312. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  313. refundAmount: 50.00,
  314. state: 0, // 未退款
  315. createdBy: testUser.id,
  316. order: testOrder // 设置关联的订单对象
  317. });
  318. await orderRefundRepository.save(pendingRefund);
  319. const processingRefund = orderRefundRepository.create({
  320. orderNo: testOrder.orderNo,
  321. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  322. refundAmount: 25.00,
  323. state: 1, // 退款中
  324. createdBy: testUser.id,
  325. order: testOrder // 设置关联的订单对象
  326. });
  327. await orderRefundRepository.save(processingRefund);
  328. const completedRefund = orderRefundRepository.create({
  329. orderNo: testOrder.orderNo,
  330. refundOrderNo: `REFUND_${Math.floor(Math.random() * 100000)}`,
  331. refundAmount: 75.00,
  332. state: 2, // 退款成功
  333. createdBy: testUser.id,
  334. order: testOrder // 设置关联的订单对象
  335. });
  336. await orderRefundRepository.save(completedRefund);
  337. // 验证状态过滤
  338. const response = await client.index.$get({
  339. query: { filters: JSON.stringify({ state: 0 }) }
  340. }, {
  341. headers: {
  342. 'Authorization': `Bearer ${adminToken}`
  343. }
  344. });
  345. expect(response.status).toBe(200);
  346. const data = await response.json();
  347. // 类型检查确保data属性存在
  348. if ('data' in data && Array.isArray(data.data)) {
  349. // 应该只返回未退款状态的退款
  350. const pendingRefundsInResponse = data.data.filter((refund: any) => refund.state === 0);
  351. const processingRefundsInResponse = data.data.filter((refund: any) => refund.state === 1);
  352. const completedRefundsInResponse = data.data.filter((refund: any) => refund.state === 2);
  353. expect(pendingRefundsInResponse.length).toBeGreaterThan(0);
  354. expect(processingRefundsInResponse.length).toBe(0);
  355. expect(completedRefundsInResponse.length).toBe(0);
  356. } else {
  357. // 如果响应是错误格式,应该失败
  358. expect(data).toHaveProperty('data');
  359. }
  360. });
  361. });
  362. });