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

Flaky: test/subscriptions.test.ts #2126

Merged opened this

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

Merged

scout/forge:scout/2113-flaky-test-subscriptions-test-ts → main 1 commit fork

  • Merged. scout/2113-flaky-test-subscriptions-test-ts was merged into main as 5bc2713.

Review

Sign in to review this change.
2 files changed +57 −37
scripts/gate.ts −5
@@ -98,11 +98,6 @@ export const EXCLUDED: readonly Exclusion[] = [
9898 { file: "test/ssh.test.ts", reason: "not yet admitted", issue: 261 },
9999 { file: "test/standards.test.ts", reason: "not yet admitted", issue: 261 },
100100 { file: "test/studio-phone.test.ts", reason: "not yet admitted", issue: 261 },
101 {
102 file: "test/subscriptions.test.ts",
103 reason: "not yet admitted",
104 issue: 261,
105 },
106101 {
107102 file: "test/tui-code.test.ts",
108103 reason:
test/subscriptions.test.ts +57 −32
@@ -1,5 +1,5 @@
1 import { getHeadroom } from "../src/core/standing.ts";
2 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
1 import { getHeadroom, setHeadroom } from "../src/core/standing.ts";
2 import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, test } from "bun:test";
33 import { chmodSync, existsSync, readFileSync, writeFileSync } from "node:fs";
44 import { mkdtemp, rm } from "node:fs/promises";
55 import { tmpdir } from "node:os";
@@ -67,11 +67,11 @@ describe("the pool", () => {
6767 let dataDir: string;
6868 let app: App;
6969
70 beforeAll(async () => {
70 beforeEach(async () => {
7171 dataDir = await mkdtemp(join(tmpdir(), "forge-subs-"));
7272 app = createApp({ dataDir, port: 0 });
7373 });
74 afterAll(async () => {
74 afterEach(async () => {
7575 app.close();
7676 await rm(dataDir, { recursive: true, force: true });
7777 });
@@ -147,9 +147,12 @@ describe("the pool", () => {
147147 test("picks the nearest grant whole, an agent's before everyone's, and rotates", () => {
148148 const work = addSubscription(app.db, dataDir, { name: "work", token: KEY });
149149 if (!work.ok) throw new Error(work.error);
150 const personal = listSubscriptions(app.db).find(
151 (s) => s.name === "Personal",
152 )!;
150 const added = addSubscription(app.db, dataDir, {
151 name: "Personal",
152 token: OAT,
153 });
154 if (!added.ok) throw new Error(added.error);
155 const personal = added.value;
153156 const scope = { repoId: "repo-1", ownerId: "org-1", agentId: "bot-1" };
154157 const now = new Date("2026-09-28T12:00:00Z");
155158
@@ -359,14 +362,26 @@ echo '{"type":"result","subtype":"success","is_error":false,"result":"Added CREW
359362 throw new Error("did not happen in time");
360363 }
361364
365 // Both accounts on the repository's grant, awake, whatever ran before.
366 function pool() {
367 const named = (name: string, token: string) => {
368 const found = listSubscriptions(app.db).find((s) => s.name === name);
369 if (found) return found;
370 const added = addSubscription(app.db, dataDir, { name, token });
371 if (!added.ok) throw new Error(added.error);
372 return added.value;
373 };
374 const personal = named("personal", OAT);
375 const work = named("work", KEY);
376 wakeSubscription(app.db, personal.id);
377 wakeSubscription(app.db, work.id);
378 setGrants(app.db, repo.id, EVERYONE, [personal.id, work.id]);
379 return { personal, work };
380 }
381
362382 test("rests the account the provider refused and hands the run to the next", async () => {
363 const personal = addSubscription(app.db, dataDir, {
364 name: "personal",
365 token: OAT,
366 });
367 const work = addSubscription(app.db, dataDir, { name: "work", token: KEY });
368 if (!personal.ok || !work.ok) throw new Error("pool");
369 setGrants(app.db, repo.id, EVERYONE, [personal.value.id, work.value.id]);
383 const { personal, work } = pool();
384 const used = invocations();
370385
371386 const issue = openIssue(app, alice, repo, {
372387 title: "Plant tulips",
@@ -379,18 +394,18 @@ echo '{"type":"result","subtype":"success","is_error":false,"result":"Added CREW
379394 (r) => r.issueId === issue.value.id && r.state === "ok",
380395 ),
381396 );
382 expect(invocations()).toBe(2);
397 expect(invocations()).toBe(used + 2);
383398 // Neither used before: the first by name went first, and was refused.
384 expect(envOf(1)).toBe(`OAUTH=${OAT}\nKEY=\n`);
385 expect(envOf(2)).toBe(`OAUTH=\nKEY=${KEY}\n`);
399 expect(envOf(used + 1)).toBe(`OAUTH=${OAT}\nKEY=\n`);
400 expect(envOf(used + 2)).toBe(`OAUTH=\nKEY=${KEY}\n`);
386401
387 const rested = getSubscription(app.db, personal.value.id)!;
402 const rested = getSubscription(app.db, personal.id)!;
388403 expect(rested.restingUntil).not.toBeNull();
389404 expect(new Date(rested.restingUntil!).getTime()).toBeGreaterThan(
390405 Date.now(),
391406 );
392407 expect(rested.restingSaid).toContain("weekly limit");
393 expect(getSubscription(app.db, work.value.id)!.restingUntil).toBeNull();
408 expect(getSubscription(app.db, work.id)!.restingUntil).toBeNull();
394409
395410 const runs = listRuns(app.db, repo.id, { kind: "agent" }).filter(
396411 (r) => r.issueId === issue.value.id,
@@ -409,7 +424,7 @@ echo '{"type":"result","subtype":"success","is_error":false,"result":"Added CREW
409424 "SELECT subscription_id FROM run WHERE id = ?1",
410425 )
411426 .get(refused.id)!;
412 expect(first.subscription_id).toBe(personal.value.id);
427 expect(first.subscription_id).toBe(personal.id);
413428 expect(done.id).not.toBe(refused.id);
414429 const spent = app.db
415430 .query<
@@ -417,17 +432,14 @@ echo '{"type":"result","subtype":"success","is_error":false,"result":"Added CREW
417432 [string]
418433 >("SELECT subscription_id FROM run WHERE id = ?1")
419434 .get(done.id)!;
420 expect(spent.subscription_id).toBe(work.value.id);
435 expect(spent.subscription_id).toBe(work.id);
421436 }, 60_000);
422437
423438 test("holds a run without a call while every account on the grant rests", async () => {
424 const work = listSubscriptions(app.db).find((s) => s.name === "work")!;
425 restSubscription(
426 app.db,
427 work.id,
428 new Date(Date.now() + 3_600_000).toISOString(),
429 "session limit",
430 );
439 const { personal, work } = pool();
440 const later = new Date(Date.now() + 3_600_000).toISOString();
441 restSubscription(app.db, personal.id, later, "weekly limit");
442 restSubscription(app.db, work.id, later, "session limit");
431443 const before = invocations();
432444 const issue = openIssue(app, alice, repo, {
433445 title: "Plant roses",
@@ -506,7 +518,18 @@ describe("the pages", () => {
506518 const get = (path: string) =>
507519 fetch(`${base}${path}`, { headers: { cookie } }).then((r) => r.text());
508520
521 // The one account the pages work on, added once whatever ran before.
522 const account = () => {
523 const found = listSubscriptions(app.db).find((s) => s.name === "work");
524 if (found) return found;
525 const added = addSubscription(app.db, dataDir, { name: "work", token: KEY });
526 if (!added.ok) throw new Error(added.error);
527 return added.value;
528 };
529
509530 test("an admin adds an account and sees its name, never its token", async () => {
531 // The empty pool is what this test starts from, whatever ran before.
532 for (const s of listSubscriptions(app.db)) deleteSubscription(app.db, s.id);
510533 let html = await get("/settings/subscriptions");
511534 expect(html).toContain("Add one");
512535 expect(html).toContain("The crew works on the forge");
@@ -528,7 +551,7 @@ describe("the pages", () => {
528551 });
529552
530553 test("the crew page grants it to the repository, and a run there picks it", async () => {
531 const work = listSubscriptions(app.db)[0]!;
554 const work = account();
532555 let html = await get(`/ada/hello/settings/crew`);
533556 expect(html).toContain('class="table grants"');
534557 expect(html).toContain(`value="*:${work.id}"`);
@@ -563,7 +586,7 @@ describe("the pages", () => {
563586 test("an organization's page carries the table for every repository under it", async () => {
564587 const org = createOrg(app, ada, "acme");
565588 if (!org.ok) throw new Error(String(org.error));
566 const work = listSubscriptions(app.db)[0]!;
589 const work = account();
567590 let html = await get("/acme");
568591 expect(html).toContain("Subscriptions for every repository here");
569592 const saved = await post(
@@ -579,7 +602,8 @@ describe("the pages", () => {
579602 });
580603
581604 test("an admin keeps a share of each window for people, and sees each window", async () => {
582 const work = listSubscriptions(app.db)[0]!;
605 const work = account();
606 setHeadroom(app.db, work.id, 0);
583607 let html = await get("/settings/subscriptions");
584608 expect(html).toContain(`action="/settings/subscriptions/${work.id}/headroom"`);
585609 expect(html).toContain("5-hour: ~$0.00 used · opens on next use");
@@ -630,8 +654,9 @@ describe("the pages", () => {
630654 redirect: "manual",
631655 });
632656 expect(posted.status).toBe(404);
657 const work = account();
633658 expect(listSubscriptions(app.db)).toHaveLength(1);
634 const work = listSubscriptions(app.db)[0]!;
659 setHeadroom(app.db, work.id, 25);
635660 const headroom = await fetch(`${base}/settings/subscriptions/${work.id}/headroom`, {
636661 method: "POST",
637662 headers: { cookie: guest, origin: base },

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs