use super::{BedrockProvider, LlmProvider, LlmRequest, map_sdk_error, validate_model_id};
use crate::core::sm::providers::SmLlmError;
#[tokio::test]
async fn bedrock_region_resolution() {
let explicit = BedrockProvider::new("us.anthropic.claude-haiku", Some("eu-west-1"))
.await
.expect("construct");
assert_eq!(explicit.region(), "eu-west-1");
if std::env::var("TRUSTY_AWS_REGION").is_err() && std::env::var("AWS_REGION").is_err() {
let empty = BedrockProvider::new("us.anthropic.claude-haiku", Some(""))
.await
.expect("construct");
assert_eq!(empty.region(), "us-east-1");
}
}
#[test]
fn bedrock_prefix_validation() {
for id in [
"us.anthropic.claude-sonnet-4-6",
"eu.anthropic.claude-sonnet-4-6",
"ap.anthropic.claude-haiku",
"jp.anthropic.claude-haiku",
"global.anthropic.claude-opus",
] {
assert!(validate_model_id(id).is_ok(), "{id} should validate");
}
let err = validate_model_id("anthropic.claude-sonnet-4-6").unwrap_err();
assert!(matches!(err, SmLlmError::Validation(_)));
assert!(err.is_alarm());
assert!(!err.is_retryable());
}
#[tokio::test]
async fn bedrock_provider_stores_model_and_region() {
let provider = BedrockProvider::new("us.anthropic.claude-haiku", Some("us-east-1"))
.await
.expect("construct");
assert_eq!(provider.name(), "bedrock");
assert_eq!(provider.region(), "us-east-1");
assert_eq!(provider.model, "us.anthropic.claude-haiku");
}
#[tokio::test]
async fn bedrock_new_rejects_bare_model_id() {
let err = BedrockProvider::new("anthropic.claude-sonnet-4-6", None)
.await
.expect_err("bare id must fail validation");
assert!(matches!(err, SmLlmError::Validation(_)));
}
#[tokio::test]
async fn bedrock_empty_messages_is_validation_error() {
let provider = BedrockProvider::new("us.anthropic.claude-sonnet-4-6", Some("us-east-1"))
.await
.expect("construct");
let req = LlmRequest {
model: "us.anthropic.claude-sonnet-4-6".to_string(),
system: "sys".to_string(),
messages: vec![],
temperature: 0.3,
max_tokens: 256,
};
let err = provider.complete(req).await.expect_err("empty must fail");
assert!(matches!(err, SmLlmError::Validation(_)));
}
#[test]
fn map_sdk_error_classifies_aws_markers() {
let model = "us.anthropic.claude-sonnet-4-6";
let region = "us-east-1";
let wrap = |ctx: &str| {
format!(
"inference provider error: Converse call failed (model={model}, region={region}): {ctx}"
)
};
for ctx in [
"AccessDeniedException: User is not authorized to perform bedrock:InvokeModel",
"NoCredentialsError: no credentials in the property bag",
"dispatch failure: could not load credentials",
] {
let err = map_sdk_error(wrap(ctx), model, region);
assert!(
matches!(err, SmLlmError::AccessDenied(_)),
"{ctx} => {err:?}"
);
assert!(err.is_alarm() && !err.is_retryable());
}
let err = map_sdk_error(
wrap("ResourceNotFoundException: model not available"),
model,
region,
);
assert!(matches!(err, SmLlmError::ModelNotFound(_)));
assert!(err.is_alarm());
let err = map_sdk_error(
wrap("ThrottlingException: too many requests"),
model,
region,
);
assert!(matches!(err, SmLlmError::RateLimited));
assert!(err.is_retryable());
let err = map_sdk_error(wrap("ServiceUnavailableException"), model, region);
assert!(matches!(err, SmLlmError::Upstream { status: 503, .. }));
assert!(err.is_retryable());
let err = map_sdk_error(
wrap("ValidationException: malformed request"),
model,
region,
);
assert!(matches!(err, SmLlmError::Validation(_)));
assert!(err.is_alarm());
let err = map_sdk_error(wrap("connection reset by peer"), model, region);
assert!(matches!(err, SmLlmError::Transport(_)));
assert!(err.is_retryable());
}