import { describe, it, expect } from "vitest";
import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
import {
BirstErrorCode,
createMcpError,
mapHttpErrorToMcp,
extractErrorMessage,
} from "../../src/utils/errors.js";
describe("errors", () => {
describe("BirstErrorCode", () => {
it("should have correct error codes", () => {
expect(BirstErrorCode.AUTH_REQUIRED).toBe(-32001);
expect(BirstErrorCode.FORBIDDEN).toBe(-32002);
expect(BirstErrorCode.NOT_FOUND).toBe(-32003);
expect(BirstErrorCode.RATE_LIMITED).toBe(-32004);
expect(BirstErrorCode.BQL_SYNTAX_ERROR).toBe(-32005);
});
});
describe("createMcpError", () => {
it("should create an MCP error with code and message", () => {
const error = createMcpError(-32001, "Test error");
expect(error.code).toBe(-32001);
// MCP SDK formats message as "MCP error {code}: {message}"
expect(error.message).toContain("Test error");
});
it("should include data when provided", () => {
const data = { detail: "extra info" };
const error = createMcpError(-32001, "Test error", data);
expect(error.data).toEqual(data);
});
});
describe("mapHttpErrorToMcp", () => {
it("should map 400 to InvalidParams", () => {
const error = mapHttpErrorToMcp(400, "Invalid request");
expect(error.code).toBe(ErrorCode.InvalidParams);
expect(error.message).toContain("Bad request");
});
it("should map 401 to AUTH_REQUIRED", () => {
const error = mapHttpErrorToMcp(401, "Unauthorized");
expect(error.code).toBe(BirstErrorCode.AUTH_REQUIRED);
expect(error.message).toContain("Authentication required");
});
it("should map 403 to FORBIDDEN", () => {
const error = mapHttpErrorToMcp(403, "Access denied");
expect(error.code).toBe(BirstErrorCode.FORBIDDEN);
expect(error.message).toContain("Access forbidden");
});
it("should map 404 to NOT_FOUND", () => {
const error = mapHttpErrorToMcp(404, "Not found");
expect(error.code).toBe(BirstErrorCode.NOT_FOUND);
expect(error.message).toContain("Resource not found");
});
it("should map 409 to InvalidParams", () => {
const error = mapHttpErrorToMcp(409, "Conflict");
expect(error.code).toBe(ErrorCode.InvalidParams);
expect(error.message).toContain("Conflict");
});
it("should map 422 to BQL_SYNTAX_ERROR", () => {
const error = mapHttpErrorToMcp(422, "Validation failed");
expect(error.code).toBe(BirstErrorCode.BQL_SYNTAX_ERROR);
expect(error.message).toContain("Validation error");
});
it("should map 429 to RATE_LIMITED", () => {
const error = mapHttpErrorToMcp(429, "Too many requests");
expect(error.code).toBe(BirstErrorCode.RATE_LIMITED);
expect(error.message).toContain("Rate limited");
});
it("should map 500 to InternalError", () => {
const error = mapHttpErrorToMcp(500, "Server error");
expect(error.code).toBe(ErrorCode.InternalError);
expect(error.message).toContain("Server error");
});
it("should map 502 to InternalError", () => {
const error = mapHttpErrorToMcp(502, "Bad gateway");
expect(error.code).toBe(ErrorCode.InternalError);
expect(error.message).toContain("Server error");
});
it("should map 503 to InternalError", () => {
const error = mapHttpErrorToMcp(503, "Service unavailable");
expect(error.code).toBe(ErrorCode.InternalError);
expect(error.message).toContain("Server error");
});
it("should map unknown status to InternalError", () => {
const error = mapHttpErrorToMcp(418, "I'm a teapot");
expect(error.code).toBe(ErrorCode.InternalError);
expect(error.message).toContain("Unexpected error (418)");
});
it("should include body data in error", () => {
const body = { details: "extra info" };
const error = mapHttpErrorToMcp(400, "Bad request", body);
expect(error.data).toEqual(body);
});
});
describe("extractErrorMessage", () => {
it("should return string body as-is", () => {
expect(extractErrorMessage("Error message")).toBe("Error message");
});
it("should extract message field from object", () => {
expect(extractErrorMessage({ message: "Error message" })).toBe(
"Error message"
);
});
it("should extract error field from object", () => {
expect(extractErrorMessage({ error: "Error message" })).toBe(
"Error message"
);
});
it("should extract errorMessage field from object", () => {
expect(extractErrorMessage({ errorMessage: "Error message" })).toBe(
"Error message"
);
});
it("should extract detail field from object", () => {
expect(extractErrorMessage({ detail: "Error message" })).toBe(
"Error message"
);
});
it("should extract message from nested error object", () => {
expect(
extractErrorMessage({ error: { message: "Nested error" } })
).toBe("Nested error");
});
it("should return 'Unknown error' for null", () => {
expect(extractErrorMessage(null)).toBe("Unknown error");
});
it("should return 'Unknown error' for undefined", () => {
expect(extractErrorMessage(undefined)).toBe("Unknown error");
});
it("should return 'Unknown error' for empty object", () => {
expect(extractErrorMessage({})).toBe("Unknown error");
});
it("should prioritize message over other fields", () => {
expect(
extractErrorMessage({
message: "Primary",
error: "Secondary",
detail: "Tertiary",
})
).toBe("Primary");
});
});
});