Files
crank/crates/crank-community-mcp/tests/unit/tool_error.rs
T

147 lines
5.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crank_community_mcp::tool_error::{
tool_error_contract_from_failure, tool_error_contract_from_runtime,
};
use crank_core::{
CorrelationContext, ExecutionErrorCode, ExecutionFailure, ExecutionLocale,
ProtocolAdapterError, Retryability,
};
use crank_runtime::RuntimeError;
#[test]
fn maps_upstream_429_to_recoverable_structured_tool_error() {
let contract = tool_error_contract_from_runtime(
&RuntimeError::ProtocolAdapter(ProtocolAdapterError::UnexpectedStatus {
status: 429,
dispatch: crank_core::DispatchEvidence::MayHaveDispatched,
}),
"req-429",
"0af7651916cd43dd8448eb211c80319c",
ExecutionLocale::Ru,
);
assert_eq!(contract.error_code, "upstream_rate_limited");
assert_eq!(contract.stage, "upstream");
assert_eq!(contract.retryability, "after_delay");
assert_eq!(contract.outcome_certainty, "certain");
assert!(contract.recoverable);
assert_eq!(contract.upstream_status, Some(429));
assert_eq!(contract.request_id, "req-429");
assert_eq!(contract.trace_id, "0af7651916cd43dd8448eb211c80319c");
assert_eq!(contract.suggested_action, Some("Повторите запрос позже."));
assert!(!contract.message.contains("internal_trace"));
}
#[test]
fn maps_upstream_429_to_english_suggested_action() {
let contract = tool_error_contract_from_runtime(
&RuntimeError::ProtocolAdapter(ProtocolAdapterError::UnexpectedStatus {
status: 429,
dispatch: crank_core::DispatchEvidence::MayHaveDispatched,
}),
"req-429",
"0af7651916cd43dd8448eb211c80319c",
ExecutionLocale::En,
);
assert_eq!(contract.error_code, "upstream_rate_limited");
assert_eq!(contract.suggested_action, Some("Retry the request later."));
}
#[test]
fn maps_mapping_error_to_non_recoverable_structured_tool_error() {
let contract = tool_error_contract_from_runtime(
&RuntimeError::InvalidPreparedRequest {
field: "query.base".to_owned(),
reason: "expected string".to_owned(),
},
"req-map",
"0af7651916cd43dd8448eb211c80319c",
ExecutionLocale::Ru,
);
assert_eq!(contract.error_code, "prepared_request_invalid");
assert_eq!(contract.stage, "request_preparation");
assert_eq!(contract.retryability, "never");
assert!(!contract.recoverable);
assert_eq!(contract.upstream_status, None);
assert_eq!(contract.request_id, "req-map");
assert_eq!(contract.trace_id, "0af7651916cd43dd8448eb211c80319c");
assert_eq!(contract.suggested_action, None);
}
#[test]
fn ambiguous_timeout_requires_manual_reconciliation() {
let contract = tool_error_contract_from_runtime(
&RuntimeError::ExecutionDeadlineElapsed {
may_have_dispatched: true,
},
"req-timeout",
"0af7651916cd43dd8448eb211c80319c",
ExecutionLocale::Ru,
);
assert_eq!(contract.error_code, "upstream_timeout");
assert_eq!(contract.retryability, "manual_reconcile");
assert_eq!(contract.outcome_certainty, "outcome_unknown");
assert!(!contract.recoverable);
}
#[test]
fn every_execution_code_projects_the_same_canonical_semantics_as_admin() {
for code in ExecutionErrorCode::ALL {
let contract = tool_error_contract_from_failure(
&ExecutionFailure::new(code, CorrelationContext::generate()),
ExecutionLocale::En,
);
assert_eq!(contract.error_code, code.as_str());
assert_eq!(contract.stage, code.stage().as_str());
assert_eq!(contract.retryability, code.retryability().as_str());
assert_eq!(
contract.outcome_certainty,
code.outcome_certainty().as_str()
);
assert_eq!(
contract.recoverable,
matches!(
code.retryability(),
Retryability::Safe | Retryability::AfterDelay | Retryability::RequiresConfirmation
)
);
assert!(!contract.message.is_empty());
}
}
#[test]
fn confirmation_challenge_is_localized_for_mcp_clients() {
let failure = ExecutionFailure::new(
ExecutionErrorCode::ConfirmationRequired,
CorrelationContext::generate(),
)
.try_with_confirmation("ct_safe", 30_000)
.expect("valid confirmation challenge");
let en = tool_error_contract_from_failure(&failure, ExecutionLocale::En);
assert!(
en.message.contains(
"Repeat the call with _crank_confirmation_token=\"ct_safe\" within 30 seconds."
),
"{:?}",
en.message
);
assert_eq!(
en.suggested_action,
Some("Repeat the call with the provided confirmation token.")
);
let ru = tool_error_contract_from_failure(&failure, ExecutionLocale::Ru);
assert!(
ru.message
.contains("Повторите вызов с _crank_confirmation_token=\"ct_safe\""),
"{:?}",
ru.message
);
assert_eq!(
ru.suggested_action,
Some("Повторите вызов с указанным токеном подтверждения.")
);
}