import { describe, expect, test } from "bun:test" import { dirname, join } from "node:path" import { fileURLToPath } from "node:url" import { loadPrompt, PromptFileNotFoundError } from "./loader" import type { PromptSource } from "./types" const fixtureSource: PromptSource = { baseDir: join(dirname(fileURLToPath(import.meta.url)), "__test_fixtures__"), } class ResolverFailureError extends Error { readonly name = "ResolverFailureError" } class ExpectedErrorMissingError extends Error { readonly name = "ExpectedErrorMissingError" } describe("loadPrompt", () => { test("#given markdown fixture #then returns markdown body verbatim", async () => { const prompt = await loadPrompt({ source: fixtureSource, name: "test-prompt", variant: "default" }) expect(prompt.body).toBe("Default prompt body with {X}.\nSecond line remains verbatim.\n") }) test("#given frontmatter fixture #then returns parsed frontmatter", async () => { const prompt = await loadPrompt<{ readonly title: string; readonly enabled: boolean }>({ source: fixtureSource, name: "test-prompt", variant: "default", }) expect(prompt.frontmatter.title).toBe("Test Prompt") expect(prompt.frontmatter.enabled).toBe(true) }) test("#given empty frontmatter #then parses without crashing", async () => { const prompt = await loadPrompt({ source: fixtureSource, name: "test-prompt", variant: "gpt" }) expect(prompt.frontmatter).toEqual({}) expect(prompt.body).toBe("GPT prompt body with {A} and {B}.\n") }) test("#given missing file #then error mentions prompt name and variant", async () => { const error = await captureError(() => loadPrompt({ source: fixtureSource, name: "test-prompt", variant: "missing" }) ) expect(error).toBeInstanceOf(PromptFileNotFoundError) expect(expectError(error).message).toContain("test-prompt/missing") }) test("#given runtime injection #then replaces placeholder in body", async () => { const prompt = await loadPrompt({ source: fixtureSource, name: "test-prompt", variant: "default", inject: [{ placeholder: "{X}", resolver: () => "Y" }], }) expect(prompt.body).toBe("Default prompt body with Y.\nSecond line remains verbatim.\n") }) test("#given multiple runtime injections #then applies all and ignores absent placeholders", async () => { const prompt = await loadPrompt({ source: fixtureSource, name: "test-prompt", variant: "gpt", inject: [ { placeholder: "{A}", resolver: () => "Alpha" }, { placeholder: "{B}", resolver: () => "Beta" }, { placeholder: "{ABSENT}", resolver: () => "No-op" }, ], }) expect(prompt.body).toBe("GPT prompt body with Alpha and Beta.\n") }) test("#given injection resolver throws #then propagates the error", async () => { const error = await captureError(() => loadPrompt({ source: fixtureSource, name: "test-prompt", variant: "default", inject: [ { placeholder: "{X}", resolver: () => { throw new ResolverFailureError("resolver failed") }, }, ], }) ) expect(error).toBeInstanceOf(ResolverFailureError) }) }) async function captureError(operation: () => Promise): Promise { try { await operation() return undefined } catch (error) { return error } } function expectError(error: unknown): Error { if (error instanceof Error) return error throw new ExpectedErrorMissingError("Expected operation to throw an Error instance") }