use std::error::Error;
use std::io;
use swink_agent::{AgentError, DowncastError};
#[test]
fn display_messages() {
assert_eq!(
AgentError::context_overflow("claude-sonnet-4-6").to_string(),
"context window overflow for model: claude-sonnet-4-6"
);
assert_eq!(
AgentError::ModelThrottled.to_string(),
"model request throttled (rate limited)"
);
assert_eq!(
AgentError::network(io::Error::new(io::ErrorKind::ConnectionReset, "reset")).to_string(),
"network error"
);
assert_eq!(
AgentError::structured_output_failed(3, "schema mismatch").to_string(),
"structured output failed after 3 attempts: schema mismatch"
);
assert_eq!(
AgentError::AlreadyRunning.to_string(),
"agent is already running"
);
assert_eq!(
AgentError::NoMessages.to_string(),
"cannot continue with empty message history"
);
assert_eq!(
AgentError::InvalidContinue.to_string(),
"cannot continue when last message is an assistant message"
);
assert_eq!(
AgentError::stream(io::Error::new(io::ErrorKind::InvalidData, "bad data")).to_string(),
"stream error"
);
assert_eq!(
AgentError::Aborted.to_string(),
"operation aborted via cancellation token"
);
}
#[test]
fn implements_std_error() {
let err = AgentError::ModelThrottled;
let _: &dyn Error = &err;
}
#[test]
fn source_chain() {
let inner = io::Error::new(io::ErrorKind::ConnectionReset, "reset");
let err = AgentError::network(inner);
assert!(err.source().is_some());
let inner = io::Error::new(io::ErrorKind::InvalidData, "bad");
let err = AgentError::stream(inner);
assert!(err.source().is_some());
}
#[test]
fn retryable_classification() {
assert!(AgentError::ModelThrottled.is_retryable());
assert!(AgentError::network(io::Error::other("timeout")).is_retryable());
assert!(!AgentError::Aborted.is_retryable());
assert!(!AgentError::AlreadyRunning.is_retryable());
assert!(!AgentError::CacheMiss.is_retryable());
assert!(!AgentError::NoMessages.is_retryable());
assert!(!AgentError::InvalidContinue.is_retryable());
assert!(!AgentError::context_overflow("x").is_retryable());
assert!(!AgentError::structured_output_failed(1, "e").is_retryable());
assert!(!AgentError::stream(io::Error::other("x")).is_retryable());
}
#[test]
fn send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<AgentError>();
}
#[test]
fn all_variants_are_send_sync() {
fn assert_send_sync(_: &(impl Send + Sync)) {}
let variants: Vec<AgentError> = vec![
AgentError::context_overflow("model-x"),
AgentError::ModelThrottled,
AgentError::network(io::Error::other("net")),
AgentError::structured_output_failed(1, "fail"),
AgentError::AlreadyRunning,
AgentError::NoMessages,
AgentError::InvalidContinue,
AgentError::stream(io::Error::other("stream")),
AgentError::Aborted,
];
for v in &variants {
assert_send_sync(v);
}
}
#[test]
fn display_all_variants_non_empty() {
let variants: Vec<AgentError> = vec![
AgentError::context_overflow("model-x"),
AgentError::ModelThrottled,
AgentError::network(io::Error::other("net")),
AgentError::structured_output_failed(1, "fail"),
AgentError::AlreadyRunning,
AgentError::NoMessages,
AgentError::InvalidContinue,
AgentError::stream(io::Error::other("stream")),
AgentError::Aborted,
];
for v in &variants {
let display = v.to_string();
assert!(
!display.is_empty(),
"variant {v:?} has empty display string"
);
}
}
#[test]
fn network_error_preserves_source() {
let inner = io::Error::new(io::ErrorKind::ConnectionRefused, "connection refused");
let err = AgentError::network(inner);
let source = err.source().expect("NetworkError should have a source");
assert_eq!(source.to_string(), "connection refused");
}
#[test]
fn aborted_is_not_retryable() {
assert!(
!AgentError::Aborted.is_retryable(),
"Aborted should not be retryable"
);
}
#[test]
fn context_overflow_is_not_retryable() {
let err = AgentError::context_overflow("claude-sonnet-4-6");
assert!(
!err.is_retryable(),
"ContextWindowOverflow should not be retryable"
);
}
#[test]
fn named_constructors_produce_correct_variants() {
let err = AgentError::network(io::Error::other("test"));
assert!(matches!(err, AgentError::NetworkError { .. }));
let err = AgentError::stream(io::Error::other("test"));
assert!(matches!(err, AgentError::StreamError { .. }));
let err = AgentError::context_overflow("model-y");
assert!(matches!(
err,
AgentError::ContextWindowOverflow { ref model } if model == "model-y"
));
let err = AgentError::structured_output_failed(5, "bad schema");
assert!(matches!(
err,
AgentError::StructuredOutputFailed { attempts: 5, ref last_error }
if last_error == "bad schema"
));
}
#[test]
fn error_context_overflow_display() {
let err = AgentError::context_overflow("claude-sonnet-4-6");
let display = err.to_string();
assert!(
display.contains("claude-sonnet-4-6"),
"display should contain model name, got: {display}"
);
}
#[test]
fn error_structured_output_display() {
let err = AgentError::structured_output_failed(3, "schema mismatch");
let display = err.to_string();
assert!(
display.contains('3'),
"display should contain attempt count, got: {display}"
);
assert!(
display.contains("schema mismatch"),
"display should contain last_error, got: {display}"
);
}
#[test]
fn error_all_variants_implement_std_error() {
let variants: Vec<AgentError> = vec![
AgentError::context_overflow("model-x"),
AgentError::ModelThrottled,
AgentError::network(io::Error::other("net")),
AgentError::structured_output_failed(1, "fail"),
AgentError::AlreadyRunning,
AgentError::NoMessages,
AgentError::InvalidContinue,
AgentError::stream(io::Error::other("stream")),
AgentError::Aborted,
];
for v in &variants {
let _: &dyn Error = v;
}
}
#[test]
fn error_retryable_classification() {
assert!(AgentError::ModelThrottled.is_retryable());
assert!(AgentError::network(io::Error::other("x")).is_retryable());
assert!(!AgentError::CacheMiss.is_retryable());
assert!(!AgentError::context_overflow("m").is_retryable());
assert!(!AgentError::structured_output_failed(1, "e").is_retryable());
assert!(!AgentError::AlreadyRunning.is_retryable());
assert!(!AgentError::NoMessages.is_retryable());
assert!(!AgentError::InvalidContinue.is_retryable());
assert!(!AgentError::stream(io::Error::other("x")).is_retryable());
assert!(!AgentError::Aborted.is_retryable());
}
#[test]
fn error_stream_error_source_chain() {
let inner = io::Error::new(io::ErrorKind::InvalidData, "bad data");
let err = AgentError::stream(inner);
let source = err.source().expect("StreamError should have a source");
assert_eq!(source.to_string(), "bad data");
}
#[test]
fn downcast_error_display() {
let err = DowncastError {
expected: "MyCustomType",
actual: "OtherType".into(),
};
let display = err.to_string();
assert!(
display.contains("MyCustomType"),
"should contain expected type name, got: {display}"
);
assert!(
display.contains("OtherType"),
"should contain actual type name, got: {display}"
);
assert!(
display.contains("Downcast failed"),
"should contain 'Downcast failed', got: {display}"
);
let _: &dyn Error = &err;
}