Skip to main content
Glama
IBM
by IBM
scheduler.test.ts6.92 kB
/** * @fileoverview Tests for the SchedulerService singleton and job management functionality. * @module tests/utils/scheduling/scheduler.test */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { SchedulerService } from "../../../src/utils/scheduling/scheduler.js"; // Mock only the utilities, keep node-cron real for validation vi.mock("../../../src/utils/internal/index.js", () => ({ logger: { info: vi.fn(), warning: vi.fn(), error: vi.fn(), }, })); vi.mock("../../../src/utils/internal/requestContext.js", () => ({ requestContextService: { createRequestContext: vi.fn(() => ({ requestId: "test-request-id", timestamp: new Date().toISOString(), jobId: "test-job", schedule: "* * * * *", })), }, })); describe("SchedulerService", () => { let scheduler: SchedulerService; let mockTaskFunction: ReturnType<typeof vi.fn>; beforeEach(() => { vi.clearAllMocks(); // Reset singleton instance for clean tests (SchedulerService as unknown as { instance?: SchedulerService }).instance = undefined; scheduler = SchedulerService.getInstance(); mockTaskFunction = vi.fn().mockResolvedValue(undefined); // Clear any existing jobs const existingJobs = scheduler.listJobs(); existingJobs.forEach((job) => { try { scheduler.remove(job.id); } catch { // Job might already be removed } }); }); afterEach(() => { vi.restoreAllMocks(); // Clean up any remaining jobs const jobs = scheduler.listJobs(); jobs.forEach((job) => { try { scheduler.remove(job.id); } catch { // Job might already be removed } }); // Reset singleton for next test (SchedulerService as unknown as { instance?: SchedulerService }).instance = undefined; }); describe("Singleton Pattern", () => { it("should return the same instance when getInstance is called multiple times", () => { const instance1 = SchedulerService.getInstance(); const instance2 = SchedulerService.getInstance(); expect(instance1).toBe(instance2); expect(instance1).toBe(scheduler); }); }); describe("schedule", () => { it("should throw error when scheduling job with duplicate ID", () => { const jobId = "duplicate-job"; const schedule = "0 0 * * *"; const description = "First job"; scheduler.schedule(jobId, schedule, mockTaskFunction, description); expect(() => { scheduler.schedule(jobId, schedule, mockTaskFunction, "Second job"); }).toThrow(`Job with ID '${jobId}' already exists.`); }); it("should throw error when providing invalid cron schedule", () => { const jobId = "invalid-schedule-job"; const invalidSchedule = "invalid cron pattern"; const description = "Job with invalid schedule"; expect(() => { scheduler.schedule( jobId, invalidSchedule, mockTaskFunction, description, ); }).toThrow(`Invalid cron schedule: ${invalidSchedule}`); }); it("should validate different cron patterns correctly", () => { const validSchedules = [ "* * * * *", // Every minute "0 0 * * *", // Daily at midnight "0 0 * * 0", // Weekly on Sunday "0 0 1 * *", // Monthly on 1st "*/5 * * * *", // Every 5 minutes ]; validSchedules.forEach((schedule, index) => { const jobId = `valid-job-${index}`; expect(() => { scheduler.schedule( jobId, schedule, mockTaskFunction, `Valid job ${index}`, ); }).not.toThrow(); }); }); }); describe("start", () => { it("should throw error when starting non-existent job", () => { const nonExistentJobId = "non-existent-job"; expect(() => { scheduler.start(nonExistentJobId); }).toThrow(`Job with ID '${nonExistentJobId}' not found.`); }); }); describe("stop", () => { it("should throw error when stopping non-existent job", () => { const nonExistentJobId = "non-existent-job"; expect(() => { scheduler.stop(nonExistentJobId); }).toThrow(`Job with ID '${nonExistentJobId}' not found.`); }); }); describe("remove", () => { it("should throw error when removing non-existent job", () => { const nonExistentJobId = "non-existent-job"; expect(() => { scheduler.remove(nonExistentJobId); }).toThrow(`Job with ID '${nonExistentJobId}' not found.`); }); }); describe("listJobs", () => { it("should return empty array when no jobs are scheduled", () => { const jobs = scheduler.listJobs(); expect(jobs).toEqual([]); }); it("should return all scheduled jobs", () => { const job1Id = "list-job-1"; const job2Id = "list-job-2"; const job1 = scheduler.schedule( job1Id, "0 0 * * *", mockTaskFunction, "First job", ); const job2 = scheduler.schedule( job2Id, "0 12 * * *", mockTaskFunction, "Second job", ); const jobs = scheduler.listJobs(); expect(jobs).toHaveLength(2); expect(jobs).toContainEqual(job1); expect(jobs).toContainEqual(job2); }); it("should reflect job state changes in listed jobs", () => { const jobId = "state-tracking-job"; scheduler.schedule( jobId, "0 0 * * *", mockTaskFunction, "State tracking job", ); const jobsBefore = scheduler.listJobs(); expect(jobsBefore[0].isRunning).toBe(false); // Simulate job running state change const job = scheduler.listJobs()[0]; job.isRunning = true; const jobsAfter = scheduler.listJobs(); expect(jobsAfter[0].isRunning).toBe(true); }); }); describe("Integration with node-cron", () => { it("should use real cron validation for schedule patterns", () => { // Test that we're actually using node-cron's validation expect(() => { scheduler.schedule("test-job", "invalid", mockTaskFunction, "Test"); }).toThrow("Invalid cron schedule: invalid"); // Verify valid patterns work expect(() => { scheduler.schedule("valid-job", "0 0 * * *", mockTaskFunction, "Valid"); }).not.toThrow(); }); it("should create actual ScheduledTask objects", () => { const job = scheduler.schedule( "task-test", "0 0 * * *", mockTaskFunction, "Task test", ); // Verify the task has node-cron ScheduledTask methods expect(job.task).toHaveProperty("start"); expect(job.task).toHaveProperty("stop"); expect(typeof job.task.start).toBe("function"); expect(typeof job.task.stop).toBe("function"); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/IBM/ibmi-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server