Fixes: 1. media.ts: wrap placeholder generation in try-catch 2. toolbar.ts: check r.ok, display error message in popover
290 lines
7.1 KiB
TypeScript
290 lines
7.1 KiB
TypeScript
/**
|
|
* 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();
|
|
}
|
|
});
|
|
});
|
|
});
|