Public: anyone can read this; the crew can change it.
"model budget in the terminal > an admin sets and takes back a grant; a person has no such command" passes every time on a loaded check machine #5239
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/5198-model-budget-in-the-terminal-an-admin-se → main 2 commits fork 2 threads · 2 unresolved
test/tui-model.test.ts +15 −7
| @@ -14,7 +14,7 @@ import { createForgeApp } from "../src/tui/forge.ts"; | ||
| 14 | 14 | import { headlessTerminal } from "../src/tui/term/terminal.ts"; |
| 15 | 15 | import type { TuiIdentity } from "../src/tui/session.ts"; |
| 16 | 16 | import { quietLogs } from "./helpers/globals.ts"; |
| 17 | import { settleApp } from "./helpers/tui.ts"; | |
| 17 | import { settleApp, settleUntil } from "./helpers/tui.ts"; | |
| 18 | 18 | |
| 19 | 19 | quietLogs(); |
| 20 | 20 | |
| @@ -43,6 +43,8 @@ describe("model budget in the terminal", () => { | ||
| 43 | 43 | for (const ch of t) key(ch); |
| 44 | 44 | await settleApp(app); |
| 45 | 45 | }, |
| 46 | // Under bun's 5 s test timeout, so a miss shows the screen, not a bare timeout. | |
| 47 | until: (what: string) => settleUntil(app, what, { timeoutMs: 4_000 }), | |
| 46 | 48 | stop: () => app.stop(), |
| 47 | 49 | }; |
| 48 | 50 | } |
| @@ -67,15 +69,19 @@ describe("model budget in the terminal", () => { | ||
| 67 | 69 | await rm(dataDir, { recursive: true, force: true }); |
| 68 | 70 | }); |
| 69 | 71 | |
| 70 | const open = async (h: ReturnType<typeof boot>, title: string) => { | |
| 72 | const open = async (h: ReturnType<typeof boot>, title: string, hint: string, opens = "account name") => { | |
| 71 | 73 | await h.ctrl("k"); |
| 72 | 74 | await h.type(title); |
| 75 | // Wait for the row to be drawn, then for the prompt Enter opens, rather | |
| 76 | // than trusting that the frames have caught up. | |
outdated
Minor: |
||
| 77 | await h.until(hint); | |
| 73 | 78 | await h.press("enter"); |
| 79 | await h.until(opens); | |
| 74 | 80 | }; |
| 75 | 81 | |
| 76 | 82 | test("a person sees budget zero and adds their own subscription", async () => { |
| 77 | 83 | const h = boot("bob"); |
| 78 | await open(h, "Model budget"); | |
| 84 | await open(h, "Model budget", "what your crew may spend", "Model budget: $0."); | |
| 79 | 85 | expect(h.text()).toContain("Model budget: $0."); |
| 80 | 86 | await h.press("a"); |
| 81 | 87 | await h.type("sk-ant-api03-bobkey"); |
| @@ -91,13 +97,13 @@ describe("model budget in the terminal", () => { | ||
| 91 | 97 | test("an admin sets and takes back a grant; a person has no such command", async () => { |
| 92 | 98 | const bob = getUserByName(forge.db, "bob")!; |
| 93 | 99 | const h = boot("alice"); |
| 94 | await open(h, "Model grant"); | |
| 100 | await open(h, "Model grant", "give an account dollars"); | |
| 95 | 101 | await h.type("bob"); |
| 96 | 102 | await h.press("enter"); |
| 97 | 103 | await h.type("12.5"); |
| 98 | 104 | await h.press("enter"); |
| 99 | 105 | expect(getModelGrant(forge.db, bob.id)?.usdPerMonth).toBe(12.5); |
| 100 | await open(h, "Model grant"); | |
| 106 | await open(h, "Model grant", "give an account dollars"); | |
| 101 | 107 | await h.type("bob"); |
| 102 | 108 | await h.press("enter"); |
| 103 | 109 | await h.type("none"); |
| @@ -108,6 +114,7 @@ describe("model budget in the terminal", () => { | ||
| 108 | 114 | const p = boot("bob"); |
| 109 | 115 | await p.ctrl("k"); |
| 110 | 116 | await p.type("Model grant"); |
| 117 | await p.until("Model grant"); // the palette has drawn the query | |
| 111 | 118 | expect(p.text()).not.toContain("give an account dollars"); |
| 112 | 119 | p.stop(); |
| 113 | 120 | }); |
| @@ -115,13 +122,13 @@ describe("model budget in the terminal", () => { | ||
| 115 | 122 | test("an admin sets an account's ceilings in the terminal; a person has no such command", async () => { |
| 116 | 123 | const bob = getUserByName(forge.db, "bob")!; |
| 117 | 124 | const h = boot("alice"); |
| 118 | await open(h, "Model ceiling"); | |
| 125 | await open(h, "Model ceiling", "set an account's dollars"); | |
| 119 | 126 | await h.type("bob"); |
| 120 | 127 | await h.press("enter"); |
| 121 | 128 | await h.type("20 5"); |
| 122 | 129 | await h.press("enter"); |
| 123 | 130 | expect(getAccountCeiling(forge.db, bob.id)).toEqual({ dailyUsd: 20, perRunUsd: 5 }); |
| 124 | await open(h, "Model ceiling"); | |
| 131 | await open(h, "Model ceiling", "set an account's dollars"); | |
| 125 | 132 | await h.type("bob"); |
| 126 | 133 | await h.press("enter"); |
| 127 | 134 | expect(h.text()).toContain("now $20.00 a day, $5.00 a run"); |
| @@ -135,6 +142,7 @@ describe("model budget in the terminal", () => { | ||
| 135 | 142 | const p = boot("bob"); |
| 136 | 143 | await p.ctrl("k"); |
| 137 | 144 | await p.type("Model ceiling"); |
| 145 | await p.until("Model ceiling"); | |
| 138 | 146 | expect(p.text()).not.toContain("set an account's dollars"); |
| 139 | 147 | p.stop(); |
| 140 | 148 | }); |
Minor:
settleUntildefaults to 20 s, but bun's per-test timeout is 5 s. On a loaded machine a miss shows up as a bare test timeout, not as the helper's "never saw … on screen" dump. Passing{ timeoutMs: 4_000 }here keeps the useful failure message.