print-task-logging.test.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  2. import { DataSource, Repository } from 'typeorm';
  3. import { PrintTaskService } from '../../src/services/print-task.service';
  4. import { FeiePrintTaskMt } from '../../src/entities/feie-print-task.mt.entity';
  5. import { FeiePrinterMt } from '../../src/entities/feie-printer.mt.entity';
  6. import type { FeieApiConfig, CreatePrintTaskDto } from '../../src/types/feie.types';
  7. import { FileLogger } from '@d8d/shared-utils';
  8. // Mock dependencies
  9. vi.mock('../../src/services/feie-api.service', () => {
  10. return {
  11. FeieApiService: vi.fn().mockImplementation(() => ({
  12. printReceipt: vi.fn().mockResolvedValue({ ret: 0, msg: 'success', data: '123456' }),
  13. queryOrderStatus: vi.fn().mockResolvedValue({ ret: 0, data: '已打印' })
  14. }))
  15. };
  16. });
  17. vi.mock('../../src/services/printer.service', () => {
  18. return {
  19. PrinterService: vi.fn().mockImplementation(() => ({}))
  20. };
  21. });
  22. vi.mock('@d8d/shared-crud', () => {
  23. return {
  24. GenericCrudService: vi.fn().mockImplementation(() => ({
  25. create: vi.fn(),
  26. findOne: vi.fn(),
  27. findMany: vi.fn(),
  28. update: vi.fn(),
  29. delete: vi.fn(),
  30. repository: {
  31. findAndCount: vi.fn()
  32. }
  33. }))
  34. };
  35. });
  36. // Mock the file logger and other exports from shared-utils
  37. vi.mock('@d8d/shared-utils', () => {
  38. const mockLogger = {
  39. info: vi.fn(),
  40. warn: vi.fn(),
  41. error: vi.fn(),
  42. debug: vi.fn(),
  43. log: vi.fn()
  44. };
  45. return {
  46. createServiceLogger: vi.fn().mockReturnValue(mockLogger),
  47. FileLogger: vi.fn().mockImplementation(() => mockLogger),
  48. // Mock other exports that might be needed
  49. jwt: {
  50. sign: vi.fn(),
  51. verify: vi.fn()
  52. },
  53. errorHandler: vi.fn(),
  54. parseWithAwait: vi.fn(),
  55. logger: {
  56. info: vi.fn(),
  57. warn: vi.fn(),
  58. error: vi.fn(),
  59. debug: vi.fn()
  60. },
  61. redis: {
  62. get: vi.fn(),
  63. set: vi.fn(),
  64. del: vi.fn()
  65. }
  66. };
  67. });
  68. describe('PrintTaskService 日志功能测试', () => {
  69. let printTaskService: PrintTaskService;
  70. let mockDataSource: DataSource;
  71. let mockPrinterRepository: Repository<FeiePrinterMt>;
  72. let mockLogger: any;
  73. const mockFeieConfig: FeieApiConfig = {
  74. baseUrl: 'http://api.feieyun.cn/Api/Open/',
  75. user: 'test_user',
  76. ukey: 'test_ukey'
  77. };
  78. beforeEach(() => {
  79. vi.clearAllMocks();
  80. vi.useFakeTimers();
  81. mockPrinterRepository = {
  82. findOne: vi.fn()
  83. } as unknown as Repository<FeiePrinterMt>;
  84. mockDataSource = {
  85. getRepository: vi.fn().mockReturnValue(mockPrinterRepository),
  86. query: vi.fn().mockResolvedValue([{ db_time: new Date() }])
  87. } as unknown as DataSource;
  88. // Get the mocked logger instance
  89. const { createServiceLogger } = require('@d8d/shared-utils');
  90. mockLogger = createServiceLogger();
  91. printTaskService = new PrintTaskService(mockDataSource, mockFeieConfig);
  92. });
  93. afterEach(() => {
  94. vi.useRealTimers();
  95. });
  96. describe('日志记录功能', () => {
  97. it('应该在创建打印任务时记录日志', async () => {
  98. const tenantId = 1;
  99. const taskDto: CreatePrintTaskDto = {
  100. printerSn: 'TEST123456',
  101. content: '测试打印内容',
  102. printType: 'RECEIPT',
  103. delaySeconds: 0
  104. };
  105. const mockPrinter: Partial<FeiePrinterMt> = {
  106. id: 1,
  107. tenantId,
  108. printerSn: taskDto.printerSn,
  109. printerStatus: 'ACTIVE'
  110. };
  111. const mockTask: Partial<FeiePrintTaskMt> = {
  112. id: 1,
  113. tenantId,
  114. taskId: 'FEIE_123456789_1234',
  115. printerSn: taskDto.printerSn,
  116. content: taskDto.content,
  117. printType: taskDto.printType,
  118. printStatus: 'PENDING'
  119. };
  120. // Mock dependencies
  121. vi.mocked(mockPrinterRepository.findOne).mockResolvedValue(mockPrinter as FeiePrinterMt);
  122. (printTaskService as any).create = vi.fn().mockResolvedValue(mockTask);
  123. (printTaskService as any).executePrintTask = vi.fn().mockResolvedValue(mockTask);
  124. await printTaskService.createPrintTask(tenantId, taskDto);
  125. // 验证日志器被正确创建
  126. const { createServiceLogger } = require('@d8d/shared-utils');
  127. expect(createServiceLogger).toHaveBeenCalledWith('feie-print-task-service');
  128. });
  129. it('应该在获取数据库时间失败时记录警告日志', async () => {
  130. const tenantId = 1;
  131. const taskDto: CreatePrintTaskDto = {
  132. printerSn: 'TEST123456',
  133. content: '测试打印内容',
  134. printType: 'RECEIPT',
  135. delaySeconds: 10
  136. };
  137. const mockPrinter: Partial<FeiePrinterMt> = {
  138. id: 1,
  139. tenantId,
  140. printerSn: taskDto.printerSn,
  141. printerStatus: 'ACTIVE'
  142. };
  143. const mockTask: Partial<FeiePrintTaskMt> = {
  144. id: 1,
  145. tenantId,
  146. taskId: 'FEIE_123456789_1234',
  147. printerSn: taskDto.printerSn,
  148. content: taskDto.content,
  149. printType: taskDto.printType,
  150. printStatus: 'DELAYED'
  151. };
  152. // Mock database query to fail
  153. vi.mocked(mockDataSource.query).mockRejectedValue(new Error('数据库连接失败'));
  154. vi.mocked(mockPrinterRepository.findOne).mockResolvedValue(mockPrinter as FeiePrinterMt);
  155. (printTaskService as any).create = vi.fn().mockResolvedValue(mockTask);
  156. await printTaskService.createPrintTask(tenantId, taskDto);
  157. // 验证警告日志被记录
  158. expect(mockLogger.warn).toHaveBeenCalledWith(
  159. expect.stringContaining('获取数据库时间失败,使用本地时间'),
  160. expect.any(Error)
  161. );
  162. });
  163. it('应该在执行打印任务时记录调试日志', async () => {
  164. const tenantId = 1;
  165. const taskId = 'FEIE_123456789_1234';
  166. const mockTask: Partial<FeiePrintTaskMt> = {
  167. id: 1,
  168. tenantId,
  169. taskId,
  170. printerSn: 'TEST123456',
  171. content: '测试内容',
  172. printType: 'RECEIPT',
  173. printStatus: 'PENDING',
  174. retryCount: 0,
  175. maxRetries: 3
  176. };
  177. const updatedTask: Partial<FeiePrintTaskMt> = {
  178. ...mockTask,
  179. printStatus: 'SUCCESS',
  180. printedAt: new Date()
  181. };
  182. // Mock dependencies
  183. (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
  184. (printTaskService as any).update = vi.fn().mockResolvedValue(updatedTask);
  185. await printTaskService.executePrintTask(tenantId, taskId);
  186. // 验证调试日志被记录
  187. expect(mockLogger.debug).toHaveBeenCalledWith(
  188. '调用飞鹅API打印response',
  189. expect.objectContaining({ response: expect.any(Object) })
  190. );
  191. expect(mockLogger.debug).toHaveBeenCalledWith(
  192. '更新任务状态为成功updatedTask:',
  193. expect.objectContaining({ updatedTask: expect.any(Object) })
  194. );
  195. });
  196. it('应该在打印失败时记录错误日志并安排重试', async () => {
  197. const tenantId = 1;
  198. const taskId = 'FEIE_123456789_1234';
  199. const mockTask: Partial<FeiePrintTaskMt> = {
  200. id: 1,
  201. tenantId,
  202. taskId,
  203. printerSn: 'TEST123456',
  204. content: '测试内容',
  205. printType: 'RECEIPT',
  206. printStatus: 'PENDING',
  207. retryCount: 0,
  208. maxRetries: 3
  209. };
  210. // Mock dependencies
  211. (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
  212. (printTaskService as any).update = vi.fn().mockImplementation(async (id, data) => ({
  213. ...mockTask,
  214. ...data
  215. }));
  216. // Mock printReceipt to throw error
  217. const mockFeieApiService = {
  218. printReceipt: vi.fn().mockRejectedValue(new Error('打印失败'))
  219. };
  220. (printTaskService as any).feieApiService = mockFeieApiService;
  221. await expect(printTaskService.executePrintTask(tenantId, taskId))
  222. .rejects
  223. .toThrow('打印失败');
  224. // 验证错误日志被记录
  225. expect(mockLogger.info).toHaveBeenCalledWith(
  226. 'executePrintTask执行失败:',
  227. expect.objectContaining({ error: expect.any(Error) })
  228. );
  229. expect(mockLogger.info).toHaveBeenCalledWith(
  230. 'maxRetries最大重试次数:',
  231. expect.objectContaining({ maxRetries: expect.any(Number) })
  232. );
  233. expect(mockLogger.info).toHaveBeenCalledWith(
  234. expect.stringContaining('打印任务失败,5000ms后重试')
  235. );
  236. });
  237. it('应该在订单号重复时记录信息日志并标记为成功', async () => {
  238. const tenantId = 1;
  239. const taskId = 'FEIE_123456789_1234';
  240. const mockTask: Partial<FeiePrintTaskMt> = {
  241. id: 1,
  242. tenantId,
  243. taskId,
  244. printerSn: 'TEST123456',
  245. content: '测试内容',
  246. printType: 'RECEIPT',
  247. printStatus: 'PENDING',
  248. retryCount: 0,
  249. maxRetries: 3
  250. };
  251. const updatedTask: Partial<FeiePrintTaskMt> = {
  252. ...mockTask,
  253. printStatus: 'SUCCESS',
  254. printedAt: new Date(),
  255. errorMessage: '订单号重复,飞鹅API已打印',
  256. retryCount: 1
  257. };
  258. // Mock dependencies
  259. (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
  260. (printTaskService as any).update = vi.fn().mockResolvedValue(updatedTask);
  261. // Mock printReceipt to throw order duplicate error
  262. const mockFeieApiService = {
  263. printReceipt: vi.fn().mockRejectedValue(new Error('错误代码: -6, 订单号重复'))
  264. };
  265. (printTaskService as any).feieApiService = mockFeieApiService;
  266. const result = await printTaskService.executePrintTask(tenantId, taskId);
  267. // 验证信息日志被记录
  268. expect(mockLogger.info).toHaveBeenCalledWith(
  269. expect.stringContaining('打印任务 订单号重复,飞鹅API已打印,标记为成功')
  270. );
  271. expect(result.printStatus).toBe('SUCCESS');
  272. });
  273. it('应该在任务已经在打印中时记录警告日志', async () => {
  274. const tenantId = 1;
  275. const taskId = 'FEIE_123456789_1234';
  276. const mockTask: Partial<FeiePrintTaskMt> = {
  277. id: 1,
  278. tenantId,
  279. taskId,
  280. printerSn: 'TEST123456',
  281. content: '测试内容',
  282. printType: 'RECEIPT',
  283. printStatus: 'PRINTING', // 已经在打印中
  284. retryCount: 0,
  285. maxRetries: 3
  286. };
  287. // Mock dependencies
  288. (printTaskService as any).findOne = vi.fn().mockResolvedValue(mockTask);
  289. const result = await printTaskService.executePrintTask(tenantId, taskId);
  290. // 验证警告日志被记录
  291. expect(mockLogger.warn).toHaveBeenCalledWith(
  292. expect.stringContaining('打印任务 已经在打印中,跳过重复执行')
  293. );
  294. expect(result).toEqual(mockTask);
  295. });
  296. it('应该在回滚事务失败时记录警告日志', async () => {
  297. const tenantId = 1;
  298. const taskId = 'FEIE_123456789_1234';
  299. // Mock dependencies to throw error during transaction
  300. (printTaskService as any).findOne = vi.fn().mockRejectedValue(new Error('事务执行失败'));
  301. // Mock queryRunner to throw error during rollback
  302. const mockQueryRunner = {
  303. connect: vi.fn(),
  304. startTransaction: vi.fn(),
  305. rollbackTransaction: vi.fn().mockRejectedValue(new Error('回滚失败')),
  306. release: vi.fn()
  307. };
  308. (printTaskService as any).dataSource.createQueryRunner = vi.fn().mockReturnValue(mockQueryRunner);
  309. await expect(printTaskService.executePrintTask(tenantId, taskId))
  310. .rejects
  311. .toThrow('事务执行失败');
  312. // 验证警告日志被记录
  313. expect(mockLogger.warn).toHaveBeenCalledWith(
  314. expect.stringContaining('回滚事务失败'),
  315. expect.any(Error)
  316. );
  317. });
  318. it('应该在获取租户配置失败时记录警告日志', async () => {
  319. const tenantId = 1;
  320. const key = 'test_key';
  321. const defaultValue = 'default_value';
  322. // Mock config repository to throw error
  323. const mockConfigRepository = {
  324. findOne: vi.fn().mockRejectedValue(new Error('数据库查询失败'))
  325. };
  326. (printTaskService as any).configRepository = mockConfigRepository;
  327. const result = await (printTaskService as any).getTenantConfigValue(tenantId, key, defaultValue);
  328. // 验证警告日志被记录
  329. expect(mockLogger.warn).toHaveBeenCalledWith(
  330. expect.stringContaining(`[租户${tenantId}] 获取配置失败,key: ${key}`),
  331. expect.any(Error)
  332. );
  333. expect(result).toBe(defaultValue);
  334. });
  335. });
  336. describe('日志级别测试', () => {
  337. it('应该支持所有日志级别', async () => {
  338. // 测试各种日志级别
  339. await mockLogger.info('测试信息日志', { data: 'test' });
  340. await mockLogger.warn('测试警告日志', { data: 'test' });
  341. await mockLogger.error('测试错误日志', { data: 'test' });
  342. await mockLogger.debug('测试调试日志', { data: 'test' });
  343. expect(mockLogger.info).toHaveBeenCalledWith('测试信息日志', { data: 'test' });
  344. expect(mockLogger.warn).toHaveBeenCalledWith('测试警告日志', { data: 'test' });
  345. expect(mockLogger.error).toHaveBeenCalledWith('测试错误日志', { data: 'test' });
  346. expect(mockLogger.debug).toHaveBeenCalledWith('测试调试日志', { data: 'test' });
  347. });
  348. it('应该正确处理日志元数据', async () => {
  349. const testData = {
  350. taskId: 'FEIE_123456789_1234',
  351. tenantId: 1,
  352. status: 'PENDING',
  353. nested: {
  354. level1: {
  355. level2: 'value'
  356. }
  357. }
  358. };
  359. await mockLogger.info('测试结构化日志', testData);
  360. expect(mockLogger.info).toHaveBeenCalledWith(
  361. '测试结构化日志',
  362. testData
  363. );
  364. });
  365. });
  366. });