more tweaks
All checks were successful
ci/woodpecker/cron/ci Pipeline was successful

This commit is contained in:
2026-01-19 22:37:53 -05:00
parent 9332ac6f94
commit 96223b81e6
4 changed files with 373 additions and 55 deletions

91
test/integration.test.js Normal file
View File

@@ -0,0 +1,91 @@
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
}
}
});
});