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

A test pins `previewOf`, the answer behind MCP `preview`: a preview host names its pull, check and first failure, and anything else is refused plainly #6727

Merged opened this

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

Merged

scout/forge:scout/6717-a-test-pins-previewof-the-answer-behind → main 1 commit fork

  • Merged. scout/6717-a-test-pins-previewof-the-answer-behind was merged into main as 789296d.

Review

Sign in to review this change.
1 file changed +101 −0
test/preview-view.test.ts added +101
@@ -0,0 +1,101 @@
1 /**
2 * `previewOf`, the answer behind MCP `preview`, called directly: a preview
3 * host names its pull, check and first failure; anything else is refused.
4 */
5 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
6 import { mkdtemp, rm } from "node:fs/promises";
7 import { tmpdir } from "node:os";
8 import { join } from "node:path";
9 import { createApp, type App } from "../src/app.ts";
10 import { newApp } from "../src/actions/apps.ts";
11 import { openPull } from "../src/actions/pulls.ts";
12 import { commitToBranch } from "../src/actions/repos.ts";
13 import { previewOf } from "../src/apps/preview_view.ts";
14 import { getRepo } from "../src/core/repos.ts";
15 import { claimRun, createRun, finishRun } from "../src/core/runs.ts";
16 import { getPull } from "../src/core/pulls.ts";
17 import { createUser, getUserByName, type User } from "../src/core/users.ts";
18 import { seedAdmin } from "./helpers/admin.ts";
19
20 describe("previewOf", () => {
21 let dataDir: string;
22 let app: App;
23 let alice: User;
24 let bob: User;
25 let redPull = 0;
26 const domain = "apps.test";
27 const origin = "http://forge.test";
28 const host = (label: string) => `http://${label}.${domain}/x`;
29 const ask = (user: User, raw: unknown) => previewOf(app, domain, user, origin, raw);
30
31 beforeAll(async () => {
32 dataDir = await mkdtemp(join(tmpdir(), "forge-preview-view-"));
33 app = createApp({ dataDir, port: 0 });
34 await seedAdmin(app, { username: "alice", email: "[email protected]", password: "a-good-passphrase" });
35 const b = await createUser(app.db, { origin: "seed", trusted: true, username: "bob", email: "[email protected]", password: "a-good-passphrase" });
36 if (!b.ok) throw new Error(b.error);
37 bob = b.value;
38 alice = getUserByName(app.db, "alice")!;
39 for (const [name, visibility] of [["hello", "public"], ["vault", "private"]] as const) {
40 const made = await newApp(app, alice, { name, visibility });
41 if (!made.ok) throw new Error(String(made.error));
42 }
43 const repo = getRepo(app.db, "alice", "hello")!;
44 const committed = await commitToBranch(app, alice, repo, {
45 branch: "red",
46 base: repo.defaultBranch,
47 message: "red",
48 files: [{ path: "red.md", content: "x\n" }],
49 });
50 if (!committed.ok) throw new Error(JSON.stringify(committed.error));
51 const opened = await openPull(app, alice, repo, { title: "Make it red", baseBranch: repo.defaultBranch, headBranch: "red" });
52 if (!opened.ok) throw new Error(String(opened.error));
53 redPull = opened.value.number;
54 const pull = getPull(app.db, repo.id, redPull)!;
55 const run = createRun(app.db, { repoId: repo.id, kind: "check", issueId: pull.id, commitOid: pull.headOid });
56 claimRun(app.db, run.id);
57 finishRun(app.db, run.id, { state: "failed", summary: "1 failed", checkDetail: { passed: 2, failed: 1, names: ["adds"] } });
58 const agent = await createUser(app.db, { origin: "seed", trusted: true, username: "worker", email: "[email protected]", password: "a-good-passphrase" });
59 if (!agent.ok) throw new Error(agent.error);
60 claimRun(app.db, createRun(app.db, { repoId: repo.id, kind: "agent", issueId: pull.id, actorId: agent.value.id }).id);
61 }, 30_000);
62
63 afterAll(async () => {
64 app.close();
65 await rm(dataDir, { recursive: true, force: true });
66 });
67
68 test("a production host answers the app, production, and no pull", () => {
69 const r = ask(bob, host("hello"));
70 expect(r.ok && r.value.app).toBe("alice/hello");
71 expect(r.ok && r.value.environment).toBe("production");
72 expect(r.ok && r.value.pull).toBeNull();
73 expect(r.ok && r.value.check).toBeNull();
74 });
75
76 test("a preview host names its pull, the red check, first failure, log and worker", () => {
77 const r = ask(alice, host(`hello--${redPull}`));
78 if (!r.ok) throw new Error(r.error);
79 expect(r.value.environment).toBe(`pull-${redPull}`);
80 expect(r.value.pull).toEqual({ number: redPull, title: "Make it red" });
81 expect(r.value.check).toBe("red");
82 expect(r.value.firstFailure).toBe("adds");
83 expect(r.value.logUrl).toMatch(/^http:\/\/forge\.test\/alice\/hello\/runs\/[^/]+$/);
84 expect(r.value.working).toEqual(["worker"]);
85 });
86
87 test("each refusal has its own error", () => {
88 expect(ask(bob, "https://example.com/")).toEqual({ ok: false, error: "foreign" });
89 expect(ask(bob, "not a url")).toEqual({ ok: false, error: "foreign" });
90 expect(ask(bob, 42)).toEqual({ ok: false, error: "foreign" });
91 expect(ask(bob, "ftp://hello.apps.test/")).toEqual({ ok: false, error: "foreign" });
92 expect(previewOf(app, undefined, bob, origin, host("hello"))).toEqual({ ok: false, error: "foreign" });
93 expect(ask(bob, host("nothing"))).toEqual({ ok: false, error: "no_such_app" });
94 expect(ask(bob, host("hello--999"))).toEqual({ ok: false, error: "no_such_pull", number: 999 });
95 });
96
97 test("a private app is refused to one who cannot read it, as an unknown one is", () => {
98 expect(ask(alice, host("vault")).ok).toBe(true);
99 expect(ask(bob, host("vault"))).toEqual(ask(bob, host("nothing")));
100 });
101 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs