|
|
@@ -0,0 +1,434 @@
|
|
|
+import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
|
+import { DataSource, Repository } from 'typeorm';
|
|
|
+import { PrintTaskService } from '../../src/services/print-task.service';
|
|
|
+import { FeiePrintTaskMt } from '../../src/entities/feie-print-task.mt.entity';
|
|
|
+import { FeiePrinterMt } from '../../src/entities/feie-printer.mt.entity';
|
|
|
+import type { FeieApiConfig, CreatePrintTaskDto } from '../../src/types/feie.types';
|
|
|
+import { FileLogger } from '@d8d/shared-utils';
|
|
|
+
|
|
|
+// Mock dependencies
|
|
|
+vi.mock('../../src/services/feie-api.service', () => {
|
|
|
+ return {
|
|
|
+ FeieApiService: vi.fn().mockImplementation(() => ({
|
|
|
+ printReceipt: vi.fn().mockResolvedValue({ ret: 0, msg: 'success', data: '123456' }),
|
|
|
+ queryOrderStatus: vi.fn().mockResolvedValue({ ret: 0, data: '已打印' })
|
|
|
+ }))
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+vi.mock('../../src/services/printer.service', () => {
|
|
|
+ return {
|
|
|
+ PrinterService: vi.fn().mockImplementation(() => ({}))
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+vi.mock('@d8d/shared-crud', () => {
|
|
|
+ return {
|
|
|
+ GenericCrudService: vi.fn().mockImplementation(() => ({
|
|
|
+ create: vi.fn(),
|
|
|
+ findOne: vi.fn(),
|
|
|
+ findMany: vi.fn(),
|
|
|
+ update: vi.fn(),
|
|
|
+ delete: vi.fn(),
|
|
|
+ repository: {
|
|
|
+ findAndCount: vi.fn()
|
|
|
+ }
|
|
|
+ }))
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+// Mock the file logger and other exports from shared-utils
|
|
|
+vi.mock('@d8d/shared-utils', () => {
|
|
|
+ const mockLogger = {
|
|
|
+ info: vi.fn(),
|
|
|
+ warn: vi.fn(),
|
|
|
+ error: vi.fn(),
|
|
|
+ debug: vi.fn(),
|
|
|
+ log: vi.fn()
|
|
|
+ };
|
|
|
+
|
|
|
+ return {
|
|
|
+ createServiceLogger: vi.fn().mockReturnValue(mockLogger),
|
|
|
+ FileLogger: vi.fn().mockImplementation(() => mockLogger),
|
|
|
+ // Mock other exports that might be needed
|
|
|
+ jwt: {
|
|
|
+ sign: vi.fn(),
|
|
|
+ verify: vi.fn()
|
|
|
+ },
|
|
|
+ errorHandler: vi.fn(),
|
|
|
+ parseWithAwait: vi.fn(),
|
|
|
+ logger: {
|
|
|
+ info: vi.fn(),
|
|
|
+ warn: vi.fn(),
|
|
|
+ error: vi.fn(),
|
|
|
+ debug: vi.fn()
|
|
|
+ },
|
|
|
+ redis: {
|
|
|
+ get: vi.fn(),
|
|
|
+ set: vi.fn(),
|
|
|
+ del: vi.fn()
|
|
|
+ }
|
|
|
+ };
|
|
|
+});
|
|
|
+
|
|
|
+describe('PrintTaskService 日志功能测试', () => {
|
|
|
+ let printTaskService: PrintTaskService;
|
|
|
+ let mockDataSource: DataSource;
|
|
|
+ let mockPrinterRepository: Repository<FeiePrinterMt>;
|
|
|
+ let mockLogger: any;
|
|
|
+ const mockFeieConfig: FeieApiConfig = {
|
|
|
+ baseUrl: 'http://api.feieyun.cn/Api/Open/',
|
|
|
+ user: 'test_user',
|
|
|
+ ukey: 'test_ukey'
|
|
|
+ };
|
|
|
+
|
|
|
+ beforeEach(() => {
|
|
|
+ vi.clearAllMocks();
|
|
|
+ vi.useFakeTimers();
|
|
|
+
|
|
|
+ mockPrinterRepository = {
|
|
|
+ findOne: vi.fn()
|
|
|
+ } as unknown as Repository<FeiePrinterMt>;
|
|
|
+
|
|
|
+ mockDataSource = {
|
|
|
+ getRepository: vi.fn().mockReturnValue(mockPrinterRepository),
|
|
|
+ query: vi.fn().mockResolvedValue([{ db_time: new Date() }])
|
|
|
+ } as unknown as DataSource;
|
|
|
+
|
|
|
+ // Get the mocked logger instance
|
|
|
+ const { createServiceLogger } = require('@d8d/shared-utils');
|
|
|
+ mockLogger = createServiceLogger();
|
|
|
+
|
|
|
+ printTaskService = new PrintTaskService(mockDataSource, mockFeieConfig);
|
|
|
+ });
|
|
|
+
|
|
|
+ afterEach(() => {
|
|
|
+ vi.useRealTimers();
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('日志记录功能', () => {
|
|
|
+ it('应该在创建打印任务时记录日志', async () => {
|
|
|
+ const tenantId = 1;
|
|
|
+ const taskDto: CreatePrintTaskDto = {
|
|
|
+ printerSn: 'TEST123456',
|
|
|
+ content: '测试打印内容',
|
|
|
+ printType: 'RECEIPT',
|
|
|
+ delaySeconds: 0
|
|
|
+ };
|
|
|
+
|
|
|
+ const mockPrinter: Partial<FeiePrinterMt> = {
|
|
|
+ id: 1,
|
|
|
+ tenantId,
|
|
|
+ printerSn: taskDto.printerSn,
|
|
|
+ printerStatus: 'ACTIVE'
|
|
|
+ };
|
|
|
+
|
|
|
+ const mockTask: Partial<FeiePrintTaskMt> = {
|
|
|
+ id: 1,
|
|
|
+ tenantId,
|
|
|
+ taskId: 'FEIE_123456789_1234',
|
|
|
+ printerSn: taskDto.printerSn,
|
|
|
+ content: taskDto.content,
|
|
|
+ printType: taskDto.printType,
|
|
|
+ printStatus: 'PENDING'
|
|
|
+ };
|
|
|
+
|
|
|
+ // Mock dependencies
|
|
|
+ vi.mocked(mockPrinterRepository.findOne).mockResolvedValue(mockPrinter as FeiePrinterMt);
|
|
|
+ (printTaskService as any).create = vi.fn().mockResolvedValue(mockTask);
|
|
|
+ (printTaskService as any).executePrintTask = vi.fn().mockResolvedValue(mockTask);
|
|
|
+
|
|
|
+ await printTaskService.createPrintTask(tenantId, taskDto);
|
|
|
+
|
|
|
+ // 验证日志器被正确创建
|
|
|
+ const { createServiceLogger } = require('@d8d/shared-utils');
|
|
|
+ expect(createServiceLogger).toHaveBeenCalledWith('feie-print-task-service');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该在获取数据库时间失败时记录警告日志', async () => {
|
|
|
+ const tenantId = 1;
|
|
|
+ const taskDto: CreatePrintTaskDto = {
|
|
|
+ printerSn: 'TEST123456',
|
|
|
+ content: '测试打印内容',
|
|
|
+ printType: 'RECEIPT',
|
|
|
+ delaySeconds: 10
|
|
|
+ };
|
|
|
+
|
|
|
+ const mockPrinter: Partial<FeiePrinterMt> = {
|
|
|
+ id: 1,
|
|
|
+ tenantId,
|
|
|
+ printerSn: taskDto.printerSn,
|
|
|
+ printerStatus: 'ACTIVE'
|
|
|
+ };
|
|
|
+
|
|
|
+ const mockTask: Partial<FeiePrintTaskMt> = {
|
|
|
+ id: 1,
|
|
|
+ tenantId,
|
|
|
+ taskId: 'FEIE_123456789_1234',
|
|
|
+ printerSn: taskDto.printerSn,
|
|
|
+ content: taskDto.content,
|
|
|
+ printType: taskDto.printType,
|
|
|
+ printStatus: 'DELAYED'
|
|
|
+ };
|
|
|
+
|
|
|
+ // Mock database query to fail
|
|
|
+ vi.mocked(mockDataSource.query).mockRejectedValue(new Error('数据库连接失败'));
|
|
|
+ vi.mocked(mockPrinterRepository.findOne).mockResolvedValue(mockPrinter as FeiePrinterMt);
|
|
|
+ (printTaskService as any).create = vi.fn().mockResolvedValue(mockTask);
|
|
|
+
|
|
|
+ await printTaskService.createPrintTask(tenantId, taskDto);
|
|
|
+
|
|
|
+ // 验证警告日志被记录
|
|
|
+ expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
|
+ expect.stringContaining('获取数据库时间失败,使用本地时间'),
|
|
|
+ expect.any(Error)
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该在执行打印任务时记录调试日志', async () => {
|
|
|
+ const tenantId = 1;
|
|
|
+ const taskId = 'FEIE_123456789_1234';
|
|
|
+
|
|
|
+ const mockTask: Partial<FeiePrintTaskMt> = {
|
|
|
+ id: 1,
|
|
|
+ tenantId,
|
|
|
+ taskId,
|
|
|
+ printerSn: 'TEST123456',
|
|
|
+ content: '测试内容',
|
|
|
+ printType: 'RECEIPT',
|
|
|
+ printStatus: 'PENDING',
|
|
|
+ retryCount: 0,
|
|
|
+ maxRetries: 3
|
|
|
+ };
|
|
|
+
|
|
|
+ const updatedTask: Partial<FeiePrintTaskMt> = {
|
|
|
+ ...mockTask,
|
|
|
+ printStatus: 'SUCCESS',
|
|
|
+ printedAt: new Date()
|
|
|
+ };
|
|
|
+
|
|
|
+ // Mock dependencies
|
|
|
+ (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
|
|
|
+ (printTaskService as any).update = vi.fn().mockResolvedValue(updatedTask);
|
|
|
+
|
|
|
+ await printTaskService.executePrintTask(tenantId, taskId);
|
|
|
+
|
|
|
+ // 验证调试日志被记录
|
|
|
+ expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
|
+ '调用飞鹅API打印response',
|
|
|
+ expect.objectContaining({ response: expect.any(Object) })
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(mockLogger.debug).toHaveBeenCalledWith(
|
|
|
+ '更新任务状态为成功updatedTask:',
|
|
|
+ expect.objectContaining({ updatedTask: expect.any(Object) })
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该在打印失败时记录错误日志并安排重试', async () => {
|
|
|
+ const tenantId = 1;
|
|
|
+ const taskId = 'FEIE_123456789_1234';
|
|
|
+
|
|
|
+ const mockTask: Partial<FeiePrintTaskMt> = {
|
|
|
+ id: 1,
|
|
|
+ tenantId,
|
|
|
+ taskId,
|
|
|
+ printerSn: 'TEST123456',
|
|
|
+ content: '测试内容',
|
|
|
+ printType: 'RECEIPT',
|
|
|
+ printStatus: 'PENDING',
|
|
|
+ retryCount: 0,
|
|
|
+ maxRetries: 3
|
|
|
+ };
|
|
|
+
|
|
|
+ // Mock dependencies
|
|
|
+ (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
|
|
|
+ (printTaskService as any).update = vi.fn().mockImplementation(async (id, data) => ({
|
|
|
+ ...mockTask,
|
|
|
+ ...data
|
|
|
+ }));
|
|
|
+
|
|
|
+ // Mock printReceipt to throw error
|
|
|
+ const mockFeieApiService = {
|
|
|
+ printReceipt: vi.fn().mockRejectedValue(new Error('打印失败'))
|
|
|
+ };
|
|
|
+ (printTaskService as any).feieApiService = mockFeieApiService;
|
|
|
+
|
|
|
+ await expect(printTaskService.executePrintTask(tenantId, taskId))
|
|
|
+ .rejects
|
|
|
+ .toThrow('打印失败');
|
|
|
+
|
|
|
+ // 验证错误日志被记录
|
|
|
+ expect(mockLogger.info).toHaveBeenCalledWith(
|
|
|
+ 'executePrintTask执行失败:',
|
|
|
+ expect.objectContaining({ error: expect.any(Error) })
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(mockLogger.info).toHaveBeenCalledWith(
|
|
|
+ 'maxRetries最大重试次数:',
|
|
|
+ expect.objectContaining({ maxRetries: expect.any(Number) })
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(mockLogger.info).toHaveBeenCalledWith(
|
|
|
+ expect.stringContaining('打印任务失败,5000ms后重试')
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该在订单号重复时记录信息日志并标记为成功', async () => {
|
|
|
+ const tenantId = 1;
|
|
|
+ const taskId = 'FEIE_123456789_1234';
|
|
|
+
|
|
|
+ const mockTask: Partial<FeiePrintTaskMt> = {
|
|
|
+ id: 1,
|
|
|
+ tenantId,
|
|
|
+ taskId,
|
|
|
+ printerSn: 'TEST123456',
|
|
|
+ content: '测试内容',
|
|
|
+ printType: 'RECEIPT',
|
|
|
+ printStatus: 'PENDING',
|
|
|
+ retryCount: 0,
|
|
|
+ maxRetries: 3
|
|
|
+ };
|
|
|
+
|
|
|
+ const updatedTask: Partial<FeiePrintTaskMt> = {
|
|
|
+ ...mockTask,
|
|
|
+ printStatus: 'SUCCESS',
|
|
|
+ printedAt: new Date(),
|
|
|
+ errorMessage: '订单号重复,飞鹅API已打印',
|
|
|
+ retryCount: 1
|
|
|
+ };
|
|
|
+
|
|
|
+ // Mock dependencies
|
|
|
+ (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
|
|
|
+ (printTaskService as any).update = vi.fn().mockResolvedValue(updatedTask);
|
|
|
+
|
|
|
+ // Mock printReceipt to throw order duplicate error
|
|
|
+ const mockFeieApiService = {
|
|
|
+ printReceipt: vi.fn().mockRejectedValue(new Error('错误代码: -6, 订单号重复'))
|
|
|
+ };
|
|
|
+ (printTaskService as any).feieApiService = mockFeieApiService;
|
|
|
+
|
|
|
+ const result = await printTaskService.executePrintTask(tenantId, taskId);
|
|
|
+
|
|
|
+ // 验证信息日志被记录
|
|
|
+ expect(mockLogger.info).toHaveBeenCalledWith(
|
|
|
+ expect.stringContaining('打印任务 订单号重复,飞鹅API已打印,标记为成功')
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(result.printStatus).toBe('SUCCESS');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该在任务已经在打印中时记录警告日志', async () => {
|
|
|
+ const tenantId = 1;
|
|
|
+ const taskId = 'FEIE_123456789_1234';
|
|
|
+
|
|
|
+ const mockTask: Partial<FeiePrintTaskMt> = {
|
|
|
+ id: 1,
|
|
|
+ tenantId,
|
|
|
+ taskId,
|
|
|
+ printerSn: 'TEST123456',
|
|
|
+ content: '测试内容',
|
|
|
+ printType: 'RECEIPT',
|
|
|
+ printStatus: 'PRINTING', // 已经在打印中
|
|
|
+ retryCount: 0,
|
|
|
+ maxRetries: 3
|
|
|
+ };
|
|
|
+
|
|
|
+ // Mock dependencies
|
|
|
+ (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
|
|
|
+
|
|
|
+ const result = await printTaskService.executePrintTask(tenantId, taskId);
|
|
|
+
|
|
|
+ // 验证警告日志被记录
|
|
|
+ expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
|
+ expect.stringContaining('打印任务 已经在打印中,跳过重复执行')
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(result).toEqual(mockTask);
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该在回滚事务失败时记录警告日志', async () => {
|
|
|
+ const tenantId = 1;
|
|
|
+ const taskId = 'FEIE_123456789_1234';
|
|
|
+
|
|
|
+ // Mock dependencies to throw error during transaction
|
|
|
+ (printTaskService as any).findOne = vi.fn().mockRejectedValue(new Error('事务执行失败'));
|
|
|
+
|
|
|
+ // Mock queryRunner to throw error during rollback
|
|
|
+ const mockQueryRunner = {
|
|
|
+ connect: vi.fn(),
|
|
|
+ startTransaction: vi.fn(),
|
|
|
+ rollbackTransaction: vi.fn().mockRejectedValue(new Error('回滚失败')),
|
|
|
+ release: vi.fn()
|
|
|
+ };
|
|
|
+ (printTaskService as any).dataSource.createQueryRunner = vi.fn().mockReturnValue(mockQueryRunner);
|
|
|
+
|
|
|
+ await expect(printTaskService.executePrintTask(tenantId, taskId))
|
|
|
+ .rejects
|
|
|
+ .toThrow('事务执行失败');
|
|
|
+
|
|
|
+ // 验证警告日志被记录
|
|
|
+ expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
|
+ expect.stringContaining('回滚事务失败'),
|
|
|
+ expect.any(Error)
|
|
|
+ );
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该在获取租户配置失败时记录警告日志', async () => {
|
|
|
+ const tenantId = 1;
|
|
|
+ const key = 'test_key';
|
|
|
+ const defaultValue = 'default_value';
|
|
|
+
|
|
|
+ // Mock config repository to throw error
|
|
|
+ const mockConfigRepository = {
|
|
|
+ findOne: vi.fn().mockRejectedValue(new Error('数据库查询失败'))
|
|
|
+ };
|
|
|
+ (printTaskService as any).configRepository = mockConfigRepository;
|
|
|
+
|
|
|
+ const result = await (printTaskService as any).getTenantConfigValue(tenantId, key, defaultValue);
|
|
|
+
|
|
|
+ // 验证警告日志被记录
|
|
|
+ expect(mockLogger.warn).toHaveBeenCalledWith(
|
|
|
+ expect.stringContaining(`[租户${tenantId}] 获取配置失败,key: ${key}`),
|
|
|
+ expect.any(Error)
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(result).toBe(defaultValue);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ describe('日志级别测试', () => {
|
|
|
+ it('应该支持所有日志级别', async () => {
|
|
|
+ // 测试各种日志级别
|
|
|
+ await mockLogger.info('测试信息日志', { data: 'test' });
|
|
|
+ await mockLogger.warn('测试警告日志', { data: 'test' });
|
|
|
+ await mockLogger.error('测试错误日志', { data: 'test' });
|
|
|
+ await mockLogger.debug('测试调试日志', { data: 'test' });
|
|
|
+
|
|
|
+ expect(mockLogger.info).toHaveBeenCalledWith('测试信息日志', { data: 'test' });
|
|
|
+ expect(mockLogger.warn).toHaveBeenCalledWith('测试警告日志', { data: 'test' });
|
|
|
+ expect(mockLogger.error).toHaveBeenCalledWith('测试错误日志', { data: 'test' });
|
|
|
+ expect(mockLogger.debug).toHaveBeenCalledWith('测试调试日志', { data: 'test' });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理日志元数据', async () => {
|
|
|
+ const testData = {
|
|
|
+ taskId: 'FEIE_123456789_1234',
|
|
|
+ tenantId: 1,
|
|
|
+ status: 'PENDING',
|
|
|
+ nested: {
|
|
|
+ level1: {
|
|
|
+ level2: 'value'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ await mockLogger.info('测试结构化日志', testData);
|
|
|
+
|
|
|
+ expect(mockLogger.info).toHaveBeenCalledWith(
|
|
|
+ '测试结构化日志',
|
|
|
+ testData
|
|
|
+ );
|
|
|
+ });
|
|
|
+ });
|
|
|
+});
|