Skip to content
188 linesCodeBlameRaw

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Artifacts contracts: folios, their four kinds, dashboard datasets, folio events and the artifacts scopes are typed and validated the same in TypeScript and Rust, with nothing using them yet1import assert from "node:assert/strict";
2import { readFileSync } from "node:fs";
3import { test } from "node:test";
4
5import type { ServiceBinding } from "./clients.ts";
6import { datasetQueryError } from "./datasets.ts";
7import { dashboardGridError, dashboardOpError } from "./folios-dashboard.ts";
8import { DESIGN_MAX_NODES, countDesignSpecs, designOpError } from "./folios-design.ts";
9import { SLIDE_LAYOUTS, SLIDE_LAYOUT_REGIONS, slideFragment, slidesOpError } from "./folios-slides.ts";
10import {
11 FOLIO_KINDS,
12 FOLIO_RPC_METHODS,
13 FOLIO_ROUTE_SEGMENTS,
14 folioAccessChangeError,
15 folioAgentEditError,
16 folioCanHaveChildren,
17 folioIdFrom,
18 folioListQueryError,
19 folioPath,
20 folioSlug,
21 foliosClient,
22 linkedFolioIds,
23 newFolioError,
24 type FolioAccessChange,
25 type NewFolio,
26} from "./folios.ts";
27import type { User } from "./identity.ts";
28
29const fixtures = JSON.parse(readFileSync(new URL("./folios.fixtures.json", import.meta.url), "utf8")) as {
30 new_folio: { input: unknown; error: string | null }[];
31 access_change: { change: unknown; error: string | null }[];
32 agent_edit: { edit: unknown; error: string | null }[];
33 folio_ids: { segment: string; id: string | null }[];
34};
35
36function expect(label: string, actual: string | null, error: string | null) {
37 if (error === null) assert.equal(actual, null, label);
38 else if (error === "*") assert.notEqual(actual, null, `${label} should be refused`);
39 else assert.equal(actual, error, label);
40}
41
42test("agrees with the Rust validators on every shared case", () => {
43 for (const { input, error } of fixtures.new_folio) expect(JSON.stringify(input), newFolioError(input as NewFolio), error);
44 for (const { change, error } of fixtures.access_change) expect(JSON.stringify(change), folioAccessChangeError(change as FolioAccessChange), error);
45 for (const { edit, error } of fixtures.agent_edit) expect(JSON.stringify(edit), folioAgentEditError(edit), error);
46 for (const { segment, id } of fixtures.folio_ids) assert.equal(folioIdFrom(segment), id, segment);
47});
48
49test("addresses are flat, end in the id and survive renames", () => {
50 const id = "fol_01jb2k7x9hfq0b3zj0f5s2m8ra";
51 assert.equal(folioPath("acme", "Q4 roadmap: pricing", id), `/acme/-/artifacts/q4-roadmap-pricing-${id}`);
52 assert.equal(folioSlug("", id), id);
53 assert.equal(folioSlug("Café ✨ notes", id), `cafe-notes-${id}`);
54 assert.equal(folioIdFrom(folioSlug("Anything at all", id)), id);
55 assert.deepEqual(linkedFolioIds(`see /acme/-/artifacts/x-${id} and ${id}`), [id]);
56 for (const segment of FOLIO_ROUTE_SEGMENTS) assert.equal(folioIdFrom(segment), null, segment);
57});
58
59test("only a doc holds other folios", () => {
60 assert.deepEqual(FOLIO_KINDS.filter(folioCanHaveChildren), ["doc"]);
61});
62
63test("list queries are checked", () => {
64 assert.equal(folioListQueryError({ tab: "shared", kinds: ["slides"], limit: 50 }), null);
65 assert.equal(folioListQueryError({ tab: "everything" as "all" }), "tab is all, yours or shared.");
66 assert.equal(folioListQueryError({ tab: "all", limit: 500 }), "limit is between 1 and 100.");
67 assert.equal(folioListQueryError({ tab: "all", kinds: ["sheet" as "doc"] }), "There is no kind of artifact called sheet.");
68});
69
70test("the client calls exactly the docs service's folio methods", async () => {
71 const called: string[] = [];
72 const bodies: Record<string, unknown>[] = [];
73 const binding: ServiceBinding = {
74 fetch: async (input: RequestInfo | URL, init?: RequestInit) => {
75 called.push(String(input).replace("https://service/rpc/", ""));
76 bodies.push(JSON.parse(String(init?.body)));
77 return new Response(JSON.stringify({ ok: true, value: null }), { headers: { "content-type": "application/json" } });
78 },
79 } as ServiceBinding;
80 const client = foliosClient(binding);
81 const viewer = { id: "usr_1" } as User;
82 const ws = "acme";
83 const f = "fol_1";
84 await Promise.all([
85 client.list(ws, viewer, { tab: "all" }),
86 client.sidebar(ws, viewer),
87 client.folio(ws, viewer, f),
88 client.create(ws, viewer, { kind: "doc" }),
89 client.update(ws, viewer, f, { title: "x" }),
90 client.move(ws, viewer, f, { space_id: null, parent_id: null }),
91 client.duplicate(ws, viewer, f),
92 client.trash(ws, viewer, f),
93 client.restore(ws, viewer, f),
94 client.delete(ws, viewer, f),
95 client.trashed(ws, viewer),
96 client.favorite(ws, viewer, f, true),
97 client.content(ws, viewer, f),
98 client.edit(ws, viewer, f, { kind: "doc", target: { kind: "append" }, markdown: "x" }),
99 client.access(ws, viewer, f),
100 client.changeAccess(ws, viewer, f, { op: "grant", principal: "user:usr_2", role: "view" }),
101 client.changeAccess(ws, viewer, f, { op: "general", access: "workspace", role: "view" }),
102 client.requestAccess(ws, viewer, f),
103 client.joinSpace(ws, viewer, "spc_1"),
104 client.leaveSpace(ws, viewer, "spc_1"),
105 client.search(ws, viewer, { q: "roadmap" }),
106 client.versions(ws, viewer, f),
107 client.version(ws, viewer, f, "ver_1"),
108 client.restoreVersion(ws, viewer, f, "ver_1"),
109 client.templates(ws, viewer),
110 client.saveTemplate(ws, viewer, { folio_id: f, name: "T" }),
111 client.deleteTemplate(ws, viewer, "tpl_1"),
112 client.export(ws, viewer, f),
113 client.suggestions(ws, viewer, f),
114 client.decideSuggestion(ws, viewer, "sug_1", "accept"),
115 client.proposals(ws, viewer, f),
116 client.decideProposal(ws, viewer, "prp_1", "reject"),
117 client.thread(ws, viewer, f, { op: "resolve", thread_id: "thr_1" }),
118 client.threads(ws, viewer, f),
119 client.queryTile(ws, viewer, f, "t1"),
120 client.queryDataset(ws, viewer, { dataset: "issues", measure: { op: "count" } }),
121 client.queryDatasetForAgent(ws, "agt_1", viewer, { folio_id: f, tile_id: "t1" }),
122 client.foliosForAgent(ws, "agt_1", viewer, { tab: "all" }),
123 client.readForAgent(ws, "agt_1", viewer, f),
124 client.createAsAgent(ws, "agt_1", viewer, { kind: "doc", title: "Notes", where: "private" }),
125 client.editAsAgent(ws, "agt_1", viewer, f, { kind: "slides", ops: [{ op: "delete_slides", slide_ids: ["s1"] }] }),
126 client.shareAsAgent(ws, "agt_1", viewer, f, { user_ids: ["usr_2"], role: "view" }, { kind: "people", user_ids: ["usr_1", "usr_2"] }),
127 client.recallForAgent(ws, "agt_1", viewer, { query: "pricing" }),
128 client.staleForAgent(ws, "agt_1", viewer),
129 client.markCurrent(ws, viewer, f),
130 client.reindex(ws, viewer),
131 ]);
132 assert.deepEqual([...new Set(called)].sort(), [...FOLIO_RPC_METHODS].sort());
133 // Every body names the workspace and the viewer, and keys are snake_case.
134 for (const body of bodies) {
135 assert.equal(body.workspace, ws);
136 assert.ok(body.viewer);
137 for (const key of Object.keys(body)) assert.match(key, /^[a-z_]+$/, key);
138 }
139});
140
141test("slides ops are checked by shape", () => {
142 assert.equal(slidesOpError({ op: "insert_slides", after_slide_id: null, markdown: "---" }), null);
143 assert.equal(slidesOpError({ op: "move_slide", slide_id: "s1", after_slide_id: "s2" }), null);
144 assert.equal(slidesOpError({ op: "set_theme", theme: "night", accent: "#8b7cf6" }), null);
145 assert.equal(slidesOpError({ op: "set_theme", theme: "night", accent: "lavender" }), "accent is a colour like #8b7cf6, or null.");
146 assert.equal(slidesOpError({ op: "replace_slide", markdown: "# x" }), "replace_slide needs slide_id.");
147 assert.equal(slidesOpError({ op: "delete_slides", slide_ids: [] }), "delete_slides needs slide_ids.");
148 assert.equal(slidesOpError({ op: "shuffle" }), "There is no slides op called shuffle.");
149 for (const layout of SLIDE_LAYOUTS) assert.ok(SLIDE_LAYOUT_REGIONS[layout].includes("notes"), layout);
150 assert.equal(slideFragment("s1", "left"), "slide:s1:left");
151});
152
153test("design ops are checked by shape and size", () => {
154 const frame = { type: "frame", name: "Home", layout: "column", gap: 16, children: [{ type: "text", text: "Hello" }, { type: "html", html: "<b>hi</b>" }] };
155 assert.equal(designOpError({ op: "upsert_nodes", nodes: [frame] }), null);
156 assert.deepEqual(countDesignSpecs([frame as never]), { nodes: 3, depth: 2 });
157 assert.equal(designOpError({ op: "upsert_nodes", nodes: [{ type: "rect", children: [{ type: "text" }] }] }), "Only frames and components have children.");
158 assert.equal(designOpError({ op: "upsert_nodes", nodes: [{ type: "text", html: "<b>" }] }), "Only an html node has html.");
159 assert.equal(designOpError({ op: "upsert_nodes", nodes: [{ type: "blob" }] }), "There is no node type called blob.");
160 assert.equal(designOpError({ op: "set_html", node_id: "n1", html: "x".repeat(200 * 1024 + 1) }), "An html node holds at most 200 KB.");
161 assert.equal(designOpError({ op: "set_html", node_id: "n1", html: "é".repeat(100 * 1024) }), null);
162 assert.equal(designOpError({ op: "set_html", node_id: "n1", html: "é".repeat(100 * 1024 + 1) }), "An html node holds at most 200 KB.");
163 const many = Array.from({ length: DESIGN_MAX_NODES + 1 }, () => ({ type: "rect" }));
164 assert.equal(designOpError({ op: "upsert_nodes", nodes: many }), `A design holds at most ${DESIGN_MAX_NODES} nodes.`);
165 assert.equal(designOpError({ op: "move", ids: ["a"], dx: 1, dy: Number.NaN }), "move needs dx and dy.");
166 assert.equal(designOpError({ op: "instance" }), "There is no design op called instance.");
167});
168
169test("dashboard ops check tiles, their queries and the grid", () => {
170 const tile = {
171 id: "t1",
172 type: "line",
173 title: "Merged per week",
174 description: "",
175 query: { dataset: "pull_requests", measure: { op: "count" }, interval: "week", time: "merged_at" },
176 viz: {},
177 grid: { x: 0, y: 0, w: 6, h: 4 },
178 };
179 assert.equal(dashboardOpError({ op: "upsert_tile", tile }, datasetQueryError), null);
180 assert.equal(dashboardOpError({ op: "upsert_tile", tile: { ...tile, query: { ...tile.query, group_by: "secret" } } }, datasetQueryError), "pull_requests can't be grouped by secret.");
181 assert.equal(dashboardOpError({ op: "upsert_tile", tile: { ...tile, type: "markdown", query: null, markdown: "Notes" } }, datasetQueryError), null);
182 assert.equal(dashboardOpError({ op: "upsert_tile", tile: { ...tile, type: "markdown" } }, datasetQueryError), "A text tile has no query.");
183 assert.equal(dashboardOpError({ op: "upsert_tile", tile: { ...tile, grid: { x: 8, y: 0, w: 6, h: 4 } } }, datasetQueryError), "A tile fits in 12 columns.");
184 assert.equal(dashboardOpError({ op: "set_filters", filters: { range: "30d", repos: ["acme/web"] } }, datasetQueryError), null);
185 assert.equal(dashboardOpError({ op: "set_filters", filters: { range: { from: "2026-02-30", to: "2026-03-01" } } }, datasetQueryError), "A range's from and to are dates or RFC 3339 UTC times.");
186 assert.equal(dashboardOpError({ op: "set_layout", tiles: [{ id: "t1", grid: { x: 0, y: 0, w: 0, h: 1 } }] }, datasetQueryError), "grid starts at 0, 0 and is at least 1 by 1.");
187 assert.equal(dashboardGridError({ x: 0, y: 0, w: 12, h: 1 }), null);
188});