import { afterEach, describe, expect, it } from "bun:test"; import { fetchJobCompletion, fetchSubscribedJobs, parseJobCompletionTransition, resetJobsClientConnectorForTests, setJobsClientConnectorForTests, } from "../src/jobs-client.ts"; describe("fetchSubscribedJobs", () => { afterEach(resetJobsClientConnectorForTests); it("maps ci.subscribed's RunSnapshot-shaped runs to JobsWidgetRow", async () => { setJobsClientConnectorForTests( () => ({ async call() { return { runs: [ { backend: "jenkins-auto", jobRef: "ocp-baremetal-ipi-deployment", runId: "40531", status: "running", result: "", url: "https://jenkins.example/40531", startedAt: new Date(0), fetchedAt: new Date(0), watched: true, progressPercent: 42, estimatedMs: 100_000, overdue: false, }, ], }; }, // biome-ignore lint/suspicious/noExplicitAny: minimal test double, not the real PipesClient shape }) as any, ); const rows = await fetchSubscribedJobs(); expect(rows).toEqual([ { backend: "jenkins-auto", jobRef: "ocp-baremetal-ipi-deployment", runId: "40531", status: "running", url: "https://jenkins.example/40531", progressPercent: 42, overdue: false, startedAt: new Date(0), durationMs: undefined, }, ]); }); it("maps ci.subscribed's own durationMs through once a run has settled", async () => { setJobsClientConnectorForTests( () => ({ async call() { return { runs: [ { backend: "jenkins-auto", jobRef: "ocp-baremetal-ipi-deployment", runId: "40531", status: "success", result: "SUCCESS", url: "", startedAt: new Date(0), fetchedAt: new Date(0), watched: true, durationMs: 123_456, }, ], }; }, // biome-ignore lint/suspicious/noExplicitAny: minimal test double }) as any, ); const rows = await fetchSubscribedJobs(); expect(rows[0]?.durationMs).toBe(123_456); expect(rows[0]?.startedAt).toEqual(new Date(0)); }); it("leaves progressPercent/overdue undefined when ci.subscribed's own run carries none (e.g. GitLab)", async () => { setJobsClientConnectorForTests( () => ({ async call() { return { runs: [ { backend: "gl", jobRef: "project", runId: "9", status: "running", result: "", url: "", startedAt: new Date(0), fetchedAt: new Date(0), watched: true, }, ], }; }, // biome-ignore lint/suspicious/noExplicitAny: minimal test double }) as any, ); const rows = await fetchSubscribedJobs(); expect(rows[0]?.progressPercent).toBeUndefined(); expect(rows[0]?.overdue).toBeUndefined(); }); it("maps ci.subscribed's own projectName through when present", async () => { setJobsClientConnectorForTests( () => ({ async call() { return { runs: [ { backend: "jenkins-auto", jobRef: "ocp-baremetal-ipi-deployment", runId: "40531", status: "running", result: "", url: "", startedAt: new Date(0), fetchedAt: new Date(0), watched: true, projectRoot: "/home/x/pipes", projectName: "pipes", }, ], }; }, // biome-ignore lint/suspicious/noExplicitAny: minimal test double }) as any, ); const rows = await fetchSubscribedJobs(); expect(rows[0]?.projectName).toBe("pipes"); }); it("coerces startedAt into a real Date even when the wire actually sends a plain ISO string -- AuthenticatedRpcClient.call() is response.json() with no reviver, so RunSnapshot's own `startedAt: Date` type is a lie once it crosses the real HTTP transport; a mocked Date instance here would never have caught this", async () => { setJobsClientConnectorForTests( () => ({ async call() { return { runs: [ { backend: "jenkins-auto", jobRef: "ocp-far-edge-vran-deployment", runId: "9253", status: "running", result: "", url: "", // The real wire shape: a plain ISO string, not a Date instance. startedAt: "2026-08-24T14:07:18.795Z", fetchedAt: "2026-08-24T14:07:18.795Z", watched: true, }, ], }; }, // biome-ignore lint/suspicious/noExplicitAny: minimal test double, deliberately not the real PipesClient's declared (and here, misleading) types }) as any, ); const rows = await fetchSubscribedJobs(); expect(rows[0]?.startedAt).toBeInstanceOf(Date); expect(rows[0]?.startedAt?.getTime()).toBe(Date.parse("2026-08-24T14:07:18.795Z")); }); it("propagates a connector failure (e.g. daemon not running) as a rejection -- the overlay is responsible for catching it", async () => { setJobsClientConnectorForTests(() => { throw new Error("Pipes daemon is not running; run `pipes serve`."); }); await expect(fetchSubscribedJobs()).rejects.toThrow(/daemon is not running/); }); }); describe("completion delivery", () => { afterEach(resetJobsClientConnectorForTests); it("resolves the terminal verdict after a subscribed row disappears", async () => { setJobsClientConnectorForTests( () => ({ async call() { return { verdict: { check: { status: "failure", url: "https://ci.example/run/9" }, failure: { classification: "test_failure" }, }, }; }, // biome-ignore lint/suspicious/noExplicitAny: minimal typed-RPC test double }) as any, ); const result = await fetchJobCompletion({ backend: "gh", jobRef: "ci.yml", runId: "9", status: "running" }); expect(result).toEqual({ backend: "gh", jobRef: "ci.yml", runId: "9", status: "failure", url: "https://ci.example/run/9", failureClassification: "test_failure", }); }); it("accepts terminal push transitions only for the subscribing session", () => { const payload = { backend: "gh", jobRef: "ci.yml", runId: "9", status: "success", result: "SUCCESS", url: "https://ci.example/run/9", subscriberIds: ["session-a"], }; expect(parseJobCompletionTransition(payload, "session-a")?.result).toBe("SUCCESS"); expect(parseJobCompletionTransition(payload, "session-b")).toBeUndefined(); expect(parseJobCompletionTransition({ ...payload, status: "running" }, "session-a")).toBeUndefined(); }); });