use super::*;
use crate::client::QueueProvider;
use crate::message::{Message, QueueName, SessionId};
use crate::provider::{AwsSqsConfig, ProviderType, SessionSupport};
use bytes::Bytes;
use chrono::Duration;
fn create_test_provider_config(use_fifo: bool) -> AwsSqsConfig {
AwsSqsConfig {
region: "us-east-1".to_string(),
access_key_id: Some("AKIAIOSFODNN7EXAMPLE".to_string()),
secret_access_key: Some("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_string()),
use_fifo_queues: use_fifo,
}
}
fn create_test_message(body: &str) -> Message {
Message::new(Bytes::from(body.to_string()))
}
fn create_test_message_with_session(body: &str, session_id: SessionId) -> Message {
Message::new(Bytes::from(body.to_string())).with_session_id(session_id)
}
mod configuration_tests {
use super::*;
#[tokio::test]
async fn test_provider_creation_with_credentials() {
let config = create_test_provider_config(false);
let result = AwsSqsProvider::new(config).await;
assert!(
result.is_ok(),
"Provider creation should succeed with test credentials"
);
let provider = result.unwrap();
assert_eq!(provider.provider_type(), ProviderType::AwsSqs);
}
#[tokio::test]
async fn test_provider_creation_without_credentials() {
let config = AwsSqsConfig {
region: "us-east-1".to_string(),
access_key_id: None,
secret_access_key: None,
use_fifo_queues: false,
};
let result = AwsSqsProvider::new(config).await;
assert!(
result.is_ok(),
"Provider creation should succeed without credentials (IAM role)"
);
}
#[tokio::test]
async fn test_fifo_queue_configuration() {
let config = create_test_provider_config(true);
let result = AwsSqsProvider::new(config).await;
assert!(result.is_ok());
let provider = result.unwrap();
assert_eq!(provider.supports_sessions(), SessionSupport::Emulated);
}
#[tokio::test]
async fn test_provider_capabilities() {
let config = create_test_provider_config(false);
let provider = AwsSqsProvider::new(config).await.unwrap();
assert_eq!(provider.provider_type(), ProviderType::AwsSqs);
assert!(provider.supports_batching(), "AWS SQS supports batching");
assert_eq!(provider.max_batch_size(), 10, "AWS SQS max batch is 10");
assert_eq!(provider.supports_sessions(), SessionSupport::Emulated);
}
}
mod signature_tests {
use super::*;
#[tokio::test]
async fn test_signature_generation() {
let config = create_test_provider_config(false);
let provider = AwsSqsProvider::new(config).await.unwrap();
assert_eq!(provider.provider_type(), ProviderType::AwsSqs);
}
}
mod xml_parsing_tests {
use super::*;
#[tokio::test]
async fn test_parse_queue_url_response() {
let xml = r#"
<GetQueueUrlResponse>
<GetQueueUrlResult>
<QueueUrl>https://sqs.us-east-1.amazonaws.com/123456789012/test-queue</QueueUrl>
</GetQueueUrlResult>
</GetQueueUrlResponse>
"#;
let config = create_test_provider_config(false);
let provider = AwsSqsProvider::new(config).await.unwrap();
let result = provider.parse_queue_url_response(xml);
assert!(result.is_ok(), "QueueUrl parsing should succeed");
assert!(result.unwrap().contains("test-queue"));
}
#[tokio::test]
async fn test_parse_send_message_response() {
let xml = r#"
<SendMessageResponse>
<SendMessageResult>
<MessageId>5fea7756-0ea4-451a-a703-a558b933e274</MessageId>
</SendMessageResult>
</SendMessageResponse>
"#;
let config = create_test_provider_config(false);
let provider = AwsSqsProvider::new(config).await.unwrap();
let result = provider.parse_send_message_response(xml);
assert!(
result.is_ok(),
"SendMessage response parsing should succeed"
);
}
#[tokio::test]
async fn test_parse_error_response() {
let xml = r#"
<ErrorResponse>
<Error>
<Type>Sender</Type>
<Code>InvalidParameterValue</Code>
<Message>Invalid queue name</Message>
</Error>
</ErrorResponse>
"#;
let config = create_test_provider_config(false);
let provider = AwsSqsProvider::new(config).await.unwrap();
let result = provider.parse_error_response(xml, 400);
assert!(
matches!(result, AwsError::ServiceError(_)),
"Error response should be parsed as AwsError"
);
}
}
mod error_handling_tests {
use super::*;
#[test]
fn test_error_transient_classification() {
let network_error = AwsError::NetworkError("Connection timeout".to_string());
assert!(
network_error.is_transient(),
"Network errors should be transient"
);
let service_error = AwsError::ServiceError("Internal error".to_string());
assert!(
service_error.is_transient(),
"Service errors should be transient"
);
let auth_error = AwsError::Authentication("Invalid credentials".to_string());
assert!(
!auth_error.is_transient(),
"Auth errors should not be transient"
);
}
#[test]
fn test_error_to_queue_error_mapping() {
let aws_error = AwsError::InvalidReceipt("bad-handle".to_string());
let queue_error = aws_error.to_queue_error();
assert!(matches!(
queue_error,
crate::error::QueueError::InvalidReceipt { .. }
));
}
}
mod operation_tests {
use super::*;
#[tokio::test]
async fn test_send_message_with_test_credentials() {
let config = create_test_provider_config(false);
let provider = AwsSqsProvider::new(config).await.unwrap();
let queue = QueueName::new("test-queue".to_string()).unwrap();
let message = create_test_message("test body");
let result = provider.send_message(&queue, &message).await;
assert!(result.is_err(), "Should fail with test credentials");
}
#[tokio::test]
async fn test_receive_message_with_test_credentials() {
let config = create_test_provider_config(false);
let provider = AwsSqsProvider::new(config).await.unwrap();
let queue = QueueName::new("test-queue".to_string()).unwrap();
let result = provider.receive_message(&queue, Duration::seconds(1)).await;
assert!(result.is_err(), "Should fail with test credentials");
}
#[tokio::test]
async fn test_complete_message_invalid_receipt() {
let config = create_test_provider_config(false);
let provider = AwsSqsProvider::new(config).await.unwrap();
use crate::message::ReceiptHandle;
let receipt = ReceiptHandle::new(
"invalid".to_string(),
crate::message::Timestamp::now(),
ProviderType::AwsSqs,
);
let result = provider.complete_message(&receipt).await;
assert!(result.is_err(), "Should fail with invalid receipt format");
}
#[tokio::test]
async fn test_abandon_message_invalid_receipt() {
let config = create_test_provider_config(false);
let provider = AwsSqsProvider::new(config).await.unwrap();
use crate::message::ReceiptHandle;
let receipt = ReceiptHandle::new(
"invalid".to_string(),
crate::message::Timestamp::now(),
ProviderType::AwsSqs,
);
let result = provider.abandon_message(&receipt).await;
assert!(result.is_err(), "Should fail with invalid receipt format");
}
#[tokio::test]
async fn test_fifo_queue_session_support() {
let config = create_test_provider_config(true);
let provider = AwsSqsProvider::new(config).await.unwrap();
let queue = QueueName::new("test-queue-fifo".to_string()).unwrap();
let session_id = SessionId::new("session-1".to_string()).unwrap();
let result = provider
.create_session_client(&queue, Some(session_id))
.await;
assert!(
result.is_err(),
"Standard queue should reject session requests"
);
}
#[tokio::test]
async fn test_standard_queue_rejects_sessions() {
let config = create_test_provider_config(false);
let provider = AwsSqsProvider::new(config).await.unwrap();
let queue = QueueName::new("test-queue".to_string()).unwrap();
let session_id = SessionId::new("session-1".to_string()).unwrap();
let result = provider
.create_session_client(&queue, Some(session_id))
.await;
assert!(
result.is_err(),
"Standard queue should reject session requests"
);
}
}
mod receipt_handle_tests {
#[test]
fn test_receipt_handle_format() {
let handle = "test-queue|AQEBwJxS8...token...";
let parts: Vec<&str> = handle.split('|').collect();
assert_eq!(parts.len(), 2, "Receipt handle should have queue and token");
assert_eq!(parts[0], "test-queue");
}
}
mod fifo_tests {
use super::*;
#[test]
fn test_fifo_queue_detection() {
let fifo_name = "test-fifo";
let standard_name = "test-queue";
assert!(
!AwsSqsProvider::is_fifo_queue(&QueueName::new(fifo_name.to_string()).unwrap()),
"Queue name without .fifo suffix should not be detected as FIFO"
);
assert!(
!AwsSqsProvider::is_fifo_queue(&QueueName::new(standard_name.to_string()).unwrap()),
"Standard queue should not be detected as FIFO"
);
}
}
mod session_message_tests {
use super::*;
#[test]
fn test_session_message_carries_session_id() {
let session_id = SessionId::new("order-42".to_string()).unwrap();
let msg = create_test_message_with_session("payload", session_id.clone());
assert_eq!(
msg.session_id.as_ref(),
Some(&session_id),
"Message should carry the provided session ID"
);
}
#[test]
fn test_regular_message_has_no_session_id() {
let msg = create_test_message("payload");
assert!(
msg.session_id.is_none(),
"Regular message should have no session ID"
);
}
#[tokio::test]
async fn test_send_session_message_to_standard_queue_fails() {
let config = create_test_provider_config(false); let provider = AwsSqsProvider::new(config).await.unwrap();
let queue = QueueName::new("test-queue".to_string()).unwrap();
let session_id = SessionId::new("order-42".to_string()).unwrap();
let msg = create_test_message_with_session("payload", session_id);
let result = provider.send_message(&queue, &msg).await;
assert!(
result.is_err(),
"Sending a session message to a standard queue should fail"
);
}
}
mod security_tests {
use super::*;
#[test]
fn aws_config_debug_does_not_expose_secret_key() {
let config = AwsSqsConfig {
region: "us-east-1".to_string(),
access_key_id: Some("AKIAIOSFODNN7EXAMPLE".to_string()),
secret_access_key: Some("wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY".to_string()),
use_fifo_queues: false,
};
let debug_output = format!("{:?}", config);
assert!(
!debug_output.contains("wJalrXUtnFEMI"),
"Debug output must not contain the raw secret key value. \
Add a custom Debug impl that emits \"<REDACTED>\" for secret_access_key. \
Got: {}",
debug_output
);
}
}