user-orders-routes.integration.test.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  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 { UserEntityMt, RoleMt } from '@d8d/user-module-mt';
  6. import { DeliveryAddressMt } from '@d8d/delivery-address-module-mt';
  7. import { AreaEntityMt } from '@d8d/geo-areas-mt';
  8. import { MerchantMt } from '@d8d/merchant-module-mt';
  9. import { SupplierMt } from '@d8d/supplier-module-mt';
  10. import { FileMt } from '@d8d/file-module-mt';
  11. import { GoodsMt, GoodsCategoryMt } from '@d8d/goods-module-mt';
  12. import userOrderRoutes from '../../src/routes/user/orders.mt';
  13. import { OrderMt, OrderGoodsMt } from '../../src/entities';
  14. import { OrdersTestFactory } from '../factories/orders-test-factory';
  15. // 设置集成测试钩子
  16. setupIntegrationDatabaseHooksWithEntities([
  17. UserEntityMt, RoleMt, OrderMt, OrderGoodsMt, DeliveryAddressMt, MerchantMt, SupplierMt, FileMt, AreaEntityMt, GoodsMt, GoodsCategoryMt
  18. ])
  19. describe('多租户用户订单管理API集成测试', () => {
  20. let client: ReturnType<typeof testClient<typeof userOrderRoutes>>;
  21. let testFactory: OrdersTestFactory;
  22. let userToken: string;
  23. let otherUserToken: string;
  24. let otherTenantUserToken: string;
  25. let testUser: UserEntityMt;
  26. let otherUser: UserEntityMt;
  27. let otherTenantUser: UserEntityMt;
  28. beforeEach(async () => {
  29. // 创建测试客户端
  30. client = testClient(userOrderRoutes);
  31. // 获取数据源并创建测试工厂
  32. const dataSource = await IntegrationTestDatabase.getDataSource();
  33. testFactory = new OrdersTestFactory(dataSource);
  34. // 创建测试用户
  35. testUser = await testFactory.createTestUser(1);
  36. otherUser = await testFactory.createTestUser(1);
  37. otherTenantUser = await testFactory.createTestUser(2);
  38. // 生成JWT令牌
  39. userToken = JWTUtil.generateToken({ id: testUser.id, username: testUser.username, tenantId: 1 });
  40. otherUserToken = JWTUtil.generateToken({ id: otherUser.id, username: otherUser.username, tenantId: 1 });
  41. otherTenantUserToken = JWTUtil.generateToken({ id: otherTenantUser.id, username: otherTenantUser.username, tenantId: 2 });
  42. });
  43. describe('租户数据隔离验证', () => {
  44. it('应该只能访问自己租户的订单', async () => {
  45. // 创建租户1的订单
  46. const tenant1Order = await testFactory.createTestOrder(testUser.id, { tenantId: 1 });
  47. // 创建租户2的订单
  48. const tenant2Order = await testFactory.createTestOrder(otherTenantUser.id, { tenantId: 2 });
  49. // 使用租户1的用户查询订单列表
  50. const response = await client.index.$get({
  51. query: {}
  52. }, {
  53. headers: {
  54. 'Authorization': `Bearer ${userToken}`
  55. }
  56. });
  57. expect(response.status).toBe(200);
  58. if(response.status === 200){
  59. const data = await response.json();
  60. // 应该只返回租户1的订单
  61. expect(data.data).toHaveLength(1);
  62. expect(data.data[0].tenantId).toBe(1);
  63. expect(data.data[0].id).toBe(tenant1Order.id);
  64. }
  65. });
  66. it('不应该访问其他租户的订单详情', async () => {
  67. // 创建租户2的订单
  68. const otherTenantOrder = await testFactory.createTestOrder(otherTenantUser.id, { tenantId: 2 });
  69. // 使用租户1的用户尝试访问租户2的订单
  70. const response = await client[':id'].$get({
  71. param: { id: otherTenantOrder.id }
  72. }, {
  73. headers: {
  74. 'Authorization': `Bearer ${userToken}`
  75. }
  76. });
  77. // 应该返回404,因为订单不在当前租户
  78. expect(response.status).toBe(404);
  79. });
  80. it('应该正确过滤跨租户订单访问', async () => {
  81. // 创建租户1的订单
  82. const tenant1Order = await testFactory.createTestOrder(testUser.id, { tenantId: 1 });
  83. // 使用租户2的用户尝试访问租户1的订单
  84. const response = await client[':id'].$get({
  85. param: { id: tenant1Order.id }
  86. }, {
  87. headers: {
  88. 'Authorization': `Bearer ${otherTenantUserToken}`
  89. }
  90. });
  91. // 应该返回404,因为订单不在当前租户
  92. expect(response.status).toBe(404);
  93. });
  94. });
  95. describe('用户数据权限验证', () => {
  96. it('应该只能访问自己的订单', async () => {
  97. // 创建当前用户的订单
  98. const myOrder = await testFactory.createTestOrder(testUser.id, { tenantId: 1 });
  99. // 创建其他用户的订单(同一租户)
  100. const otherUserOrder = await testFactory.createTestOrder(otherUser.id, { tenantId: 1 });
  101. // 使用当前用户查询订单列表
  102. const response = await client.index.$get({
  103. query: {}
  104. }, {
  105. headers: {
  106. 'Authorization': `Bearer ${userToken}`
  107. }
  108. });
  109. expect(response.status).toBe(200);
  110. if (response.status === 200) {
  111. const data = await response.json();
  112. // 应该只返回当前用户的订单
  113. expect(data.data).toHaveLength(1);
  114. expect(data.data[0].userId).toBe(testUser.id);
  115. expect(data.data[0].id).toBe(myOrder.id);
  116. }
  117. });
  118. it('不应该访问其他用户的订单详情', async () => {
  119. // 创建其他用户的订单
  120. const otherUserOrder = await testFactory.createTestOrder(otherUser.id, { tenantId: 1 });
  121. console.debug('创建的订单:', { id: otherUserOrder.id, userId: otherUserOrder.userId, tenantId: otherUserOrder.tenantId });
  122. // 使用当前用户尝试访问其他用户的订单
  123. const response = await client[':id'].$get({
  124. param: { id: otherUserOrder.id }
  125. }, {
  126. headers: {
  127. 'Authorization': `Bearer ${userToken}`
  128. }
  129. });
  130. // 应该返回403,因为无权访问其他用户的订单
  131. console.debug('响应状态码:', response.status);
  132. expect(response.status).toBe(403);
  133. });
  134. });
  135. describe('订单商品关联验证', () => {
  136. it('应该返回包含订单商品信息的订单详情', async () => {
  137. // 创建测试订单和订单商品
  138. const order = await testFactory.createTestOrder(testUser.id, { tenantId: 1 });
  139. const testGoods = await testFactory.createTestGoods(testUser.id, { tenantId: 1 });
  140. const orderGoods = await testFactory.createTestOrderGoods(order.id, testGoods.id, { tenantId: 1 });
  141. // 查询订单详情
  142. const response = await client[':id'].$get({
  143. param: { id: order.id }
  144. }, {
  145. headers: {
  146. 'Authorization': `Bearer ${userToken}`
  147. }
  148. });
  149. expect(response.status).toBe(200);
  150. if (response.status === 200) {
  151. const orderDetail = await response.json();
  152. // 验证订单详情包含订单商品信息
  153. expect(orderDetail.orderGoods).toBeDefined();
  154. expect(Array.isArray(orderDetail.orderGoods)).toBe(true);
  155. expect(orderDetail.orderGoods).toHaveLength(1);
  156. const goods = orderDetail.orderGoods[0];
  157. expect(goods.id).toBe(orderGoods.id);
  158. expect(goods.goodsId).toBe(orderGoods.goodsId);
  159. expect(goods.goodsName).toBe(orderGoods.goodsName);
  160. expect(goods.price).toBe(Number(orderGoods.price));
  161. expect(goods.num).toBe(orderGoods.num);
  162. }
  163. });
  164. it('应该返回包含订单商品信息的订单列表', async () => {
  165. // 创建测试订单和订单商品
  166. const order = await testFactory.createTestOrder(testUser.id, { tenantId: 1 });
  167. const testGoods = await testFactory.createTestGoods(testUser.id, { tenantId: 1 });
  168. await testFactory.createTestOrderGoods(order.id, testGoods.id, { tenantId: 1 });
  169. // 查询订单列表
  170. const response = await client.index.$get({
  171. query: {}
  172. }, {
  173. headers: {
  174. 'Authorization': `Bearer ${userToken}`
  175. }
  176. });
  177. expect(response.status).toBe(200);
  178. if (response.status === 200) {
  179. const data = await response.json();
  180. // 验证订单列表包含订单商品信息
  181. expect(data.data).toHaveLength(1);
  182. expect(data.data[0].orderGoods).toBeDefined();
  183. expect(Array.isArray(data.data[0].orderGoods)).toBe(true);
  184. expect(data.data[0].orderGoods).toHaveLength(1);
  185. const goods = data.data[0].orderGoods[0];
  186. expect(goods.goodsId).toBeGreaterThan(0);
  187. expect(goods.goodsName).toBeDefined();
  188. expect(goods.price).toBeGreaterThan(0);
  189. expect(goods.num).toBeGreaterThan(0);
  190. }
  191. });
  192. });
  193. describe('订单创建验证', () => {
  194. it('应该自动设置租户ID', async () => {
  195. // 创建必要的关联实体
  196. const testSupplier = await testFactory.createTestSupplier(testUser.id, { tenantId: 1 });
  197. const testMerchant = await testFactory.createTestMerchant(testUser.id, { tenantId: 1 });
  198. const testDeliveryAddress = await testFactory.createTestDeliveryAddress(testUser.id, { tenantId: 1 });
  199. const testGoods = await testFactory.createTestGoods(testUser.id, {
  200. tenantId: 1,
  201. merchantId: testMerchant.id,
  202. supplierId: testSupplier.id
  203. });
  204. const orderData = {
  205. addressId: testDeliveryAddress.id,
  206. productOwn: '自营',
  207. consumeFrom: '积分兑换',
  208. products: [
  209. { id: testGoods.id, num: 2 }
  210. ]
  211. };
  212. const response = await client['create-order'].$post({
  213. json: orderData
  214. }, {
  215. headers: {
  216. 'Authorization': `Bearer ${userToken}`
  217. }
  218. });
  219. console.debug('订单创建响应状态码:', response.status);
  220. if (response.status !== 201) {
  221. const errorResult = await response.json();
  222. console.debug('订单创建错误响应:', errorResult);
  223. }
  224. expect(response.status).toBe(201);
  225. if (response.status === 201) {
  226. const createdOrder = await response.json();
  227. // 验证订单创建成功
  228. expect(createdOrder.success).toBe(true);
  229. expect(createdOrder.orderId).toBeGreaterThan(0);
  230. expect(createdOrder.orderNo).toBeDefined();
  231. expect(createdOrder.amount).toBeGreaterThan(0);
  232. expect(createdOrder.payAmount).toBeGreaterThan(0);
  233. }
  234. });
  235. it('应该为子商品订单快照生成包含父商品名称的商品名称', async () => {
  236. // 创建必要的关联实体
  237. const testSupplier = await testFactory.createTestSupplier(testUser.id, { tenantId: 1 });
  238. const testMerchant = await testFactory.createTestMerchant(testUser.id, { tenantId: 1 });
  239. const testDeliveryAddress = await testFactory.createTestDeliveryAddress(testUser.id, { tenantId: 1 });
  240. // 创建父商品
  241. const parentGoods = await testFactory.createTestGoods(testUser.id, {
  242. tenantId: 1,
  243. merchantId: testMerchant.id,
  244. supplierId: testSupplier.id,
  245. name: '连衣裙'
  246. });
  247. // 创建子商品(规格商品)
  248. const childGoods = await testFactory.createTestGoods(testUser.id, {
  249. tenantId: 1,
  250. merchantId: testMerchant.id,
  251. supplierId: testSupplier.id,
  252. name: '红色 大码',
  253. spuId: parentGoods.id
  254. });
  255. const orderData = {
  256. addressId: testDeliveryAddress.id,
  257. productOwn: '自营',
  258. consumeFrom: '积分兑换',
  259. products: [
  260. { id: childGoods.id, num: 1 }
  261. ]
  262. };
  263. const response = await client['create-order'].$post({
  264. json: orderData
  265. }, {
  266. headers: {
  267. 'Authorization': `Bearer ${userToken}`
  268. }
  269. });
  270. console.debug('子商品订单创建响应状态码:', response.status);
  271. if (response.status !== 201) {
  272. const errorResult = await response.json();
  273. console.debug('子商品订单创建错误响应:', errorResult);
  274. }
  275. expect(response.status).toBe(201);
  276. if (response.status === 201) {
  277. const createdOrder = await response.json();
  278. // 验证订单创建成功
  279. expect(createdOrder.success).toBe(true);
  280. expect(createdOrder.orderId).toBeGreaterThan(0);
  281. // 查询订单商品明细,验证商品名称格式
  282. const dataSource = await IntegrationTestDatabase.getDataSource();
  283. const orderGoods = await dataSource.getRepository(OrderGoodsMt).find({
  284. where: { orderId: createdOrder.orderId, tenantId: 1 }
  285. });
  286. expect(orderGoods).toHaveLength(1);
  287. expect(orderGoods[0].goodsName).toBe('连衣裙 红色 大码');
  288. }
  289. });
  290. it('应该为单规格商品保持原有商品名称', async () => {
  291. // 创建必要的关联实体
  292. const testSupplier = await testFactory.createTestSupplier(testUser.id, { tenantId: 1 });
  293. const testMerchant = await testFactory.createTestMerchant(testUser.id, { tenantId: 1 });
  294. const testDeliveryAddress = await testFactory.createTestDeliveryAddress(testUser.id, { tenantId: 1 });
  295. // 创建单规格商品(spuId = 0)
  296. const singleSpecGoods = await testFactory.createTestGoods(testUser.id, {
  297. tenantId: 1,
  298. merchantId: testMerchant.id,
  299. supplierId: testSupplier.id,
  300. name: '单规格商品',
  301. spuId: 0
  302. });
  303. const orderData = {
  304. addressId: testDeliveryAddress.id,
  305. productOwn: '自营',
  306. consumeFrom: '积分兑换',
  307. products: [
  308. { id: singleSpecGoods.id, num: 1 }
  309. ]
  310. };
  311. const response = await client['create-order'].$post({
  312. json: orderData
  313. }, {
  314. headers: {
  315. 'Authorization': `Bearer ${userToken}`
  316. }
  317. });
  318. console.debug('单规格商品订单创建响应状态码:', response.status);
  319. expect(response.status).toBe(201);
  320. if (response.status === 201) {
  321. const createdOrder = await response.json();
  322. // 验证订单创建成功
  323. expect(createdOrder.success).toBe(true);
  324. expect(createdOrder.orderId).toBeGreaterThan(0);
  325. // 查询订单商品明细,验证商品名称保持不变
  326. const dataSource = await IntegrationTestDatabase.getDataSource();
  327. const orderGoods = await dataSource.getRepository(OrderGoodsMt).find({
  328. where: { orderId: createdOrder.orderId, tenantId: 1 }
  329. });
  330. expect(orderGoods).toHaveLength(1);
  331. expect(orderGoods[0].goodsName).toBe('单规格商品');
  332. }
  333. });
  334. });
  335. describe('取消订单功能验证', () => {
  336. it('应该成功取消未支付订单', async () => {
  337. // 创建未支付订单
  338. const order = await testFactory.createTestOrder(testUser.id, {
  339. tenantId: 1,
  340. payState: 0, // 未支付
  341. state: 0
  342. });
  343. const cancelData = {
  344. orderId: order.id,
  345. reason: '用户主动取消'
  346. };
  347. const response = await client['cancel-order'].$post({
  348. json: cancelData
  349. }, {
  350. headers: {
  351. 'Authorization': `Bearer ${userToken}`
  352. }
  353. });
  354. expect(response.status).toBe(200);
  355. if (response.status === 200) {
  356. const result = await response.json();
  357. expect(result.success).toBe(true);
  358. expect(result.message).toBe('订单取消成功');
  359. }
  360. // 验证订单状态已更新
  361. const dataSource = await IntegrationTestDatabase.getDataSource();
  362. const updatedOrder = await dataSource.getRepository(OrderMt).findOne({
  363. where: { id: order.id, tenantId: 1 }
  364. });
  365. expect(updatedOrder?.payState).toBe(5); // 订单关闭
  366. expect(updatedOrder?.cancelReason).toBe('用户主动取消');
  367. expect(updatedOrder?.cancelTime).toBeInstanceOf(Date);
  368. });
  369. it('应该成功取消已支付订单', async () => {
  370. // 创建已支付订单
  371. const order = await testFactory.createTestOrder(testUser.id, {
  372. tenantId: 1,
  373. payState: 2, // 支付成功
  374. state: 0
  375. });
  376. const cancelData = {
  377. orderId: order.id,
  378. reason: '用户主动取消(已支付)'
  379. };
  380. const response = await client['cancel-order'].$post({
  381. json: cancelData
  382. }, {
  383. headers: {
  384. 'Authorization': `Bearer ${userToken}`
  385. }
  386. });
  387. expect(response.status).toBe(200);
  388. if (response.status === 200) {
  389. const result = await response.json();
  390. expect(result.success).toBe(true);
  391. expect(result.message).toBe('订单取消成功');
  392. }
  393. // 验证订单状态已更新
  394. const dataSource = await IntegrationTestDatabase.getDataSource();
  395. const updatedOrder = await dataSource.getRepository(OrderMt).findOne({
  396. where: { id: order.id, tenantId: 1 }
  397. });
  398. expect(updatedOrder?.payState).toBe(5); // 订单关闭
  399. expect(updatedOrder?.cancelReason).toBe('用户主动取消(已支付)');
  400. expect(updatedOrder?.cancelTime).toBeInstanceOf(Date);
  401. });
  402. it('应该拒绝取消不允许的订单状态', async () => {
  403. // 创建已发货订单(支付状态=2,订单状态=1)
  404. const order = await testFactory.createTestOrder(testUser.id, {
  405. tenantId: 1,
  406. payState: 2, // 支付成功
  407. state: 1 // 已发货
  408. });
  409. const cancelData = {
  410. orderId: order.id,
  411. reason: '尝试取消已发货订单'
  412. };
  413. const response = await client['cancel-order'].$post({
  414. json: cancelData
  415. }, {
  416. headers: {
  417. 'Authorization': `Bearer ${userToken}`
  418. }
  419. });
  420. // 应该返回403,因为已发货订单不允许取消
  421. expect(response.status).toBe(403);
  422. if (response.status === 403) {
  423. const result = await response.json();
  424. expect(result.message).toBe('当前订单状态不允许取消');
  425. }
  426. });
  427. it('应该拒绝取消不存在的订单', async () => {
  428. const cancelData = {
  429. orderId: 99999, // 不存在的订单ID
  430. reason: '取消不存在的订单'
  431. };
  432. const response = await client['cancel-order'].$post({
  433. json: cancelData
  434. }, {
  435. headers: {
  436. 'Authorization': `Bearer ${userToken}`
  437. }
  438. });
  439. // 应该返回404
  440. expect(response.status).toBe(404);
  441. if (response.status === 404) {
  442. const result = await response.json();
  443. expect(result.message).toBe('订单不存在');
  444. }
  445. });
  446. it('应该拒绝跨租户取消订单', async () => {
  447. // 创建租户2的订单
  448. const otherTenantOrder = await testFactory.createTestOrder(otherTenantUser.id, {
  449. tenantId: 2,
  450. payState: 0
  451. });
  452. const cancelData = {
  453. orderId: otherTenantOrder.id,
  454. reason: '跨租户取消尝试'
  455. };
  456. const response = await client['cancel-order'].$post({
  457. json: cancelData
  458. }, {
  459. headers: {
  460. 'Authorization': `Bearer ${userToken}`
  461. }
  462. });
  463. // 应该返回404,因为订单不在当前租户
  464. expect(response.status).toBe(404);
  465. if (response.status === 404) {
  466. const result = await response.json();
  467. expect(result.message).toBe('订单不存在');
  468. }
  469. });
  470. it('应该拒绝跨用户取消订单', async () => {
  471. // 创建其他用户的订单(同一租户)
  472. const otherUserOrder = await testFactory.createTestOrder(otherUser.id, {
  473. tenantId: 1,
  474. payState: 0
  475. });
  476. const cancelData = {
  477. orderId: otherUserOrder.id,
  478. reason: '跨用户取消尝试'
  479. };
  480. const response = await client['cancel-order'].$post({
  481. json: cancelData
  482. }, {
  483. headers: {
  484. 'Authorization': `Bearer ${userToken}`
  485. }
  486. });
  487. // 应该返回404,因为无权访问其他用户的订单
  488. expect(response.status).toBe(404);
  489. const result = await response.json();
  490. expect(result.message).toBe('订单不存在');
  491. });
  492. });
  493. });