/** * Which failures a user can act on, which just need reporting, and whether * the setup diagnosis names the right step. */ import { test } from "node:test"; import assert from "node:assert/strict"; import { CONTACT, ImpossibleRequest, NO_CARD, NO_GREETING, SuckerError, TOO_FEW_MESSAGES, diagnoseSetup, isImpossibleRequest, isSuckerError, userFacingMessage, } from "../src/lib/errors.ts"; const CARD = "a tired innkeeper"; const ok = (prompt: string, persona: string) => `${prompt}\n${CARD}\n${persona}`; test("a correctly set up card has nothing to diagnose", () => { assert.equal(diagnoseSetup(ok("<.>", ".")), null); }); test("a missing custom prompt is named with certainty, not blamed on truncation", () => { const msg = diagnoseSetup(ok("", "."))!.message; assert.match(msg, /custom prompt is not coming through as <\.>/); assert.ok(!/cut it short|Context Size/.test(msg), "truncation cannot remove the first three characters"); assert.ok(!/persona/i.test(msg), "the persona is fine and must not be mentioned"); }); test("a wrong persona description is quoted back, so there is nothing to guess", () => { const msg = diagnoseSetup(ok("<.>", "I am a knight"))!.message; assert.match(msg, /persona description is "I am a knight"/); assert.match(msg, /just a dot/); assert.ok(!/Context Size/.test(msg), "this case is certain; truncation is not the story"); }); test("an empty persona description says so", () => { assert.match(diagnoseSetup(ok("<.>", ""))!.message, /description is empty/); }); test("a long persona description is trimmed to one readable line", () => { const long = "x".repeat(80) + "\n\n y"; const msg = diagnoseSetup(ok("<.>", `${long}`))!.message; assert.match(msg, /"x{40}…"/); assert.ok(!msg.includes("\n"), "no raw newlines from user content"); }); test("a persona marker that is gone leads with truncation, the common cause", () => { const msg = diagnoseSetup(ok("<.>", ""))!.message; assert.match(msg, /end of it is missing/); assert.ok(msg.indexOf("Context Size") < msg.indexOf("persona"), "truncation first, persona second"); }); test("a persona marker cut off mid-tag is truncation, not a wrong description", () => { const msg = diagnoseSetup(ok("<.>", ". { const msg = diagnoseSetup(ok("", "knight"))!.message; assert.match(msg, /custom prompt is not coming through/); assert.match(msg, /description is "knight" as well/); const neither = diagnoseSetup(ok("", ""))!.message; assert.match(neither, /custom prompt is not coming through/); assert.match(neither, /persona did not come through either/); }); test("every setup message is shown verbatim and says where to go when stuck", () => { for (const card of [ok("", "."), ok("<.>", "x"), ok("<.>", ""), ok("", "")]) { const e = diagnoseSetup(card)!; assert.ok(isSuckerError(e)); assert.equal(userFacingMessage(e), e.message); assert.ok(e.message.includes(CONTACT)); } }); test("shapes Janitor cannot produce never tell the user to fix them", () => { for (const e of [NO_CARD, NO_GREETING, TOO_FEW_MESSAGES]) { const msg = userFacingMessage(e); assert.match(msg, /not your fault and there is nothing for you to fix/i); assert.ok(msg.includes(CONTACT), "must say where to report it"); assert.ok(!/open the chat|try again|point sucker|nothing was saved/i.test(msg), `overclaim or advice leaked into: ${msg}`); } }); test("the report carries which assumption broke, so it is useful", () => { assert.match(userFacingMessage(NO_GREETING), /\(no greeting in the request\)/); assert.match(userFacingMessage(NO_CARD), /\(no system message in the request\)/); assert.match(userFacingMessage(TOO_FEW_MESSAGES), /\(fewer than two messages/); }); test("a genuine bug reports itself without leaking internals", () => { const msg = userFacingMessage(new TypeError("Cannot read properties of undefined")); assert.match(msg, /not your fault/i); assert.ok(msg.includes(CONTACT)); assert.ok(!msg.includes("Cannot read properties")); assert.ok(!/\($/.test(msg.trim()), "no empty detail parenthesis"); }); test("nobody is called a dingus", () => { for (const e of [diagnoseSetup("nothing")!, NO_CARD, NO_GREETING, TOO_FEW_MESSAGES, new Error("x")]) { assert.ok(!/dingus/i.test(userFacingMessage(e))); } }); test("classified across module boundaries, not just by identity", () => { assert.ok(isSuckerError(new SuckerError("x"))); assert.ok(isSuckerError({ name: "SuckerError", message: "x" })); assert.ok(isImpossibleRequest(new ImpossibleRequest("x"))); assert.ok(isImpossibleRequest({ name: "ImpossibleRequest", detail: "x" })); assert.ok(!isSuckerError(new Error("x"))); assert.ok(!isImpossibleRequest(new SuckerError("x"))); assert.ok(!isSuckerError(null)); });