stemedb/applications/aphoria/dogfood/cachewrap/tests/basic.rs
jml e758f2ebfb feat(aphoria): implement programmatic extractors for Option<T> semantics
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>
2026-02-11 06:43:10 +00:00

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
}