/** * Tests for CLI seed commands */ import { mkdtemp, rm, writeFile, mkdir, readFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, it, expect, beforeEach, afterEach } from "vitest"; import { createDatabase } from "../../../src/database/connection.js"; import { runMigrations } from "../../../src/database/migrations/runner.js"; import { applySeed } from "../../../src/seed/apply.js"; import type { SeedFile } from "../../../src/seed/types.js"; import { validateSeed } from "../../../src/seed/validate.js"; describe("CLI Seed Commands", () => { let tempDir: string; beforeEach(async () => { tempDir = await mkdtemp(join(tmpdir(), "emdash-cli-test-")); }); afterEach(async () => { await rm(tempDir, { recursive: true, force: true }); }); describe("seed file resolution", () => { it("should resolve .emdash/seed.json by convention", async () => { // Create convention seed file const emdashDir = join(tempDir, ".emdash"); await mkdir(emdashDir); const seedPath = join(emdashDir, "seed.json"); const seed: SeedFile = { version: "1", settings: { title: "Convention Seed" }, }; await writeFile(seedPath, JSON.stringify(seed)); // Read it back const content = await readFile(seedPath, "utf-8"); const parsed = JSON.parse(content); expect(parsed.settings.title).toBe("Convention Seed"); }); it("should resolve seed from package.json emdash.seed", async () => { // Create seed file in custom location const customDir = join(tempDir, "custom"); await mkdir(customDir); const seedPath = join(customDir, "my-seed.json"); const seed: SeedFile = { version: "1", settings: { title: "Package.json Seed" }, }; await writeFile(seedPath, JSON.stringify(seed)); // Create package.json referencing it const pkg = { name: "test-project", emdash: { seed: "custom/my-seed.json", }, }; await writeFile(join(tempDir, "package.json"), JSON.stringify(pkg)); // Verify the referenced path works const content = await readFile(seedPath, "utf-8"); const parsed = JSON.parse(content); expect(parsed.settings.title).toBe("Package.json Seed"); }); }); describe("seed validation", () => { it("should validate a valid seed file", () => { const seed: SeedFile = { version: "1", settings: { title: "Test Site" }, collections: [ { slug: "posts", label: "Posts", fields: [{ slug: "title", label: "Title", type: "string", required: true }], }, ], }; const result = validateSeed(seed); expect(result.valid).toBe(true); expect(result.errors).toHaveLength(0); }); it("should reject invalid seed version", () => { const seed = { version: "999", settings: {}, }; const result = validateSeed(seed); expect(result.valid).toBe(false); expect(result.errors.some((e) => e.includes("version"))).toBe(true); }); it("should reject seed with invalid collection", () => { const seed: SeedFile = { version: "1", collections: [ { slug: "", // Invalid: empty slug label: "Posts", fields: [], }, ], }; const result = validateSeed(seed); expect(result.valid).toBe(false); }); }); describe("seed application", () => { it("should apply settings from seed", async () => { const dbPath = join(tempDir, "test.db"); const db = createDatabase({ url: `file:${dbPath}` }); try { await runMigrations(db); const seed: SeedFile = { version: "1", settings: { title: "My Test Site", tagline: "A test site for testing", }, }; const result = await applySeed(db, seed, {}); expect(result.settings.applied).toBe(2); } finally { await db.destroy(); } }); it("should apply collections from seed", async () => { const dbPath = join(tempDir, "test.db"); const db = createDatabase({ url: `file:${dbPath}` }); try { await runMigrations(db); const seed: SeedFile = { version: "1", collections: [ { slug: "articles", label: "Articles", labelSingular: "Article", fields: [ { slug: "title", label: "Title", type: "string", required: true, }, { slug: "body", label: "Body", type: "portableText" }, ], }, ], }; const result = await applySeed(db, seed, {}); expect(result.collections.created).toBe(1); expect(result.fields.created).toBe(2); } finally { await db.destroy(); } }); it("should be idempotent (skip existing)", async () => { const dbPath = join(tempDir, "test.db"); const db = createDatabase({ url: `file:${dbPath}` }); try { await runMigrations(db); const seed: SeedFile = { version: "1", collections: [ { slug: "pages", label: "Pages", fields: [{ slug: "title", label: "Title", type: "string" }], }, ], }; // First apply const result1 = await applySeed(db, seed, {}); expect(result1.collections.created).toBe(1); expect(result1.collections.skipped).toBe(0); // Second apply - should skip const result2 = await applySeed(db, seed, {}); expect(result2.collections.created).toBe(0); expect(result2.collections.skipped).toBe(1); } finally { await db.destroy(); } }); }); describe("export-seed output", () => { it("should produce valid seed from exported data", async () => { const dbPath = join(tempDir, "test.db"); const db = createDatabase({ url: `file:${dbPath}` }); try { await runMigrations(db); // Apply a seed first const inputSeed: SeedFile = { version: "1", settings: { title: "Export Test" }, collections: [ { slug: "docs", label: "Documentation", fields: [ { slug: "title", label: "Title", type: "string" }, { slug: "content", label: "Content", type: "portableText" }, ], }, ], }; await applySeed(db, inputSeed, {}); // Now export (simulating what export-seed does) // For this test, we just verify the input seed validates const validation = validateSeed(inputSeed); expect(validation.valid).toBe(true); } finally { await db.destroy(); } }); }); describe("content export with $media", () => { it("should handle content without media gracefully", async () => { const dbPath = join(tempDir, "test.db"); const db = createDatabase({ url: `file:${dbPath}` }); try { await runMigrations(db); const seed: SeedFile = { version: "1", collections: [ { slug: "posts", label: "Posts", fields: [{ slug: "title", label: "Title", type: "string" }], }, ], content: { posts: [ { id: "post-1", slug: "hello-world", status: "published", data: { title: "Hello World" }, }, ], }, }; const result = await applySeed(db, seed, { includeContent: true }); expect(result.collections.created).toBe(1); expect(result.content.created).toBe(1); } finally { await db.destroy(); } }); }); });