Files
openclaw/src/infra/errors.test.ts
Peter Steinberger eecbfcc960 fix(infra): unify env-truthiness, missing-path, realpath, and abort-sleep semantics (#120359)
* fix(infra): unify environment truthiness

* fix(infra): unify missing path classification

* refactor(infra): unify realpath fallbacks

* fix(infra): unify abortable sleep errors

* docs(infra): clarify path fallback semantics

* fix(infra): route realpaths through policy wrapper

* fix(infra): ratchet plugin SDK wildcard budget

* fix(agents): preserve zero-delay abort precedence

* fix(infra): preserve fallback and media recovery contracts

* fix(plugins): share quarantine path resolution
2026-08-08 10:58:57 -07:00

190 lines
6.7 KiB
TypeScript

// Tests shared infra error formatting helpers.
import { describe, expect, it } from "vitest";
import { collectNestedErrorCandidates, extractErrorCodeOrErrno } from "./error-graph-internal.js";
import {
collectErrorGraphCandidates,
extractErrorCode,
formatErrorMessage,
formatUncaughtError,
hasErrnoCode,
isErrno,
isMissingPathError,
readErrorName,
} from "./errors.js";
function createCircularObject() {
const circular: { self?: unknown } = {};
circular.self = circular;
return circular;
}
describe("error helpers", () => {
it.each([
{ value: { code: "EADDRINUSE" }, expected: "EADDRINUSE" },
{ value: { code: 429 }, expected: "429" },
{ value: { code: false }, expected: undefined },
{ value: "boom", expected: undefined },
])("extracts error codes from %j", ({ value, expected }) => {
expect(extractErrorCode(value)).toBe(expected);
});
it.each([
{ value: { name: "AbortError" }, expected: "AbortError" },
{ value: { name: 42 }, expected: "" },
{ value: null, expected: "" },
])("reads error names from %j", ({ value, expected }) => {
expect(readErrorName(value)).toBe(expected);
});
it("walks nested error graphs once in breadth-first order", () => {
const leaf = { name: "leaf" };
const child = { name: "child" } as {
name: string;
cause?: unknown;
errors?: unknown[];
};
const root = { name: "root", cause: child, errors: [leaf, child] };
child.cause = root;
expect(
collectErrorGraphCandidates(root, (current) => [
current.cause,
...((current as { errors?: unknown[] }).errors ?? []),
]),
).toEqual([root, child, leaf]);
expect(collectErrorGraphCandidates(null)).toStrictEqual([]);
});
it("walks every canonical wrapper edge once despite duplicates and cycles", () => {
const cause = { name: "cause" } as { name: string; cause?: unknown };
const reason = { name: "reason" };
const original = { name: "original" };
const error = { name: "error" };
const data = { name: "data" };
const aggregate = { name: "aggregate" };
const root = {
name: "root",
cause,
reason,
original,
error,
data,
errors: [aggregate, cause],
};
cause.cause = root;
expect(collectNestedErrorCandidates(root)).toEqual([
root,
cause,
reason,
original,
error,
data,
aggregate,
]);
});
it.each([
{ value: { code: " econnreset " }, expected: "ECONNRESET" },
{ value: { errno: " eai_again " }, expected: "EAI_AGAIN" },
{ value: { errno: -3001 }, expected: "-3001" },
{ value: { errno: false }, expected: undefined },
])("normalizes error code or errno from %#", ({ value, expected }) => {
expect(extractErrorCodeOrErrno(value)).toBe(expected);
});
it("matches errno-shaped errors by code", () => {
const err = Object.assign(new Error("busy"), { code: "EADDRINUSE" });
expect(isErrno(err)).toBe(true);
expect(hasErrnoCode(err, "EADDRINUSE")).toBe(true);
expect(hasErrnoCode(err, "ENOENT")).toBe(false);
expect(isErrno("busy")).toBe(false);
});
it.each(["ENOENT", "ENOTDIR", "not-found"])(
"classifies %s as a missing path without requiring Error identity",
(code) => {
expect(isMissingPathError({ code })).toBe(true);
},
);
it("does not classify other fs-safe or errno failures as missing paths", () => {
expect(isMissingPathError({ code: "path-alias" })).toBe(false);
expect(isMissingPathError(new Error("ENOENT"))).toBe(false);
});
it.each([
{ value: 123n, expected: "123" },
{ value: false, expected: "false" },
{ value: createCircularObject(), expected: "[object Object]" },
])("formats error messages for case %#", ({ value, expected }) => {
expect(formatErrorMessage(value)).toBe(expected);
});
it("traverses .cause chain to include nested error messages", () => {
const rootCause = new Error("ECONNRESET");
const httpError = Object.assign(new Error("Network request for 'sendMessage' failed!"), {
cause: rootCause,
});
const formatted = formatErrorMessage(httpError);
expect(formatted).toContain("Network request for 'sendMessage' failed!");
expect(formatted).toContain("ECONNRESET");
});
it("handles circular .cause references without infinite loop", () => {
const a: Error & { cause?: unknown } = new Error("error A");
const b: Error & { cause?: unknown } = new Error("error B");
a.cause = b;
b.cause = a;
const formatted = formatErrorMessage(a);
expect(formatted).toBe("error A | error B");
});
it("dedupes repeated cause messages while preserving deeper distinct causes", () => {
const rootCause = new Error("provider auth lookup failed");
const inner = new Error('No API key found for provider "openai".', { cause: rootCause });
const wrapper = new Error(inner.message, { cause: inner });
expect(formatErrorMessage(wrapper)).toBe(`${inner.message} | ${rootCause.message}`);
});
it("redacts sensitive tokens from formatted error messages", () => {
const token = "sk-abcdefghijklmnopqrstuv";
const formatted = formatErrorMessage(new Error(`Authorization: Bearer ${token}`));
expect(formatted).toContain("Authorization: Bearer");
expect(formatted).not.toContain(token);
});
it("redacts HTTP client config secrets from formatted error chains", () => {
const appSecret = "feishu_app_secret_1234567890";
const tenantToken = "feishu_tenant_access_abcdef123456";
const rootCause = new Error(
`request config: { appSecret: '${appSecret}', headers: { authorization: 'Bearer ${tenantToken}' } }`,
);
const httpError = Object.assign(new Error(`POST /auth/v3/tenant_access_token failed`), {
cause: rootCause,
});
const formatted = formatErrorMessage(httpError);
expect(formatted).toContain("POST /auth/v3/tenant_access_token failed");
expect(formatted).toContain("appSecret:");
expect(formatted).toContain("authorization:");
expect(formatted).not.toContain(appSecret);
expect(formatted).not.toContain(tenantToken);
});
it("uses message-only formatting for INVALID_CONFIG and stack formatting otherwise", () => {
const invalidConfig = Object.assign(new Error("TOKEN=sk-abcdefghijklmnopqrstuv"), {
code: "INVALID_CONFIG",
stack: "Error: TOKEN=sk-abcdefghijklmnopqrstuv\n at ignored",
});
expect(formatUncaughtError(invalidConfig)).not.toContain("at ignored");
const uncaught = new Error("boom");
uncaught.stack = "Error: Authorization: Bearer sk-abcdefghijklmnopqrstuv\n at runTask";
const formatted = formatUncaughtError(uncaught);
expect(formatted).toContain("at runTask");
expect(formatted).not.toContain("sk-abcdefghijklmnopqrstuv");
});
});