acpx/test/error-normalization.test.ts
Onur Solmaz be510ba918
refactor: add acpx runtime embedding API (#220)
* refactor: add acpx runtime embedding API

* fix: preserve session state on replay failure

* fix: honor startup controls in runtime manager

* Runtime: deduplicate shared session engine

* Runtime: honor oneshot and steer inputs

* Runtime: keep persistent sessions stable

* Runtime: initialize embedded event logs

* Runtime: drop unsupported ensure env

* Refactor: reorganize ACPX source layout

* refactor: finish ACPX source layout cleanup

* docs: note runtime embedding API
2026-04-06 00:52:21 +02:00

171 lines
4.9 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import {
exitCodeForOutputErrorCode,
normalizeOutputError,
isAcpQueryClosedBeforeResponseError,
isAcpResourceNotFoundError,
} from "../src/acp/error-normalization.js";
import {
PermissionPromptUnavailableError,
QueueConnectionError,
AuthPolicyError,
} from "../src/errors.js";
test("normalizeOutputError maps permission prompt unavailable errors", () => {
const normalized = normalizeOutputError(new PermissionPromptUnavailableError(), {
origin: "runtime",
});
assert.equal(normalized.code, "PERMISSION_PROMPT_UNAVAILABLE");
assert.equal(normalized.origin, "runtime");
assert.match(normalized.message, /Permission prompt unavailable/i);
});
test("normalizeOutputError maps ACP resource not found errors to NO_SESSION", () => {
const error = {
code: -32002,
message: "Resource not found: session",
data: {
sessionId: "abc",
},
};
const normalized = normalizeOutputError(error, {
origin: "acp",
});
assert.equal(normalized.code, "NO_SESSION");
assert.equal(normalized.origin, "acp");
assert.deepEqual(normalized.acp, {
code: -32002,
message: "Resource not found: session",
data: {
sessionId: "abc",
},
});
assert.equal(isAcpResourceNotFoundError(error), true);
});
test("isAcpResourceNotFoundError recognizes session-not-found hints in nested errors", () => {
assert.equal(
isAcpResourceNotFoundError({
cause: {
message: "session not found while reconnecting",
},
}),
true,
);
});
test("isAcpResourceNotFoundError recognizes Cursor session-not-found format", () => {
// Cursor returns: {"code":-32602,"message":"Invalid params","data":{"message":"Session \"xxx\" not found"}}
const cursorError = {
code: -32602,
message: "Invalid params",
data: {
message: 'Session "nonexistent-session-id" not found',
},
};
assert.equal(isAcpResourceNotFoundError(cursorError), true);
});
test("isAcpQueryClosedBeforeResponseError matches typed ACP payload", () => {
const error = {
code: -32603,
message: "Internal error",
data: {
details: "Query closed before response received",
},
};
assert.equal(isAcpQueryClosedBeforeResponseError(error), true);
});
test("isAcpQueryClosedBeforeResponseError ignores unrelated ACP errors", () => {
const error = {
code: -32603,
message: "Internal error",
data: {
details: "other detail",
},
};
assert.equal(isAcpQueryClosedBeforeResponseError(error), false);
});
test("normalizeOutputError preserves queue metadata from typed queue errors", () => {
const error = new QueueConnectionError("Queue denied control request", {
outputCode: "PERMISSION_DENIED",
detailCode: "QUEUE_CONTROL_REQUEST_FAILED",
origin: "queue",
retryable: false,
});
const normalized = normalizeOutputError(error);
assert.equal(normalized.code, "PERMISSION_DENIED");
assert.equal(normalized.detailCode, "QUEUE_CONTROL_REQUEST_FAILED");
assert.equal(normalized.origin, "queue");
assert.equal(normalized.retryable, false);
});
test("normalizeOutputError maps AuthPolicyError to AUTH_REQUIRED detail", () => {
const normalized = normalizeOutputError(
new AuthPolicyError("missing credentials for auth method token"),
);
assert.equal(normalized.code, "RUNTIME");
assert.equal(normalized.detailCode, "AUTH_REQUIRED");
assert.equal(normalized.origin, "acp");
});
test("normalizeOutputError infers AUTH_REQUIRED detail from ACP payload", () => {
const normalized = normalizeOutputError({
error: {
code: -32000,
message: "Authentication required",
data: {
methodId: "token",
},
},
});
assert.equal(normalized.code, "RUNTIME");
assert.equal(normalized.detailCode, "AUTH_REQUIRED");
assert.equal(normalized.acp?.code, -32000);
});
test("normalizeOutputError extracts ACP payload from wrapped errors", () => {
const wrapped = new Error("Agent rejected session/set_mode");
(
wrapped as Error & {
acp?: { code: number; message: string; data?: unknown };
}
).acp = {
code: -32602,
message: "Invalid params",
data: {
method: "session/set_mode",
modeId: "plan",
},
};
const normalized = normalizeOutputError(wrapped);
assert.equal(normalized.code, "RUNTIME");
assert.equal(normalized.acp?.code, -32602);
assert.deepEqual(normalized.acp?.data, {
method: "session/set_mode",
modeId: "plan",
});
});
test("exitCodeForOutputErrorCode maps machine codes to stable exits", () => {
assert.equal(exitCodeForOutputErrorCode("USAGE"), 2);
assert.equal(exitCodeForOutputErrorCode("TIMEOUT"), 3);
assert.equal(exitCodeForOutputErrorCode("NO_SESSION"), 4);
assert.equal(exitCodeForOutputErrorCode("PERMISSION_DENIED"), 5);
assert.equal(exitCodeForOutputErrorCode("PERMISSION_PROMPT_UNAVAILABLE"), 5);
assert.equal(exitCodeForOutputErrorCode("RUNTIME"), 1);
});