155 lines
8.2 KiB
TypeScript
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");
|
|
});
|
|
});
|