order-timeout-scheduler.integration.test.ts 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
  2. import { DataSource } from 'typeorm';
  3. import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
  4. import { UserEntityMt, RoleMt } from '@d8d/user-module-mt';
  5. import { DeliveryAddressMt } from '@d8d/delivery-address-module-mt';
  6. import { AreaEntityMt } from '@d8d/geo-areas-mt';
  7. import { MerchantMt } from '@d8d/merchant-module-mt';
  8. import { SupplierMt } from '@d8d/supplier-module-mt';
  9. import { FileMt } from '@d8d/file-module-mt';
  10. import { GoodsMt, GoodsCategoryMt } from '@d8d/goods-module-mt';
  11. import { OrderMt, OrderGoodsMt, OrderRefundMt } from '../../src/entities';
  12. import { OrderTimeoutSchedulerService } from '../../src/services/order-timeout-scheduler.service';
  13. import { OrdersTestFactory } from '../factories/orders-test-factory';
  14. // 设置集成测试钩子
  15. setupIntegrationDatabaseHooksWithEntities([
  16. UserEntityMt, RoleMt, OrderMt, OrderGoodsMt, OrderRefundMt, DeliveryAddressMt, MerchantMt, SupplierMt, FileMt, AreaEntityMt, GoodsMt, GoodsCategoryMt
  17. ])
  18. describe('订单超时调度器集成测试', () => {
  19. let dataSource: DataSource;
  20. let schedulerService: OrderTimeoutSchedulerService;
  21. let testFactory: OrdersTestFactory;
  22. let testUser: UserEntityMt;
  23. beforeEach(async () => {
  24. // 获取数据源
  25. dataSource = await IntegrationTestDatabase.getDataSource();
  26. // 创建测试工厂
  27. testFactory = new OrdersTestFactory(dataSource);
  28. // 创建测试用户
  29. testUser = await testFactory.createTestUser(1);
  30. // 创建调度器服务(特定租户)
  31. schedulerService = new OrderTimeoutSchedulerService(dataSource, 1);
  32. });
  33. afterEach(async () => {
  34. // 清理测试数据
  35. await testFactory.cleanup();
  36. });
  37. describe('超时订单检测逻辑', () => {
  38. it('应该正确识别24小时前创建的未支付订单为超时订单', async () => {
  39. // 创建一个未支付订单
  40. const testOrder = await testFactory.createTestOrder(testUser.id, {
  41. tenantId: 1,
  42. payState: 0, // 未支付
  43. state: 0, // 未发货
  44. expireTime: null // 无过期时间
  45. });
  46. // 更新订单创建时间为25小时前
  47. const twentyFiveHoursAgo = new Date();
  48. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  49. const orderRepository = dataSource.getRepository(OrderMt);
  50. await orderRepository.update(
  51. { id: testOrder.id, tenantId: 1 },
  52. { createdAt: twentyFiveHoursAgo }
  53. );
  54. // 手动触发处理以检查是否能检测到超时订单
  55. const result = await schedulerService.triggerManualProcess(1);
  56. // 验证处理结果
  57. expect(result.success).toBe(true);
  58. expect(result.processedOrders).toBeGreaterThan(0);
  59. expect(result.message).toContain('成功处理');
  60. // 验证订单状态已更新为关闭
  61. const updatedOrder = await orderRepository.findOne({
  62. where: { id: testOrder.id, tenantId: 1 }
  63. });
  64. expect(updatedOrder).toBeDefined();
  65. expect(updatedOrder!.payState).toBe(5); // 订单关闭
  66. expect(updatedOrder!.cancelReason).toBe('订单超时未支付,系统自动取消');
  67. expect(updatedOrder!.cancelTime).toBeDefined();
  68. });
  69. it('不应该识别23小时前创建的未支付订单为超时订单', async () => {
  70. // 创建一个23小时前创建的未支付订单(不到24小时)
  71. const twentyThreeHoursAgo = new Date();
  72. twentyThreeHoursAgo.setHours(twentyThreeHoursAgo.getHours() - 23);
  73. await testFactory.createTestOrder(testUser.id, {
  74. tenantId: 1,
  75. createdAt: twentyThreeHoursAgo,
  76. payState: 0, // 未支付
  77. state: 0 // 未发货
  78. });
  79. // 手动触发处理
  80. const result = await schedulerService.triggerManualProcess(1);
  81. // 验证没有处理任何订单
  82. expect(result.success).toBe(true);
  83. expect(result.processedOrders).toBe(0);
  84. });
  85. it('不应该识别已支付的订单为超时订单', async () => {
  86. // 创建一个25小时前创建的已支付订单
  87. const twentyFiveHoursAgo = new Date();
  88. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  89. const testOrder = await testFactory.createTestOrder(testUser.id, {
  90. tenantId: 1,
  91. createdAt: twentyFiveHoursAgo,
  92. payState: 2, // 已支付
  93. state: 0 // 未发货
  94. });
  95. // 手动触发处理
  96. const result = await schedulerService.triggerManualProcess(1);
  97. // 验证没有处理已支付订单
  98. expect(result.success).toBe(true);
  99. expect(result.processedOrders).toBe(0);
  100. // 验证订单状态未改变
  101. const orderRepository = dataSource.getRepository(OrderMt);
  102. const updatedOrder = await orderRepository.findOne({
  103. where: { id: testOrder.id, tenantId: 1 }
  104. });
  105. expect(updatedOrder).toBeDefined();
  106. expect(updatedOrder!.payState).toBe(2); // 保持已支付状态
  107. });
  108. it('不应该识别已发货的订单为超时订单', async () => {
  109. // 创建一个25小时前创建的未支付但已发货的订单
  110. const twentyFiveHoursAgo = new Date();
  111. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  112. const testOrder = await testFactory.createTestOrder(testUser.id, {
  113. tenantId: 1,
  114. createdAt: twentyFiveHoursAgo,
  115. payState: 0, // 未支付
  116. state: 1 // 已发货
  117. });
  118. // 手动触发处理
  119. const result = await schedulerService.triggerManualProcess(1);
  120. // 验证没有处理已发货订单
  121. expect(result.success).toBe(true);
  122. expect(result.processedOrders).toBe(0);
  123. });
  124. it('应该考虑订单过期时间(expireTime)', async () => {
  125. // 创建一个25小时前创建的订单,已设置过期时间为10小时前
  126. const twentyFiveHoursAgo = new Date();
  127. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  128. const tenHoursAgo = new Date();
  129. tenHoursAgo.setHours(tenHoursAgo.getHours() - 10);
  130. const testOrder = await testFactory.createTestOrder(testUser.id, {
  131. tenantId: 1,
  132. createdAt: twentyFiveHoursAgo,
  133. payState: 0, // 未支付
  134. state: 0, // 未发货
  135. expireTime: tenHoursAgo // 已过期
  136. });
  137. // 手动触发处理
  138. const result = await schedulerService.triggerManualProcess(1);
  139. // 验证处理了过期订单(创建时间超过24小时且已过期)
  140. expect(result.success).toBe(true);
  141. expect(result.processedOrders).toBe(1);
  142. });
  143. it('不应该处理未来过期时间的订单', async () => {
  144. // 创建一个25小时前创建的订单,但过期时间在未来
  145. const twentyFiveHoursAgo = new Date();
  146. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  147. const futureTime = new Date();
  148. futureTime.setHours(futureTime.getHours() + 1); // 1小时后过期
  149. await testFactory.createTestOrder(testUser.id, {
  150. tenantId: 1,
  151. createdAt: twentyFiveHoursAgo,
  152. payState: 0, // 未支付
  153. state: 0, // 未发货
  154. expireTime: futureTime // 未来才过期
  155. });
  156. // 手动触发处理
  157. const result = await schedulerService.triggerManualProcess(1);
  158. // 验证没有处理(因为过期时间还没到)
  159. expect(result.success).toBe(true);
  160. expect(result.processedOrders).toBe(0);
  161. });
  162. });
  163. describe('手动触发处理功能', () => {
  164. it('应该能够手动触发处理指定租户的超时订单', async () => {
  165. // 为租户1创建超时订单
  166. const twentyFiveHoursAgo = new Date();
  167. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  168. const order1 = await testFactory.createTestOrder(testUser.id, {
  169. tenantId: 1,
  170. createdAt: twentyFiveHoursAgo,
  171. payState: 0,
  172. state: 0
  173. });
  174. // 手动触发处理租户1
  175. const result = await schedulerService.triggerManualProcess(1);
  176. expect(result.success).toBe(true);
  177. expect(result.processedOrders).toBe(1);
  178. expect(result.message).toContain('成功处理 1 个超时订单');
  179. });
  180. it('应该正确处理多个超时订单', async () => {
  181. // 创建多个超时订单
  182. const twentyFiveHoursAgo = new Date();
  183. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  184. // 创建3个超时订单
  185. for (let i = 0; i < 3; i++) {
  186. await testFactory.createTestOrder(testUser.id, {
  187. tenantId: 1,
  188. createdAt: twentyFiveHoursAgo,
  189. payState: 0,
  190. state: 0
  191. });
  192. }
  193. // 手动触发处理
  194. const result = await schedulerService.triggerManualProcess(1);
  195. expect(result.success).toBe(true);
  196. expect(result.processedOrders).toBe(3);
  197. });
  198. it('应该处理订单状态在检查期间发生变化的情况', async () => {
  199. // 创建一个超时订单
  200. const twentyFiveHoursAgo = new Date();
  201. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  202. const testOrder = await testFactory.createTestOrder(testUser.id, {
  203. tenantId: 1,
  204. createdAt: twentyFiveHoursAgo,
  205. payState: 0,
  206. state: 0
  207. });
  208. // 在手动处理前,先手动支付订单
  209. const orderRepository = dataSource.getRepository(OrderMt);
  210. await orderRepository.update(
  211. { id: testOrder.id, tenantId: 1 },
  212. { payState: 2, updatedAt: new Date() }
  213. );
  214. // 手动触发处理
  215. const result = await schedulerService.triggerManualProcess(1);
  216. // 验证没有处理已支付的订单
  217. expect(result.success).toBe(true);
  218. expect(result.processedOrders).toBe(0);
  219. // 验证订单状态仍然是已支付
  220. const updatedOrder = await orderRepository.findOne({
  221. where: { id: testOrder.id, tenantId: 1 }
  222. });
  223. expect(updatedOrder!.payState).toBe(2);
  224. });
  225. it('应该恢复超时订单的商品库存', async () => {
  226. // 创建测试商品
  227. const testGoods = await testFactory.createTestGoods(testUser.id, {
  228. tenantId: 1,
  229. stock: 10
  230. });
  231. // 创建超时订单
  232. const twentyFiveHoursAgo = new Date();
  233. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  234. const testOrder = await testFactory.createTestOrder(testUser.id, {
  235. tenantId: 1,
  236. createdAt: twentyFiveHoursAgo,
  237. payState: 0,
  238. state: 0
  239. });
  240. // 创建订单商品(购买2个)
  241. await testFactory.createTestOrderGoods(testOrder.id, testGoods.id, {
  242. tenantId: 1,
  243. num: 2
  244. });
  245. // 手动触发处理
  246. const result = await schedulerService.triggerManualProcess(1);
  247. expect(result.success).toBe(true);
  248. expect(result.processedOrders).toBe(1);
  249. // 验证商品库存已恢复
  250. const goodsRepository = dataSource.getRepository(GoodsMt);
  251. const updatedGoods = await goodsRepository.findOne({
  252. where: { id: testGoods.id, tenantId: 1 }
  253. });
  254. expect(Number(updatedGoods!.stock)).toBe(12); // 原库存10 + 退回2 = 12
  255. });
  256. it('处理失败时应该返回错误信息', async () => {
  257. // 创建未指定租户的调度器(tenantId为null)
  258. const invalidScheduler = new OrderTimeoutSchedulerService(dataSource);
  259. // 尝试处理但不指定租户ID
  260. const result = await invalidScheduler.triggerManualProcess();
  261. expect(result.success).toBe(false);
  262. expect(result.processedOrders).toBe(0);
  263. expect(result.message).toContain('未指定租户ID');
  264. });
  265. });
  266. describe('调度器生命周期管理', () => {
  267. it('应该能够成功启动和停止调度器', async () => {
  268. // 启动调度器
  269. await schedulerService.start();
  270. // 验证调度器状态
  271. const status = schedulerService.getStatus();
  272. expect(status.isRunning).toBe(true);
  273. expect(status.tenantId).toBe(1);
  274. expect(status.checkInterval).toBe('*/5 * * * *'); // 默认检查间隔
  275. // 停止调度器
  276. await schedulerService.stop();
  277. // 验证调度器已停止
  278. const stoppedStatus = schedulerService.getStatus();
  279. expect(stoppedStatus.isRunning).toBe(false);
  280. });
  281. it('重复启动调度器应该抛出错误', async () => {
  282. // 第一次启动
  283. await schedulerService.start();
  284. // 第二次启动应该失败
  285. await expect(schedulerService.start()).rejects.toThrow('订单超时调度器已经在运行中');
  286. // 清理:停止调度器
  287. await schedulerService.stop();
  288. });
  289. it('停止未运行的调度器应该抛出错误', async () => {
  290. await expect(schedulerService.stop()).rejects.toThrow('订单超时调度器未在运行中');
  291. });
  292. it('健康检查应该返回正确的状态', async () => {
  293. // 调度器未启动时的健康检查
  294. let health = await schedulerService.healthCheck();
  295. expect(health.healthy).toBe(false);
  296. expect(health.isRunning).toBe(false);
  297. expect(health.timestamp).toBeInstanceOf(Date);
  298. // 启动调度器后的健康检查
  299. await schedulerService.start();
  300. health = await schedulerService.healthCheck();
  301. expect(health.healthy).toBe(true);
  302. expect(health.isRunning).toBe(true);
  303. // 停止调度器
  304. await schedulerService.stop();
  305. });
  306. });
  307. describe('租户数据隔离', () => {
  308. it('应该只处理指定租户的超时订单', async () => {
  309. // 创建租户1的用户和订单
  310. const tenant1User = await testFactory.createTestUser(1);
  311. const tenant2User = await testFactory.createTestUser(2);
  312. const twentyFiveHoursAgo = new Date();
  313. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  314. // 租户1的超时订单
  315. await testFactory.createTestOrder(tenant1User.id, {
  316. tenantId: 1,
  317. createdAt: twentyFiveHoursAgo,
  318. payState: 0,
  319. state: 0
  320. });
  321. // 租户2的超时订单
  322. await testFactory.createTestOrder(tenant2User.id, {
  323. tenantId: 2,
  324. createdAt: twentyFiveHoursAgo,
  325. payState: 0,
  326. state: 0
  327. });
  328. // 创建租户1的调度器
  329. const tenant1Scheduler = new OrderTimeoutSchedulerService(dataSource, 1);
  330. // 处理租户1
  331. const result = await tenant1Scheduler.triggerManualProcess(1);
  332. // 验证只处理了租户1的订单
  333. expect(result.success).toBe(true);
  334. expect(result.processedOrders).toBe(1);
  335. // 验证租户2的订单未被处理
  336. const orderRepository = dataSource.getRepository(OrderMt);
  337. const tenant2Orders = await orderRepository.find({
  338. where: { tenantId: 2, payState: 0 }
  339. });
  340. expect(tenant2Orders.length).toBe(1); // 租户2的订单仍然是未支付状态
  341. });
  342. it('应该能够处理多个租户的超时订单', async () => {
  343. // 创建多个租户的用户和订单
  344. const tenant1User = await testFactory.createTestUser(1);
  345. const tenant2User = await testFactory.createTestUser(2);
  346. const tenant3User = await testFactory.createTestUser(3);
  347. const twentyFiveHoursAgo = new Date();
  348. twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
  349. // 每个租户创建一个超时订单
  350. await testFactory.createTestOrder(tenant1User.id, {
  351. tenantId: 1,
  352. createdAt: twentyFiveHoursAgo,
  353. payState: 0,
  354. state: 0
  355. });
  356. await testFactory.createTestOrder(tenant2User.id, {
  357. tenantId: 2,
  358. createdAt: twentyFiveHoursAgo,
  359. payState: 0,
  360. state: 0
  361. });
  362. await testFactory.createTestOrder(tenant3User.id, {
  363. tenantId: 3,
  364. createdAt: twentyFiveHoursAgo,
  365. payState: 0,
  366. state: 0
  367. });
  368. // 创建未指定租户的调度器(可以处理多个租户)
  369. const globalScheduler = new OrderTimeoutSchedulerService(dataSource);
  370. // 为每个租户单独处理
  371. const result1 = await globalScheduler.triggerManualProcess(1);
  372. const result2 = await globalScheduler.triggerManualProcess(2);
  373. const result3 = await globalScheduler.triggerManualProcess(3);
  374. // 验证每个租户都处理成功
  375. expect(result1.success).toBe(true);
  376. expect(result2.success).toBe(true);
  377. expect(result3.success).toBe(true);
  378. expect(result1.processedOrders + result2.processedOrders + result3.processedOrders).toBe(3);
  379. // 验证所有订单都被关闭
  380. const orderRepository = dataSource.getRepository(OrderMt);
  381. const openOrders = await orderRepository.find({
  382. where: { payState: 0 }
  383. });
  384. expect(openOrders.length).toBe(0); // 所有未支付订单都应被关闭
  385. });
  386. });
  387. });