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; 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; 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 = { id: 1, tenantId, printerSn: taskDto.printerSn, printerStatus: 'ACTIVE' }; const mockTask: Partial = { 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 = { id: 1, tenantId, printerSn: taskDto.printerSn, printerStatus: 'ACTIVE' }; const mockTask: Partial = { 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 = { id: 1, tenantId, taskId, printerSn: 'TEST123456', content: '测试内容', printType: 'RECEIPT', printStatus: 'PENDING', retryCount: 0, maxRetries: 3 }; const updatedTask: Partial = { ...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 = { 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 = { id: 1, tenantId, taskId, printerSn: 'TEST123456', content: '测试内容', printType: 'RECEIPT', printStatus: 'PENDING', retryCount: 0, maxRetries: 3 }; const updatedTask: Partial = { ...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 = { 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 ); }); }); });