delay-scheduler.service.test.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
  2. import { DataSource } from 'typeorm';
  3. import * as cron from 'node-cron';
  4. import { DelaySchedulerService } from '../../src/services/delay-scheduler.service';
  5. import type { FeieApiConfig } from '../../src/types/feie.types';
  6. // Mock dependencies
  7. vi.mock('../../src/services/print-task.service', () => {
  8. return {
  9. PrintTaskService: vi.fn().mockImplementation(() => ({
  10. getPendingDelayedTasks: vi.fn().mockResolvedValue([]),
  11. executePrintTask: vi.fn().mockResolvedValue({})
  12. }))
  13. };
  14. });
  15. vi.mock('node-cron', () => {
  16. return {
  17. schedule: vi.fn().mockReturnValue({
  18. start: vi.fn(),
  19. stop: vi.fn()
  20. })
  21. };
  22. });
  23. describe('DelaySchedulerService', () => {
  24. let delaySchedulerService: DelaySchedulerService;
  25. let mockDataSource: DataSource;
  26. const mockFeieConfig: FeieApiConfig = {
  27. baseUrl: 'http://api.feieyun.cn/Api/Open/',
  28. user: 'test_user',
  29. ukey: 'test_ukey'
  30. };
  31. beforeEach(() => {
  32. vi.clearAllMocks();
  33. vi.useFakeTimers();
  34. mockDataSource = {
  35. getRepository: vi.fn().mockImplementation(() => {
  36. throw new Error('Repository not found');
  37. }),
  38. query: vi.fn().mockResolvedValue([])
  39. } as any;
  40. delaySchedulerService = new DelaySchedulerService(mockDataSource, mockFeieConfig);
  41. });
  42. afterEach(() => {
  43. vi.useRealTimers();
  44. });
  45. describe('constructor', () => {
  46. it('应该创建调度器实例', () => {
  47. expect(delaySchedulerService).toBeInstanceOf(DelaySchedulerService);
  48. });
  49. it('应该设置默认延迟时间为120秒', () => {
  50. expect(delaySchedulerService.getDefaultDelaySeconds()).toBe(120);
  51. });
  52. });
  53. describe('start', () => {
  54. it('应该启动调度器', async () => {
  55. await delaySchedulerService.start();
  56. expect(cron.schedule).toHaveBeenCalledWith('*/30 * * * * *', expect.any(Function));
  57. expect(delaySchedulerService.getStatus().isRunning).toBe(true);
  58. });
  59. it('应该在调度器已在运行时抛出错误', async () => {
  60. await delaySchedulerService.start();
  61. await expect(delaySchedulerService.start())
  62. .rejects
  63. .toThrow('调度器已经在运行中');
  64. });
  65. });
  66. describe('stop', () => {
  67. it('应该停止调度器', async () => {
  68. await delaySchedulerService.start();
  69. await delaySchedulerService.stop();
  70. expect(delaySchedulerService.getStatus().isRunning).toBe(false);
  71. });
  72. it('应该在调度器未运行时抛出错误', async () => {
  73. await expect(delaySchedulerService.stop())
  74. .rejects
  75. .toThrow('调度器未在运行中');
  76. });
  77. });
  78. describe('setDefaultDelaySeconds', () => {
  79. it('应该设置默认延迟时间', () => {
  80. delaySchedulerService.setDefaultDelaySeconds(180);
  81. expect(delaySchedulerService.getDefaultDelaySeconds()).toBe(180);
  82. });
  83. it('应该在延迟时间为负数时抛出错误', () => {
  84. expect(() => delaySchedulerService.setDefaultDelaySeconds(-1))
  85. .toThrow('延迟时间不能为负数');
  86. });
  87. });
  88. describe('getStatus', () => {
  89. it('应该返回调度器状态', () => {
  90. const status = delaySchedulerService.getStatus();
  91. expect(status).toEqual({
  92. isRunning: false,
  93. defaultDelaySeconds: 120,
  94. tenantId: null
  95. });
  96. });
  97. it('应该在调度器运行时返回正确状态', async () => {
  98. await delaySchedulerService.start();
  99. const status = delaySchedulerService.getStatus();
  100. expect(status.isRunning).toBe(true);
  101. expect(status.defaultDelaySeconds).toBe(120);
  102. });
  103. });
  104. describe('triggerManualProcess', () => {
  105. it('应该手动触发任务处理', async () => {
  106. const tenantId = 1;
  107. const mockTasks = [
  108. { taskId: 'TASK1', printerSn: 'PRINTER1' },
  109. { taskId: 'TASK2', printerSn: 'PRINTER2' }
  110. ];
  111. // Mock getPendingDelayedTasks to return tasks
  112. const mockPrintTaskService = {
  113. getPendingDelayedTasks: vi.fn().mockResolvedValue(mockTasks),
  114. executePrintTask: vi.fn().mockResolvedValue({})
  115. };
  116. (delaySchedulerService as any).printTaskService = mockPrintTaskService;
  117. const result = await delaySchedulerService.triggerManualProcess(tenantId);
  118. expect(result).toEqual({
  119. success: true,
  120. processedTasks: 2,
  121. message: '成功处理 2 个延迟打印任务'
  122. });
  123. expect(mockPrintTaskService.getPendingDelayedTasks).toHaveBeenCalledWith(tenantId);
  124. expect(mockPrintTaskService.executePrintTask).toHaveBeenCalledTimes(2);
  125. });
  126. it('应该在处理失败时返回错误信息', async () => {
  127. const tenantId = 1;
  128. // Mock getPendingDelayedTasks to throw error
  129. const mockPrintTaskService = {
  130. getPendingDelayedTasks: vi.fn().mockRejectedValue(new Error('数据库错误'))
  131. };
  132. (delaySchedulerService as any).printTaskService = mockPrintTaskService;
  133. const result = await delaySchedulerService.triggerManualProcess(tenantId);
  134. expect(result).toEqual({
  135. success: false,
  136. processedTasks: 0,
  137. message: '手动处理失败: 数据库错误'
  138. });
  139. });
  140. });
  141. describe('healthCheck', () => {
  142. it('应该返回健康状态', async () => {
  143. const health = await delaySchedulerService.healthCheck();
  144. expect(health).toEqual({
  145. healthy: false,
  146. isRunning: false,
  147. timestamp: expect.any(Date)
  148. });
  149. });
  150. it('应该在调度器运行时返回健康状态', async () => {
  151. await delaySchedulerService.start();
  152. const health = await delaySchedulerService.healthCheck();
  153. expect(health).toEqual({
  154. healthy: true,
  155. isRunning: true,
  156. timestamp: expect.any(Date)
  157. });
  158. });
  159. });
  160. describe('processDelayedTasks', () => {
  161. it('应该处理延迟任务', async () => {
  162. // 由于processDelayedTasks是私有方法,我们通过start方法来测试
  163. // 当调度器启动时,它会定期调用processDelayedTasks
  164. await delaySchedulerService.start();
  165. // 手动触发一次处理
  166. const scheduleCallback = vi.mocked(cron.schedule).mock.calls[0][1];
  167. await scheduleCallback();
  168. // 验证处理逻辑被调用
  169. expect(true).toBe(true);
  170. });
  171. it('应该在处理失败时记录错误', async () => {
  172. const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
  173. // Mock getTenantsWithDelayedTasks to throw error
  174. const originalGetTenantsWithDelayedTasks = (delaySchedulerService as any).getTenantsWithDelayedTasks;
  175. (delaySchedulerService as any).getTenantsWithDelayedTasks = vi.fn().mockRejectedValue(new Error('处理失败'));
  176. await delaySchedulerService.start();
  177. const scheduleCallback = vi.mocked(cron.schedule).mock.calls[0][1];
  178. await scheduleCallback();
  179. expect(consoleErrorSpy).toHaveBeenCalledWith('处理延迟打印任务失败:', expect.any(Error));
  180. // Restore
  181. (delaySchedulerService as any).getTenantsWithDelayedTasks = originalGetTenantsWithDelayedTasks;
  182. consoleErrorSpy.mockRestore();
  183. });
  184. });
  185. });