198 lines
6.2 KiB
TypeScript
198 lines
6.2 KiB
TypeScript
import { mock, test, expect } from "bun:test";
|
|
import * as TE from "fp-ts/lib/TaskEither";
|
|
import type { Logger } from "../../src/util";
|
|
import {
|
|
perform,
|
|
type JobsTable,
|
|
execute,
|
|
formatJobResults,
|
|
logExecutingJobs,
|
|
type Job
|
|
} from "../../src/canary/scheduler";
|
|
|
|
const getMocks = () => {
|
|
const mockLogger: Logger = {
|
|
log: mock(),
|
|
error: mock()
|
|
};
|
|
return { mockLogger };
|
|
};
|
|
|
|
const schedule = {
|
|
every: 200,
|
|
jitter: 100
|
|
};
|
|
|
|
test("logging", () => {
|
|
const { mockLogger } = getMocks();
|
|
const jobs: Job[] = [
|
|
{ id: "1", toString: () => "Job 1", execute: mock(), schedule, maxRetries: 3 },
|
|
{ id: "2", toString: () => "Job 2", execute: mock(), schedule, maxRetries: 3 }
|
|
];
|
|
const now = new Date("2023-01-01T00:00:00Z");
|
|
|
|
logExecutingJobs(jobs, now, mockLogger);
|
|
|
|
expect(mockLogger.log).toHaveBeenCalledWith("Executing Job 1|Job 2 at Sun, 01 Jan 2023 00:00:00 GMT");
|
|
});
|
|
|
|
test("should separate jobs into successful and failed executions", async () => {
|
|
const job1: Job = {
|
|
id: "1",
|
|
toString: () => "Job 1",
|
|
execute: mock(() => TE.right("Result 1") as any),
|
|
schedule,
|
|
maxRetries: 3
|
|
};
|
|
const job2: Job = {
|
|
id: "2",
|
|
toString: () => "Job 2",
|
|
execute: mock(() => TE.left(new Error("Failure 2")) as any),
|
|
schedule,
|
|
maxRetries: 3
|
|
};
|
|
const jobs: Job[] = [job1, job2];
|
|
|
|
const result = await execute(jobs)();
|
|
|
|
expect(result.left).toEqual([[job2, new Error("Failure 2")]]);
|
|
expect(result.right).toEqual([[job1, "Result 1"]]);
|
|
});
|
|
|
|
test("should format job results correctly", () => {
|
|
const jobsTable: JobsTable = new Map([["1", { scheduled: new Date("2023-01-01T00:00:00Z"), retries: 1 }]]);
|
|
const left = [
|
|
[{ id: "1", toString: () => "Job 1", execute: mock(), schedule: {}, maxRetries: 3 }, new Error("Error 1")]
|
|
];
|
|
const right = [[{ id: "2", toString: () => "Job 2", execute: mock(), schedule: {}, maxRetries: 3 }, "Success 2"]];
|
|
|
|
const result = formatJobResults(jobsTable, { left, right } as any);
|
|
|
|
expect(result).toContain("Job 1 | 1 / 3 | (retry) :/ | Error 1");
|
|
expect(result).toContain("Job 2 | (success) :) | Success 2");
|
|
});
|
|
|
|
test("should update jobsTable and lastPingAck correctly", async () => {
|
|
const jobsTable: JobsTable = new Map([["1", { scheduled: new Date("2023-01-01T00:00:00Z"), retries: 1 }]]);
|
|
const jobs: Job[] = [
|
|
{
|
|
id: "1",
|
|
toString: () => "Job 1",
|
|
execute: mock(() => TE.right("Result 1") as any),
|
|
schedule,
|
|
maxRetries: 3
|
|
}
|
|
];
|
|
const publishers: any = [];
|
|
const lastPingAck = new Date("2023-01-01T00:00:00Z");
|
|
|
|
const result = await perform(jobsTable, jobs, publishers, lastPingAck)();
|
|
|
|
expect(result.lastPingAck).not.toEqual(lastPingAck);
|
|
expect(result.jobsTable.get("1")).toEqual({
|
|
retries: 0,
|
|
scheduled: expect.any(Date)
|
|
});
|
|
});
|
|
|
|
test("should update a job with retry count on failure", async () => {
|
|
// Create a mock job that fails the first time but succeeds the second time
|
|
const job1: Job = {
|
|
id: "1",
|
|
toString: () => "Job 1",
|
|
execute: mock(() => TE.left(new Error("Error 1")) as any),
|
|
schedule,
|
|
maxRetries: 2
|
|
};
|
|
|
|
const jobsTable: JobsTable = new Map([["1", { scheduled: new Date("2023-01-01T00:00:00Z"), retries: 0 }]]);
|
|
const jobs: Job[] = [job1];
|
|
const publishers: any = [];
|
|
const lastPingAck = new Date("2023-01-01T00:00:00Z");
|
|
|
|
const result = await perform(jobsTable, jobs, publishers, lastPingAck, 24 * 60 * 60 * 1000)();
|
|
|
|
// Assert the job was retried once and then succeeded
|
|
expect(job1.execute).toHaveBeenCalled();
|
|
|
|
// Check the jobsTable for the updated state
|
|
expect(result.jobsTable.get("1")).toEqual({
|
|
retries: 1,
|
|
scheduled: expect.any(Date)
|
|
});
|
|
});
|
|
|
|
test("should reschedule a job that hits max retries", async () => {
|
|
// Create a mock job that fails the first time but succeeds the second time
|
|
const job1: Job = {
|
|
id: "1",
|
|
toString: () => "Job 1",
|
|
execute: mock().mockReturnValue(TE.left(new Error("Error 1"))),
|
|
schedule,
|
|
maxRetries: 4
|
|
};
|
|
|
|
const jobsTable: JobsTable = new Map([["1", { scheduled: new Date("2023-01-01T00:00:00Z"), retries: 4 }]]);
|
|
const jobs: Job[] = [job1];
|
|
const publishers: any = [mock().mockReturnValue(TE.right(200))];
|
|
|
|
const lastPingAck = new Date("2023-01-01T00:00:00Z");
|
|
|
|
const now = Date.now();
|
|
const result = await perform(jobsTable, jobs, publishers, lastPingAck, 24 * 60 * 60 * 1000)();
|
|
const delta = Date.now() - now;
|
|
|
|
// Assert the job was retried once and then fail
|
|
expect(job1.execute).toHaveBeenCalled();
|
|
|
|
// Check the jobsTable for the updated state
|
|
const { retries, scheduled } = result.jobsTable.get("1")!;
|
|
expect(retries).toEqual(0);
|
|
expect(publishers[0]).toHaveBeenCalled();
|
|
|
|
expect(scheduled.getTime()).toBeGreaterThan(now - delta + schedule.every);
|
|
expect(scheduled.getTime()).toBeLessThan(now + delta + schedule.every + schedule.jitter);
|
|
});
|
|
|
|
test("should not publish only successes when should not ack", async () => {
|
|
// Create a mock job that fails the first time but succeeds the second time
|
|
const job1: Job = {
|
|
id: "1",
|
|
toString: () => "Job 1",
|
|
execute: mock().mockReturnValue(TE.right(new Error("Error 1"))),
|
|
schedule,
|
|
maxRetries: 4
|
|
};
|
|
|
|
const jobsTable: JobsTable = new Map([["1", { scheduled: new Date("2023-01-01T00:00:00Z"), retries: 0 }]]);
|
|
const jobs: Job[] = [job1];
|
|
const publishers: any = [mock().mockReturnValue(TE.right(200))];
|
|
const lastPingAck = new Date();
|
|
|
|
await perform(jobsTable, jobs, publishers, lastPingAck, 24 * 60 * 60 * 1000)();
|
|
|
|
expect(job1.execute).toHaveBeenCalled();
|
|
expect(publishers[0]).toHaveBeenCalledTimes(0);
|
|
});
|
|
|
|
test("should publish when should ack", async () => {
|
|
// Create a mock job that fails the first time but succeeds the second time
|
|
const job1: Job = {
|
|
id: "1",
|
|
toString: () => "Job 1",
|
|
execute: mock().mockReturnValue(TE.right(new Error("Error 1"))),
|
|
schedule,
|
|
maxRetries: 4
|
|
};
|
|
|
|
const jobsTable: JobsTable = new Map([["1", { scheduled: new Date("2023-01-01T00:00:00Z"), retries: 0 }]]);
|
|
const jobs: Job[] = [job1];
|
|
const publishers: any = [mock().mockReturnValue(TE.right(200))];
|
|
const lastPingAck = new Date("2023-01-01T00:00:00Z");
|
|
|
|
await perform(jobsTable, jobs, publishers, lastPingAck, 24 * 60 * 60 * 1000)();
|
|
|
|
expect(job1.execute).toHaveBeenCalled();
|
|
expect(publishers[0]).toHaveBeenCalled();
|
|
});
|