77 lines
2.1 KiB
JavaScript
77 lines
2.1 KiB
JavaScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
ERROR_REGISTRY,
|
|
createFailure,
|
|
kindForStatus,
|
|
normalizeUnknownFailure,
|
|
} from "../../src/contracts/errors.js";
|
|
|
|
describe("frontend failure classification", () => {
|
|
it("defines all 26 stable error kinds with the seven contract fields", () => {
|
|
expect(Object.keys(ERROR_REGISTRY)).toHaveLength(31);
|
|
for (const definition of Object.values(ERROR_REGISTRY)) {
|
|
expect(definition).toEqual(
|
|
expect.objectContaining({
|
|
kind: expect.any(String),
|
|
defaultRetryable: expect.any(Boolean),
|
|
severity: expect.any(String),
|
|
userMessageKey: expect.any(String),
|
|
action: expect.any(String),
|
|
telemetryEvent: expect.any(String),
|
|
redaction: expect.any(Array),
|
|
}),
|
|
);
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
[401, "AUTH_REQUIRED"],
|
|
[403, "FORBIDDEN"],
|
|
[404, "NOT_FOUND"],
|
|
[409, "CONFLICT"],
|
|
[422, "VALIDATION_REJECTED"],
|
|
[418, "UNKNOWN_CLIENT_FAILURE"],
|
|
[429, "RATE_LIMITED"],
|
|
[503, "SERVER_FAILURE"],
|
|
])("maps HTTP %i to %s", (status, kind) => {
|
|
expect(kindForStatus(status)).toBe(kind);
|
|
});
|
|
|
|
it("projects only allowlisted safe fields", () => {
|
|
const untrustedDetails = {
|
|
code: "TEMPORARY",
|
|
httpStatus: 503,
|
|
requestId: "request-1",
|
|
stack: "must not leak",
|
|
body: "must not leak",
|
|
authorization: "Bearer secret",
|
|
};
|
|
const result = createFailure(
|
|
"SERVER_FAILURE",
|
|
"LIST_SAMPLE_RESOURCES",
|
|
0,
|
|
untrustedDetails,
|
|
);
|
|
|
|
expect(result).toMatchObject({
|
|
kind: "SERVER_FAILURE",
|
|
code: "TEMPORARY",
|
|
httpStatus: 503,
|
|
requestId: "request-1",
|
|
});
|
|
expect(JSON.stringify(result)).not.toMatch(/stack|body|Bearer|secret/);
|
|
});
|
|
|
|
it("normalizes any thrown value without leaking it", () => {
|
|
const secret = { token: "sensitive", nested: { rawBody: "private" } };
|
|
const result = normalizeUnknownFailure(secret);
|
|
|
|
expect(result).toMatchObject({
|
|
kind: "UNKNOWN_FAILURE",
|
|
causeClass: "object",
|
|
});
|
|
expect(JSON.stringify(result)).not.toMatch(/sensitive|private|token|rawBody/);
|
|
});
|
|
});
|