|
@@ -1,31 +1,31 @@
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
-import { DataSource } from 'typeorm';
|
|
|
|
|
|
|
+import { DataSource, Repository } from 'typeorm';
|
|
|
import * as cron from 'node-cron';
|
|
import * as cron from 'node-cron';
|
|
|
import { DelaySchedulerService } from '../../src/services/delay-scheduler.service';
|
|
import { DelaySchedulerService } from '../../src/services/delay-scheduler.service';
|
|
|
import type { FeieApiConfig } from '../../src/types/feie.types';
|
|
import type { FeieApiConfig } from '../../src/types/feie.types';
|
|
|
|
|
+import { PrintTaskService } from '../../src/services/print-task.service';
|
|
|
|
|
+import { OrderMt } from '@d8d/orders-module-mt';
|
|
|
|
|
+import { FeiePrintTaskMt } from '../../src/entities';
|
|
|
|
|
|
|
|
-// Mock dependencies
|
|
|
|
|
-vi.mock('../../src/services/print-task.service', () => {
|
|
|
|
|
|
|
+// Mock node-cron
|
|
|
|
|
+vi.mock('node-cron', () => {
|
|
|
return {
|
|
return {
|
|
|
- PrintTaskService: vi.fn().mockImplementation(() => ({
|
|
|
|
|
- getPendingDelayedTasks: vi.fn().mockResolvedValue([]),
|
|
|
|
|
- executePrintTask: vi.fn().mockResolvedValue({})
|
|
|
|
|
- }))
|
|
|
|
|
|
|
+ schedule: vi.fn()
|
|
|
};
|
|
};
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-vi.mock('node-cron', () => {
|
|
|
|
|
|
|
+// Mock PrintTaskService
|
|
|
|
|
+vi.mock('../../src/services/print-task.service', () => {
|
|
|
return {
|
|
return {
|
|
|
- schedule: vi.fn().mockReturnValue({
|
|
|
|
|
- start: vi.fn(),
|
|
|
|
|
- stop: vi.fn()
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ PrintTaskService: vi.fn()
|
|
|
};
|
|
};
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
describe('DelaySchedulerService', () => {
|
|
describe('DelaySchedulerService', () => {
|
|
|
- let delaySchedulerService: DelaySchedulerService;
|
|
|
|
|
|
|
+ let service: DelaySchedulerService;
|
|
|
let mockDataSource: DataSource;
|
|
let mockDataSource: DataSource;
|
|
|
|
|
+ let mockPrintTaskService: PrintTaskService;
|
|
|
|
|
+ let mockOrderRepository: Repository<OrderMt>;
|
|
|
const mockFeieConfig: FeieApiConfig = {
|
|
const mockFeieConfig: FeieApiConfig = {
|
|
|
baseUrl: 'http://api.feieyun.cn/Api/Open/',
|
|
baseUrl: 'http://api.feieyun.cn/Api/Open/',
|
|
|
user: 'test_user',
|
|
user: 'test_user',
|
|
@@ -36,13 +36,33 @@ describe('DelaySchedulerService', () => {
|
|
|
vi.clearAllMocks();
|
|
vi.clearAllMocks();
|
|
|
vi.useFakeTimers();
|
|
vi.useFakeTimers();
|
|
|
|
|
|
|
|
|
|
+ // Mock PrintTaskService
|
|
|
|
|
+ mockPrintTaskService = {
|
|
|
|
|
+ getPendingDelayedTasks: vi.fn(),
|
|
|
|
|
+ executePrintTask: vi.fn(),
|
|
|
|
|
+ cancelPrintTask: vi.fn()
|
|
|
|
|
+ } as any;
|
|
|
|
|
+
|
|
|
|
|
+ // Mock Order Repository
|
|
|
|
|
+ mockOrderRepository = {
|
|
|
|
|
+ findOne: vi.fn()
|
|
|
|
|
+ } as any;
|
|
|
|
|
+
|
|
|
|
|
+ // Mock DataSource
|
|
|
mockDataSource = {
|
|
mockDataSource = {
|
|
|
- getRepository: vi.fn().mockImplementation(() => {
|
|
|
|
|
- throw new Error('Repository not found');
|
|
|
|
|
|
|
+ getRepository: vi.fn((entity) => {
|
|
|
|
|
+ if (entity === OrderMt) {
|
|
|
|
|
+ return mockOrderRepository;
|
|
|
|
|
+ }
|
|
|
|
|
+ return {} as any;
|
|
|
}),
|
|
}),
|
|
|
- query: vi.fn().mockResolvedValue([])
|
|
|
|
|
|
|
+ query: vi.fn()
|
|
|
} as any;
|
|
} as any;
|
|
|
- delaySchedulerService = new DelaySchedulerService(mockDataSource, mockFeieConfig);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // Mock PrintTaskService constructor
|
|
|
|
|
+ vi.mocked(PrintTaskService).mockImplementation(() => mockPrintTaskService);
|
|
|
|
|
+
|
|
|
|
|
+ service = new DelaySchedulerService(mockDataSource, mockFeieConfig);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
afterEach(() => {
|
|
@@ -51,26 +71,46 @@ describe('DelaySchedulerService', () => {
|
|
|
|
|
|
|
|
describe('constructor', () => {
|
|
describe('constructor', () => {
|
|
|
it('应该创建调度器实例', () => {
|
|
it('应该创建调度器实例', () => {
|
|
|
- expect(delaySchedulerService).toBeInstanceOf(DelaySchedulerService);
|
|
|
|
|
|
|
+ expect(service).toBeInstanceOf(DelaySchedulerService);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
it('应该设置默认延迟时间为120秒', () => {
|
|
it('应该设置默认延迟时间为120秒', () => {
|
|
|
- expect(delaySchedulerService.getDefaultDelaySeconds()).toBe(120);
|
|
|
|
|
|
|
+ expect(service.getDefaultDelaySeconds()).toBe(120);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('应该初始化PrintTaskService', () => {
|
|
|
|
|
+ expect(PrintTaskService).toHaveBeenCalledWith(mockDataSource, mockFeieConfig);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('应该尝试获取订单仓库', () => {
|
|
|
|
|
+ expect(mockDataSource.getRepository).toHaveBeenCalledWith(OrderMt);
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
describe('start', () => {
|
|
describe('start', () => {
|
|
|
it('应该启动调度器', async () => {
|
|
it('应该启动调度器', async () => {
|
|
|
- await delaySchedulerService.start();
|
|
|
|
|
|
|
+ const mockCronJob = {
|
|
|
|
|
+ start: vi.fn(),
|
|
|
|
|
+ stop: vi.fn()
|
|
|
|
|
+ };
|
|
|
|
|
+ vi.mocked(cron.schedule).mockReturnValue(mockCronJob as any);
|
|
|
|
|
+
|
|
|
|
|
+ await service.start();
|
|
|
|
|
|
|
|
expect(cron.schedule).toHaveBeenCalledWith('*/30 * * * * *', expect.any(Function));
|
|
expect(cron.schedule).toHaveBeenCalledWith('*/30 * * * * *', expect.any(Function));
|
|
|
- expect(delaySchedulerService.getStatus().isRunning).toBe(true);
|
|
|
|
|
|
|
+ expect(service.getStatus().isRunning).toBe(true);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
it('应该在调度器已在运行时抛出错误', async () => {
|
|
it('应该在调度器已在运行时抛出错误', async () => {
|
|
|
- await delaySchedulerService.start();
|
|
|
|
|
|
|
+ const mockCronJob = {
|
|
|
|
|
+ start: vi.fn(),
|
|
|
|
|
+ stop: vi.fn()
|
|
|
|
|
+ };
|
|
|
|
|
+ vi.mocked(cron.schedule).mockReturnValue(mockCronJob as any);
|
|
|
|
|
+
|
|
|
|
|
+ await service.start();
|
|
|
|
|
|
|
|
- await expect(delaySchedulerService.start())
|
|
|
|
|
|
|
+ await expect(service.start())
|
|
|
.rejects
|
|
.rejects
|
|
|
.toThrow('调度器已经在运行中');
|
|
.toThrow('调度器已经在运行中');
|
|
|
});
|
|
});
|
|
@@ -78,14 +118,21 @@ describe('DelaySchedulerService', () => {
|
|
|
|
|
|
|
|
describe('stop', () => {
|
|
describe('stop', () => {
|
|
|
it('应该停止调度器', async () => {
|
|
it('应该停止调度器', async () => {
|
|
|
- await delaySchedulerService.start();
|
|
|
|
|
- await delaySchedulerService.stop();
|
|
|
|
|
|
|
+ const mockCronJob = {
|
|
|
|
|
+ start: vi.fn(),
|
|
|
|
|
+ stop: vi.fn()
|
|
|
|
|
+ };
|
|
|
|
|
+ vi.mocked(cron.schedule).mockReturnValue(mockCronJob as any);
|
|
|
|
|
+
|
|
|
|
|
+ await service.start();
|
|
|
|
|
+ await service.stop();
|
|
|
|
|
|
|
|
- expect(delaySchedulerService.getStatus().isRunning).toBe(false);
|
|
|
|
|
|
|
+ expect(mockCronJob.stop).toHaveBeenCalled();
|
|
|
|
|
+ expect(service.getStatus().isRunning).toBe(false);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
it('应该在调度器未运行时抛出错误', async () => {
|
|
it('应该在调度器未运行时抛出错误', async () => {
|
|
|
- await expect(delaySchedulerService.stop())
|
|
|
|
|
|
|
+ await expect(service.stop())
|
|
|
.rejects
|
|
.rejects
|
|
|
.toThrow('调度器未在运行中');
|
|
.toThrow('调度器未在运行中');
|
|
|
});
|
|
});
|
|
@@ -93,19 +140,19 @@ describe('DelaySchedulerService', () => {
|
|
|
|
|
|
|
|
describe('setDefaultDelaySeconds', () => {
|
|
describe('setDefaultDelaySeconds', () => {
|
|
|
it('应该设置默认延迟时间', () => {
|
|
it('应该设置默认延迟时间', () => {
|
|
|
- delaySchedulerService.setDefaultDelaySeconds(180);
|
|
|
|
|
- expect(delaySchedulerService.getDefaultDelaySeconds()).toBe(180);
|
|
|
|
|
|
|
+ service.setDefaultDelaySeconds(180);
|
|
|
|
|
+ expect(service.getDefaultDelaySeconds()).toBe(180);
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
it('应该在延迟时间为负数时抛出错误', () => {
|
|
it('应该在延迟时间为负数时抛出错误', () => {
|
|
|
- expect(() => delaySchedulerService.setDefaultDelaySeconds(-1))
|
|
|
|
|
|
|
+ expect(() => service.setDefaultDelaySeconds(-1))
|
|
|
.toThrow('延迟时间不能为负数');
|
|
.toThrow('延迟时间不能为负数');
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
describe('getStatus', () => {
|
|
describe('getStatus', () => {
|
|
|
it('应该返回调度器状态', () => {
|
|
it('应该返回调度器状态', () => {
|
|
|
- const status = delaySchedulerService.getStatus();
|
|
|
|
|
|
|
+ const status = service.getStatus();
|
|
|
|
|
|
|
|
expect(status).toEqual({
|
|
expect(status).toEqual({
|
|
|
isRunning: false,
|
|
isRunning: false,
|
|
@@ -115,8 +162,14 @@ describe('DelaySchedulerService', () => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
it('应该在调度器运行时返回正确状态', async () => {
|
|
it('应该在调度器运行时返回正确状态', async () => {
|
|
|
- await delaySchedulerService.start();
|
|
|
|
|
- const status = delaySchedulerService.getStatus();
|
|
|
|
|
|
|
+ const mockCronJob = {
|
|
|
|
|
+ start: vi.fn(),
|
|
|
|
|
+ stop: vi.fn()
|
|
|
|
|
+ };
|
|
|
|
|
+ vi.mocked(cron.schedule).mockReturnValue(mockCronJob as any);
|
|
|
|
|
+
|
|
|
|
|
+ await service.start();
|
|
|
|
|
+ const status = service.getStatus();
|
|
|
|
|
|
|
|
expect(status.isRunning).toBe(true);
|
|
expect(status.isRunning).toBe(true);
|
|
|
expect(status.defaultDelaySeconds).toBe(120);
|
|
expect(status.defaultDelaySeconds).toBe(120);
|
|
@@ -126,19 +179,43 @@ describe('DelaySchedulerService', () => {
|
|
|
describe('triggerManualProcess', () => {
|
|
describe('triggerManualProcess', () => {
|
|
|
it('应该手动触发任务处理', async () => {
|
|
it('应该手动触发任务处理', async () => {
|
|
|
const tenantId = 1;
|
|
const tenantId = 1;
|
|
|
- const mockTasks = [
|
|
|
|
|
- { taskId: 'TASK1', printerSn: 'PRINTER1' },
|
|
|
|
|
- { taskId: 'TASK2', printerSn: 'PRINTER2' }
|
|
|
|
|
|
|
+ const mockTasks: Partial<FeiePrintTaskMt>[] = [
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 1,
|
|
|
|
|
+ tenantId: 1,
|
|
|
|
|
+ taskId: 'TASK1',
|
|
|
|
|
+ orderId: 1001,
|
|
|
|
|
+ printerSn: 'PRINTER1',
|
|
|
|
|
+ content: '打印内容1',
|
|
|
|
|
+ printType: 'RECEIPT',
|
|
|
|
|
+ printStatus: 'DELAYED',
|
|
|
|
|
+ retryCount: 0,
|
|
|
|
|
+ maxRetries: 3,
|
|
|
|
|
+ scheduledAt: new Date(),
|
|
|
|
|
+ createdAt: new Date(),
|
|
|
|
|
+ updatedAt: new Date()
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ id: 2,
|
|
|
|
|
+ tenantId: 1,
|
|
|
|
|
+ taskId: 'TASK2',
|
|
|
|
|
+ orderId: 1002,
|
|
|
|
|
+ printerSn: 'PRINTER2',
|
|
|
|
|
+ content: '打印内容2',
|
|
|
|
|
+ printType: 'RECEIPT',
|
|
|
|
|
+ printStatus: 'DELAYED',
|
|
|
|
|
+ retryCount: 0,
|
|
|
|
|
+ maxRetries: 3,
|
|
|
|
|
+ scheduledAt: new Date(),
|
|
|
|
|
+ createdAt: new Date(),
|
|
|
|
|
+ updatedAt: new Date()
|
|
|
|
|
+ }
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
- // Mock getPendingDelayedTasks to return tasks
|
|
|
|
|
- const mockPrintTaskService = {
|
|
|
|
|
- getPendingDelayedTasks: vi.fn().mockResolvedValue(mockTasks),
|
|
|
|
|
- executePrintTask: vi.fn().mockResolvedValue({})
|
|
|
|
|
- };
|
|
|
|
|
- (delaySchedulerService as any).printTaskService = mockPrintTaskService;
|
|
|
|
|
|
|
+ vi.mocked(mockPrintTaskService.getPendingDelayedTasks).mockResolvedValue(mockTasks as FeiePrintTaskMt[]);
|
|
|
|
|
+ vi.mocked(mockPrintTaskService.executePrintTask).mockResolvedValue({} as any);
|
|
|
|
|
|
|
|
- const result = await delaySchedulerService.triggerManualProcess(tenantId);
|
|
|
|
|
|
|
+ const result = await service.triggerManualProcess(tenantId);
|
|
|
|
|
|
|
|
expect(result).toEqual({
|
|
expect(result).toEqual({
|
|
|
success: true,
|
|
success: true,
|
|
@@ -152,13 +229,9 @@ describe('DelaySchedulerService', () => {
|
|
|
it('应该在处理失败时返回错误信息', async () => {
|
|
it('应该在处理失败时返回错误信息', async () => {
|
|
|
const tenantId = 1;
|
|
const tenantId = 1;
|
|
|
|
|
|
|
|
- // Mock getPendingDelayedTasks to throw error
|
|
|
|
|
- const mockPrintTaskService = {
|
|
|
|
|
- getPendingDelayedTasks: vi.fn().mockRejectedValue(new Error('数据库错误'))
|
|
|
|
|
- };
|
|
|
|
|
- (delaySchedulerService as any).printTaskService = mockPrintTaskService;
|
|
|
|
|
|
|
+ vi.mocked(mockPrintTaskService.getPendingDelayedTasks).mockRejectedValue(new Error('数据库错误'));
|
|
|
|
|
|
|
|
- const result = await delaySchedulerService.triggerManualProcess(tenantId);
|
|
|
|
|
|
|
+ const result = await service.triggerManualProcess(tenantId);
|
|
|
|
|
|
|
|
expect(result).toEqual({
|
|
expect(result).toEqual({
|
|
|
success: false,
|
|
success: false,
|
|
@@ -166,11 +239,21 @@ describe('DelaySchedulerService', () => {
|
|
|
message: '手动处理失败: 数据库错误'
|
|
message: '手动处理失败: 数据库错误'
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
+
|
|
|
|
|
+ it('当未指定租户ID时应该返回错误', async () => {
|
|
|
|
|
+ const result = await service.triggerManualProcess();
|
|
|
|
|
+
|
|
|
|
|
+ expect(result).toEqual({
|
|
|
|
|
+ success: false,
|
|
|
|
|
+ processedTasks: 0,
|
|
|
|
|
+ message: '未指定租户ID,无法手动处理任务'
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
describe('healthCheck', () => {
|
|
describe('healthCheck', () => {
|
|
|
it('应该返回健康状态', async () => {
|
|
it('应该返回健康状态', async () => {
|
|
|
- const health = await delaySchedulerService.healthCheck();
|
|
|
|
|
|
|
+ const health = await service.healthCheck();
|
|
|
|
|
|
|
|
expect(health).toEqual({
|
|
expect(health).toEqual({
|
|
|
healthy: false,
|
|
healthy: false,
|
|
@@ -180,8 +263,14 @@ describe('DelaySchedulerService', () => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
it('应该在调度器运行时返回健康状态', async () => {
|
|
it('应该在调度器运行时返回健康状态', async () => {
|
|
|
- await delaySchedulerService.start();
|
|
|
|
|
- const health = await delaySchedulerService.healthCheck();
|
|
|
|
|
|
|
+ const mockCronJob = {
|
|
|
|
|
+ start: vi.fn(),
|
|
|
|
|
+ stop: vi.fn()
|
|
|
|
|
+ };
|
|
|
|
|
+ vi.mocked(cron.schedule).mockReturnValue(mockCronJob as any);
|
|
|
|
|
+
|
|
|
|
|
+ await service.start();
|
|
|
|
|
+ const health = await service.healthCheck();
|
|
|
|
|
|
|
|
expect(health).toEqual({
|
|
expect(health).toEqual({
|
|
|
healthy: true,
|
|
healthy: true,
|
|
@@ -191,36 +280,199 @@ describe('DelaySchedulerService', () => {
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- describe('processDelayedTasks', () => {
|
|
|
|
|
- it('应该处理延迟任务', async () => {
|
|
|
|
|
- // 由于processDelayedTasks是私有方法,我们通过start方法来测试
|
|
|
|
|
- // 当调度器启动时,它会定期调用processDelayedTasks
|
|
|
|
|
- await delaySchedulerService.start();
|
|
|
|
|
|
|
+ describe('shouldCancelDueToRefund', () => {
|
|
|
|
|
+ it('当订单已退款时应该返回true', async () => {
|
|
|
|
|
+ const tenantId = 1;
|
|
|
|
|
+ const orderId = 1001;
|
|
|
|
|
+ const mockOrder = {
|
|
|
|
|
+ id: orderId,
|
|
|
|
|
+ tenantId,
|
|
|
|
|
+ payState: 3, // 已退款
|
|
|
|
|
+ state: 1
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ vi.mocked(mockOrderRepository.findOne).mockResolvedValue(mockOrder as any);
|
|
|
|
|
+
|
|
|
|
|
+ // 通过私有方法测试
|
|
|
|
|
+ const result = await (service as any).shouldCancelDueToRefund(tenantId, orderId);
|
|
|
|
|
+
|
|
|
|
|
+ expect(result).toBe(true);
|
|
|
|
|
+ expect(mockOrderRepository.findOne).toHaveBeenCalledWith({
|
|
|
|
|
+ where: { id: orderId, tenantId }
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('当订单已关闭时应该返回true', async () => {
|
|
|
|
|
+ const tenantId = 1;
|
|
|
|
|
+ const orderId = 1001;
|
|
|
|
|
+ const mockOrder = {
|
|
|
|
|
+ id: orderId,
|
|
|
|
|
+ tenantId,
|
|
|
|
|
+ payState: 2, // 已支付
|
|
|
|
|
+ state: 5 // 订单关闭
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ vi.mocked(mockOrderRepository.findOne).mockResolvedValue(mockOrder as any);
|
|
|
|
|
+
|
|
|
|
|
+ const result = await (service as any).shouldCancelDueToRefund(tenantId, orderId);
|
|
|
|
|
+
|
|
|
|
|
+ expect(result).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('当订单正常时应该返回false', async () => {
|
|
|
|
|
+ const tenantId = 1;
|
|
|
|
|
+ const orderId = 1001;
|
|
|
|
|
+ const mockOrder = {
|
|
|
|
|
+ id: orderId,
|
|
|
|
|
+ tenantId,
|
|
|
|
|
+ payState: 2, // 已支付
|
|
|
|
|
+ state: 1 // 正常
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ vi.mocked(mockOrderRepository.findOne).mockResolvedValue(mockOrder as any);
|
|
|
|
|
+
|
|
|
|
|
+ const result = await (service as any).shouldCancelDueToRefund(tenantId, orderId);
|
|
|
|
|
+
|
|
|
|
|
+ expect(result).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('当订单不存在时应该返回true', async () => {
|
|
|
|
|
+ const tenantId = 1;
|
|
|
|
|
+ const orderId = 1001;
|
|
|
|
|
+
|
|
|
|
|
+ vi.mocked(mockOrderRepository.findOne).mockResolvedValue(null);
|
|
|
|
|
+
|
|
|
|
|
+ const result = await (service as any).shouldCancelDueToRefund(tenantId, orderId);
|
|
|
|
|
+
|
|
|
|
|
+ expect(result).toBe(true);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('当订单仓库不可用时应该返回false', async () => {
|
|
|
|
|
+ const tenantId = 1;
|
|
|
|
|
+ const orderId = 1001;
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟订单仓库不可用
|
|
|
|
|
+ (service as any).orderRepository = null;
|
|
|
|
|
+
|
|
|
|
|
+ const result = await (service as any).shouldCancelDueToRefund(tenantId, orderId);
|
|
|
|
|
+
|
|
|
|
|
+ expect(result).toBe(false);
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ describe('processSingleDelayedTask', () => {
|
|
|
|
|
+ it('一次调度周期内每个打印任务应该只执行一次', async () => {
|
|
|
|
|
+ const tenantId = 1;
|
|
|
|
|
+ const mockTask: Partial<FeiePrintTaskMt> = {
|
|
|
|
|
+ id: 1,
|
|
|
|
|
+ tenantId: 1,
|
|
|
|
|
+ taskId: 'TASK1',
|
|
|
|
|
+ orderId: 1001,
|
|
|
|
|
+ printerSn: 'PRINTER1',
|
|
|
|
|
+ content: '打印内容',
|
|
|
|
|
+ printType: 'RECEIPT',
|
|
|
|
|
+ printStatus: 'DELAYED',
|
|
|
|
|
+ retryCount: 0,
|
|
|
|
|
+ maxRetries: 3,
|
|
|
|
|
+ scheduledAt: new Date(),
|
|
|
|
|
+ createdAt: new Date(),
|
|
|
|
|
+ updatedAt: new Date()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // Mock 订单状态正常(未退款)
|
|
|
|
|
+ vi.mocked(mockOrderRepository.findOne).mockResolvedValue({
|
|
|
|
|
+ id: 1001,
|
|
|
|
|
+ tenantId: 1,
|
|
|
|
|
+ payState: 2, // 已支付
|
|
|
|
|
+ state: 1 // 正常
|
|
|
|
|
+ } as any);
|
|
|
|
|
+
|
|
|
|
|
+ // Mock executePrintTask 成功
|
|
|
|
|
+ vi.mocked(mockPrintTaskService.executePrintTask).mockResolvedValue({} as any);
|
|
|
|
|
+
|
|
|
|
|
+ // 调用私有方法 processSingleDelayedTask
|
|
|
|
|
+ await (service as any).processSingleDelayedTask(tenantId, mockTask);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证 executePrintTask 只被调用了一次
|
|
|
|
|
+ expect(mockPrintTaskService.executePrintTask).toHaveBeenCalledTimes(1);
|
|
|
|
|
+ expect(mockPrintTaskService.executePrintTask).toHaveBeenCalledWith(tenantId, 'TASK1');
|
|
|
|
|
+
|
|
|
|
|
+ // 验证订单状态检查被调用
|
|
|
|
|
+ expect(mockOrderRepository.findOne).toHaveBeenCalledWith({
|
|
|
|
|
+ where: { id: 1001, tenantId: 1 }
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- // 手动触发一次处理
|
|
|
|
|
- const scheduleCallback = vi.mocked(cron.schedule).mock.calls[0][1];
|
|
|
|
|
- await scheduleCallback();
|
|
|
|
|
|
|
+ it('当任务状态为最终状态时应该跳过执行', async () => {
|
|
|
|
|
+ const tenantId = 1;
|
|
|
|
|
|
|
|
- // 验证处理逻辑被调用
|
|
|
|
|
- expect(true).toBe(true);
|
|
|
|
|
|
|
+ // 测试各种最终状态
|
|
|
|
|
+ const finalStatuses = ['SUCCESS', 'FAILED', 'CANCELLED'];
|
|
|
|
|
+
|
|
|
|
|
+ for (const status of finalStatuses) {
|
|
|
|
|
+ const mockTask: Partial<FeiePrintTaskMt> = {
|
|
|
|
|
+ id: 1,
|
|
|
|
|
+ tenantId: 1,
|
|
|
|
|
+ taskId: 'TASK1',
|
|
|
|
|
+ orderId: 1001,
|
|
|
|
|
+ printerSn: 'PRINTER1',
|
|
|
|
|
+ content: '打印内容',
|
|
|
|
|
+ printType: 'RECEIPT',
|
|
|
|
|
+ printStatus: status,
|
|
|
|
|
+ retryCount: 0,
|
|
|
|
|
+ maxRetries: 3,
|
|
|
|
|
+ scheduledAt: new Date(),
|
|
|
|
|
+ createdAt: new Date(),
|
|
|
|
|
+ updatedAt: new Date()
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 重置mock调用计数
|
|
|
|
|
+ vi.mocked(mockPrintTaskService.executePrintTask).mockClear();
|
|
|
|
|
+
|
|
|
|
|
+ // 调用私有方法 processSingleDelayedTask
|
|
|
|
|
+ await (service as any).processSingleDelayedTask(tenantId, mockTask);
|
|
|
|
|
+
|
|
|
|
|
+ // 验证 executePrintTask 没有被调用(因为任务已经是最终状态)
|
|
|
|
|
+ expect(mockPrintTaskService.executePrintTask).not.toHaveBeenCalled();
|
|
|
|
|
+ }
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- it('应该在处理失败时记录错误', async () => {
|
|
|
|
|
- const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
|
|
|
|
|
+ it('当订单已退款时应该取消打印任务', async () => {
|
|
|
|
|
+ const tenantId = 1;
|
|
|
|
|
+ const mockTask: Partial<FeiePrintTaskMt> = {
|
|
|
|
|
+ id: 1,
|
|
|
|
|
+ tenantId: 1,
|
|
|
|
|
+ taskId: 'TASK1',
|
|
|
|
|
+ orderId: 1001,
|
|
|
|
|
+ printerSn: 'PRINTER1',
|
|
|
|
|
+ content: '打印内容',
|
|
|
|
|
+ printType: 'RECEIPT',
|
|
|
|
|
+ printStatus: 'DELAYED',
|
|
|
|
|
+ retryCount: 0,
|
|
|
|
|
+ maxRetries: 3,
|
|
|
|
|
+ scheduledAt: new Date(),
|
|
|
|
|
+ createdAt: new Date(),
|
|
|
|
|
+ updatedAt: new Date()
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- // Mock getTenantsWithDelayedTasks to throw error
|
|
|
|
|
- const originalGetTenantsWithDelayedTasks = (delaySchedulerService as any).getTenantsWithDelayedTasks;
|
|
|
|
|
- (delaySchedulerService as any).getTenantsWithDelayedTasks = vi.fn().mockRejectedValue(new Error('处理失败'));
|
|
|
|
|
|
|
+ // Mock 订单状态为已退款
|
|
|
|
|
+ vi.mocked(mockOrderRepository.findOne).mockResolvedValue({
|
|
|
|
|
+ id: 1001,
|
|
|
|
|
+ tenantId: 1,
|
|
|
|
|
+ payState: 3, // 已退款
|
|
|
|
|
+ state: 1
|
|
|
|
|
+ } as any);
|
|
|
|
|
|
|
|
- await delaySchedulerService.start();
|
|
|
|
|
- const scheduleCallback = vi.mocked(cron.schedule).mock.calls[0][1];
|
|
|
|
|
- await scheduleCallback();
|
|
|
|
|
|
|
+ // Mock cancelPrintTask
|
|
|
|
|
+ vi.mocked(mockPrintTaskService.cancelPrintTask).mockResolvedValue({} as any);
|
|
|
|
|
|
|
|
- expect(consoleErrorSpy).toHaveBeenCalledWith('处理延迟打印任务失败:', expect.any(Error));
|
|
|
|
|
|
|
+ // 调用私有方法 processSingleDelayedTask
|
|
|
|
|
+ await (service as any).processSingleDelayedTask(tenantId, mockTask);
|
|
|
|
|
|
|
|
- // Restore
|
|
|
|
|
- (delaySchedulerService as any).getTenantsWithDelayedTasks = originalGetTenantsWithDelayedTasks;
|
|
|
|
|
- consoleErrorSpy.mockRestore();
|
|
|
|
|
|
|
+ // 验证 cancelPrintTask 被调用,而不是 executePrintTask
|
|
|
|
|
+ expect(mockPrintTaskService.cancelPrintTask).toHaveBeenCalledTimes(1);
|
|
|
|
|
+ expect(mockPrintTaskService.cancelPrintTask).toHaveBeenCalledWith(tenantId, 'TASK1', 'REFUND');
|
|
|
|
|
+ expect(mockPrintTaskService.executePrintTask).not.toHaveBeenCalled();
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|
|
|
});
|
|
});
|