import { existsSync, mkdtempSync, readFileSync, rmSync, statSync, } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, it } from "@suaveplan/testing/runner"; import { SCAFFOLD_CODES, ScaffoldError } from "../../src/error-codes.js"; import { scaffold } from "../../src/generator/generator.js"; import { answersForFixture } from "../../src/prompt/prompt.js"; import { renderTree } from "../../src/templates/templates.js"; const FIXTURE_BY_DIR: Record< string, | "trusted-postgres-full" | "isolated-sqlite-rest-only" | "sandboxed-none-rest-only" > = { "trusted-postgres-full": "trusted-postgres-full", "isolated-sqlite-rest-only": "isolated-sqlite-rest-only", "sandboxed-none-rest-only": "sandboxed-none-rest-only", }; const SNAPSHOT_ROOT = join(import.meta.dirname, "snapshots"); function makeTmp(prefix: string): string { return mkdtempSync(join(tmpdir(), `${prefix}-`)); } function readSnapshot(fixtureDir: string, relPath: string): string { return readFileSync(join(SNAPSHOT_ROOT, fixtureDir, relPath), "utf8"); } for (const [snapshotDir, fixture] of Object.entries(FIXTURE_BY_DIR)) { describe(`scaffold-output: ${snapshotDir}`, () => { it(`${snapshotDir} snapshot match`, { timeout: 30000 }, () => { const files = renderTree(answersForFixture(fixture, "demo-plugin")); for (const f of files) { const expected = readSnapshot(snapshotDir, f.path); expect(f.content).toBe(expected); } }); }); } describe("scaffold-output: sandboxed-none-rest-only emits no integration / migrations", () => { it("directories omitted when DB is none", { timeout: 5000 }, () => { const files = renderTree( answersForFixture("sandboxed-none-rest-only", "demo-plugin") ); expect(files.some((f) => f.path.startsWith("tests/integration"))).toBe( false ); expect(files.some((f) => f.path.startsWith("migrations/"))).toBe(false); }); }); describe("scaffold-output: vitest configs use createTestConfig", () => { it("generated vitest configs use createTestConfig", { timeout: 5000, }, () => { const files = renderTree( answersForFixture("trusted-postgres-full", "demo-plugin") ); const cfg = files.find((f) => f.path === "vitest.config.ts"); expect(cfg?.content.includes("@suaveplan/testing/config")).toBe(true); expect(cfg?.content.includes("createTestConfig")).toBe(true); expect(cfg?.content.includes("baseVitestConfig")).toBe(false); const cfgI = files.find( (f) => f.path === "vitest.integration.config.ts" ); expect(cfgI?.content.includes("createTestConfig")).toBe(true); }); }); describe("scaffold-output: postgres selection wires testing-integration-postgres", () => { it("postgres selection wires testing-integration-postgres", { timeout: 5000, }, () => { const files = renderTree( answersForFixture("trusted-postgres-full", "demo-plugin") ); const it_ = files.find( (f) => f.path === "tests/integration/example.integration.test.ts" ); expect( it_?.content.includes("@suaveplan/testing-integration-postgres") ).toBe(true); expect(it_?.content.includes("INTEGRATION")).toBe(true); }); }); describe("scaffold-output: pre-existing directory refuses without force", () => { it("pre-existing directory raises without force", { timeout: 5000 }, () => { const root = makeTmp("scaffold-out-exists"); const answers = answersForFixture("sandboxed-none-rest-only", "demo"); // First write succeeds. scaffold({ answers, targetDir: root, force: true }); // Second write without --force raises. try { expect(() => scaffold({ answers, targetDir: root })).toThrowError( ScaffoldError ); try { scaffold({ answers, targetDir: root }); } catch (err) { expect((err as ScaffoldError).code).toBe( SCAFFOLD_CODES.DIRECTORY_EXISTS ); } } finally { rmSync(root, { recursive: true, force: true }); } }); it("--force overwrites prior files", { timeout: 5000 }, () => { const root = makeTmp("scaffold-out-force"); const answers = answersForFixture("sandboxed-none-rest-only", "demo"); scaffold({ answers, targetDir: root, force: true }); scaffold({ answers, targetDir: root, force: true }); expect(existsSync(join(root, "plugin.json"))).toBe(true); expect(statSync(join(root, "plugin.json")).isFile()).toBe(true); rmSync(root, { recursive: true, force: true }); }); });