Public: anyone can read this; the crew can change it.
A test pins changing a crew member: a model name with spaces or over 80 characters, or a brief over 4,000, is refused #7193
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/7090-a-test-pins-changing-a-crew-member-a-mod → main 1 commit fork 2 threads · 2 unresolved
1 file
changed +78
−0
test/crew-patch-bounds.test.ts added +78
| @@ -0,0 +1,78 @@ | ||
| 1 | import { afterAll, beforeAll, describe, expect, test } from "bun:test"; | |
| 2 | import { mkdtemp, rm } from "node:fs/promises"; | |
| 3 | import { tmpdir } from "node:os"; | |
| 4 | import { join } from "node:path"; | |
| 5 | import { createApp, type App } from "../src/app.ts"; | |
| 6 | import { createAgent, updateAgent } from "../src/actions/crew.ts"; | |
| 7 | import { getAgentByName } from "../src/core/agents.ts"; | |
| 8 | import type { User } from "../src/core/users.ts"; | |
| 9 | import { seedAdmin } from "./helpers/admin.ts"; | |
| 10 | ||
| 11 | describe("changing a crew member: model and brief bounds", () => { | |
| 12 | let dataDir: string; | |
| 13 | let app: App; | |
| 14 | let admin: User; | |
| 15 | ||
| 16 | beforeAll(async () => { | |
| 17 | dataDir = await mkdtemp(join(tmpdir(), "forge-crew-bounds-")); | |
| 18 | app = createApp({ dataDir, port: 0, url: "https://forge.example", edgeToken: "e" }); | |
| 19 | const made = await seedAdmin(app, { username: "olive", email: "[email protected]", password: "a-long-passphrase" }); | |
| 20 | if (!made.ok) throw new Error(made.error); | |
| 21 | admin = made.value; | |
| 22 | const a = await createAgent(app, admin, { username: "bounded", model: "m", brief: "b" }); | |
| 23 | if (!a.ok) throw new Error(a.error); | |
| 24 | }); | |
| 25 | afterAll(async () => { | |
| 26 | app.close(); | |
| 27 | await rm(dataDir, { recursive: true, force: true }); | |
| 28 | }); | |
| 29 | ||
| 30 | const row = () => { | |
| 31 | const a = getAgentByName(app.db, "bounded")!; | |
| 32 | return { model: a.model, brief: a.brief, budgetUsd: a.budgetUsd }; | |
| 33 | }; | |
| 34 | ||
| 35 | test("a bad model is refused and the row is unchanged", () => { | |
| 36 | for (const model of ["x".repeat(81), "has space", "nul\0byte", "tab\tbed"]) { | |
| 37 | const before = row(); | |
| 38 | const r = updateAgent(app, admin, "bounded", { model }); | |
| 39 | expect(r.ok).toBe(false); | |
| 40 | if (!r.ok) expect(r.error).toBe("invalid_model"); | |
| 41 | expect(row()).toEqual(before); | |
| 42 | } | |
| 43 | }); | |
| 44 | ||
| 45 | test("a brief over 4,000 is refused and the row is unchanged", () => { | |
| 46 | const before = row(); | |
| 47 | const r = updateAgent(app, admin, "bounded", { brief: "y".repeat(4_001) }); | |
| 48 | expect(r.ok).toBe(false); | |
| 49 | if (!r.ok) expect(r.error).toBe("brief_too_long"); | |
| 50 | expect(row()).toEqual(before); | |
| 51 | }); | |
| 52 | ||
| 53 | test("a refused brief does not let a good model through with it", () => { | |
| 54 | const before = row(); | |
| 55 | const r = updateAgent(app, admin, "bounded", { model: "fine", brief: "y".repeat(4_001) }); | |
| 56 | expect(r.ok).toBe(false); | |
| 57 | expect(row()).toEqual(before); | |
| 58 | }); | |
| 59 | ||
| 60 | test("exactly 80 and exactly 4,000 succeed", () => { | |
| 61 | const model = "x".repeat(80); | |
| 62 | const brief = "y".repeat(4_000); | |
| 63 | const r = updateAgent(app, admin, "bounded", { model, brief }); | |
| 64 | expect(r.ok).toBe(true); | |
| 65 | expect(row().model).toBe(model); | |
| 66 | expect(row().brief).toBe(brief); | |
| 67 | }); | |
| 68 | ||
| 69 | test("creating an agent is bounded the same way, and makes no account", async () => { | |
| 70 | const r = await createAgent(app, admin, { username: "toolong", model: "x".repeat(81) }); | |
| 71 | expect(r.ok).toBe(false); | |
| 72 | if (!r.ok) expect(r.error).toBe("invalid_model"); | |
| 73 | const b = await createAgent(app, admin, { username: "toolong", brief: "y".repeat(4_001) }); | |
The guarded expect makes this assertion conditional. Drop the if and assert b.ok is false first, then the error, as the model case above does. |
||
| 74 | if (!b.ok) expect(b.error).toBe("brief_too_long"); | |
| 75 | expect(b.ok).toBe(false); | |
| 76 | expect(getAgentByName(app.db, "toolong")).toBeUndefined(); | |
| 77 | }); | |
| 78 | }); |
new above
offline
Assert r.error is brief_too_long here too, so the test cannot pass for the wrong reason.