Public: anyone can read this; the crew can change it.

A test pins the home's idle reason: all busy says nothing, then no slots, the ceiling, or no ready work, in that order #6960

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/6872-a-test-pins-the-home-s-idle-reason-all-b → main 1 commit fork

  • Merged. scout/6872-a-test-pins-the-home-s-idle-reason-all-b was merged into main as 740174c.

Review

Sign in to review this change.
1 file changed +83 −0
test/home-truth.test.ts added +83
@@ -0,0 +1,83 @@
1 /** The owner's home idle reason and waiting figures (#6872). */
2 import { afterAll, beforeAll, expect, test } from "bun:test";
3 import { mkdtemp, rm } from "node:fs/promises";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createRepo, moveArchived } from "../src/core/repos.ts";
8 import { createUser, type User } from "../src/core/users.ts";
9 import { id } from "../src/kernel/id.ts";
10 import { homeTruth, idleReason } from "../src/system/home_truth.ts";
11
12 test("idleReason says nothing when every slot runs, even over the ceiling", () => {
13 expect(idleReason({ running: 3, slots: 3 }, { todayUsd: 0, ceilingUsd: 10 })).toBeNull();
14 expect(idleReason({ running: 4, slots: 3 }, { todayUsd: 99, ceilingUsd: 10 })).toBeNull();
15 });
16
17 test("idleReason: no slots first, then the ceiling, then no ready work", () => {
18 expect(idleReason({ running: 0, slots: 0 }, { todayUsd: 99, ceilingUsd: 10 })).toBe("no slots set");
19 expect(idleReason({ running: 1, slots: 3 }, { todayUsd: 10, ceilingUsd: 10 })).toBe("daily ceiling of $10 reached");
20 expect(idleReason({ running: 1, slots: 3 }, { todayUsd: 12, ceilingUsd: 10 })).toBe("daily ceiling of $10 reached");
21 expect(idleReason({ running: 1, slots: 3 }, { todayUsd: 9.99, ceilingUsd: 10 })).toBe("no ready work");
22 expect(idleReason({ running: 0, slots: 3 }, { todayUsd: 0, ceilingUsd: 10 })).toBe("no ready work");
23 });
24
25 test("idleReason never answers the ceiling when there is none", () => {
26 expect(idleReason({ running: 0, slots: 3 }, { todayUsd: 1e6, ceilingUsd: null })).toBe("no ready work");
27 });
28
29 let dataDir: string;
30 let app: App;
31 let owner: User;
32 let stranger: User;
33 const now = new Date("2026-10-27T12:00:00Z");
34
35 beforeAll(async () => {
36 dataDir = await mkdtemp(join(tmpdir(), "forge-home-truth-"));
37 app = createApp({ dataDir, port: 0 });
38 const mk = async (name: string, first: boolean) => {
39 const r = await createUser(app.db, { origin: "seed", trusted: true, username: name, email: `${name}@forge.local`, password: "a-good-passphrase", adminIfFirst: first });
40 if (!r.ok) throw new Error(r.error);
41 return r.value;
42 };
43 owner = await mk("keeper", true);
44 stranger = await mk("stranger", false);
45 const repos: Record<string, string> = {};
46 for (const [name, visibility] of [["open", "public"], ["secret", "private"], ["shelved", "public"]] as const) {
47 const r = await createRepo(app.db, app.reposRoot, { ownerId: owner.id, name, visibility, description: name });
48 if (!r.ok) throw new Error(r.error);
49 repos[name] = r.value.id;
50 }
51 // Open issues assigned to the stranger: an assignment is an ask of its own.
52 const assign = (repo: string, title: string, at: string) => {
53 const number = app.db.query<{ issue_seq: number }, [string]>("UPDATE repo SET issue_seq = issue_seq + 1 WHERE id = ?1 RETURNING issue_seq").get(repos[repo]!)!.issue_seq;
54 app.db
55 .query(`INSERT INTO issue (id, repo_id, number, kind, title, body, author_id, state, created_at, updated_at, assignee_id) VALUES (?1, ?2, ?3, 'issue', ?4, '', ?5, 'open', ?6, ?6, ?7)`)
56 .run(id(), repos[repo]!, number, title, owner.id, at, stranger.id);
57 };
58 assign("open", "Newer", "2026-10-26T10:00:00.000Z");
59 assign("open", "Older", "2026-10-25T08:00:00.000Z");
60 assign("secret", "Hidden", "2026-10-01T00:00:00.000Z");
61 assign("shelved", "Put away", "2026-09-01T00:00:00.000Z");
62 moveArchived(app.db, repos.shelved!, true);
63 });
64
65 afterAll(async () => {
66 app.close();
67 await rm(dataDir, { recursive: true, force: true });
68 });
69
70 test("homeTruth counts only visible, unarchived asks and names the oldest time", () => {
71 const w = homeTruth(app, stranger, now).waiting;
72 expect(w.count).toBe(2);
73 expect(w.oldestAt).toBe("2026-10-25T08:00:00.000Z");
74 });
75
76 test("homeTruth with nothing waiting has no oldest time", () => {
77 expect(homeTruth(app, owner, now).waiting).toEqual({ count: 0, oldestAt: null });
78 });
79
80 test("homeTruth carries the idle reason beside the builders", () => {
81 const t = homeTruth(app, owner, now);
82 expect(t.builders.idle).toBe(idleReason(t.builders, t.spend));
83 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs