Skip to content
126 linesCodeBlameRaw
1import assert from "node:assert/strict";
2import { readFileSync, readdirSync } from "node:fs";
3import { DatabaseSync } from "node:sqlite";
4import { test } from "node:test";
5
6import { MAX_OPEN_REQUESTS, cleanRequestNote, parseListing } from "@g1t/contracts/marketplace";
7
8import { answerLine, findListing, listRequests, listingPath, openRequest, resolveListing, resolveRequest } from "./installs.ts";
9
10/** D1, as far as install requests use it, over node's SQLite with the service's migrations. */
11function fakeD1(): D1Database {
12 const db = new DatabaseSync(":memory:");
13 const dir = new URL("../migrations/", import.meta.url);
14 for (const file of readdirSync(dir).sort()) db.exec(readFileSync(new URL(file, dir), "utf8"));
15 const statement = (sql: string, params: unknown[] = []) => ({
16 bind: (...values: unknown[]) => statement(sql, values),
17 first: async () => (db.prepare(sql).get(...(params as never[])) as unknown) ?? null,
18 run: async () => db.prepare(sql).run(...(params as never[])),
19 all: async () => ({ results: db.prepare(sql).all(...(params as never[])) }),
20 });
21 return { prepare: (sql: string) => statement(sql) } as unknown as D1Database;
22}
23
24let ids = 0;
25const nextId = () => `ins_${++ids}`;
26const ws = "wsp_acme";
27const ana = { id: "usr_ana", username: "ana" };
28const bo = { id: "usr_bo", username: "bo" };
29const owner = { id: "usr_own", username: "chase" };
30const at = new Date("2026-10-10T12:00:00.000Z");
31const later = (days: number) => new Date(at.getTime() + days * 86_400_000);
32
33test("only what a workspace can add today is a listing", () => {
34 assert.deepEqual(findListing("agent:engineering"), { ref: "agent:engineering", kind: "agent", id: "engineering", name: "Software Engineer" });
35 assert.equal(findListing("integration:sentry")?.name, "Sentry");
36 assert.equal(findListing("agent:astronaut"), null, "no such role");
37 assert.equal(findListing("integration:slack"), null, "Slack is planned, not available");
38 assert.equal(findListing("integration:mcp"), null, "MCP clients are connected by each person, not a workspace");
39 assert.equal(findListing("extension:support"), null, "no extensions are listed yet");
40 assert.equal(findListing(42), null);
41});
42
43test("references parse strictly, and notes are tidied", () => {
44 assert.deepEqual(parseListing(" integration:alerts-webhook "), { kind: "integration", id: "alerts-webhook" });
45 assert.equal(parseListing("agent:"), null);
46 assert.equal(parseListing("agent:Engineering"), null);
47 assert.equal(parseListing("agent:a b"), null);
48 assert.equal(cleanRequestNote(" for on-call "), "for on-call");
49 assert.equal(cleanRequestNote(" "), null);
50 assert.equal(cleanRequestNote("x".repeat(400))?.length, 280);
51 assert.equal(cleanRequestNote(null), null);
52});
53
54test("a member's request is kept, and asking twice is one request", async () => {
55 const db = fakeD1();
56 const listing = findListing("agent:qa")!;
57 const first = await openRequest(db, ws, nextId(), listing, ana, "Flaky checks", at);
58 assert.ok(first.ok);
59 assert.equal(first.value.status, "open");
60 assert.equal(first.value.name, "QA Engineer");
61 assert.equal(first.value.note, "Flaky checks");
62 const again = await openRequest(db, ws, nextId(), listing, ana, null, later(0.1));
63 assert.equal(again.ok, false);
64 assert.equal(!again.ok && again.error.code, "conflict");
65 // Someone else asking for the same thing is their own request.
66 assert.ok((await openRequest(db, ws, nextId(), listing, bo, null, at)).ok);
67});
68
69test("an owner sees every request; a member sees their own", async () => {
70 const db = fakeD1();
71 await openRequest(db, ws, nextId(), findListing("agent:qa")!, ana, null, at);
72 await openRequest(db, ws, nextId(), findListing("integration:linear")!, bo, null, later(0.01));
73 await openRequest(db, "wsp_other", nextId(), findListing("agent:docs")!, ana, null, at);
74 assert.deepEqual((await listRequests(db, ws, owner, true, at)).map((r) => r.listing), ["integration:linear", "agent:qa"]);
75 assert.deepEqual((await listRequests(db, ws, ana, false, at)).map((r) => r.listing), ["agent:qa"]);
76});
77
78test("answering a request: once, by status, and answered ones age out", async () => {
79 const db = fakeD1();
80 const opened = await openRequest(db, ws, nextId(), findListing("integration:sentry")!, ana, null, at);
81 assert.ok(opened.ok);
82 const answered = await resolveRequest(db, ws, opened.value.id, "declined", owner, later(1));
83 assert.ok(answered.ok);
84 assert.equal(answered.value.request.status, "declined");
85 assert.equal(answered.value.request.resolved_by, "chase");
86 assert.equal(answered.value.requested_by_id, "usr_ana");
87 const twice = await resolveRequest(db, ws, opened.value.id, "done", owner, later(1));
88 assert.equal(!twice.ok && twice.error.code, "conflict");
89 const elsewhere = await resolveRequest(db, "wsp_other", opened.value.id, "done", owner, later(1));
90 assert.equal(!elsewhere.ok && elsewhere.error.code, "not_found");
91 // Turned down, the person may ask again.
92 assert.ok((await openRequest(db, ws, nextId(), findListing("integration:sentry")!, ana, null, later(2))).ok);
93 // Answered requests stay listed for 30 days.
94 assert.equal((await listRequests(db, ws, owner, true, later(20))).length, 2);
95 assert.equal((await listRequests(db, ws, owner, true, later(40))).length, 1, "only the open one");
96});
97
98test("adding a listing answers every open request for it", async () => {
99 const db = fakeD1();
100 await openRequest(db, ws, nextId(), findListing("agent:support")!, ana, null, at);
101 await openRequest(db, ws, nextId(), findListing("agent:support")!, bo, null, at);
102 await openRequest(db, ws, nextId(), findListing("agent:sales")!, bo, null, at);
103 const answered = await resolveListing(db, ws, "agent:support", owner, later(1));
104 assert.deepEqual(answered.map((a) => a.requested_by_id).sort(), ["usr_ana", "usr_bo"]);
105 assert.ok(answered.every((a) => a.request.status === "done"));
106 const open = (await listRequests(db, ws, owner, true, later(1))).filter((r) => r.status === "open");
107 assert.deepEqual(open.map((r) => r.listing), ["agent:sales"]);
108});
109
110test("a person keeps at most so many requests open", async () => {
111 const db = fakeD1();
112 // The catalog has fewer listings than the limit, so the same few are asked, answered and asked again.
113 const listings = ["agent:engineering", "agent:qa", "agent:operations", "agent:docs", "agent:product", "agent:support", "agent:sales", "integration:sentry", "integration:datadog", "integration:linear", "integration:jira", "integration:github", "integration:webhooks", "integration:alerts-webhook", "integration:anthropic", "integration:openai", "integration:gemini", "integration:xai", "integration:mistral", "integration:deepseek"];
114 assert.equal(listings.length, MAX_OPEN_REQUESTS);
115 for (const ref of listings) assert.ok((await openRequest(db, ws, nextId(), findListing(ref)!, ana, null, at)).ok, ref);
116 const over = await openRequest(db, ws, nextId(), findListing("integration:groq")!, ana, null, at);
117 assert.equal(!over.ok && over.error.code, "limit");
118});
119
120test("what the asker is told, and where it leads", () => {
121 const base = { id: "ins_1", listing: "agent:qa", kind: "agent" as const, name: "QA Engineer", note: null, requested_by: "ana", requested_at: at.toISOString(), resolved_by: "chase", resolved_at: at.toISOString() };
122 assert.equal(answerLine({ ...base, status: "done" }, "@chase").title, "QA Engineer was added to the workspace");
123 assert.match(answerLine({ ...base, status: "declined" }, "@chase").title, /@chase turned down your request for QA Engineer/);
124 assert.equal(listingPath("acme", "agent:qa"), "/acme/-/marketplace/agents/qa");
125 assert.equal(listingPath("acme", "integration:sentry"), "/acme/-/marketplace/integrations");
126});