mirror of
https://github.com/Kilo-Org/kilocode.git
synced 2026-08-28 19:11:03 +08:00
168 lines
6.0 KiB
TypeScript
168 lines
6.0 KiB
TypeScript
import { describe, it, expect } from "bun:test"
|
|
import type { AssistantMessage } from "@kilocode/sdk/v2"
|
|
import {
|
|
unwrapError,
|
|
parseAssistantError,
|
|
parseProviderAuthError,
|
|
isUnauthorizedPaidModelError,
|
|
isUnauthorizedPromotionLimitError,
|
|
} from "../../webview-ui/src/utils/errorUtils"
|
|
|
|
type AssistantError = AssistantMessage["error"]
|
|
|
|
describe("unwrapError", () => {
|
|
it("returns plain string messages unchanged", () => {
|
|
expect(unwrapError("something went wrong")).toBe("something went wrong")
|
|
})
|
|
|
|
it("extracts message from JSON error object with error.message", () => {
|
|
const input = JSON.stringify({ error: { message: "rate limit exceeded" } })
|
|
expect(unwrapError(input)).toBe("rate limit exceeded")
|
|
})
|
|
|
|
it("returns original message for malformed JSON", () => {
|
|
expect(unwrapError("{not valid json")).toBe("{not valid json")
|
|
})
|
|
|
|
it("strips leading 'Error: ' prefix before parsing", () => {
|
|
const json = JSON.stringify({ message: "connection refused" })
|
|
expect(unwrapError(`Error: ${json}`)).toBe("connection refused")
|
|
})
|
|
|
|
it("formats empty provider rate-limit errors", () => {
|
|
const body = {
|
|
type: "error",
|
|
sequence_number: 2,
|
|
error: { type: "tokens", code: "rate_limit_exceeded", message: "", param: null },
|
|
}
|
|
const input = JSON.stringify({ message: JSON.stringify(body) })
|
|
|
|
expect(unwrapError(input)).toBe("Provider rate limit exceeded. Please try again shortly.")
|
|
})
|
|
|
|
it("preserves provider details when a rate-limit message is present", () => {
|
|
const input = JSON.stringify({
|
|
type: "error",
|
|
error: { type: "tokens", code: "rate_limit_exceeded", message: "Try again in 30 seconds." },
|
|
})
|
|
|
|
expect(unwrapError(input)).toBe("tokens: Try again in 30 seconds.")
|
|
})
|
|
})
|
|
|
|
describe("parseAssistantError", () => {
|
|
it("returns null for null input", () => {
|
|
expect(parseAssistantError(null)).toBeNull()
|
|
})
|
|
|
|
it("returns null for undefined input", () => {
|
|
expect(parseAssistantError(undefined)).toBeNull()
|
|
})
|
|
|
|
it("returns null for non-APIError (e.g. MessageAbortedError)", () => {
|
|
const error: AssistantError = { name: "MessageAbortedError", data: { message: "aborted" } }
|
|
expect(parseAssistantError(error)).toBeNull()
|
|
})
|
|
|
|
it("returns null when APIError has no data", () => {
|
|
// Simulate a malformed error where data is missing at runtime
|
|
const error = { name: "APIError" } as unknown as AssistantError
|
|
expect(parseAssistantError(error)).toBeNull()
|
|
})
|
|
|
|
it("extracts statusCode and message from APIError data", () => {
|
|
const error: AssistantError = {
|
|
name: "APIError",
|
|
data: { statusCode: 401, message: "Unauthorized", isRetryable: false },
|
|
}
|
|
const result = parseAssistantError(error)
|
|
expect(result).toEqual({ statusCode: 401, code: undefined, message: "Unauthorized" })
|
|
})
|
|
|
|
it("extracts code from responseBody JSON with error.code", () => {
|
|
const responseBody = JSON.stringify({ error: { code: "PAID_MODEL_AUTH_REQUIRED" } })
|
|
const error: AssistantError = {
|
|
name: "APIError",
|
|
data: { statusCode: 401, message: "Unauthorized", isRetryable: false, responseBody },
|
|
}
|
|
const result = parseAssistantError(error)
|
|
expect(result).toEqual({ statusCode: 401, code: "PAID_MODEL_AUTH_REQUIRED", message: "Unauthorized" })
|
|
})
|
|
|
|
it("extracts code from responseBody JSON with top-level code", () => {
|
|
const responseBody = JSON.stringify({ code: "PROMOTION_MODEL_LIMIT_REACHED" })
|
|
const error: AssistantError = {
|
|
name: "APIError",
|
|
data: { statusCode: 429, message: "Too Many Requests", isRetryable: false, responseBody },
|
|
}
|
|
const result = parseAssistantError(error)
|
|
expect(result).toEqual({ statusCode: 429, code: "PROMOTION_MODEL_LIMIT_REACHED", message: "Too Many Requests" })
|
|
})
|
|
|
|
it("handles invalid responseBody JSON gracefully", () => {
|
|
const error: AssistantError = {
|
|
name: "APIError",
|
|
data: { statusCode: 500, message: "Server Error", isRetryable: false, responseBody: "not json" },
|
|
}
|
|
const result = parseAssistantError(error)
|
|
expect(result).toEqual({ statusCode: 500, code: undefined, message: "Server Error" })
|
|
})
|
|
|
|
it("handles missing responseBody", () => {
|
|
const error: AssistantError = {
|
|
name: "APIError",
|
|
data: { statusCode: 403, message: "Forbidden", isRetryable: false },
|
|
}
|
|
const result = parseAssistantError(error)
|
|
expect(result).toEqual({ statusCode: 403, code: undefined, message: "Forbidden" })
|
|
})
|
|
})
|
|
|
|
describe("parseProviderAuthError", () => {
|
|
it("extracts provider auth errors", () => {
|
|
const error: AssistantError = {
|
|
name: "ProviderAuthError",
|
|
data: { providerID: "openai", message: "Sign in again" },
|
|
}
|
|
|
|
expect(parseProviderAuthError(error)).toEqual({ providerID: "openai", message: "Sign in again" })
|
|
})
|
|
|
|
it("returns null for non-provider-auth errors", () => {
|
|
const error: AssistantError = {
|
|
name: "APIError",
|
|
data: { statusCode: 401, message: "Unauthorized", isRetryable: false },
|
|
}
|
|
|
|
expect(parseProviderAuthError(error)).toBeNull()
|
|
})
|
|
})
|
|
|
|
describe("isUnauthorizedPaidModelError", () => {
|
|
it("returns true for 401 + PAID_MODEL_AUTH_REQUIRED", () => {
|
|
expect(isUnauthorizedPaidModelError({ statusCode: 401, code: "PAID_MODEL_AUTH_REQUIRED" })).toBe(true)
|
|
})
|
|
|
|
it("returns false for 401 + different code", () => {
|
|
expect(isUnauthorizedPaidModelError({ statusCode: 401, code: "SOMETHING_ELSE" })).toBe(false)
|
|
})
|
|
|
|
it("returns false for null input", () => {
|
|
expect(isUnauthorizedPaidModelError(null)).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe("isUnauthorizedPromotionLimitError", () => {
|
|
it("returns true for 401 + PROMOTION_MODEL_LIMIT_REACHED", () => {
|
|
expect(isUnauthorizedPromotionLimitError({ statusCode: 401, code: "PROMOTION_MODEL_LIMIT_REACHED" })).toBe(true)
|
|
})
|
|
|
|
it("returns true for 429 + PROMOTION_MODEL_LIMIT_REACHED", () => {
|
|
expect(isUnauthorizedPromotionLimitError({ statusCode: 429, code: "PROMOTION_MODEL_LIMIT_REACHED" })).toBe(true)
|
|
})
|
|
|
|
it("returns false for null input", () => {
|
|
expect(isUnauthorizedPromotionLimitError(null)).toBe(false)
|
|
})
|
|
})
|