import { assert, assertEquals, assertMatch, assertRejects } from "@std/assert"; import { join } from "node:path"; import { tempDir } from "@berry/util"; import { denoCheck, type ExtensionEntry } from "./loader.ts"; import { extensionWeb } from "./web.ts"; import { extension, fixture, open, put } from "./loader-fixture.ts"; Deno.test("an aborted check rejects with its reason instead of reporting a failure", async () => { const stopping = new AbortController(); stopping.abort(new Error("berry is stopping")); await assertRejects( () => denoCheck(Deno.cwd(), ["server/main.ts"], stopping.signal), Error, "berry is stopping", ); }); Deno.test("startup traces include slow and failed imports and survive later reloads", () => fixture({ "slow/mod.ts": `await new Promise((resolve) => setTimeout(resolve, 20));\n${extension("slow")}`, "slow/version.ts": `export const VERSION = "one";`, "slow/web.tsx": `export default {};`, "broken/mod.ts": `await new Promise((resolve) => setTimeout(resolve, 20)); throw new Error("broken import");`, }, async (root) => { const { loader, close } = await open(root); try { await loader.loadAll(); const startup = loader.entries().find((entry) => entry.name === "slow")!.startup; assert(startup); assert(startup.phases.find((phase) => phase.name === "import")!.durationMs >= 10); assertEquals(startup.phases.find((phase) => phase.name === "frontend")!.source, "build"); assert(startup.phases.some((phase) => phase.name === "install")); assert(startup.totalMs >= startup.phases.reduce((sum, phase) => sum + phase.durationMs, 0)); const failed = loader.entries().find((entry) => entry.name === "broken")!.startup; assert(failed); assertEquals(failed.status, "failed"); assert(failed.phases.find((phase) => phase.name === "import")!.durationMs >= 10); assert(!failed.phases.some((phase) => phase.name === "install")); await loader.reload("slow"); assertEquals(loader.entries().find((entry) => entry.name === "slow")!.startup, startup); await put(join(root, "extensions/slow"), { "version.ts": `export const VERSION = ;` }); await loader.reload("slow"); const current = loader.entries().find((entry) => entry.name === "slow")!; assertEquals(current.status, "failed"); assertEquals(current.startup, startup); } finally { await close(); } })); Deno.test("startup traces distinguish restored frontends from failed builds", () => fixture({ "cached/mod.ts": extension("cached", `const VERSION = "one";`), "cached/web.tsx": "export default {};", "badweb/mod.ts": extension("badweb", `const VERSION = "one";`), "badweb/web.tsx": "export default (;", }, async (root) => { await using cache = await tempDir(); await extensionWeb({ cacheDirectory: cache.path }).build( "cached", join(root, "extensions/cached/web.tsx"), ); const { loader, close } = await open( root, undefined, extensionWeb({ cacheDirectory: cache.path }), ); try { await loader.loadAll(); const restored = loader.entries().find((entry) => entry.name === "cached")!.startup!; assertEquals(restored.status, "loaded"); assertEquals(restored.phases.find((phase) => phase.name === "frontend")!.source, "cache"); const failed = loader.entries().find((entry) => entry.name === "badweb")!.startup!; assertEquals(failed.status, "failed"); assertEquals(failed.phases.find((phase) => phase.name === "frontend")!.source, "build"); } finally { await close(); } })); Deno.test("subagent is a selectable builtin and disk reloads cannot replace it", () => fixture({ "subagent/mod.ts": extension("subagent"), "subagent/version.ts": `export const VERSION = "shadow";`, }, async (root) => { const { loader, close } = await open(root); const feed = loader.feed(); try { await loader.loadAll(); const installed = (feed.value as ExtensionEntry[]).find((ext) => ext.name === "subagent"); assert(installed); assert(installed.builtin && installed.running); const result = await loader.reload("subagent"); assertEquals(result.status, "failed"); assertMatch(result.error!, /restart berry/); assertEquals((feed.value as ExtensionEntry[]).filter((ext) => ext.name === "subagent"), [ installed, ]); } finally { await feed.stop(); await close(); } })); Deno.test("the ui lists extensions by configuration order, and those without one after them by name", () => fixture({ "middle/mod.ts": extension("middle", `const VERSION = "middle";`, 20), "beta/mod.ts": extension("beta", `const VERSION = "beta";`), "alpha/mod.ts": extension("alpha", `const VERSION = "alpha";`), }, async (root) => { const { loader, close } = await open(root); const feed = loader.feed(); try { await loader.loadAll(); assertEquals( (feed.value as ExtensionEntry[]).map((entry) => entry.name), ["environment", "middle", "subagent", "alpha", "beta"], ); } finally { await feed.stop(); await close(); } })); Deno.test("the ui sees which extensions a new conversation starts with", () => fixture({ "lean/mod.ts": `import { defineExtension } from "@berry/api"; export default defineExtension({ name: "lean", defaultEnabled: true, initial: () => null, setup() {}, });`, "shy/mod.ts": extension("shy"), }, async (root) => { const { loader, close } = await open(root); try { await loader.loadAll(); const byName = Object.fromEntries(loader.entries().map((entry) => [entry.name, entry])); assertEquals(byName.lean.defaultEnabled, true); assertEquals(byName.shy.defaultEnabled, false); } finally { await close(); } })); Deno.test("each extension loads on its own, and one that fails stops none of the others", () => fixture({ "good/mod.ts": extension("good"), "good/version.ts": `export const VERSION = "one";`, "misnamed/mod.ts": extension("other", `const VERSION = "x";`), "empty/notes.md": "no mod.ts here", }, async (root) => { const { loader, close, describe } = await open(root); await loader.loadAll(); const byName = Object.fromEntries(loader.entries().map((e) => [e.name, e])); assertEquals(byName.good.status, "loaded"); assertEquals(describe("good"), "one"); assertEquals(byName.misnamed.status, "failed"); assertMatch(byName.misnamed.error!, /names itself other/); assertMatch(byName.empty.error!, /has no mod\.ts/); await close(); }));