mirror of
https://github.com/stablyai/agent-slack.git
synced 2026-09-19 01:06:54 +08:00
126d8046a9
Follow-ups to the #126/#133/#135 merges, from the review of each. From #135 (draft attachments): - upload.ts: completedFileIds compacted its result with .filter() while the caller indexed it positionally against the staged list. If Slack omitted an id for any file but the last, every subsequent id shifted up a slot and the wrong file was attached to the draft. Ids are now positionally aligned with undefined holes preserved, and the response is only trusted when its length matches what was sent. - message-draft-actions.ts: cleanupOrphanedDraftFiles takes an input object, matching the codebase convention and clearing the new max-params warning. - drafts.ts: correct ensureDraftOk's comment — both transports already throw on ok:false, so it is defence-in-depth, not the load-bearing guard it claimed to be. - Reformat the files the PR left failing oxfmt. From #126 (wrapped error causes): - String() on a cause is not total: a null-prototype object threw inside the CLI's own catch handlers, turning a printable error into a crash. Route all stringification through safeString(). - Cap the cause walk at 8 levels so a long chain cannot emit a multi-hundred- kilobyte single-line message. - Collapse a cause that merely restates its parent ("fetch failed: fetch failed"). Docs (AGENTS.md requires SKILL.md track the code): - README/SKILL.md/llms.txt document --attach on draft create/update, including update's merge-into-existing semantics. - README: note --no-unfurl works with --schedule, which was implemented and tested in #133 but left out of the flag list. CI: - Run typecheck and format:check. Neither ran anywhere, so a type error or formatting drift could reach main — #135 arrived with oxfmt already failing. - Trigger on push to main. CI was pull_request-only, so the merge commits main is actually built from were never validated. - Gate the release workflow on typecheck too.
76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { errorMessage } from "../src/cli/context.ts";
|
|
|
|
describe("errorMessage", () => {
|
|
test("returns the message for a plain error", () => {
|
|
expect(errorMessage(new Error("boom"))).toBe("boom");
|
|
});
|
|
|
|
test("stringifies non-Error values", () => {
|
|
expect(errorMessage("boom")).toBe("boom");
|
|
});
|
|
|
|
test("appends a single Error cause", () => {
|
|
const err = new Error("fetch failed", { cause: new Error("connect ECONNREFUSED") });
|
|
expect(errorMessage(err)).toBe("fetch failed: connect ECONNREFUSED");
|
|
});
|
|
|
|
test("appends AggregateError causes from a failed fetch through a dead proxy", () => {
|
|
const cause = new AggregateError(
|
|
[
|
|
new Error("connect ECONNREFUSED 127.0.0.1:9090"),
|
|
new Error("connect ECONNREFUSED ::1:9090"),
|
|
],
|
|
"ECONNREFUSED",
|
|
);
|
|
const err = new Error("fetch failed", { cause });
|
|
expect(errorMessage(err)).toBe(
|
|
"fetch failed: connect ECONNREFUSED 127.0.0.1:9090; connect ECONNREFUSED ::1:9090",
|
|
);
|
|
});
|
|
|
|
test("walks multi-level cause chains", () => {
|
|
const root = new Error("connect ECONNREFUSED");
|
|
const middle = new Error("request failed", { cause: root });
|
|
const err = new Error("fetch failed", { cause: middle });
|
|
expect(errorMessage(err)).toBe("fetch failed: request failed: connect ECONNREFUSED");
|
|
});
|
|
|
|
test("terminates on a self-referential cause instead of looping forever", () => {
|
|
const err = new Error("boom");
|
|
err.cause = err;
|
|
expect(errorMessage(err)).toBe("boom");
|
|
});
|
|
|
|
test("terminates on a longer cause cycle", () => {
|
|
const a = new Error("a");
|
|
const b = new Error("b", { cause: a });
|
|
a.cause = b;
|
|
expect(errorMessage(a)).toBe("a: b");
|
|
});
|
|
test("does not crash on a cause that cannot be stringified", () => {
|
|
const err = new Error("boom");
|
|
err.cause = Object.create(null);
|
|
expect(errorMessage(err)).toBe("boom: [unprintable]");
|
|
});
|
|
|
|
test("does not crash on a non-Error thrown value that cannot be stringified", () => {
|
|
expect(errorMessage(Object.create(null))).toBe("[unprintable]");
|
|
});
|
|
|
|
test("caps how far a long cause chain is walked", () => {
|
|
let err = new Error("leaf");
|
|
for (let i = 0; i < 200; i++) {
|
|
err = new Error(`level-${i}`, { cause: err });
|
|
}
|
|
const parts = errorMessage(err).split(": ");
|
|
expect(parts).toHaveLength(9);
|
|
expect(parts[0]).toBe("level-199");
|
|
});
|
|
|
|
test("collapses a cause that merely restates its parent", () => {
|
|
const err = new Error("fetch failed", { cause: new Error("fetch failed") });
|
|
expect(errorMessage(err)).toBe("fetch failed");
|
|
});
|
|
});
|