Completes Task #3 of httpclient dogfooding with 100% detection rate (7/7 violations). ## New Extractors - **OptionBoundsExtractor**: Detects Option<T> fields set to None (unbounded) - **OptionValueExtractor**: Extracts values from Some(n) for threshold checks Both extractors use context-aware pattern matching to understand Rust Option<T> semantics, which declarative extractors cannot handle. ## Implementation **Files Created**: - applications/aphoria/src/extractors/option_bounds.rs (257 lines) - applications/aphoria/src/extractors/option_value.rs (277 lines) - applications/aphoria/docs/examples/extractors/programmatic-option-semantics.md **Files Modified**: - applications/aphoria/src/extractors/mod.rs - Added module declarations - applications/aphoria/src/extractors/registry.rs - Registered extractors - applications/aphoria/dogfood/httpclient/.aphoria/claims.toml - Added 4 claims - applications/aphoria/dogfood/httpclient/TASK-1-SUMMARY.md - Task #3 completion ## Results | Metric | Value | |--------|-------| | Detection Rate | 100% (7/7 violations) | | Improvement | +29 percentage points (from 71%) | | New Violations | 2 (max_redirects, max_retries unbounded) | | Unit Tests | 13 (all passing) | ## Two-Claim Strategy For each bounded Option<T> field: 1. **configured** claim - Detects None (unbounded) 2. **max_value** claim - Validates Some(n) threshold Example: - `max_redirects: None` → CONFLICT (not configured) - `max_redirects: Some(20)` → CONFLICT (exceeds 10) - `max_redirects: Some(5)` → PASS ## Enterprise Quality ✓ Proper error handling (no unwrap/expect) ✓ Comprehensive tests (6+7 unit tests) ✓ Full documentation with examples ✓ Reusable for 10+ similar patterns ✓ Screening patterns for performance ## Cachewrap Dogfood Also includes complete cachewrap dogfood exercise: - 10 claims for Redis cache wrapper - Day 1-5 summaries - Full retrospective and evaluation - Declarative extractors for all patterns Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
199 lines
6.8 KiB
Rust
199 lines
6.8 KiB
Rust
//! Basic integration tests for cachewrap
|
|
//!
|
|
//! Note: These tests assume a Redis instance running at localhost:6379
|
|
//! They pass DESPITE the violations because violations are configuration/usage issues,
|
|
//! not logic errors.
|
|
|
|
use cachewrap::{CacheClient, CacheConfig, EvictionPolicy};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[tokio::test]
|
|
async fn test_config_creation() {
|
|
let config = CacheConfig::new("redis://127.0.0.1:6379".to_string());
|
|
assert_eq!(config.url, "redis://127.0.0.1:6379");
|
|
|
|
// ✅ All violations now fixed in default config
|
|
assert_eq!(config.password, ""); // ✅ From env (empty if not set)
|
|
assert!(config.verify_tls); // ✅ Enabled
|
|
assert_eq!(config.timeout.as_secs(), 5); // ✅ 5 second timeout
|
|
assert_eq!(config.max_size, Some(1000 * 1024 * 1024)); // ✅ 1GB limit
|
|
assert_eq!(config.eviction_policy, Some(EvictionPolicy::LRU)); // ✅ LRU policy
|
|
assert!(config.metrics_enabled); // ✅ Metrics enabled
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn test_config_builder_pattern() {
|
|
let config = CacheConfig::new("redis://localhost:6379".to_string())
|
|
.with_password("testpass".to_string())
|
|
.with_tls_verification(true)
|
|
.with_timeout(std::time::Duration::from_secs(5))
|
|
.with_max_size(1000)
|
|
.with_eviction_policy(EvictionPolicy::LRU)
|
|
.with_metrics(true);
|
|
|
|
assert_eq!(config.password, "testpass");
|
|
assert!(config.verify_tls);
|
|
assert_eq!(config.timeout.as_secs(), 5);
|
|
assert_eq!(config.max_size, Some(1000));
|
|
assert_eq!(config.eviction_policy, Some(EvictionPolicy::LRU));
|
|
assert!(config.metrics_enabled);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running Redis instance (ConnectionManager connects immediately)
|
|
async fn test_client_creation() {
|
|
let config = CacheConfig::new("redis://127.0.0.1:6379".to_string());
|
|
let result = CacheClient::new(config).await;
|
|
|
|
// Client creation should succeed (violations don't prevent instantiation)
|
|
assert!(result.is_ok());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running Redis instance
|
|
async fn test_health_check() {
|
|
let config = CacheConfig::new("redis://127.0.0.1:6379".to_string());
|
|
let client = CacheClient::new(config).await.unwrap();
|
|
|
|
let health = client.health_check().await;
|
|
assert!(health.is_ok());
|
|
assert!(health.unwrap());
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running Redis instance
|
|
async fn test_set_and_get() {
|
|
let config = CacheConfig::new("redis://127.0.0.1:6379".to_string());
|
|
let client = CacheClient::new(config).await.unwrap();
|
|
|
|
// Set a value (⚠️ no TTL - violation!)
|
|
let set_result = client.set("test_key", "test_value").await;
|
|
assert!(set_result.is_ok());
|
|
|
|
// Get the value (⚠️ no key validation - violation!)
|
|
let get_result = client.get("test_key").await;
|
|
assert!(get_result.is_ok());
|
|
assert_eq!(get_result.unwrap(), Some("test_value".to_string()));
|
|
|
|
// Cleanup
|
|
let _ = client.delete("test_key").await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running Redis instance
|
|
async fn test_set_with_ttl() {
|
|
let config = CacheConfig::new("redis://127.0.0.1:6379".to_string());
|
|
let client = CacheClient::new(config).await.unwrap();
|
|
|
|
// Use the correct version with TTL
|
|
let set_result = client.set_with_ttl("ttl_key", "ttl_value", 10).await;
|
|
assert!(set_result.is_ok());
|
|
|
|
let get_result = client.get("ttl_key").await;
|
|
assert!(get_result.is_ok());
|
|
assert_eq!(get_result.unwrap(), Some("ttl_value".to_string()));
|
|
|
|
// Cleanup
|
|
let _ = client.delete("ttl_key").await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running Redis instance
|
|
async fn test_delete() {
|
|
let config = CacheConfig::new("redis://127.0.0.1:6379".to_string());
|
|
let client = CacheClient::new(config).await.unwrap();
|
|
|
|
// Set then delete
|
|
let _ = client.set("delete_key", "delete_value").await;
|
|
let delete_result = client.delete("delete_key").await;
|
|
assert!(delete_result.is_ok());
|
|
|
|
// Verify deleted
|
|
let get_result = client.get("delete_key").await;
|
|
assert!(get_result.is_ok());
|
|
assert_eq!(get_result.unwrap(), None);
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running Redis instance
|
|
async fn test_get_nonexistent_key() {
|
|
let config = CacheConfig::new("redis://127.0.0.1:6379".to_string());
|
|
let client = CacheClient::new(config).await.unwrap();
|
|
|
|
let get_result = client.get("nonexistent_key_12345").await;
|
|
assert!(get_result.is_ok());
|
|
assert_eq!(get_result.unwrap(), None);
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
|
struct TestStruct {
|
|
name: String,
|
|
value: u32,
|
|
}
|
|
|
|
#[tokio::test]
|
|
#[ignore] // Requires running Redis instance
|
|
async fn test_typed_get_set() {
|
|
let config = CacheConfig::new("redis://127.0.0.1:6379".to_string());
|
|
let client = CacheClient::new(config).await.unwrap();
|
|
|
|
let test_data = TestStruct {
|
|
name: "test".to_string(),
|
|
value: 42,
|
|
};
|
|
|
|
// Set typed value
|
|
let set_result = client.set_typed("typed_key", &test_data).await;
|
|
assert!(set_result.is_ok());
|
|
|
|
// Get typed value
|
|
let get_result: Result<Option<TestStruct>, _> = client.get_typed("typed_key").await;
|
|
assert!(get_result.is_ok());
|
|
assert_eq!(get_result.unwrap(), Some(test_data));
|
|
|
|
// Cleanup
|
|
let _ = client.delete("typed_key").await;
|
|
}
|
|
|
|
// ✅ REMOVED: test_blocking_get() - blocking_get() method removed (Violation 6 fixed)
|
|
|
|
#[test]
|
|
fn test_eviction_policy_equality() {
|
|
assert_eq!(EvictionPolicy::LRU, EvictionPolicy::LRU);
|
|
assert_eq!(EvictionPolicy::LFU, EvictionPolicy::LFU);
|
|
assert_eq!(EvictionPolicy::TTL, EvictionPolicy::TTL);
|
|
assert_ne!(EvictionPolicy::LRU, EvictionPolicy::LFU);
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_default_violations() {
|
|
let config = CacheConfig::default();
|
|
|
|
// ✅ All violations are now FIXED in default config
|
|
assert_eq!(config.password, ""); // ✅ Fixed: From env
|
|
assert!(config.verify_tls); // ✅ Fixed: Enabled
|
|
assert_eq!(config.timeout.as_secs(), 5); // ✅ Fixed: 5 seconds
|
|
assert_eq!(config.max_size, Some(1000 * 1024 * 1024)); // ✅ Fixed: 1GB
|
|
assert_eq!(config.eviction_policy, Some(EvictionPolicy::LRU)); // ✅ Fixed: LRU
|
|
assert!(config.metrics_enabled); // ✅ Fixed: Enabled
|
|
}
|
|
|
|
#[test]
|
|
fn test_config_fixes_violations() {
|
|
let config = CacheConfig::default()
|
|
.with_password(std::env::var("REDIS_PASSWORD").unwrap_or_else(|_| "from_env".to_string()))
|
|
.with_tls_verification(true)
|
|
.with_timeout(std::time::Duration::from_secs(5))
|
|
.with_max_size(1000)
|
|
.with_eviction_policy(EvictionPolicy::LRU)
|
|
.with_metrics(true);
|
|
|
|
// Verify violations are fixed
|
|
assert_ne!(config.password, "secret123"); // ✅ Fixed
|
|
assert!(config.verify_tls); // ✅ Fixed
|
|
assert_ne!(config.timeout.as_secs(), 0); // ✅ Fixed
|
|
assert!(config.max_size.is_some()); // ✅ Fixed
|
|
assert!(config.eviction_policy.is_some()); // ✅ Fixed
|
|
assert!(config.metrics_enabled); // ✅ Fixed
|
|
}
|