92 lines
3.7 KiB
JavaScript
92 lines
3.7 KiB
JavaScript
import { test } from "node:test";
|
|
import assert from "node:assert";
|
|
import { generateDungeon } from "../dungeonGenerator.js";
|
|
import { generatePDF } from "../generatePDF.js";
|
|
import fs from "fs/promises";
|
|
import path from "path";
|
|
|
|
const OLLAMA_API_URL = process.env.OLLAMA_API_URL;
|
|
|
|
test("Integration tests", { skip: !OLLAMA_API_URL }, async (t) => {
|
|
let dungeonData;
|
|
|
|
await t.test("Generate dungeon", async () => {
|
|
dungeonData = await generateDungeon();
|
|
assert(dungeonData, "Dungeon data should be generated");
|
|
});
|
|
|
|
await t.test("Title is 2-4 words, no colons", () => {
|
|
assert(dungeonData.title, "Title should exist");
|
|
const words = dungeonData.title.split(/\s+/);
|
|
assert(words.length >= 2 && words.length <= 4, `Title should be 2-4 words, got ${words.length}: "${dungeonData.title}"`);
|
|
assert(!dungeonData.title.includes(":"), `Title should not contain colons: "${dungeonData.title}"`);
|
|
});
|
|
|
|
await t.test("Flavor text is ≤60 words", () => {
|
|
assert(dungeonData.flavor, "Flavor text should exist");
|
|
const words = dungeonData.flavor.split(/\s+/);
|
|
assert(words.length <= 60, `Flavor text should be ≤60 words, got ${words.length}`);
|
|
});
|
|
|
|
await t.test("Hooks have no title prefixes", () => {
|
|
assert(dungeonData.hooksRumors, "Hooks should exist");
|
|
dungeonData.hooksRumors.forEach((hook, i) => {
|
|
assert(!hook.match(/^[^:]+:\s/), `Hook ${i + 1} should not have title prefix: "${hook}"`);
|
|
});
|
|
});
|
|
|
|
await t.test("Exactly 6 random events", () => {
|
|
assert(dungeonData.randomEvents, "Random events should exist");
|
|
assert.strictEqual(dungeonData.randomEvents.length, 6, `Should have exactly 6 random events, got ${dungeonData.randomEvents.length}`);
|
|
});
|
|
|
|
await t.test("Encounter details don't include encounter name", () => {
|
|
assert(dungeonData.encounters, "Encounters should exist");
|
|
dungeonData.encounters.forEach((encounter) => {
|
|
if (encounter.details) {
|
|
const detailsLower = encounter.details.toLowerCase();
|
|
const nameLower = encounter.name.toLowerCase();
|
|
assert(!detailsLower.startsWith(nameLower), `Encounter "${encounter.name}" details should not start with encounter name: "${encounter.details}"`);
|
|
}
|
|
});
|
|
});
|
|
|
|
await t.test("Treasure uses em-dash format, no 'description' text", () => {
|
|
assert(dungeonData.treasure, "Treasure should exist");
|
|
dungeonData.treasure.forEach((item, i) => {
|
|
if (typeof item === "object" && item.description) {
|
|
assert(!item.description.toLowerCase().startsWith("description"), `Treasure ${i + 1} description should not start with 'description': "${item.description}"`);
|
|
}
|
|
});
|
|
});
|
|
|
|
await t.test("NPCs have no 'description' text", () => {
|
|
assert(dungeonData.npcs, "NPCs should exist");
|
|
dungeonData.npcs.forEach((npc, i) => {
|
|
if (npc.trait) {
|
|
assert(!npc.trait.toLowerCase().startsWith("description"), `NPC ${i + 1} trait should not start with 'description': "${npc.trait}"`);
|
|
}
|
|
});
|
|
});
|
|
|
|
await t.test("PDF fits on one page", async () => {
|
|
const testPdfPath = path.join(process.cwd(), "test-output.pdf");
|
|
try {
|
|
await generatePDF(dungeonData, testPdfPath);
|
|
const pdfBuffer = await fs.readFile(testPdfPath);
|
|
// Check PDF page count by counting "%%EOF" markers (rough estimate)
|
|
const pdfText = pdfBuffer.toString("binary");
|
|
const pageCount = (pdfText.match(/\/Type\s*\/Page[^s]/g) || []).length;
|
|
// Should be 1 page for content, or 2 if map exists
|
|
const expectedPages = dungeonData.map ? 2 : 1;
|
|
assert(pageCount <= expectedPages, `PDF should have ≤${expectedPages} page(s), got ${pageCount}`);
|
|
} finally {
|
|
try {
|
|
await fs.unlink(testPdfPath);
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
}
|
|
});
|
|
});
|