mirror of
https://github.com/severian-dev/sucker.severian.dev.git
synced 2026-10-18 01:20:29 +00:00
Say what actually went wrong
Every failure produced the same reply: read the directions, and check your context size. For a card Janitor truncated that was fair. For a request shape the user never chose, or for a bug in sucker, it blamed them for something they could not have done anything about — and sent them back to directions they had already followed. Failures that can be explained now explain themselves: no card in the request, no greeting in the chat, a card that arrived truncated (which keeps the context-size advice, since that is where it belongs), and a request with nothing in it. Anything else is a bug nobody anticipated, so the reply says as much, says it is not the user's fault, and asks them to DM @lyseverian on Discord — an unexplained failure nobody reports never gets fixed. Internals are never echoed back. SuckerError is recognised by name as well as by identity, because Next can bundle a module into more than one chunk and a mismatched class would quietly send every explained error down the unknown-bug path. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NX2SsmUqQftWTZAY4BDuFa
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Which failures explain themselves, and which ask to be reported.
|
||||
*/
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
CONTACT,
|
||||
NO_CARD,
|
||||
NO_GREETING,
|
||||
SuckerError,
|
||||
TOO_FEW_MESSAGES,
|
||||
TRUNCATED_CARD,
|
||||
isSuckerError,
|
||||
userFacingMessage,
|
||||
} from "../src/lib/errors.ts";
|
||||
|
||||
test("an explained failure is shown to the user as written", () => {
|
||||
for (const e of [NO_CARD, NO_GREETING, TOO_FEW_MESSAGES, TRUNCATED_CARD]) {
|
||||
assert.equal(userFacingMessage(e), e.message);
|
||||
assert.ok(!/dingus/i.test(e.message), "nobody is called a dingus for these");
|
||||
}
|
||||
});
|
||||
|
||||
test("an unexplained failure owns it and asks to be reported", () => {
|
||||
const msg = userFacingMessage(new TypeError("Cannot read properties of undefined"));
|
||||
assert.match(msg, /not your fault/i);
|
||||
assert.ok(msg.includes(CONTACT), "must say where to report it");
|
||||
// The internals must not leak into a user-facing reply.
|
||||
assert.ok(!msg.includes("Cannot read properties"));
|
||||
});
|
||||
|
||||
test("the truncated-card case keeps the context-size advice", () => {
|
||||
assert.match(TRUNCATED_CARD.message, /Context Size/);
|
||||
assert.ok(TRUNCATED_CARD.message.includes(CONTACT));
|
||||
});
|
||||
|
||||
test("recognised across module boundaries, not just by identity", () => {
|
||||
assert.ok(isSuckerError(new SuckerError("x")));
|
||||
// What a second copy of the class in another bundle would look like.
|
||||
assert.ok(isSuckerError({ name: "SuckerError", message: "x" }));
|
||||
assert.ok(!isSuckerError(new Error("x")));
|
||||
assert.ok(!isSuckerError(null));
|
||||
assert.ok(!isSuckerError("SuckerError"));
|
||||
});
|
||||
Reference in New Issue
Block a user