Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100import { afterEach, beforeEach, describe, expect, it } from "vitest";import { CentralStore, type JobRow } from "../../central/store";
/** What the dispatcher asks of the database: jobs that carry which dispatch and retry they belong to and whether they post, a count of posting jobs per day, and the jobs of one dispatch. */
let s: CentralStore;beforeEach(() => { s = new CentralStore(":memory:"); for (const id of ["dev-a", "dev-b"]) s.insertDevice({ id, name: id, person: "p", origins: ["http://x.example"], createdAt: 1 });});afterEach(() => s.close());
const job = (id: string, deviceId: string, at: number, over: Partial<JobRow> = {}): JobRow => ({ id, createdAt: at, deviceId, state: "offered", attempt: 1, test: { name: "t", bundle: "b".repeat(64), file: "scripts/a.aite" }, start: "http://x.example/", allow: ["http://x.example"], budget: { totalMs: 1000, postsAllowed: 0 }, ...over,});const DAY = Date.parse("2026-10-15T00:00:00Z");
describe("jobs of a dispatch", () => { it("keep their dispatch id, what they are a retry of, and whether they post", () => { s.insertJob(job("j1", "dev-a", DAY, { dispatchId: "d1", posts: true })); s.insertJob(job("j2", "dev-b", DAY + 1, { dispatchId: "d1", retryOf: "j1", posts: false })); expect(s.jobById("j1")).toMatchObject({ dispatchId: "d1", posts: true }); expect(s.jobById("j2")).toMatchObject({ dispatchId: "d1", retryOf: "j1", posts: false }); expect(s.jobsOfDispatch("d1").map((j) => j.id)).toEqual(["j1", "j2"]); expect(s.jobsOfDispatch("nope")).toEqual([]); }); it("can be skipped: a job that was never offered, with its reason", () => { s.insertJob(job("j1", "dev-a", DAY, { state: "skipped", failReason: "post cap reached", finishedAt: DAY })); expect(s.jobById("j1")).toMatchObject({ state: "skipped", failReason: "post cap reached" }); expect(s.listJobs({ state: "skipped" })).toHaveLength(1); });});
describe("counting posts", () => { it("counts jobs that post since a time, for one device or for all, and does not count skipped ones or ones that do not post", () => { s.insertJob(job("p1", "dev-a", DAY + 10, { posts: true })); s.insertJob(job("p2", "dev-a", DAY + 20, { posts: true })); s.insertJob(job("p3", "dev-b", DAY + 30, { posts: true })); s.insertJob(job("n1", "dev-a", DAY + 40, { posts: false })); s.insertJob(job("old", "dev-a", DAY - 5, { posts: true })); s.insertJob(job("sk", "dev-a", DAY + 50, { posts: true, state: "skipped" })); expect(s.countPostsSince(DAY, "dev-a")).toBe(2); expect(s.countPostsSince(DAY, "dev-b")).toBe(1); expect(s.countPostsSince(DAY)).toBe(3); expect(s.countPostsSince(DAY + 100)).toBe(0); });});
describe("counting spends (#69)", () => { it("counts jobs that spend since a time, for one device or for all, apart from posts, and not skipped ones", () => { s.insertJob(job("s1", "dev-a", DAY + 10, { spends: true })); s.insertJob(job("s2", "dev-a", DAY + 20, { spends: true, posts: true })); s.insertJob(job("s3", "dev-b", DAY + 30, { spends: true })); s.insertJob(job("p1", "dev-a", DAY + 40, { posts: true, spends: false })); s.insertJob(job("old", "dev-a", DAY - 5, { spends: true })); s.insertJob(job("sk", "dev-a", DAY + 50, { spends: true, state: "skipped" })); expect(s.countSpendsSince(DAY, "dev-a")).toBe(2); expect(s.countSpendsSince(DAY, "dev-b")).toBe(1); expect(s.countSpendsSince(DAY)).toBe(3); expect(s.countSpendsSince(DAY + 100)).toBe(0); expect(s.countPostsSince(DAY)).toBe(2); expect(s.jobById("s2")).toMatchObject({ spends: true, posts: true }); expect(s.jobById("p1")).toMatchObject({ spends: false }); });});
describe("counting a device's open jobs", () => { it("counts offered and accepted jobs of a device, for choosing the least loaded", () => { s.insertJob(job("a1", "dev-a", DAY)); s.insertJob(job("a2", "dev-a", DAY + 1)); s.insertJob(job("b1", "dev-b", DAY + 2, { state: "done" })); expect(s.openJobCounts()).toEqual({ "dev-a": 2 }); }); it("the time a device last got a job, for taking turns", () => { s.insertJob(job("a1", "dev-a", DAY + 5)); expect(s.lastJobAt("dev-a")).toBe(DAY + 5); expect(s.lastJobAt("dev-b")).toBeUndefined(); });});
describe("stopping accepted jobs", () => { it("cancels every accepted job with a reason and says which, and leaves offered ones", () => { s.insertJob(job("a1", "dev-a", DAY)); s.insertJob(job("a2", "dev-b", DAY + 1)); s.accept("a1", "dev-a", DAY + 2, DAY + 60_000); expect(s.cancelAccepted("stopped by the operator", DAY + 3)).toEqual(["a1"]); expect(s.jobById("a1")).toMatchObject({ state: "cancelled", failReason: "stopped by the operator" }); expect(s.jobById("a2")?.state).toBe("offered"); });});
describe("a job given up for good", () => { it("lists jobs that ended without a result in a dispatch, for reassigning", () => { s.insertJob(job("j1", "dev-a", DAY, { dispatchId: "d1" })); s.accept("j1", "dev-a", DAY + 1, DAY + 100); expect(s.release("j1", "dev-a", { state: "failed", reason: "aborted-by-device: paused", at: DAY + 2 })).toBe(true); expect(s.jobsOfDispatch("d1")[0]).toMatchObject({ state: "failed", failReason: "aborted-by-device: paused" }); });});