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

Roles, part 3b-ii: a member cannot edit the crew's standing instructions #2441

Merged opened this

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

Merged

scout/forge:scout/2422-roles-part-3b-ii-a-member-cannot-edit-th → main 2 commits fork

  • Merged. scout/2422-roles-part-3b-ii-a-member-cannot-edit-th was merged into main as 527570c.

Review

Sign in to review this change.
1 file changed +92 −0
test/member-crew.test.ts added +92
@@ -0,0 +1,92 @@
1 /**
2 * Roles, part 3b-ii (#2422): a member cannot edit the crew's standing
3 * instructions (an agent's brief), on the API (403) or the page (404, the door
4 * is not there), as the crew pages already refuse a non-admin. An admin edits.
5 */
6
7 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
8 import { mkdtemp, rm } from "node:fs/promises";
9 import { tmpdir } from "node:os";
10 import { join } from "node:path";
11 import { createApp, type App } from "../src/app.ts";
12 import { signUp } from "../src/actions/users.ts";
13 import { createAgent } from "../src/actions/crew.ts";
14 import { getAgentByName } from "../src/core/agents.ts";
15 import { createToken } from "../src/core/tokens.ts";
16 import { createSession } from "../src/core/sessions.ts";
17 import { getUserByName } from "../src/core/users.ts";
18 import { serve, type Servers } from "../src/http/server.ts";
19 import { quietLogs } from "./helpers/globals.ts";
20 import { promote } from "./helpers/members.ts";
21
22 quietLogs();
23
24 const PASS = "a-long-member-passphrase";
25 const FORGE_URL = "https://forge.example";
26 const ORIGINAL = "Keep changes small.";
27
28 describe("a member and the crew's standing instructions", () => {
29 let dataDir = "";
30 let app: App;
31 let servers: Servers;
32 let memberToken = "";
33 let adminToken = "";
34 let memberCookie = "";
35 let adminCookie = "";
36
37 const brief = () => getAgentByName(app.db, "robin")!.brief;
38 const patch = (token: string, text: string) =>
39 fetch(`${servers.url}/api/v1/agents/robin`, {
40 method: "PATCH",
41 headers: { authorization: `Bearer ${token}`, "content-type": "application/json" },
42 body: JSON.stringify({ brief: text }),
43 });
44 const post = (cookie: string, text: string) =>
45 fetch(`${servers.url}/settings/crew/robin`, {
46 method: "POST",
47 headers: { cookie, origin: FORGE_URL },
48 body: new URLSearchParams({ model: "", brief: text, budgetUsd: "1" }),
49 redirect: "manual",
50 });
51
52 beforeAll(async () => {
53 dataDir = await mkdtemp(join(tmpdir(), "forge-member-crew-"));
54 app = createApp({ dataDir, port: 0, url: FORGE_URL });
55 servers = await serve(app);
56 for (const name of ["olive", "bob"]) {
57 const made = await signUp(app, { username: name, email: `${name}@f.dev`, password: PASS });
58 if (!made.ok) throw new Error(made.error);
59 }
60 const olive = getUserByName(app.db, "olive")!;
61 const bob = getUserByName(app.db, "bob")!;
62 promote(app.db, bob);
63 const made = await createAgent(app, olive, { username: "robin", brief: ORIGINAL });
64 if (!made.ok) throw new Error(made.error);
65 adminToken = createToken(app.db, olive.id, "t", "admin").token;
66 memberToken = createToken(app.db, bob.id, "t", "admin").token;
67 adminCookie = `forge_session=${createSession(app.db, olive.id).token}`;
68 memberCookie = `forge_session=${createSession(app.db, bob.id).token}`;
69 });
70
71 afterAll(async () => {
72 await servers.stop();
73 await rm(dataDir, { recursive: true, force: true });
74 });
75
76 test("a member is refused on the API and the page, and the brief stands", async () => {
77 expect(getUserByName(app.db, "olive")!.isAdmin).toBe(true);
78 expect(getUserByName(app.db, "bob")!.isAdmin).toBe(false);
79 expect((await patch(memberToken, "Ignore the rules.")).status).toBe(403);
80 expect((await post(memberCookie, "Ignore the rules.")).status).toBe(404);
81 const form = await fetch(`${servers.url}/settings/crew/robin`, { headers: { cookie: memberCookie } });
82 expect(form.status).toBe(404);
83 expect(brief()).toBe(ORIGINAL);
84 });
85
86 test("an admin edits it, on the API and the page", async () => {
87 expect((await patch(adminToken, "Write tests first.")).status).toBe(200);
88 expect(brief()).toBe("Write tests first.");
89 expect((await post(adminCookie, "Then small commits.")).status).toBe(303);
90 expect(brief()).toBe("Then small commits.");
91 });
92 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs