Files
projectbrain-Public/src/shared/lib/activityRuntimePresentation.test.ts
T

155 lines
8.2 KiB
TypeScript

import { describe, expect, it } from "vitest";
import type { RuntimeJob, RuntimeRunner } from "@/shared/api/portfolioTypes";
import { getActivityOperationSections, latestCompletedRuntimeJobByRunner, oldestRunnableRuntimeJob, prioritizeRuntimeJobs, runtimeCompatibleIdleRunnerCount, runtimeJobAgeMinutes, runtimeJobDetail, runtimeJobGroups, runtimeJobLabel, runtimeJobTimingLabel, runtimeQueueExplanation, runtimeRunnerCapacity, runtimeRunnerCapacityPresentation } from "@/shared/lib/activityRuntimePresentation";
const job = (queueState: RuntimeJob["queueState"], input: Partial<RuntimeJob> = {}): RuntimeJob => ({
id: 1,
name: "quality",
status: queueState === "runnable" ? "queued" : queueState === "dependency-blocked" ? "pending" : queueState === "running" ? "in_progress" : "completed",
queueState,
completedSteps: 0,
totalSteps: 1,
url: "https://gitea.example/jobs/1",
createdAt: "2026-09-01T12:00:00Z",
...input,
});
describe("activity runtime presentation", () => {
it("keeps running jobs ahead of newer queued jobs before the display limit", () => {
const jobs = [...Array.from({ length: 30 }, (_, i) => job("runnable", { id: 100 + i })), job("running", { id: 1 }), job("running", { id: 2 })];
expect(prioritizeRuntimeJobs(jobs).slice(0, 12).slice(0, 2).map((item) => item.id)).toEqual([2, 1]);
expect(jobs[0].id).toBe(100);
expect(prioritizeRuntimeJobs([job("completed", { id: 9 }), job("dependency-blocked", { id: 8 }), job("runnable", { id: 7 }), job("running", { id: 6 })]).map((item) => item.id)).toEqual([6, 7, 8, 9]);
});
it("orders queued work by actual waiting age rather than newest id", () => {
const ordered = prioritizeRuntimeJobs([
job("runnable", { id: 9, createdAt: "2026-09-01T12:09:00Z" }),
job("runnable", { id: 7, createdAt: "2026-09-01T12:03:00Z" }),
job("runnable", { id: 8, createdAt: "2026-09-01T12:06:00Z" }),
]);
expect(ordered.map((item) => item.id)).toEqual([7, 8, 9]);
});
it("uses oldest creation time, ascending id ties, and unknown timestamps last for queued jobs", () => {
const ordered = prioritizeRuntimeJobs([
job("runnable", { id: 9, createdAt: "2026-09-01T12:03:00Z" }),
job("runnable", { id: 4, createdAt: "2026-09-01T12:03:00Z" }),
job("runnable", { id: 7, createdAt: "2026-09-01T12:06:00Z" }),
job("runnable", { id: 2, createdAt: "not-a-timestamp" }),
]);
expect(ordered.map((item) => item.id)).toEqual([4, 9, 7, 2]);
});
it("does not present dependency-blocked work as the oldest runnable job", () => {
const blocked = job("dependency-blocked", { id: 5, status: "requested", createdAt: "2026-09-01T12:00:00Z" });
expect(runtimeJobLabel(blocked)).toBe("Wacht op eerdere job");
expect(runtimeJobDetail(blocked)).toBe("Start zodra de vereiste eerdere job is afgerond");
expect(oldestRunnableRuntimeJob([
blocked,
job("dependency-blocked", { id: 4, createdAt: "2026-09-01T11:00:00Z" }),
])).toBeUndefined();
});
it("selects the oldest visible runnable job while ignoring dependency-blocked jobs", () => {
const selected = oldestRunnableRuntimeJob([
job("dependency-blocked", { id: 1, createdAt: "2026-09-01T11:00:00Z" }),
job("runnable", { id: 9, createdAt: "2026-09-01T12:09:00Z" }),
job("runnable", { id: 4, createdAt: "2026-09-01T12:03:00Z" }),
]);
expect(selected?.id).toBe(4);
});
it("reports waiting age and compatible idle runner capacity", () => {
const queued = job("runnable", { labels: ["linux-validation"], createdAt: "2026-09-01T12:00:00Z" });
const runners: RuntimeRunner[] = [
{ id: 1, name: "validation", status: "online", busy: false, disabled: false, labels: ["linux-validation"] },
{ id: 2, name: "security", status: "online", busy: false, disabled: false, labels: ["security"] },
{ id: 3, name: "busy-validation", status: "online", busy: true, disabled: false, labels: ["linux-validation"] },
];
const now = Date.parse("2026-09-01T12:07:30Z");
expect(runtimeJobAgeMinutes(queued, now)).toBe(7);
expect(runtimeJobTimingLabel(queued, now)).toBe("Wacht 7 min");
expect(runtimeCompatibleIdleRunnerCount(queued, runners)).toBe(1);
expect(runtimeCompatibleIdleRunnerCount(job("runnable", { labels: ["missing"] }), runners)).toBe(0);
});
it("never presents a queued job as completed", () => {
expect(runtimeJobLabel(job("runnable"))).toBe("Wacht op runner");
expect(runtimeQueueExplanation(38, 0, 0)).toContain("nog geen afgeronde jobs");
});
it("keeps capacity unknown when runner discovery is incomplete", () => {
const display = runtimeRunnerCapacityPresentation(runtimeRunnerCapacity([]), false);
expect(display).toEqual({ value: "?", label: "runnercapaciteit onbekend" });
const explanation = runtimeQueueExplanation(2, 0, 0, false);
expect(explanation).toContain("capaciteit is onbekend");
expect(explanation).not.toContain("alle online runners zijn momenteel bezig");
});
it("does not describe a known-empty runner inventory as busy capacity", () => {
const explanation = runtimeQueueExplanation(2, 0, 0, true, 0);
expect(explanation).toContain("geen online runners beschikbaar");
expect(explanation).not.toContain("alle online runners zijn momenteel bezig");
});
it("describes positive online capacity with no idle runners as busy", () => {
expect(runtimeQueueExplanation(2, 0, 0, true, 3)).toContain("alle online runners zijn momenteel bezig");
});
it("explains persistent queueing when free runners exist", () => {
expect(runtimeQueueExplanation(2, 0, 1)).toContain("1 vrije runner");
expect(runtimeQueueExplanation(2, 0, 2)).toContain("joblabels");
});
it("keeps runner and dependency queues separate", () => {
const groups = runtimeJobGroups([job("running"), job("runnable", { id: 2 }), job("dependency-blocked", { id: 3 })]);
expect(groups.running).toHaveLength(1);
expect(groups.runnable).toHaveLength(1);
expect(groups.dependencyBlocked).toHaveLength(1);
});
it("classifies current work before queues and historical outcomes", () => {
const sections = getActivityOperationSections([
job("completed", { id: 1, conclusion: "success" }),
job("dependency-blocked", { id: 2 }),
job("runnable", { id: 3 }),
job("running", { id: 4 }),
job("completed", { id: 5, conclusion: "failure" }),
]);
expect(sections.active.map((item) => item.id)).toEqual([4]);
expect(sections.waitingForRunner.map((item) => item.id)).toEqual([3]);
expect(sections.waitingForDependency.map((item) => item.id)).toEqual([2]);
expect(sections.recentCompleted.map((item) => item.id)).toEqual([1]);
expect(sections.recentFailed.map((item) => item.id)).toEqual([5]);
});
it("counts only online non-busy runners as free capacity", () => {
const runners: RuntimeRunner[] = [
{ id: 1, name: "busy", status: "online", busy: true, disabled: false, labels: [] },
{ id: 2, name: "idle", status: "online", busy: false, disabled: false, labels: [] },
{ id: 3, name: "offline", status: "offline", busy: false, disabled: false, labels: [] },
{ id: 4, name: "disabled", status: "online", busy: false, disabled: true, labels: [] },
];
expect(runtimeRunnerCapacity(runners)).toEqual({ total: 4, enabled: 3, online: 2, busy: 1, idle: 1, disabled: 1 });
});
it("keeps the latest completed activity per named runner", () => {
const latest = latestCompletedRuntimeJobByRunner([
job("completed", { id: 1, runnerName: "runner-a", repository: "Jens/old", completedAt: "2026-09-01T12:05:00Z" }),
job("completed", { id: 2, runnerName: "runner-a", repository: "Jens/new", completedAt: "2026-09-01T12:10:00Z" }),
job("completed", { id: 3, runnerName: "runner-b", repository: "Jens/other", completedAt: "2026-09-01T12:07:00Z" }),
job("running", { id: 4, runnerName: "runner-a" }),
]);
expect(latest["runner-a"]?.id).toBe(2);
expect(latest["runner-b"]?.repository).toBe("Jens/other");
});
it("translates final conclusions independently from queue state", () => {
expect(runtimeJobLabel(job("completed", { conclusion: "success" }))).toBe("Geslaagd");
expect(runtimeJobLabel(job("completed", { conclusion: "failure" }))).toBe("Mislukt");
});
});