Skip to main content
Glama
tests.rs11 kB
use super::*; fn test_cache_config(key_prefix: Option<String>) -> S3CacheConfig { let base_config = ObjectStorageConfig { endpoint: "http://localhost:9200".to_string(), bucket_prefix: "test-bucket".to_string(), bucket_suffix: None, region: "us-east-1".to_string(), auth: S3AuthConfig::StaticCredentials { access_key: "key".into(), secret_key: "secret".into(), }, key_prefix, read_retry: S3ReadRetryConfig::default(), num_workers: default_num_workers(), max_parallel_per_worker: default_max_parallel_per_worker(), }; base_config.for_cache("test-cache") } #[tokio::test] async fn test_transform_and_prefix_key_with_test_prefix_passthrough() { use tempfile::TempDir; let config = test_cache_config(Some("test-uuid-1234".to_string())); let temp_dir = TempDir::new().expect("Failed to create temp dir"); let layer = S3Layer::new( config, "test-cache", KeyTransformStrategy::Passthrough, default_num_workers(), default_max_parallel_per_worker(), S3ReadRetryConfig::default(), temp_dir.path(), ) .await .expect("Failed to create S3Layer"); // Test with 6+ character key let result = layer.transform_and_prefix_key("abc123def456"); assert_eq!(result, "test-uuid-1234/ab/c1/23/abc123def456"); // Test with 4-5 character key let result = layer.transform_and_prefix_key("abcd"); assert_eq!(result, "test-uuid-1234/ab/cd/abcd"); // Test with 2-3 character key let result = layer.transform_and_prefix_key("ab"); assert_eq!(result, "test-uuid-1234/ab/ab"); } #[tokio::test] async fn test_transform_and_prefix_key_without_test_prefix_passthrough() { use tempfile::TempDir; let config = test_cache_config(None); let temp_dir = TempDir::new().expect("Failed to create temp dir"); let layer = S3Layer::new( config, "test-cache", KeyTransformStrategy::Passthrough, default_num_workers(), default_max_parallel_per_worker(), S3ReadRetryConfig::default(), temp_dir.path(), ) .await .expect("Failed to create S3Layer"); let result = layer.transform_and_prefix_key("abc123def456"); assert_eq!(result, "ab/c1/23/abc123def456"); } #[tokio::test] async fn test_transform_and_prefix_key_with_test_prefix_reverse() { use tempfile::TempDir; let config = test_cache_config(Some("test-uuid-5678".to_string())); let temp_dir = TempDir::new().expect("Failed to create temp dir"); let layer = S3Layer::new( config, "test-cache", KeyTransformStrategy::ReverseKey, default_num_workers(), default_max_parallel_per_worker(), S3ReadRetryConfig::default(), temp_dir.path(), ) .await .expect("Failed to create S3Layer"); // "abc123" reversed is "321cba" let result = layer.transform_and_prefix_key("abc123"); assert_eq!(result, "test-uuid-5678/32/1c/ba/321cba"); } #[tokio::test] async fn test_transform_and_prefix_key_without_test_prefix_reverse() { use tempfile::TempDir; let config = test_cache_config(None); let temp_dir = TempDir::new().expect("Failed to create temp dir"); let layer = S3Layer::new( config, "test-cache", KeyTransformStrategy::ReverseKey, default_num_workers(), default_max_parallel_per_worker(), S3ReadRetryConfig::default(), temp_dir.path(), ) .await .expect("Failed to create S3Layer"); // "abc123" reversed is "321cba" let result = layer.transform_and_prefix_key("abc123"); assert_eq!(result, "32/1c/ba/321cba"); } #[test] fn test_bucket_suffix_in_final_bucket_name() { let base_config = ObjectStorageConfig { endpoint: "http://localhost:9200".to_string(), bucket_prefix: "si-layer-cache".to_string(), bucket_suffix: Some("production".to_string()), region: "us-east-1".to_string(), auth: S3AuthConfig::StaticCredentials { access_key: "key".into(), secret_key: "secret".into(), }, key_prefix: None, read_retry: S3ReadRetryConfig::default(), num_workers: default_num_workers(), max_parallel_per_worker: default_max_parallel_per_worker(), }; let cache_config = base_config.for_cache("cas_objects"); assert_eq!( cache_config.bucket_name, "si-layer-cache-cas-objects-production" ); // Verify underscores are normalized to hyphens let cache_config2 = base_config.for_cache("some_cache"); assert_eq!( cache_config2.bucket_name, "si-layer-cache-some-cache-production" ); // Test without suffix let base_config_no_suffix = ObjectStorageConfig { bucket_suffix: None, ..base_config }; let cache_config3 = base_config_no_suffix.for_cache("cas_objects"); assert_eq!(cache_config3.bucket_name, "si-layer-cache-cas-objects"); } #[tokio::test] async fn test_iam_auth_config_construction() { let config = ObjectStorageConfig { endpoint: "https://s3.us-west-2.amazonaws.com".to_string(), bucket_prefix: "si-layer-cache".to_string(), bucket_suffix: Some("production".to_string()), region: "us-west-2".to_string(), auth: S3AuthConfig::IamRole, key_prefix: None, read_retry: S3ReadRetryConfig::default(), num_workers: default_num_workers(), max_parallel_per_worker: default_max_parallel_per_worker(), }; let cache_config = config.for_cache("test-cache"); // Verify IAM auth is carried through assert!(matches!(cache_config.auth, S3AuthConfig::IamRole)); assert_eq!( cache_config.bucket_name, "si-layer-cache-test-cache-production" ); // Note: We can't easily test actual IAM credential resolution without AWS credentials // That would be an integration test requiring AWS setup use tempfile::TempDir; let temp_dir = TempDir::new().expect("Failed to create temp dir"); let layer = S3Layer::new( cache_config, "test-cache", KeyTransformStrategy::Passthrough, default_num_workers(), default_max_parallel_per_worker(), S3ReadRetryConfig::default(), temp_dir.path(), ) .await; match layer { Ok(_) => { /* success - we have AWS credentials available */ } Err(e) => { // Expected failure in test environment without AWS credentials eprintln!("Expected error without AWS credentials: {e:?}"); } } } mod s3_error_classification_tests { use super::*; // Test the convenience predicates on S3ErrorKind #[test] fn test_error_kind_predicates() { let throttle = S3ErrorKind::Throttle; assert!(throttle.is_throttle()); assert!(!throttle.is_serialization()); assert!(!throttle.is_transient()); let serialization = S3ErrorKind::Serialization; assert!(!serialization.is_throttle()); assert!(serialization.is_serialization()); assert!(!serialization.is_transient()); let transient = S3ErrorKind::Transient; assert!(!transient.is_throttle()); assert!(!transient.is_serialization()); assert!(transient.is_transient()); } // Test that Hash derivation works for S3ErrorKind #[test] fn test_error_kind_hash() { use std::collections::HashSet; let mut set = HashSet::new(); set.insert(S3ErrorKind::Throttle); set.insert(S3ErrorKind::Serialization); set.insert(S3ErrorKind::Transient); assert_eq!(set.len(), 3); assert!(set.contains(&S3ErrorKind::Throttle)); assert!(set.contains(&S3ErrorKind::Serialization)); assert!(set.contains(&S3ErrorKind::Transient)); } // Test that S3ErrorKind can be cloned and compared #[test] fn test_error_kind_clone_and_eq() { let original = S3ErrorKind::Throttle; let cloned = original; assert_eq!(original, cloned); let different = S3ErrorKind::Transient; assert_ne!(original, different); } // Test that S3ErrorKind can be used in match expressions #[test] fn test_error_kind_match() { fn describe_error(kind: S3ErrorKind) -> &'static str { match kind { S3ErrorKind::Throttle => "throttle", S3ErrorKind::Serialization => "serialization", S3ErrorKind::Transient => "transient", } } assert_eq!(describe_error(S3ErrorKind::Throttle), "throttle"); assert_eq!(describe_error(S3ErrorKind::Serialization), "serialization"); assert_eq!(describe_error(S3ErrorKind::Transient), "transient"); } // Documentation of classify_s3_error behavior: // // The classify_s3_error function categorizes AWS SDK errors based on: // 1. HTTP status codes (primary mechanism): // - 503 → Throttle (with message validation as secondary check) // - Other status codes → Transient // // 2. Error type patterns: // - ServiceError: Check status code 503 for throttling // Also check message for SlowDown/RequestLimitExceeded/ServiceUnavailable // - ConstructionFailure: → Serialization (non-retryable) // - TimeoutError: → Transient (retryable) // - DispatchFailure: → Transient (retryable) // - ResponseError: → Transient (retryable) // // Testing this function with real AWS SDK errors requires either: // a) Integration tests with actual S3 backend (expensive, slow, flaky) // b) Mocking AWS SDK ServiceError with proper internal types (not exposed by SDK) // // The implementation follows the same pattern as categorize_error (lines 462-483) // which uses status code as primary classification with message as fallback. // // The straightforward pattern matching makes the implementation self-documenting, // and the predicates above verify the enum works correctly. // // Future work: Consider adding integration tests that trigger actual S3 errors // or exploring if AWS SDK test utilities become available. } #[test] fn test_extract_prefix() { // Standard case with multiple slashes assert_eq!(extract_prefix("ab/cd/ef/key"), "ab/cd/ef"); // With test prefix assert_eq!( extract_prefix("test-uuid/ab/cd/ef/key"), "test-uuid/ab/cd/ef" ); // Many levels assert_eq!(extract_prefix("a/b/c/d/e/file"), "a/b/c/d/e"); // No slashes - returns empty string assert_eq!(extract_prefix("no-slashes"), ""); // Single slash assert_eq!(extract_prefix("prefix/key"), "prefix"); // Empty string assert_eq!(extract_prefix(""), ""); // Trailing slash assert_eq!(extract_prefix("prefix/"), "prefix"); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/systeminit/si'

If you have feedback or need assistance with the MCP directory API, please join our Discord server