admin-refunds.integration.test.ts 14 KB

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