📝 Wave 112: Miscellaneous test artifacts and documentation
- storage/tests/: Storage test suite - services/api_gateway/Dockerfile.simple: Simplified API gateway Docker build - docs/WAVE108_AGENT4_AUDIT_TESTS_BATCH2.md: Historical audit test documentation - fmt_results.txt, test_results.txt: Test run artifacts
This commit is contained in:
279
docs/WAVE108_AGENT4_AUDIT_TESTS_BATCH2.md
Normal file
279
docs/WAVE108_AGENT4_AUDIT_TESTS_BATCH2.md
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
# WAVE 108 AGENT 4: Audit Test Fixes - Batch 2 (Files 4-6)
|
||||||
|
|
||||||
|
**Agent**: Claude Code Agent 4
|
||||||
|
**Wave**: 108
|
||||||
|
**Date**: 2025-10-05
|
||||||
|
**Status**: ✅ SUCCESS
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
Fix ~100 audit test compilation errors in second batch of trading_engine test files caused by Wave 107's async signature change to `AuditTrailEngine::new()`.
|
||||||
|
|
||||||
|
## Background
|
||||||
|
Wave 107 Agent 4 changed `AuditTrailEngine::new()` from synchronous to async with new signature:
|
||||||
|
```rust
|
||||||
|
// OLD (sync)
|
||||||
|
pub fn new(config: AuditTrailConfig) -> Self
|
||||||
|
|
||||||
|
// NEW (async)
|
||||||
|
pub async fn new(
|
||||||
|
config: AuditTrailConfig,
|
||||||
|
postgres_pool: Arc<PostgresPool>,
|
||||||
|
wal_path: std::path::PathBuf,
|
||||||
|
) -> Result<Self, AuditTrailError>
|
||||||
|
```
|
||||||
|
|
||||||
|
All test callsites needed updating to:
|
||||||
|
1. Pass 3 arguments (config, pool, wal_path)
|
||||||
|
2. Add `.await`
|
||||||
|
3. Handle `Result<>` return
|
||||||
|
|
||||||
|
## Files Fixed (Batch 2)
|
||||||
|
|
||||||
|
### File 4: audit_retention_tests.rs
|
||||||
|
**Location**: `/home/jgrusewski/Work/foxhunt/trading_engine/tests/audit_retention_tests.rs`
|
||||||
|
**Occurrences Fixed**: 10
|
||||||
|
|
||||||
|
**Pattern Replacements**:
|
||||||
|
```rust
|
||||||
|
// Pattern 1 (9 occurrences)
|
||||||
|
- let audit_engine = AuditTrailEngine::new(audit_config);
|
||||||
|
- audit_engine.set_postgres_pool(Arc::clone(&pool)).await;
|
||||||
|
+ let wal_path = std::env::temp_dir().join(format!("audit_test_{}.wal", uuid::Uuid::new_v4()));
|
||||||
|
+ let audit_engine = AuditTrailEngine::new(audit_config, Arc::clone(&pool), wal_path)
|
||||||
|
+ .await
|
||||||
|
+ .expect("Failed to create audit engine");
|
||||||
|
|
||||||
|
// Pattern 2 (1 occurrence - Arc wrapped)
|
||||||
|
- let audit_engine = Arc::new(AuditTrailEngine::new(audit_config));
|
||||||
|
- audit_engine.set_postgres_pool(Arc::clone(&pool)).await;
|
||||||
|
+ let wal_path = std::env::temp_dir().join(format!("audit_test_{}.wal", uuid::Uuid::new_v4()));
|
||||||
|
+ let audit_engine = Arc::new(AuditTrailEngine::new(audit_config, Arc::clone(&pool), wal_path)
|
||||||
|
+ .await
|
||||||
|
+ .expect("Failed to create audit engine"));
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests Fixed**:
|
||||||
|
- test_cleanup_expired_events_archives_to_table
|
||||||
|
- test_cleanup_respects_retention_period
|
||||||
|
- test_cleanup_atomic_archive_then_delete
|
||||||
|
- test_cleanup_performance_10k_events
|
||||||
|
- test_cleanup_concurrent_with_persistence (Arc-wrapped)
|
||||||
|
- test_cleanup_empty_table
|
||||||
|
- test_cleanup_partial_expiration
|
||||||
|
- test_archived_events_queryable
|
||||||
|
- test_cleanup_error_handling
|
||||||
|
- test_retention_policy_sox_compliance
|
||||||
|
|
||||||
|
**Compilation**: ✅ SUCCESS (0 errors)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### File 5: audit_persistence_comprehensive.rs
|
||||||
|
**Location**: `/home/jgrusewski/Work/foxhunt/trading_engine/tests/audit_persistence_comprehensive.rs`
|
||||||
|
**Occurrences Fixed**: 19
|
||||||
|
|
||||||
|
**Pattern Replacements**:
|
||||||
|
```rust
|
||||||
|
// Pattern 1: With pool parameter (11 occurrences)
|
||||||
|
- let audit_engine = AuditTrailEngine::new(audit_config);
|
||||||
|
- audit_engine.set_postgres_pool(Arc::clone(&pool)).await;
|
||||||
|
+ let wal_path = std::env::temp_dir().join(format!("audit_test_{}.wal", uuid::Uuid::new_v4()));
|
||||||
|
+ let audit_engine = AuditTrailEngine::new(audit_config, Arc::clone(&pool), wal_path)
|
||||||
|
+ .await
|
||||||
|
+ .expect("Failed to create audit engine");
|
||||||
|
|
||||||
|
// Pattern 2: With pool (not Arc::clone) (6 occurrences)
|
||||||
|
- let audit_engine = AuditTrailEngine::new(audit_config);
|
||||||
|
- audit_engine.set_postgres_pool(pool).await;
|
||||||
|
+ let wal_path = std::env::temp_dir().join(format!("audit_test_{}.wal", uuid::Uuid::new_v4()));
|
||||||
|
+ let audit_engine = AuditTrailEngine::new(audit_config, pool, wal_path)
|
||||||
|
+ .await
|
||||||
|
+ .expect("Failed to create audit engine");
|
||||||
|
|
||||||
|
// Pattern 3: Default config with pool (4 occurrences)
|
||||||
|
- let audit_engine = AuditTrailEngine::new(AuditTrailConfig::default());
|
||||||
|
- audit_engine.set_postgres_pool(pool).await;
|
||||||
|
+ let wal_path = std::env::temp_dir().join(format!("audit_test_{}.wal", uuid::Uuid::new_v4()));
|
||||||
|
+ let audit_engine = AuditTrailEngine::new(AuditTrailConfig::default(), pool, wal_path)
|
||||||
|
+ .await
|
||||||
|
+ .expect("Failed to create audit engine");
|
||||||
|
|
||||||
|
// Pattern 4: Tests without database (5 occurrences)
|
||||||
|
// Added helper function for non-DB tests
|
||||||
|
+ async fn create_mock_audit_engine(config: AuditTrailConfig) -> AuditTrailEngine {
|
||||||
|
+ let pool = match create_test_postgres_pool().await {
|
||||||
|
+ Some(p) => p,
|
||||||
|
+ None => { /* create mock pool */ }
|
||||||
|
+ };
|
||||||
|
+ let wal_path = std::env::temp_dir().join(format!("audit_test_{}.wal", uuid::Uuid::new_v4()));
|
||||||
|
+ AuditTrailEngine::new(config, pool, wal_path)
|
||||||
|
+ .await
|
||||||
|
+ .expect("Failed to create audit engine")
|
||||||
|
+ }
|
||||||
|
|
||||||
|
- let audit_engine = AuditTrailEngine::new(audit_config);
|
||||||
|
+ let audit_engine = create_mock_audit_engine(audit_config).await;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests Fixed**:
|
||||||
|
- test_audit_event_persistence_to_database
|
||||||
|
- test_batch_persistence_atomicity
|
||||||
|
- test_persistence_ordering_preserved
|
||||||
|
- test_checksum_generation_sha256
|
||||||
|
- test_checksum_verification_detects_tampering
|
||||||
|
- test_sql_injection_prevention_transaction_id
|
||||||
|
- test_sql_injection_prevention_order_id
|
||||||
|
- test_sql_injection_prevention_actor_field
|
||||||
|
- test_limit_parameter_validation
|
||||||
|
- test_offset_parameter_validation
|
||||||
|
- test_compression_with_json_data
|
||||||
|
- test_performance_high_throughput_logging
|
||||||
|
- test_risk_level_high_notional
|
||||||
|
- test_event_serialization_json
|
||||||
|
- test_event_deserialization_json
|
||||||
|
- And 4 more non-DB tests using mock helper
|
||||||
|
|
||||||
|
**Compilation**: ✅ SUCCESS (0 errors)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### File 6: audit_trail_persistence_test.rs
|
||||||
|
**Location**: `/home/jgrusewski/Work/foxhunt/trading_engine/tests/audit_trail_persistence_test.rs`
|
||||||
|
**Occurrences Fixed**: 4
|
||||||
|
|
||||||
|
**Pattern Replacements**:
|
||||||
|
```rust
|
||||||
|
// Pattern 1: With split lines (1 occurrence)
|
||||||
|
- let audit_engine = AuditTrailEngine::new(audit_config);
|
||||||
|
-
|
||||||
|
- // Set PostgreSQL pool
|
||||||
|
- audit_engine.set_postgres_pool(Arc::clone(&postgres_pool)).await;
|
||||||
|
+ let wal_path = std::env::temp_dir().join(format!("audit_test_{}.wal", uuid::Uuid::new_v4()));
|
||||||
|
+ let audit_engine = AuditTrailEngine::new(audit_config, Arc::clone(&postgres_pool), wal_path)
|
||||||
|
+ .await
|
||||||
|
+ .expect("Failed to create audit engine");
|
||||||
|
|
||||||
|
// Pattern 2: Tests without database (3 occurrences)
|
||||||
|
// Added same helper function as File 5
|
||||||
|
+ async fn create_mock_audit_engine(config: AuditTrailConfig) -> AuditTrailEngine { ... }
|
||||||
|
|
||||||
|
- let audit_config = AuditTrailConfig::default();
|
||||||
|
- let audit_engine = AuditTrailEngine::new(audit_config);
|
||||||
|
+ let audit_config = AuditTrailConfig::default();
|
||||||
|
+ let audit_engine = create_mock_audit_engine(audit_config).await;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Tests Fixed**:
|
||||||
|
- test_audit_trail_database_persistence (with pool)
|
||||||
|
- test_audit_event_checksum_generation (mock)
|
||||||
|
- test_audit_trail_buffer_capacity (mock)
|
||||||
|
- test_compliance_tags (mock)
|
||||||
|
|
||||||
|
**Compilation**: ✅ SUCCESS (0 errors)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary Statistics
|
||||||
|
|
||||||
|
| File | Occurrences | Patterns | Status |
|
||||||
|
|------|------------|----------|--------|
|
||||||
|
| audit_retention_tests.rs | 10 | 2 | ✅ PASS |
|
||||||
|
| audit_persistence_comprehensive.rs | 19 | 4 | ✅ PASS |
|
||||||
|
| audit_trail_persistence_test.rs | 4 | 2 | ✅ PASS |
|
||||||
|
| **TOTAL** | **33** | **8** | **✅ SUCCESS** |
|
||||||
|
|
||||||
|
## Error Reduction
|
||||||
|
- **Initial Errors**: ~100 (estimated from 33 occurrences × ~3 errors each)
|
||||||
|
- **Final Errors**: 0
|
||||||
|
- **Reduction**: 100 errors fixed
|
||||||
|
|
||||||
|
## Technical Approach
|
||||||
|
|
||||||
|
### 1. Database-Dependent Tests
|
||||||
|
For tests that use PostgreSQL pool:
|
||||||
|
```rust
|
||||||
|
let wal_path = std::env::temp_dir().join(format!("audit_test_{}.wal", uuid::Uuid::new_v4()));
|
||||||
|
let audit_engine = AuditTrailEngine::new(config, pool, wal_path)
|
||||||
|
.await
|
||||||
|
.expect("Failed to create audit engine");
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Non-Database Tests
|
||||||
|
For tests that only use in-memory features (checksum, buffer, etc.):
|
||||||
|
```rust
|
||||||
|
async fn create_mock_audit_engine(config: AuditTrailConfig) -> AuditTrailEngine {
|
||||||
|
let pool = match create_test_postgres_pool().await {
|
||||||
|
Some(p) => p,
|
||||||
|
None => {
|
||||||
|
// Create mock pool for tests that don't persist
|
||||||
|
Arc::new(PostgresPool::new(mock_config).await.unwrap_or_else(...))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let wal_path = std::env::temp_dir().join(format!("audit_test_{}.wal", uuid::Uuid::new_v4()));
|
||||||
|
AuditTrailEngine::new(config, pool, wal_path).await.expect(...)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Key Patterns Fixed
|
||||||
|
1. **Removed obsolete `.set_postgres_pool()` calls** - pool now passed during construction
|
||||||
|
2. **Added `.await` to async constructor**
|
||||||
|
3. **Added `.expect()` for Result handling**
|
||||||
|
4. **Generated unique WAL paths** using temp_dir + uuid
|
||||||
|
5. **Created helper functions** for non-DB tests
|
||||||
|
|
||||||
|
## Compilation Verification
|
||||||
|
|
||||||
|
All three test files compile cleanly:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cargo test -p trading_engine --test audit_retention_tests --no-run
|
||||||
|
Finished `test` profile [optimized + debuginfo] target(s) in 10.26s
|
||||||
|
|
||||||
|
$ cargo test -p trading_engine --test audit_persistence_comprehensive --no-run
|
||||||
|
Finished `test` profile [optimized + debuginfo] target(s) in 10.60s
|
||||||
|
|
||||||
|
$ cargo test -p trading_engine --test audit_trail_persistence_test --no-run
|
||||||
|
Finished `test` profile [optimized + debuginfo] target(s) in 0.25s
|
||||||
|
```
|
||||||
|
|
||||||
|
**Only warnings remain** (unused imports, dead code) - no errors.
|
||||||
|
|
||||||
|
## Impact on Wave 108 Goals
|
||||||
|
|
||||||
|
**Contribution to Wave 108 Overall**:
|
||||||
|
- Batch 2 fixed **33 callsites** across **3 files**
|
||||||
|
- Combined with Agent 3's Batch 1: **~200+ total errors fixed**
|
||||||
|
- Unblocks test execution for audit trail compliance verification
|
||||||
|
- Enables SOX/MiFID II audit trail testing
|
||||||
|
|
||||||
|
## Files Modified
|
||||||
|
|
||||||
|
1. `/home/jgrusewski/Work/foxhunt/trading_engine/tests/audit_retention_tests.rs`
|
||||||
|
2. `/home/jgrusewski/Work/foxhunt/trading_engine/tests/audit_persistence_comprehensive.rs`
|
||||||
|
3. `/home/jgrusewski/Work/foxhunt/trading_engine/tests/audit_trail_persistence_test.rs`
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
1. **Agent 5-12**: Continue fixing remaining audit test files
|
||||||
|
2. **Wave 108 Integration**: Combine all agent fixes for final test suite compilation
|
||||||
|
3. **Test Execution**: Run full audit trail test suite to verify functionality
|
||||||
|
4. **Coverage Measurement**: Confirm audit trail coverage remains >90%
|
||||||
|
|
||||||
|
## Lessons Learned
|
||||||
|
|
||||||
|
1. **Helper Functions Effective**: `create_mock_audit_engine()` elegantly handles non-DB tests
|
||||||
|
2. **Pattern Variations**: Multiple patterns needed (Arc-wrapped, default config, split lines)
|
||||||
|
3. **WAL Path Generation**: Unique temp paths prevent test interference
|
||||||
|
4. **Mock Pool Strategy**: Graceful degradation for tests without database access
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
✅ **BATCH 2 COMPLETE**
|
||||||
|
All 33 audit test callsites in files 4-6 successfully updated to async signature.
|
||||||
|
Zero compilation errors remain.
|
||||||
|
Ready for integration with other Wave 108 agents.
|
||||||
|
|
||||||
|
---
|
||||||
|
**Report Generated**: 2025-10-05
|
||||||
|
**Agent**: Claude Code Agent 4
|
||||||
|
**Wave**: 108 - Final Production Push
|
||||||
59060
fmt_results.txt
Normal file
59060
fmt_results.txt
Normal file
File diff suppressed because it is too large
Load Diff
45
services/api_gateway/Dockerfile.simple
Normal file
45
services/api_gateway/Dockerfile.simple
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Simple runtime-only Dockerfile for pre-built binaries
|
||||||
|
# Build binary first: cargo build --release -p api_gateway
|
||||||
|
# Then: docker build -f services/api_gateway/Dockerfile.simple -t foxhunt-api .
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
|
||||||
|
# Install runtime dependencies
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
ca-certificates \
|
||||||
|
libssl3 \
|
||||||
|
curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Download and install grpc_health_probe for health checks
|
||||||
|
RUN curl -sSL https://github.com/grpc-ecosystem/grpc-health-probe/releases/download/v0.4.25/grpc_health_probe-linux-amd64 \
|
||||||
|
-o /usr/local/bin/grpc_health_probe && \
|
||||||
|
chmod +x /usr/local/bin/grpc_health_probe
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN groupadd --system --gid 1000 foxhunt && \
|
||||||
|
useradd --system --uid 1000 --gid foxhunt --shell /bin/bash foxhunt
|
||||||
|
|
||||||
|
# Create application directories
|
||||||
|
RUN mkdir -p /app/config /app/logs && \
|
||||||
|
chown -R foxhunt:foxhunt /app
|
||||||
|
|
||||||
|
# Set working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy pre-built binary from host
|
||||||
|
COPY target/release/api_gateway ./api_gateway
|
||||||
|
RUN chmod +x ./api_gateway && chown foxhunt:foxhunt ./api_gateway
|
||||||
|
|
||||||
|
# Switch to non-root user
|
||||||
|
USER foxhunt
|
||||||
|
|
||||||
|
# Expose gRPC and metrics ports
|
||||||
|
EXPOSE 50050 9091
|
||||||
|
|
||||||
|
# Health check using grpc_health_probe
|
||||||
|
HEALTHCHECK --interval=10s --timeout=5s --start-period=30s --retries=3 \
|
||||||
|
CMD /usr/local/bin/grpc_health_probe -addr=localhost:50050 || exit 1
|
||||||
|
|
||||||
|
# Run the application
|
||||||
|
ENTRYPOINT ["./api_gateway"]
|
||||||
314
storage/tests/error_conversion_tests.rs
Normal file
314
storage/tests/error_conversion_tests.rs
Normal file
@@ -0,0 +1,314 @@
|
|||||||
|
//! Error Conversion and Retry Logic Tests for Storage Crate
|
||||||
|
//!
|
||||||
|
//! This module provides comprehensive tests for:
|
||||||
|
//! - StorageError to CommonError conversion (all 15 variants)
|
||||||
|
//! - StorageError::retry_delay_ms (retryable vs non-retryable errors)
|
||||||
|
//! - std::io::Error to StorageError conversion (all ErrorKind mappings)
|
||||||
|
|
||||||
|
use storage::error::StorageError;
|
||||||
|
use common::error::{CommonError, ErrorCategory};
|
||||||
|
use std::io::ErrorKind;
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// STORAGE ERROR TO COMMON ERROR CONVERSION
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_io() {
|
||||||
|
let storage_err = StorageError::IoError { message: "disk full".to_string() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_network() {
|
||||||
|
let storage_err = StorageError::NetworkError { message: "host unreachable".to_string() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::Network);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_auth() {
|
||||||
|
let storage_err = StorageError::AuthError { message: "bad credentials".to_string() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::Security);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_config() {
|
||||||
|
let storage_err = StorageError::ConfigError { message: "invalid path".to_string() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::Configuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_not_found() {
|
||||||
|
let storage_err = StorageError::NotFound { path: "/data/model.bin".to_string() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_operation_failed() {
|
||||||
|
use std::sync::Arc;
|
||||||
|
let storage_err = StorageError::OperationFailed {
|
||||||
|
operation: "write".to_string(),
|
||||||
|
path: "/data/checkpoint.bin".to_string(),
|
||||||
|
source: Arc::new(CommonError::Network("network failed".to_string())),
|
||||||
|
};
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_permission_denied() {
|
||||||
|
let storage_err = StorageError::PermissionDenied { path: "/etc/secrets".to_string() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::Security);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_timeout() {
|
||||||
|
let storage_err = StorageError::Timeout { timeout_ms: 5000 };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_rate_limited() {
|
||||||
|
let storage_err = StorageError::RateLimited { retry_after_ms: 1000 };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_quota_exceeded() {
|
||||||
|
let storage_err = StorageError::QuotaExceeded { used: 1000, limit: 500 };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_serialization() {
|
||||||
|
let storage_err = StorageError::SerializationError { message: "invalid JSON".to_string() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_compression() {
|
||||||
|
let storage_err = StorageError::CompressionError { message: "gzip failed".to_string() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_integrity_error() {
|
||||||
|
let storage_err = StorageError::IntegrityError {
|
||||||
|
path: "/data/model.bin".to_string(),
|
||||||
|
expected: "abc123".to_string(),
|
||||||
|
actual: "def456".to_string(),
|
||||||
|
};
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_generic() {
|
||||||
|
let storage_err = StorageError::Generic { message: "unknown error".to_string() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "s3")]
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_to_common_error_s3() {
|
||||||
|
let storage_err = StorageError::S3Error { message: "S3 operation failed".to_string() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::Network);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// RETRY DELAY TESTS
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_retry_delay_ms_network_error() {
|
||||||
|
let err = StorageError::NetworkError { message: "connection failed".to_string() };
|
||||||
|
assert_eq!(err.retry_delay_ms(), Some(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_retry_delay_ms_timeout() {
|
||||||
|
let err = StorageError::Timeout { timeout_ms: 500 };
|
||||||
|
assert_eq!(err.retry_delay_ms(), Some(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_retry_delay_ms_rate_limited() {
|
||||||
|
let err = StorageError::RateLimited { retry_after_ms: 1500 };
|
||||||
|
assert_eq!(err.retry_delay_ms(), Some(1500));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_retry_delay_ms_generic() {
|
||||||
|
let err = StorageError::Generic { message: "generic error".to_string() };
|
||||||
|
assert_eq!(err.retry_delay_ms(), Some(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "s3")]
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_retry_delay_ms_s3() {
|
||||||
|
let err = StorageError::S3Error { message: "S3 error".to_string() };
|
||||||
|
assert_eq!(err.retry_delay_ms(), Some(100));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_retry_delay_ms_not_found() {
|
||||||
|
let err = StorageError::NotFound { path: "/data/missing.bin".to_string() };
|
||||||
|
assert_eq!(err.retry_delay_ms(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_retry_delay_ms_quota_exceeded() {
|
||||||
|
let err = StorageError::QuotaExceeded { used: 1000, limit: 500 };
|
||||||
|
assert_eq!(err.retry_delay_ms(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_retry_delay_ms_permission_denied() {
|
||||||
|
let err = StorageError::PermissionDenied { path: "/etc/forbidden".to_string() };
|
||||||
|
assert_eq!(err.retry_delay_ms(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_retry_delay_ms_serialization_error() {
|
||||||
|
let err = StorageError::SerializationError { message: "bad JSON".to_string() };
|
||||||
|
assert_eq!(err.retry_delay_ms(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_retry_delay_ms_integrity_error() {
|
||||||
|
let err = StorageError::IntegrityError {
|
||||||
|
path: "/data/file.bin".to_string(),
|
||||||
|
expected: "abc".to_string(),
|
||||||
|
actual: "def".to_string(),
|
||||||
|
};
|
||||||
|
assert_eq!(err.retry_delay_ms(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// IO ERROR TO STORAGE ERROR CONVERSION
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_io_error_to_storage_error_not_found() {
|
||||||
|
let io_err = std::io::Error::new(ErrorKind::NotFound, "file not found");
|
||||||
|
let storage_err: StorageError = io_err.into();
|
||||||
|
assert!(matches!(storage_err, StorageError::NotFound { .. }));
|
||||||
|
if let StorageError::NotFound { path } = storage_err {
|
||||||
|
assert_eq!(path, "unknown"); // Default value from conversion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_io_error_to_storage_error_permission_denied() {
|
||||||
|
let io_err = std::io::Error::new(ErrorKind::PermissionDenied, "access denied");
|
||||||
|
let storage_err: StorageError = io_err.into();
|
||||||
|
assert!(matches!(storage_err, StorageError::PermissionDenied { .. }));
|
||||||
|
if let StorageError::PermissionDenied { path } = storage_err {
|
||||||
|
assert_eq!(path, "unknown"); // Default value from conversion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_io_error_to_storage_error_already_exists() {
|
||||||
|
let io_err = std::io::Error::new(ErrorKind::AlreadyExists, "file exists");
|
||||||
|
let storage_err: StorageError = io_err.into();
|
||||||
|
// AlreadyExists maps to IoError in current implementation
|
||||||
|
assert!(matches!(storage_err, StorageError::IoError { .. }));
|
||||||
|
if let StorageError::IoError { message } = storage_err {
|
||||||
|
assert!(message.contains("file exists"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_io_error_to_storage_error_timed_out() {
|
||||||
|
let io_err = std::io::Error::new(ErrorKind::TimedOut, "operation timed out");
|
||||||
|
let storage_err: StorageError = io_err.into();
|
||||||
|
assert!(matches!(storage_err, StorageError::Timeout { timeout_ms: 5000 }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_io_error_to_storage_error_broken_pipe() {
|
||||||
|
let io_err = std::io::Error::new(ErrorKind::BrokenPipe, "pipe broken");
|
||||||
|
let storage_err: StorageError = io_err.into();
|
||||||
|
assert!(matches!(storage_err, StorageError::IoError { .. }));
|
||||||
|
if let StorageError::IoError { message } = storage_err {
|
||||||
|
assert!(message.contains("pipe broken"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_io_error_to_storage_error_connection_reset() {
|
||||||
|
let io_err = std::io::Error::new(ErrorKind::ConnectionReset, "connection reset");
|
||||||
|
let storage_err: StorageError = io_err.into();
|
||||||
|
assert!(matches!(storage_err, StorageError::IoError { .. }));
|
||||||
|
if let StorageError::IoError { message } = storage_err {
|
||||||
|
assert!(message.contains("connection reset"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_io_error_to_storage_error_other() {
|
||||||
|
let io_err = std::io::Error::new(ErrorKind::Other, "unknown error");
|
||||||
|
let storage_err: StorageError = io_err.into();
|
||||||
|
assert!(matches!(storage_err, StorageError::IoError { .. }));
|
||||||
|
if let StorageError::IoError { message } = storage_err {
|
||||||
|
assert!(message.contains("unknown error"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// EDGE CASES AND BOUNDARY CONDITIONS
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_empty_message() {
|
||||||
|
let storage_err = StorageError::Generic { message: String::new() };
|
||||||
|
let common_err: CommonError = storage_err.into();
|
||||||
|
assert_eq!(common_err.category(), ErrorCategory::System);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_long_path() {
|
||||||
|
let long_path = "a".repeat(1000);
|
||||||
|
let storage_err = StorageError::NotFound { path: long_path.clone() };
|
||||||
|
assert!(matches!(storage_err, StorageError::NotFound { .. }));
|
||||||
|
if let StorageError::NotFound { path } = storage_err {
|
||||||
|
assert_eq!(path.len(), 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_rate_limited_zero_retry() {
|
||||||
|
let err = StorageError::RateLimited { retry_after_ms: 0 };
|
||||||
|
assert_eq!(err.retry_delay_ms(), Some(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_timeout_zero() {
|
||||||
|
let err = StorageError::Timeout { timeout_ms: 0 };
|
||||||
|
assert_eq!(err.retry_delay_ms(), Some(200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_storage_error_integrity_error_same_values() {
|
||||||
|
let err = StorageError::IntegrityError {
|
||||||
|
path: "/data/file.bin".to_string(),
|
||||||
|
expected: "abc123".to_string(),
|
||||||
|
actual: "abc123".to_string(),
|
||||||
|
};
|
||||||
|
// Even with matching values, it's still an integrity error (logic error in caller)
|
||||||
|
assert_eq!(err.retry_delay_ms(), None);
|
||||||
|
}
|
||||||
1686
test_results.txt
Normal file
1686
test_results.txt
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user