7ea9f93dc8
Replace mutable Ollama model export with a const fallback and initializeModel return value, resolving the model from the environment after optional API discovery. Use a for-of loop over attempt indices instead of let in the retry path. Continue PDF generation when map image generation or upscaling fails, and avoid mutating request headers in place. Document Open WebUI-style URLs in the README, pin OLLAMA_MODEL in the Gitea release workflow, and adjust integration and unit tests for the new initialization behavior. Reviewed-on: #9 Co-authored-by: keligrubb <keligrubb324@gmail.com> Co-committed-by: keligrubb <keligrubb324@gmail.com>
93 lines
3.0 KiB
JavaScript
93 lines
3.0 KiB
JavaScript
import { describe, it, expect, beforeAll } from "vitest";
|
|
import { generateDungeon } from "../../src/dungeonGenerator.js";
|
|
import { generatePDF } from "../../src/generatePDF.js";
|
|
import fs from "fs/promises";
|
|
import path from "path";
|
|
|
|
const hasOllama = !!process.env.OLLAMA_API_URL;
|
|
|
|
describe.skipIf(!hasOllama)("Dungeon generation (Ollama)", { timeout: 120000 }, () => {
|
|
const fixture = {};
|
|
|
|
beforeAll(async () => {
|
|
fixture.dungeon = await generateDungeon();
|
|
});
|
|
|
|
it("generates dungeon data", () => {
|
|
expect(fixture.dungeon).toBeDefined();
|
|
});
|
|
|
|
it("title is 2-4 words, no colons", () => {
|
|
expect(fixture.dungeon.title).toBeTruthy();
|
|
const words = fixture.dungeon.title.split(/\s+/);
|
|
expect(words.length).toBeGreaterThanOrEqual(2);
|
|
expect(words.length).toBeLessThanOrEqual(4);
|
|
expect(fixture.dungeon.title).not.toContain(":");
|
|
});
|
|
|
|
it("flavor text is ≤60 words", () => {
|
|
expect(fixture.dungeon.flavor).toBeTruthy();
|
|
const words = fixture.dungeon.flavor.split(/\s+/);
|
|
expect(words.length).toBeLessThanOrEqual(60);
|
|
});
|
|
|
|
it("hooks have no title prefixes", () => {
|
|
expect(fixture.dungeon.hooksRumors).toBeDefined();
|
|
fixture.dungeon.hooksRumors.forEach((hook) => {
|
|
expect(hook).not.toMatch(/^[^:]+:\s/);
|
|
});
|
|
});
|
|
|
|
it("has exactly 6 random events", () => {
|
|
expect(fixture.dungeon.randomEvents).toBeDefined();
|
|
expect(fixture.dungeon.randomEvents.length).toBe(6);
|
|
});
|
|
|
|
it("encounter details do not start with encounter name", () => {
|
|
expect(fixture.dungeon.encounters).toBeDefined();
|
|
fixture.dungeon.encounters.forEach((encounter) => {
|
|
if (encounter.details) {
|
|
const detailsLower = encounter.details.toLowerCase();
|
|
const nameLower = encounter.name.toLowerCase();
|
|
expect(detailsLower.startsWith(nameLower)).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
it("treasure descriptions do not start with 'description'", () => {
|
|
expect(fixture.dungeon.treasure).toBeDefined();
|
|
fixture.dungeon.treasure.forEach((item) => {
|
|
if (typeof item === "object" && item.description) {
|
|
expect(item.description.toLowerCase().startsWith("description")).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
it("NPC traits do not start with 'description'", () => {
|
|
expect(fixture.dungeon.npcs).toBeDefined();
|
|
fixture.dungeon.npcs.forEach((npc) => {
|
|
if (npc.trait) {
|
|
expect(npc.trait.toLowerCase().startsWith("description")).toBe(false);
|
|
}
|
|
});
|
|
});
|
|
|
|
it("PDF fits on one page", async () => {
|
|
const testPdfPath = path.join(process.cwd(), "test-output.pdf");
|
|
try {
|
|
await generatePDF(fixture.dungeon, testPdfPath);
|
|
const pdfBuffer = await fs.readFile(testPdfPath);
|
|
const pdfText = pdfBuffer.toString("binary");
|
|
const pageCount = (pdfText.match(/\/Type\s*\/Page[^s]/g) || []).length;
|
|
const expectedPages = fixture.dungeon.map ? 2 : 1;
|
|
expect(pageCount).toBeLessThanOrEqual(expectedPages);
|
|
} finally {
|
|
try {
|
|
await fs.unlink(testPdfPath);
|
|
} catch {
|
|
// Ignore cleanup errors
|
|
}
|
|
}
|
|
}, 60000);
|
|
});
|