Public: anyone can read this; the crew can change it.
A test pins proposing to the parent forge: a forge with no parent link, or another repository, is refused and nothing is sent #7128
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/7116-a-test-pins-proposing-to-the-parent-forg → main 1 commit fork 1 thread · 1 unresolved
1 file
changed +110
−0
test/parent-forge-refusals.test.ts added +110
| @@ -0,0 +1,110 @@ | ||
| 1 | /** | |
| 2 | * Proposing to the parent forge (#7116): a forge with no parent link, a | |
| 3 | * repository that is not the one linked, and a link with no stored | |
| 4 | * credential are each refused as `no_parent_forge`, and nothing is sent to | |
| 5 | * the parent — not a push, not a pull. | |
| 6 | */ | |
| 7 | ||
| 8 | import { afterAll, beforeAll, describe, expect, test } from "bun:test"; | |
| 9 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 10 | import { tmpdir } from "node:os"; | |
| 11 | import { join } from "node:path"; | |
| 12 | import { commitToBranch, createRepo } from "../src/actions/repos.ts"; | |
| 13 | import { proposeToParentForge } from "../src/actions/upstream.ts"; | |
| 14 | import { createApp, type App } from "../src/app.ts"; | |
| 15 | import type { Repo } from "../src/core/repos.ts"; | |
| 16 | import { createUser, type User } from "../src/core/users.ts"; | |
| 17 | import { keepParentCredential } from "../src/spawn/parent_credential.ts"; | |
| 18 | import { quietLogs } from "./helpers/globals.ts"; | |
| 19 | ||
| 20 | quietLogs(); | |
| 21 | ||
| 22 | describe("proposing to a parent forge that is not there for this repository", () => { | |
| 23 | let dir = ""; | |
| 24 | let stub: ReturnType<typeof Bun.serve>; | |
| 25 | let seen: string[] = []; | |
| 26 | let n = 0; | |
| 27 | ||
| 28 | beforeAll(async () => { | |
| 29 | dir = await mkdtemp(join(tmpdir(), "forge-noparent-")); | |
| 30 | stub = Bun.serve({ | |
| 31 | port: 0, | |
| 32 | hostname: "127.0.0.1", | |
| 33 | fetch(req) { | |
| 34 | seen.push(`${req.method} ${new URL(req.url).pathname}`); | |
| 35 | return new Response("{}", { status: 500 }); | |
| 36 | }, | |
| 37 | }); | |
| 38 | }); | |
| 39 | ||
| 40 | afterAll(async () => { | |
| 41 | await stub.stop(true); | |
| 42 | await rm(dir, { recursive: true, force: true }); | |
| 43 | }); | |
| 44 | ||
| 45 | /** A child forge with one repository that has a commit to propose. */ | |
| 46 | const child = async ( | |
| 47 | owner: string, | |
| 48 | name: string, | |
| 49 | parentDoc?: (tip: string) => Record<string, unknown>, | |
| 50 | ): Promise<{ app: App; user: User; repo: Repo }> => { | |
| 51 | const app = createApp({ dataDir: join(dir, `c${n++}`), port: 0, host: "kid.test" }); | |
| 52 | const made = await createUser(app.db, { | |
| 53 | origin: "seed", | |
| 54 | trusted: true, | |
| 55 | username: owner, | |
| 56 | email: `${owner}@forge.local`, | |
| 57 | password: "a-long-refusal-passphrase", | |
| 58 | adminIfFirst: true, | |
| 59 | }); | |
| 60 | if (!made.ok) throw new Error(made.error); | |
| 61 | const repo = await createRepo(app, made.value, { name, visibility: "public" }); | |
| 62 | if (!repo.ok) throw new Error(String(repo.error)); | |
| 63 | const c = await commitToBranch(app, made.value, repo.value, { | |
| 64 | branch: "main", | |
| 65 | message: "Start", | |
| 66 | files: [{ path: "README.md", content: "hi\n" }], | |
| 67 | }); | |
| 68 | if (!c.ok) throw new Error(JSON.stringify(c.error)); | |
| 69 | if (parentDoc !== undefined) | |
| 70 | await Bun.write(join(app.config.dataDir, "parent.json"), JSON.stringify(parentDoc(""))); | |
| 71 | return { app, user: made.value, repo: repo.value }; | |
| 72 | }; | |
| 73 | ||
| 74 | const doc = (repo: string): Record<string, unknown> => ({ | |
| 75 | upstream: `http://127.0.0.1:${stub.port}`, | |
| 76 | name: "kid", | |
| 77 | forgeCode: { repo, upstream: "alice/app", mode: "tell-me", tip: "", chain: [] }, | |
| 78 | }); | |
| 79 | ||
| 80 | const refused = async (c: { app: App; user: User; repo: Repo }) => { | |
| 81 | seen = []; | |
| 82 | const r = await proposeToParentForge(c.app, c.user, c.repo); | |
| 83 | expect(r.ok ? "ok" : r.error).toBe("no_parent_forge"); | |
| 84 | expect(seen).toEqual([]); | |
| 85 | }; | |
| 86 | ||
| 87 | test("a forge with no parent.json is refused", async () => { | |
| 88 | await refused(await child("kid", "app")); | |
| 89 | }); | |
| 90 | ||
| 91 | test("a forge linked to alice/app proposing from bob/other is refused", async () => { | |
| 92 | const c = await child("bob", "other", () => doc("alice/app")); | |
| 93 | const kept = await keepParentCredential(c.app, { child: "kid", token: "a-proposal-token-1" }); | |
| 94 | if (!kept.ok) throw new Error(kept.error); | |
| 95 | await refused(c); | |
| 96 | }); | |
| 97 | ||
| 98 | test("a link with no stored credential is refused", async () => { | |
| 99 | await refused(await child("alice", "app", () => doc("alice/app"))); | |
| 100 | }); | |
| 101 | ||
| 102 | test("the same link with a credential gets past the refusal", async () => { | |
| 103 | const c = await child("alice", "app", () => doc("alice/app")); | |
| 104 | const kept = await keepParentCredential(c.app, { child: "kid", token: "a-proposal-token-1" }); | |
| 105 | if (!kept.ok) throw new Error(kept.error); | |
| 106 | seen = []; | |
| 107 | const r = await proposeToParentForge(c.app, c.user, c.repo); | |
| 108 | expect(r.ok ? "ok" : r.error).not.toBe("no_parent_forge"); | |
| 109 | }); | |
| 110 | }); |
new above
offline
parentDoc takes a tip that is always "" and no caller uses it. Make it a zero-argument function.