| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479 |
- import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
- import { DataSource } from 'typeorm';
- import { IntegrationTestDatabase, setupIntegrationDatabaseHooksWithEntities } from '@d8d/shared-test-util';
- import { UserEntityMt, RoleMt } from '@d8d/user-module-mt';
- import { DeliveryAddressMt } from '@d8d/delivery-address-module-mt';
- import { AreaEntityMt } from '@d8d/geo-areas-mt';
- import { MerchantMt } from '@d8d/merchant-module-mt';
- import { SupplierMt } from '@d8d/supplier-module-mt';
- import { FileMt } from '@d8d/file-module-mt';
- import { GoodsMt, GoodsCategoryMt } from '@d8d/goods-module-mt';
- import { OrderMt, OrderGoodsMt, OrderRefundMt } from '../../src/entities';
- import { OrderTimeoutSchedulerService } from '../../src/services/order-timeout-scheduler.service';
- import { OrdersTestFactory } from '../factories/orders-test-factory';
- // 设置集成测试钩子
- setupIntegrationDatabaseHooksWithEntities([
- UserEntityMt, RoleMt, OrderMt, OrderGoodsMt, OrderRefundMt, DeliveryAddressMt, MerchantMt, SupplierMt, FileMt, AreaEntityMt, GoodsMt, GoodsCategoryMt
- ])
- describe('订单超时调度器集成测试', () => {
- let dataSource: DataSource;
- let schedulerService: OrderTimeoutSchedulerService;
- let testFactory: OrdersTestFactory;
- let testUser: UserEntityMt;
- beforeEach(async () => {
- // 获取数据源
- dataSource = await IntegrationTestDatabase.getDataSource();
- // 创建测试工厂
- testFactory = new OrdersTestFactory(dataSource);
- // 创建测试用户
- testUser = await testFactory.createTestUser(1);
- // 创建调度器服务(特定租户)
- schedulerService = new OrderTimeoutSchedulerService(dataSource, 1);
- });
- afterEach(async () => {
- // 清理测试数据
- await testFactory.cleanup();
- });
- describe('超时订单检测逻辑', () => {
- it('应该正确识别24小时前创建的未支付订单为超时订单', async () => {
- // 创建一个未支付订单
- const testOrder = await testFactory.createTestOrder(testUser.id, {
- tenantId: 1,
- payState: 0, // 未支付
- state: 0, // 未发货
- expireTime: null // 无过期时间
- });
- // 更新订单创建时间为25小时前
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- const orderRepository = dataSource.getRepository(OrderMt);
- await orderRepository.update(
- { id: testOrder.id, tenantId: 1 },
- { createdAt: twentyFiveHoursAgo }
- );
- // 手动触发处理以检查是否能检测到超时订单
- const result = await schedulerService.triggerManualProcess(1);
- // 验证处理结果
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBeGreaterThan(0);
- expect(result.message).toContain('成功处理');
- // 验证订单状态已更新为关闭
- const updatedOrder = await orderRepository.findOne({
- where: { id: testOrder.id, tenantId: 1 }
- });
- expect(updatedOrder).toBeDefined();
- expect(updatedOrder!.payState).toBe(5); // 订单关闭
- expect(updatedOrder!.cancelReason).toBe('订单超时未支付,系统自动取消');
- expect(updatedOrder!.cancelTime).toBeDefined();
- });
- it('不应该识别23小时前创建的未支付订单为超时订单', async () => {
- // 创建一个23小时前创建的未支付订单(不到24小时)
- const twentyThreeHoursAgo = new Date();
- twentyThreeHoursAgo.setHours(twentyThreeHoursAgo.getHours() - 23);
- await testFactory.createTestOrder(testUser.id, {
- tenantId: 1,
- createdAt: twentyThreeHoursAgo,
- payState: 0, // 未支付
- state: 0 // 未发货
- });
- // 手动触发处理
- const result = await schedulerService.triggerManualProcess(1);
- // 验证没有处理任何订单
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBe(0);
- });
- it('不应该识别已支付的订单为超时订单', async () => {
- // 创建一个25小时前创建的已支付订单
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- const testOrder = await testFactory.createTestOrder(testUser.id, {
- tenantId: 1,
- createdAt: twentyFiveHoursAgo,
- payState: 2, // 已支付
- state: 0 // 未发货
- });
- // 手动触发处理
- const result = await schedulerService.triggerManualProcess(1);
- // 验证没有处理已支付订单
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBe(0);
- // 验证订单状态未改变
- const orderRepository = dataSource.getRepository(OrderMt);
- const updatedOrder = await orderRepository.findOne({
- where: { id: testOrder.id, tenantId: 1 }
- });
- expect(updatedOrder).toBeDefined();
- expect(updatedOrder!.payState).toBe(2); // 保持已支付状态
- });
- it('不应该识别已发货的订单为超时订单', async () => {
- // 创建一个25小时前创建的未支付但已发货的订单
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- const testOrder = await testFactory.createTestOrder(testUser.id, {
- tenantId: 1,
- createdAt: twentyFiveHoursAgo,
- payState: 0, // 未支付
- state: 1 // 已发货
- });
- // 手动触发处理
- const result = await schedulerService.triggerManualProcess(1);
- // 验证没有处理已发货订单
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBe(0);
- });
- it('应该考虑订单过期时间(expireTime)', async () => {
- // 创建一个25小时前创建的订单,已设置过期时间为10小时前
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- const tenHoursAgo = new Date();
- tenHoursAgo.setHours(tenHoursAgo.getHours() - 10);
- const testOrder = await testFactory.createTestOrder(testUser.id, {
- tenantId: 1,
- createdAt: twentyFiveHoursAgo,
- payState: 0, // 未支付
- state: 0, // 未发货
- expireTime: tenHoursAgo // 已过期
- });
- // 手动触发处理
- const result = await schedulerService.triggerManualProcess(1);
- // 验证处理了过期订单(创建时间超过24小时且已过期)
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBe(1);
- });
- it('不应该处理未来过期时间的订单', async () => {
- // 创建一个25小时前创建的订单,但过期时间在未来
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- const futureTime = new Date();
- futureTime.setHours(futureTime.getHours() + 1); // 1小时后过期
- await testFactory.createTestOrder(testUser.id, {
- tenantId: 1,
- createdAt: twentyFiveHoursAgo,
- payState: 0, // 未支付
- state: 0, // 未发货
- expireTime: futureTime // 未来才过期
- });
- // 手动触发处理
- const result = await schedulerService.triggerManualProcess(1);
- // 验证没有处理(因为过期时间还没到)
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBe(0);
- });
- });
- describe('手动触发处理功能', () => {
- it('应该能够手动触发处理指定租户的超时订单', async () => {
- // 为租户1创建超时订单
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- const order1 = await testFactory.createTestOrder(testUser.id, {
- tenantId: 1,
- createdAt: twentyFiveHoursAgo,
- payState: 0,
- state: 0
- });
- // 手动触发处理租户1
- const result = await schedulerService.triggerManualProcess(1);
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBe(1);
- expect(result.message).toContain('成功处理 1 个超时订单');
- });
- it('应该正确处理多个超时订单', async () => {
- // 创建多个超时订单
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- // 创建3个超时订单
- for (let i = 0; i < 3; i++) {
- await testFactory.createTestOrder(testUser.id, {
- tenantId: 1,
- createdAt: twentyFiveHoursAgo,
- payState: 0,
- state: 0
- });
- }
- // 手动触发处理
- const result = await schedulerService.triggerManualProcess(1);
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBe(3);
- });
- it('应该处理订单状态在检查期间发生变化的情况', async () => {
- // 创建一个超时订单
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- const testOrder = await testFactory.createTestOrder(testUser.id, {
- tenantId: 1,
- createdAt: twentyFiveHoursAgo,
- payState: 0,
- state: 0
- });
- // 在手动处理前,先手动支付订单
- const orderRepository = dataSource.getRepository(OrderMt);
- await orderRepository.update(
- { id: testOrder.id, tenantId: 1 },
- { payState: 2, updatedAt: new Date() }
- );
- // 手动触发处理
- const result = await schedulerService.triggerManualProcess(1);
- // 验证没有处理已支付的订单
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBe(0);
- // 验证订单状态仍然是已支付
- const updatedOrder = await orderRepository.findOne({
- where: { id: testOrder.id, tenantId: 1 }
- });
- expect(updatedOrder!.payState).toBe(2);
- });
- it('应该恢复超时订单的商品库存', async () => {
- // 创建测试商品
- const testGoods = await testFactory.createTestGoods(testUser.id, {
- tenantId: 1,
- stock: 10
- });
- // 创建超时订单
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- const testOrder = await testFactory.createTestOrder(testUser.id, {
- tenantId: 1,
- createdAt: twentyFiveHoursAgo,
- payState: 0,
- state: 0
- });
- // 创建订单商品(购买2个)
- await testFactory.createTestOrderGoods(testOrder.id, testGoods.id, {
- tenantId: 1,
- num: 2
- });
- // 手动触发处理
- const result = await schedulerService.triggerManualProcess(1);
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBe(1);
- // 验证商品库存已恢复
- const goodsRepository = dataSource.getRepository(GoodsMt);
- const updatedGoods = await goodsRepository.findOne({
- where: { id: testGoods.id, tenantId: 1 }
- });
- expect(Number(updatedGoods!.stock)).toBe(12); // 原库存10 + 退回2 = 12
- });
- it('处理失败时应该返回错误信息', async () => {
- // 创建未指定租户的调度器(tenantId为null)
- const invalidScheduler = new OrderTimeoutSchedulerService(dataSource);
- // 尝试处理但不指定租户ID
- const result = await invalidScheduler.triggerManualProcess();
- expect(result.success).toBe(false);
- expect(result.processedOrders).toBe(0);
- expect(result.message).toContain('未指定租户ID');
- });
- });
- describe('调度器生命周期管理', () => {
- it('应该能够成功启动和停止调度器', async () => {
- // 启动调度器
- await schedulerService.start();
- // 验证调度器状态
- const status = schedulerService.getStatus();
- expect(status.isRunning).toBe(true);
- expect(status.tenantId).toBe(1);
- expect(status.checkInterval).toBe('*/5 * * * *'); // 默认检查间隔
- // 停止调度器
- await schedulerService.stop();
- // 验证调度器已停止
- const stoppedStatus = schedulerService.getStatus();
- expect(stoppedStatus.isRunning).toBe(false);
- });
- it('重复启动调度器应该抛出错误', async () => {
- // 第一次启动
- await schedulerService.start();
- // 第二次启动应该失败
- await expect(schedulerService.start()).rejects.toThrow('订单超时调度器已经在运行中');
- // 清理:停止调度器
- await schedulerService.stop();
- });
- it('停止未运行的调度器应该抛出错误', async () => {
- await expect(schedulerService.stop()).rejects.toThrow('订单超时调度器未在运行中');
- });
- it('健康检查应该返回正确的状态', async () => {
- // 调度器未启动时的健康检查
- let health = await schedulerService.healthCheck();
- expect(health.healthy).toBe(false);
- expect(health.isRunning).toBe(false);
- expect(health.timestamp).toBeInstanceOf(Date);
- // 启动调度器后的健康检查
- await schedulerService.start();
- health = await schedulerService.healthCheck();
- expect(health.healthy).toBe(true);
- expect(health.isRunning).toBe(true);
- // 停止调度器
- await schedulerService.stop();
- });
- });
- describe('租户数据隔离', () => {
- it('应该只处理指定租户的超时订单', async () => {
- // 创建租户1的用户和订单
- const tenant1User = await testFactory.createTestUser(1);
- const tenant2User = await testFactory.createTestUser(2);
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- // 租户1的超时订单
- await testFactory.createTestOrder(tenant1User.id, {
- tenantId: 1,
- createdAt: twentyFiveHoursAgo,
- payState: 0,
- state: 0
- });
- // 租户2的超时订单
- await testFactory.createTestOrder(tenant2User.id, {
- tenantId: 2,
- createdAt: twentyFiveHoursAgo,
- payState: 0,
- state: 0
- });
- // 创建租户1的调度器
- const tenant1Scheduler = new OrderTimeoutSchedulerService(dataSource, 1);
- // 处理租户1
- const result = await tenant1Scheduler.triggerManualProcess(1);
- // 验证只处理了租户1的订单
- expect(result.success).toBe(true);
- expect(result.processedOrders).toBe(1);
- // 验证租户2的订单未被处理
- const orderRepository = dataSource.getRepository(OrderMt);
- const tenant2Orders = await orderRepository.find({
- where: { tenantId: 2, payState: 0 }
- });
- expect(tenant2Orders.length).toBe(1); // 租户2的订单仍然是未支付状态
- });
- it('应该能够处理多个租户的超时订单', async () => {
- // 创建多个租户的用户和订单
- const tenant1User = await testFactory.createTestUser(1);
- const tenant2User = await testFactory.createTestUser(2);
- const tenant3User = await testFactory.createTestUser(3);
- const twentyFiveHoursAgo = new Date();
- twentyFiveHoursAgo.setHours(twentyFiveHoursAgo.getHours() - 25);
- // 每个租户创建一个超时订单
- await testFactory.createTestOrder(tenant1User.id, {
- tenantId: 1,
- createdAt: twentyFiveHoursAgo,
- payState: 0,
- state: 0
- });
- await testFactory.createTestOrder(tenant2User.id, {
- tenantId: 2,
- createdAt: twentyFiveHoursAgo,
- payState: 0,
- state: 0
- });
- await testFactory.createTestOrder(tenant3User.id, {
- tenantId: 3,
- createdAt: twentyFiveHoursAgo,
- payState: 0,
- state: 0
- });
- // 创建未指定租户的调度器(可以处理多个租户)
- const globalScheduler = new OrderTimeoutSchedulerService(dataSource);
- // 为每个租户单独处理
- const result1 = await globalScheduler.triggerManualProcess(1);
- const result2 = await globalScheduler.triggerManualProcess(2);
- const result3 = await globalScheduler.triggerManualProcess(3);
- // 验证每个租户都处理成功
- expect(result1.success).toBe(true);
- expect(result2.success).toBe(true);
- expect(result3.success).toBe(true);
- expect(result1.processedOrders + result2.processedOrders + result3.processedOrders).toBe(3);
- // 验证所有订单都被关闭
- const orderRepository = dataSource.getRepository(OrderMt);
- const openOrders = await orderRepository.find({
- where: { payState: 0 }
- });
- expect(openOrders.length).toBe(0); // 所有未支付订单都应被关闭
- });
- });
- });
|