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

A test pins attaching a look to an app: only `phone` or `desktop`, and only to a repository that is an app #7127

Merged opened this

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

Merged

scout/forge:scout/7113-a-test-pins-attaching-a-look-to-an-app-o → main 1 commit fork 1 thread · 1 unresolved

  • Merged. scout/7113-a-test-pins-attaching-a-look-to-an-app-o was merged into main as 0023165.

Review

Sign in to review this change.
1 file changed +129 −0
test/attach-look-refusals.test.ts added +129
@@ -0,0 +1,129 @@
1 /**
2 * Attaching a look names its width and its place (#1066): `phone` or
3 * `desktop` only, and only to a repository that is an app. Each refusal
4 * leaves nothing stored — no row, no file.
5 */
6
7 import { afterAll, beforeAll, describe, expect, test } from "bun:test";
8 import { existsSync, readdirSync } from "node:fs";
9 import { mkdtemp, rm } from "node:fs/promises";
10 import { tmpdir } from "node:os";
11 import { join } from "node:path";
12 import { attachLook } from "../src/actions/attach_look.ts";
13 import { openPull } from "../src/actions/pulls.ts";
14 import { commitToBranch, createRepo } from "../src/actions/repos.ts";
15 import { createApp, type App } from "../src/app.ts";
16 import { previewEnv } from "../src/apps/data.ts";
17 import { createApp as insertApp, type AppRecord } from "../src/core/apps.ts";
18 import { looksForEnv } from "../src/core/looks.ts";
19 import type { Pull } from "../src/core/pulls.ts";
20 import type { Repo } from "../src/core/repos.ts";
21 import { createUser, type User } from "../src/core/users.ts";
22 import { encodePng } from "../src/design/image/png.ts";
23 import { quietLogs } from "./helpers/globals.ts";
24
25 quietLogs();
26
27 const IMAGE = Buffer.from(encodePng(2, 2, new Uint8Array(16).fill(200))).toString("base64");
28
29 describe("attachLook width and place", () => {
30 let dataDir: string;
31 let app: App;
32 let alice: User;
33 let repo: Repo;
34 let record: AppRecord;
35 let pull: Pull;
36 let plainRepo: Repo;
37 let plainPull: Pull;
38
39 const attach = (width: unknown, target: [Repo, Pull] = [repo, pull]) =>
40 attachLook(app, alice, target[0], target[1], {
41 width: width as string,
42 note: "the garden",
43 image: IMAGE,
44 });
45 const kept = () => looksForEnv(app.db, record.id, previewEnv(pull.number));
46 const onDisk = () => {
47 const dir = join(dataDir, "looks");
48 return existsSync(dir) ? readdirSync(dir, { recursive: true }) : [];
49 };
50 const rowCount = () =>
51 (app.db.query("SELECT COUNT(*) AS n FROM look").get() as { n: number }).n;
52
53 async function pullFor(name: string): Promise<[Repo, Pull]> {
54 const made = await createRepo(app, alice, { name, visibility: "public" });
55 if (!made.ok) throw new Error(String(made.error));
56 await commitToBranch(app, alice, made.value, {
57 message: "seed",
58 files: [{ path: "README.md", content: `# ${name}\n` }],
59 });
60 await commitToBranch(app, alice, made.value, {
61 branch: "one",
62 base: "main",
63 message: "one",
64 files: [{ path: "one.md", content: "x\n" }],
65 });
66 const opened = await openPull(app, alice, made.value, {
67 title: "Change one",
68 body: "",
69 baseBranch: "main",
70 headBranch: "one",
71 });
72 if (!opened.ok) throw new Error(String(opened.error));
73 return [made.value, opened.value];
74 }
75
76 beforeAll(async () => {
77 dataDir = await mkdtemp(join(tmpdir(), "forge-attach-refusals-"));
78 app = createApp({ dataDir, port: 0 });
79 const made = await createUser(app.db, {
80 origin: "seed",
81 trusted: true,
82 username: "alice",
83 email: "[email protected]",
84 password: "a-good-passphrase",
85 adminIfFirst: true,
86 });
87 if (!made.ok) throw new Error(made.error);
88 alice = made.value;
89 [repo, pull] = await pullFor("garden");
90 const inserted = insertApp(app.db, { repoId: repo.id, slug: "garden", access: "public" });
91 if (!inserted.ok) throw new Error(inserted.error);
92 record = inserted.value;
93 [plainRepo, plainPull] = await pullFor("plain");
94 });
95
96 afterAll(async () => {
97 app.close?.();
98 await rm(dataDir, { recursive: true, force: true });
99 });
100
101 test("a phone look and a desktop look are both kept", () => {
102 const phone = attach("phone");
103 const desktop = attach("desktop");
104 expect(phone.ok && phone.value.kind).toBe("phone");
105 expect(desktop.ok && desktop.value.kind).toBe("desktop");
106 expect(kept().map((l) => l.kind).sort()).toEqual(["desktop", "phone"]);
107 expect(onDisk().filter((f) => /\.png$/.test(String(f))).length).toBe(2);
108 });
109
110 test("tablet, an empty string and a number are bad_width, and nothing is stored", () => {
111 const rows = rowCount();
112 const files = onDisk().length;

as never silences the type check on the expected value. Compare r.ok and !r.ok && r.error instead, or add a small typed helper.

113 for (const width of ["tablet", "", 390]) {
114 const r = attach(width);
115 expect(r).toEqual({ ok: false, error: "bad_width" } as never);
116 expect(rowCount()).toBe(rows);
117 expect(onDisk().length).toBe(files);
118 }
119 });
120
121 test("a repository that is not an app is no_app, and nothing is stored", () => {
122 const rows = rowCount();
123 const files = onDisk().length;
124 const r = attach("phone", [plainRepo, plainPull]);
125 expect(r).toEqual({ ok: false, error: "no_app" } as never);
126 expect(rowCount()).toBe(rows);
127 expect(onDisk().length).toBe(files);
128 });
129 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs