| 1 | import assert from "node:assert/strict"; |
| 2 | import { readFileSync, readdirSync } from "node:fs"; |
| 3 | import { DatabaseSync } from "node:sqlite"; |
| 4 | import { test } from "node:test"; |
| 5 | |
| 6 | import type { ExtensionManifest } from "@g1t/contracts"; |
| 7 | import { FIRST_PARTY_EXTENSIONS } from "@g1t/contracts/marketplace"; |
| 8 | |
| 9 | import { type Catalog, extensionIdOf, install, listInstalls, setBudget, setEnabled, uninstall } from "./extensions.ts"; |
| 10 | import { findListing } from "./installs.ts"; |
| 11 | |
| 12 | /** D1, as far as installs use it, over node's SQLite with the service's migrations. */ |
| 13 | function fakeD1(): D1Database { |
| 14 | const db = new DatabaseSync(":memory:"); |
| 15 | const dir = new URL("../migrations/", import.meta.url); |
| 16 | for (const file of readdirSync(dir).sort()) db.exec(readFileSync(new URL(file, dir), "utf8")); |
| 17 | const statement = (sql: string, params: unknown[] = []) => ({ |
| 18 | bind: (...values: unknown[]) => statement(sql, values), |
| 19 | first: async () => (db.prepare(sql).get(...(params as never[])) as unknown) ?? null, |
| 20 | run: async () => db.prepare(sql).run(...(params as never[])), |
| 21 | all: async () => ({ results: db.prepare(sql).all(...(params as never[])) }), |
| 22 | }); |
| 23 | return { prepare: (sql: string) => statement(sql) } as unknown as D1Database; |
| 24 | } |
| 25 | |
| 26 | const standup: ExtensionManifest = { ...FIRST_PARTY_EXTENSIONS[0]!, id: "standup", name: "Standup", status: "available", source: { repo: "flagon-io/standup", tag: "v1.2.0" }, version: "1.2.0" }; |
| 27 | const catalog: Catalog = (id) => (id === "standup" ? standup : FIRST_PARTY_EXTENSIONS.find((e) => e.id === id)); |
| 28 | const ws = "wsp_acme"; |
| 29 | const at = new Date("2026-10-10T12:00:00.000Z"); |
| 30 | |
| 31 | test("a published extension installs at its version, on the free plan, switched on", async () => { |
| 32 | const db = fakeD1(); |
| 33 | const done = await install(db, catalog, ws, "ins_1", "standup", "chase", at); |
| 34 | assert.ok(done.ok); |
| 35 | assert.deepEqual( |
| 36 | { listing: done.value.listing, version: done.value.version, plan: done.value.plan, enabled: done.value.enabled, by: done.value.installed_by }, |
| 37 | { listing: "extension:standup", version: "1.2.0", plan: "free", enabled: true, by: "chase" }, |
| 38 | ); |
| 39 | const again = await install(db, catalog, ws, "ins_2", "standup", "chase", at); |
| 40 | assert.equal(!again.ok && again.error.code, "conflict"); |
| 41 | assert.deepEqual((await listInstalls(db, ws)).map((i) => i.listing), ["extension:standup"]); |
| 42 | assert.deepEqual(await listInstalls(db, "wsp_other"), []); |
| 43 | }); |
| 44 | |
| 45 | test("nothing unpublished installs", async () => { |
| 46 | const db = fakeD1(); |
| 47 | const support = await install(db, catalog, ws, "ins_1", "support", "chase", at); |
| 48 | assert.equal(!support.ok && support.error.code, "invalid"); |
| 49 | assert.match(!support.ok ? support.error.message : "", /isn't published yet/); |
| 50 | const missing = await install(db, catalog, ws, "ins_2", "nope", "chase", at); |
| 51 | assert.equal(!missing.ok && missing.error.code, "not_found"); |
| 52 | // Nor can members ask for what can't be installed. |
| 53 | assert.equal(findListing("extension:support"), null); |
| 54 | }); |
| 55 | |
| 56 | test("the kill switch turns an install off at once, and back on", async () => { |
| 57 | const db = fakeD1(); |
| 58 | await install(db, catalog, ws, "ins_1", "standup", "chase", at); |
| 59 | const off = await setEnabled(db, ws, "extension:standup", false, "dee", at); |
| 60 | assert.ok(off.ok); |
| 61 | assert.equal(off.value.enabled, false); |
| 62 | assert.equal(off.value.disabled_by, "dee"); |
| 63 | const on = await setEnabled(db, ws, "extension:standup", true, "chase", at); |
| 64 | assert.ok(on.ok); |
| 65 | assert.equal(on.value.enabled, true); |
| 66 | assert.equal(on.value.disabled_by, null); |
| 67 | const missing = await setEnabled(db, ws, "extension:other", false, "dee", at); |
| 68 | assert.equal(!missing.ok && missing.error.code, "not_found"); |
| 69 | }); |
| 70 | |
| 71 | test("an install has a budget, and uninstalling keeps the record", async () => { |
| 72 | const db = fakeD1(); |
| 73 | await install(db, catalog, ws, "ins_1", "standup", "chase", at); |
| 74 | const capped = await setBudget(db, ws, "extension:standup", 25_000_000); |
| 75 | assert.ok(capped.ok); |
| 76 | assert.equal(capped.value.budget_monthly_micros, 25_000_000); |
| 77 | assert.equal((await setBudget(db, ws, "extension:standup", -1)).ok, false); |
| 78 | assert.ok((await uninstall(db, ws, "extension:standup", "chase", at)).ok); |
| 79 | assert.deepEqual(await listInstalls(db, ws), []); |
| 80 | assert.equal((await uninstall(db, ws, "extension:standup", "chase", at)).ok, false); |
| 81 | // Installed again, it is a new install. |
| 82 | assert.ok((await install(db, catalog, ws, "ins_2", "standup", "chase", at)).ok); |
| 83 | }); |
| 84 | |
| 85 | test("extension references", () => { |
| 86 | assert.equal(extensionIdOf("extension:standup"), "standup"); |
| 87 | assert.equal(extensionIdOf("integration:sentry"), null); |
| 88 | }); |