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

A test pins Usage › Spend as a forge admin's: a guest is asked to sign in and anyone else is told not found #6887

Merged opened this

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

Merged

scout/forge:scout/6856-a-test-pins-usage-spend-as-a-forge-admin → main 1 commit fork

  • Merged. scout/6856-a-test-pins-usage-spend-as-a-forge-admin was merged into main as f927df7.

Review

Sign in to review this change.
1 file changed +115 −0
test/spend-tab.test.ts added +115
@@ -0,0 +1,115 @@
1 /** Usage › Spend (`/settings/spend`) is a forge admin's: a guest signs in, anyone else finds nothing. */
2 import { afterAll, beforeAll, expect, setSystemTime, test } from "bun:test";
3 import { mkdtempSync, rmSync } from "node:fs";
4 import { tmpdir } from "node:os";
5 import { join } from "node:path";
6 import { createApp, type App } from "../src/app.ts";
7 import { createRepo as initRepo } from "../src/core/repos.ts";
8 import { createRun } from "../src/core/runs.ts";
9 import { addSubscription } from "../src/core/subscriptions.ts";
10 import { recordUsage } from "../src/core/usage.ts";
11 import { createUser } from "../src/core/users.ts";
12 import { serve, type Servers } from "../src/http/server.ts";
13 import { quietLogs } from "./helpers/globals.ts";
14 import { proveAll } from "./helpers/verified.ts";
15
16 quietLogs();
17
18 const NOW = "2026-10-20T12:00:00.000Z";
19 const PASS = "a-good-passphrase";
20 let root = "";
21 let app: App;
22 let servers: Servers;
23
24 async function cookieOf(username: string): Promise<string> {
25 const res = await fetch(`${servers.url}/signin`, {
26 method: "POST",
27 headers: { origin: servers.url },
28 body: new URLSearchParams({ username, password: PASS }),
29 redirect: "manual",
30 });
31 return (res.headers.get("set-cookie") ?? "").split(";")[0]!;
32 }
33
34 beforeAll(async () => {
35 setSystemTime(new Date(NOW));
36 root = mkdtempSync(join(tmpdir(), "forge-spend-tab-"));
37 app = createApp({ dataDir: root, port: 0, forgeRepo: "alice/forge" });
38 const users = [];
39 for (const [i, username] of ["alice", "bob"].entries()) {
40 const u = await createUser(app.db, {
41 origin: "seed",
42 trusted: true,
43 username,
44 email: `${username}@forge.local`,
45 password: PASS,
46 adminIfFirst: i === 0,
47 });
48 if (!u.ok) throw new Error(u.error);
49 users.push(u.value);
50 }
51 proveAll(app);
52 const bobsApp = await initRepo(app.db, app.reposRoot, {
53 ownerId: users[1]!.id,
54 name: "ledger-app",
55 visibility: "public",
56 description: "",
57 });
58 if (!bobsApp.ok) throw new Error(`repo: ${JSON.stringify(bobsApp)}`);
59 const sub = addSubscription(app.db, root, { name: "work-plan", token: "sk-ant-api-test-token" });
60 if (!sub.ok) throw new Error("subscription");
61 const run = createRun(app.db, { repoId: bobsApp.value.id, kind: "agent" }).id;
62 recordUsage(app.db, {
63 runId: run,
64 repoId: bobsApp.value.id,
65 issueId: null,
66 agentId: null,
67 subscriptionId: sub.value.id,
68 tokensIn: 1000,
69 tokensOut: 100,
70 costUsd: 12.34,
71 wallMs: 1000,
72 outcome: "ok",
73 at: "2026-10-20T10:00:00.000Z",
74 });
75 servers = await serve(app);
76 });
77 afterAll(() => {
78 setSystemTime();
79 servers.stop();
80 app.close();
81 rmSync(root, { recursive: true, force: true });
82 });
83
84 test("a guest is sent to sign in", async () => {
85 const res = await fetch(`${servers.url}/settings/spend`, { redirect: "manual" });
86 expect(res.status).toBeGreaterThanOrEqual(300);
87 expect(res.status).toBeLessThan(400);
88 expect(res.headers.get("location")).toBe("/signin?next=%2Fsettings%2Fspend");
89 expect(await res.text()).not.toContain("12.34");
90 });
91
92 test("a signed-in non-admin, even an app's owner, is told not found and sees no figure", async () => {
93 const res = await fetch(`${servers.url}/settings/spend`, {
94 headers: { cookie: await cookieOf("bob") },
95 });
96 const body = await res.text();
97 expect(res.status).toBe(404);
98 expect(body).not.toContain("12.34");
99 expect(body).not.toContain("work-plan");
100 expect(body).not.toContain("<h2>Spend</h2>");
101 });
102
103 test("a forge admin sees the seeded run's dollars by credential and by app", async () => {
104 const res = await fetch(`${servers.url}/settings/spend`, {
105 headers: { cookie: await cookieOf("alice") },
106 });
107 const body = await res.text();
108 expect(res.status).toBe(200);
109 expect(body).toContain("<h2>Spend</h2>");
110 expect(body).toMatch(/data-by="[^"]*"/);
111 expect(body).toContain("work-plan");
112 expect(body).toContain("bob/ledger-app");
113 expect(body).toContain("$12.34");
114 expect(res.headers.get("server-timing") ?? "").toMatch(/(^|, )spend;dur=\d+\.\d/);
115 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs