214 lines
7.5 KiB
TypeScript
214 lines
7.5 KiB
TypeScript
import { describe, expect, test } from "bun:test"
|
|
|
|
import {
|
|
attributeChatToolError,
|
|
reconnectActionFromChatToolResult,
|
|
} from "../src/components/tools/error-attribution"
|
|
import { normalizeErrorText } from "../src/lib/error-text"
|
|
|
|
function reconnectStatus(connectionId = "emc_knowledge", connectionName = "Knowledge Hub") {
|
|
return {
|
|
version: 1,
|
|
kind: "connection_action",
|
|
source: "openwork-cloud",
|
|
connectionId,
|
|
connectionName,
|
|
authType: "oauth",
|
|
credentialMode: "per_member",
|
|
state: "reauth_required",
|
|
actor: "member",
|
|
action: {
|
|
type: "reconnect",
|
|
surface: "openwork_your_connections",
|
|
retry: "search_capabilities",
|
|
label: "Reconnect in Your Connections",
|
|
},
|
|
}
|
|
}
|
|
|
|
describe("chat tool error attribution", () => {
|
|
test("identifies an OpenWork-created capability deadline", () => {
|
|
expect(attributeChatToolError("The capability call exceeded 180s. Retry once.")).toEqual({
|
|
label: "OpenWork timeout",
|
|
confidence: "Confirmed",
|
|
description: "OpenWork created this deadline. The external operation may still have completed, so verify its state before retrying.",
|
|
})
|
|
})
|
|
|
|
test("identifies a structured OpenWork lifecycle deadline", () => {
|
|
expect(attributeChatToolError(JSON.stringify({
|
|
error: "connection_failed",
|
|
diagnostic: {
|
|
code: "MCP_LIFECYCLE_DEADLINE",
|
|
category: "lifecycle_deadline",
|
|
phase: "MCP_TOOL_EXECUTION",
|
|
},
|
|
}))).toMatchObject({
|
|
label: "OpenWork timeout",
|
|
confidence: "Confirmed",
|
|
})
|
|
})
|
|
|
|
test("identifies an OpenWork block before send", () => {
|
|
expect(attributeChatToolError(JSON.stringify({
|
|
diagnostic: { code: "MCP_URL_BLOCKED", category: "security_blocked" },
|
|
}))).toMatchObject({
|
|
label: "Blocked by OpenWork",
|
|
confidence: "Confirmed",
|
|
})
|
|
})
|
|
|
|
test("identifies a remote MCP HTTP failure", () => {
|
|
expect(attributeChatToolError(`MCP error: ${JSON.stringify({
|
|
diagnostic: { code: "MCP_HTTP_504", httpStatus: 504 },
|
|
})} (tool execution failed)`)).toMatchObject({
|
|
label: "Remote MCP · HTTP 504",
|
|
confidence: "Confirmed",
|
|
})
|
|
})
|
|
|
|
test("identifies a provider failure returned through the remote MCP", () => {
|
|
expect(attributeChatToolError(JSON.stringify({
|
|
diagnostic: { phase: "PROVIDER_AUTHORIZATION", providerStatus: 403 },
|
|
}))).toMatchObject({
|
|
label: "Provider error",
|
|
confidence: "Confirmed",
|
|
description: "The remote MCP responded, but the downstream provider returned status 403.",
|
|
})
|
|
})
|
|
|
|
test("identifies provider attribution from a deploy-skew category and code", () => {
|
|
expect(attributeChatToolError(JSON.stringify({
|
|
diagnostic: { category: "provider_policy_denied", providerCode: "access_denied" },
|
|
}))).toMatchObject({
|
|
label: "Provider error",
|
|
confidence: "Confirmed",
|
|
})
|
|
})
|
|
|
|
test("does not claim ownership for an unstructured timeout", () => {
|
|
expect(attributeChatToolError("Tool request timed out while waiting for a response.")).toEqual({
|
|
label: "Timeout · source unclear",
|
|
confidence: "Inferred",
|
|
description: "A timeout was reported, but the client did not receive structured evidence identifying which boundary created it.",
|
|
})
|
|
})
|
|
|
|
test("does not add attribution without useful evidence", () => {
|
|
expect(attributeChatToolError("The tool failed.")).toBeNull()
|
|
})
|
|
|
|
test("bails out quickly on a pathological HTML page", () => {
|
|
const htmlError = `<!DOCTYPE html><html><head><title>502 Bad Gateway</title></head><body>${"x".repeat(1_024 * 1_024)}</body></html>`
|
|
const started = performance.now()
|
|
|
|
expect(attributeChatToolError(htmlError)).toBeNull()
|
|
expect(performance.now() - started).toBeLessThan(1_000)
|
|
})
|
|
|
|
test("keeps JSON-head attribution after surrounding error text is clamped", () => {
|
|
const diagnostic = JSON.stringify({
|
|
error: "connection_failed",
|
|
diagnostic: { code: "MCP_HTTP_504", httpStatus: 504 },
|
|
})
|
|
const unclamped = `MCP error: ${diagnostic} (tool execution failed)`
|
|
const clamped = normalizeErrorText(`${unclamped}\n${"provider detail ".repeat(1_000)}`, { cap: 512 }).display
|
|
|
|
expect(clamped).toContain(diagnostic)
|
|
expect(attributeChatToolError(clamped)).toEqual(attributeChatToolError(unclamped))
|
|
})
|
|
|
|
test("extracts a trusted reconnect action from a Cloud capability failure", () => {
|
|
const errorText = JSON.stringify({
|
|
error: "connection_failed",
|
|
connectionStatus: reconnectStatus(),
|
|
})
|
|
|
|
expect(reconnectActionFromChatToolResult("openwork-cloud_execute_capability", errorText)).toEqual({
|
|
connectionId: "emc_knowledge",
|
|
connectionName: "Knowledge Hub",
|
|
label: "Reconnect",
|
|
})
|
|
})
|
|
|
|
test("extracts the same reconnect action when live capability discovery detects expired credentials", () => {
|
|
const output = JSON.stringify({
|
|
matches: [{
|
|
kind: "connection_status",
|
|
connectionStatus: reconnectStatus(),
|
|
}],
|
|
})
|
|
|
|
expect(reconnectActionFromChatToolResult("openwork-cloud_search_capabilities", output)).toEqual({
|
|
connectionId: "emc_knowledge",
|
|
connectionName: "Knowledge Hub",
|
|
label: "Reconnect",
|
|
})
|
|
})
|
|
|
|
test("derives reconnect copy instead of rendering action labels from tool output", () => {
|
|
const errorText = JSON.stringify({
|
|
connectionStatus: {
|
|
...reconnectStatus(),
|
|
action: { ...reconnectStatus().action, label: "Open an injected link" },
|
|
},
|
|
})
|
|
|
|
expect(reconnectActionFromChatToolResult("openwork-cloud_execute_capability", errorText)).toEqual({
|
|
connectionId: "emc_knowledge",
|
|
connectionName: "Knowledge Hub",
|
|
label: "Reconnect",
|
|
})
|
|
})
|
|
|
|
test("does not create actions from arbitrary MCP tools or non-reconnect failures", () => {
|
|
const reconnectPayload = JSON.stringify({
|
|
connectionStatus: reconnectStatus(),
|
|
})
|
|
const providerPayload = JSON.stringify({
|
|
connectionStatus: {
|
|
...reconnectStatus(),
|
|
state: "provider_error",
|
|
actor: "organization_admin",
|
|
action: {
|
|
type: "inspect_connection",
|
|
surface: "openwork_organization_connections",
|
|
retry: "search_capabilities",
|
|
},
|
|
},
|
|
})
|
|
|
|
expect(reconnectActionFromChatToolResult("malicious_execute_capability", reconnectPayload)).toBeNull()
|
|
expect(reconnectActionFromChatToolResult("openwork-cloud_execute_capability", providerPayload)).toBeNull()
|
|
})
|
|
|
|
test("does not guess between multiple reconnect targets in one discovery result", () => {
|
|
const output = {
|
|
matches: ["first", "second"].map((suffix) => ({
|
|
kind: "connection_status",
|
|
connectionStatus: reconnectStatus(`emc_${suffix}`, `Knowledge ${suffix}`),
|
|
})),
|
|
}
|
|
|
|
expect(reconnectActionFromChatToolResult("openwork-cloud_search_capabilities", output)).toBeNull()
|
|
})
|
|
|
|
test("rejects unversioned, shared, and admin-owned action shapes", () => {
|
|
const legacy = reconnectStatus()
|
|
const { version: _version, kind: _kind, source: _source, ...unversioned } = legacy
|
|
const shared = {
|
|
...legacy,
|
|
credentialMode: "shared",
|
|
actor: "organization_admin",
|
|
action: {
|
|
type: "reconnect",
|
|
surface: "openwork_organization_connections",
|
|
retry: "search_capabilities",
|
|
},
|
|
}
|
|
|
|
expect(reconnectActionFromChatToolResult("openwork-cloud_execute_capability", { connectionStatus: unversioned })).toBeNull()
|
|
expect(reconnectActionFromChatToolResult("openwork-cloud_execute_capability", { connectionStatus: shared })).toBeNull()
|
|
})
|
|
})
|