Skip to content
167 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { test } from "node:test";
3
4import { HEARTBEAT_MS, type DesktopRunnerView, backendOf, diagnosisOf, jobHref, reportDue, reportKey, reportOf } from "./desktop-runner.ts";
5
6function view(over: Partial<DesktopRunnerView> = {}): DesktopRunnerView {
7 return {
8 diagnosis: { code: "no_docker", tone: "problem", title: "Not running: Docker isn't running", cause: "Docker isn't running.", fix: "Start Docker Desktop.", actions: [{ id: "open_docker", label: "Open Docker" }] },
9 closed: { code: "off", tone: "off", title: "g1t is closed on build-box", cause: null, fix: "Open g1t on build-box.", actions: [] },
10 running: false,
11 version: "0.2.3",
12 backend: "docker",
13 workspace: "acme",
14 workspaces: [{ slug: "acme", name: "Acme" }],
15 enabled: true,
16 paused: false,
17 link: "https://g1t.sh/acme/-/runners",
18 machine: "build-box",
19 platform: "windows",
20 log: ["2026-10-12T08:00:00Z a line"],
21 ...over,
22 };
23}
24
25test("a report carries the app's words, without its buttons or log", () => {
26 const report = reportOf(view());
27 assert.deepEqual(report, {
28 workspace: "acme",
29 machine: "build-box",
30 backend: "docker",
31 version: "0.2.3",
32 diagnosis: { code: "no_docker", tone: "problem", title: "Not running: Docker isn't running", cause: "Docker isn't running.", fix: "Start Docker Desktop." },
33 closed: { code: "off", tone: "off", title: "g1t is closed on build-box", cause: null, fix: "Open g1t on build-box." },
34 });
35 assert.equal(reportOf(view({ workspace: null })), null);
36});
37
38test("a report goes when what the page shows changes, and as a heartbeat", () => {
39 const report = reportOf(view())!;
40 const at = 1_000_000;
41 assert.equal(reportDue(null, report, at), true);
42 const last = { key: reportKey(report), at };
43 assert.equal(reportDue(last, report, at + 1000), false);
44 assert.equal(reportDue(last, report, at + HEARTBEAT_MS), true);
45 const busy = reportOf(view({ diagnosis: { ...view().diagnosis, code: "busy", tone: "working", title: "Running Build (acme/web)" } }))!;
46 assert.equal(reportDue(last, busy, at + 1000), true);
47});
48
49test("a sandboxed desktop reports each kind of job, without its buttons", () => {
50 const linux = { id: "linux", label: "Linux jobs", tone: "ok" as const, title: "Waiting for jobs", cause: null, fix: null, actions: [] };
51 const windows = {
52 id: "windows",
53 label: "Windows jobs",
54 tone: "off" as const,
55 title: "Not available",
56 cause: "Windows Sandbox is off, or this edition of Windows has none: Home doesn't.",
57 fix: "Linux jobs still run.",
58 actions: [{ id: "windows_features", label: "Open Windows features" }],
59 };
60 const sandboxed = view({ backend: "sandboxed", diagnosis: { code: "idle", tone: "ok", title: "Waiting for jobs", cause: null, fix: null, actions: [], capabilities: [linux, windows] } });
61 const report = reportOf(sandboxed)!;
62 assert.deepEqual(report.diagnosis.capabilities, [
63 { id: "linux", label: "Linux jobs", tone: "ok", title: "Waiting for jobs", cause: null, fix: null },
64 { id: "windows", label: "Windows jobs", tone: "off", title: "Not available", cause: windows.cause, fix: "Linux jobs still run." },
65 ]);
66 // One kind of job changing is a report due.
67 const last = { key: reportKey(report), at: 0 };
68 const running = reportOf(view({ ...sandboxed, diagnosis: { ...sandboxed.diagnosis, capabilities: [linux, { ...windows, tone: "ok", title: "Waiting for jobs", cause: null, fix: null }] } }))!;
69 assert.equal(reportDue(last, running, 1000), true);
70 // What the server keeps: only kinds of job that read, and none for a desktop without any.
71 const kept = diagnosisOf({ ...report.diagnosis, capabilities: [...report.diagnosis.capabilities!, { id: "x", tone: "loud" }, "linux"] })!;
72 assert.equal(kept.capabilities?.length, 2);
73 assert.equal(diagnosisOf({ code: "idle", tone: "ok", title: "Waiting for jobs" })!.capabilities, undefined);
74 assert.equal(reportOf(view())!.diagnosis.capabilities, undefined);
75});
76
77test("each kind of job reports its state, whether the person stopped it, and its job, without what only works here", () => {
78 const linux = {
79 id: "linux",
80 label: "Linux jobs",
81 tone: "working" as const,
82 title: "Running a job",
83 cause: null,
84 fix: null,
85 state: "busy" as const,
86 about: "A Linux VM of its own, each job in a fresh container",
87 enabled: true,
88 job: { kind: "workflow", name: "build", repo: "acme/web", link: "https://g1t.sh/acme/web/actions" },
89 runner: "build-box-linux",
90 isolation: "nested-container",
91 version: "0.3.0",
92 available: true,
93 can_toggle: true,
94 progress: null,
95 actions: [],
96 };
97 const windows = {
98 id: "windows",
99 label: "Windows jobs",
100 tone: "off" as const,
101 title: "Stopped",
102 cause: "You stopped Windows jobs on this computer. They stay stopped until you start them.",
103 fix: null,
104 state: "stopped" as const,
105 enabled: false,
106 can_toggle: true,
107 actions: [],
108 };
109 const sandboxed = view({
110 backend: "sandboxed",
111 diagnosis: { code: "busy", tone: "working", title: "Running build (acme/web)", cause: null, fix: null, actions: [], capabilities: [linux, windows], in_capabilities: true },
112 });
113 const report = reportOf(sandboxed)!;
114 assert.equal(report.diagnosis.in_capabilities, true);
115 assert.deepEqual(report.diagnosis.capabilities![0], {
116 id: "linux",
117 label: "Linux jobs",
118 tone: "working",
119 title: "Running a job",
120 cause: null,
121 fix: null,
122 state: "busy",
123 about: "A Linux VM of its own, each job in a fresh container",
124 enabled: true,
125 job: { kind: "workflow", name: "build", repo: "acme/web" },
126 runner: "build-box-linux",
127 isolation: "nested-container",
128 version: "0.3.0",
129 });
130 assert.equal(report.diagnosis.capabilities![1].enabled, false);
131 // Starting Windows jobs again is a report due.
132 const last = { key: reportKey(report), at: 0 };
133 const started = reportOf(view({ ...sandboxed, diagnosis: { ...sandboxed.diagnosis, capabilities: [linux, { ...windows, enabled: true, state: "starting", title: "Starting", tone: "working" }] } }))!;
134 assert.equal(reportDue(last, started, 1000), true);
135 // What the server keeps reads back the same, and drops what isn't one of the app's.
136 const kept = diagnosisOf(JSON.parse(JSON.stringify(report.diagnosis)))!;
137 assert.deepEqual(kept, report.diagnosis);
138 const odd = diagnosisOf({ ...report.diagnosis, capabilities: [{ ...report.diagnosis.capabilities![0], state: "dancing", enabled: "yes", job: { name: "x" } }] })!;
139 assert.equal(odd.capabilities![0].state, undefined);
140 assert.equal(odd.capabilities![0].enabled, undefined);
141 assert.equal(odd.capabilities![0].job, undefined);
142 // Only a diagnosis with kinds of job says they say it all.
143 assert.equal(diagnosisOf({ code: "idle", tone: "ok", title: "Waiting for jobs", in_capabilities: true })!.in_capabilities, undefined);
144});
145
146test("a kind of job's work links to its run when the runner says which", () => {
147 const capability = { runner: "build-box-linux", job: { kind: "workflow", name: "build", repo: "acme/web" } };
148 const runners = [{ name: "build-box-linux", work: { kind: "workflow" as const, id: "job_1", name: "build", repo: "acme/web", runId: "run_9", startedAt: null } }];
149 assert.equal(jobHref(capability, runners), "/acme/web/actions/runs/run_9");
150 assert.equal(jobHref(capability, []), "/acme/web/actions");
151 assert.equal(jobHref({ runner: null, job: { kind: "agent", name: "fix", repo: "acme/web" } }, runners), "/acme/web/agents");
152 assert.equal(jobHref({ runner: "x", job: null }, runners), null);
153});
154
155test("only a diagnosis and a backend the app makes are kept", () => {
156 assert.deepEqual(diagnosisOf({ code: "idle", tone: "ok", title: "Waiting for jobs", cause: "Running sandboxed.", extra: 1 }), {
157 code: "idle",
158 tone: "ok",
159 title: "Waiting for jobs",
160 cause: "Running sandboxed.",
161 fix: null,
162 });
163 assert.equal(diagnosisOf({ code: "idle", tone: "loud", title: "x" }), null);
164 assert.equal(diagnosisOf("idle"), null);
165 assert.equal(backendOf("host"), "host");
166 assert.equal(backendOf("vm"), null);
167});