Files
foxhunt/services/trading_agent_service/tests/monitoring_tests.rs
jgrusewski 99e8d586a8 feat(tli): Implement agent allocate-portfolio command (WAVE 12.3.3)
- Add AllocatePortfolioArgs struct with validation
- Support 5 allocation strategies (equal-weight, risk-parity, ml-optimized, mean-variance, kelly)
- Implement constraint validation (0 < min < max < 1.0, positive capital)
- Real gRPC integration with Trading Agent Service via API Gateway
- Formatted table output with portfolio allocations and risk metrics
- JWT authentication support via Bearer token in gRPC metadata
- 15 comprehensive TDD integration tests (all passing)
- Case-insensitive strategy parsing

Test Results: cargo test -p tli --test agent_commands_test
 15 passed, 0 failed

Files:
- tli/src/commands/agent.rs (NEW - 466 lines)
- tli/src/commands/mod.rs (export AgentArgs)
- tli/src/main.rs (integrate agent command)
- tli/tests/agent_commands_test.rs (NEW - 15 tests)
- tli/proto/trading_agent.proto (NEW)

Co-authored-by: Wave 12.3.3 TDD Implementation
2025-10-16 08:18:42 +02:00

317 lines
8.9 KiB
Rust

//! TDD Tests for Trading Agent Monitoring Module
//!
//! Testing Strategy:
//! 1. Metrics initialization and registration
//! 2. Recording operations (universe, assets, allocation, orders)
//! 3. Error tracking
//! 4. Prometheus export
//!
//! Status: TDD - Tests written FIRST
use prometheus::{Encoder, TextEncoder};
use trading_agent_service::monitoring::TradingAgentMetrics;
#[test]
fn test_metrics_initialization() {
// Test that metrics can be created without panicking
let metrics = TradingAgentMetrics::new();
// Verify metrics object is valid (size can be 0 for ZST)
assert!(std::mem::size_of_val(&metrics) >= 0);
// Basic smoke test - should not panic
drop(metrics);
}
#[test]
fn test_record_universe_selection() {
let metrics = TradingAgentMetrics::new();
// Record a universe selection operation
metrics.record_universe_selection(125.5, 150);
// Record multiple operations to test counter increments
metrics.record_universe_selection(98.2, 140);
metrics.record_universe_selection(201.3, 165);
// Verify no panics occurred
}
#[test]
fn test_record_asset_selection() {
let metrics = TradingAgentMetrics::new();
// Record asset selection operations
metrics.record_asset_selection(45.2, 25);
metrics.record_asset_selection(52.8, 30);
metrics.record_asset_selection(38.1, 20);
// Verify no panics occurred
}
#[test]
fn test_record_allocation() {
let metrics = TradingAgentMetrics::new();
// Record portfolio allocation operations
metrics.record_allocation(78.5, 1_000_000.0);
metrics.record_allocation(92.1, 1_250_000.0);
metrics.record_allocation(65.3, 980_000.0);
// Verify no panics occurred
}
#[test]
fn test_record_order_generation() {
let metrics = TradingAgentMetrics::new();
// Record order generation operations
metrics.record_order_generation(12.4, 5);
metrics.record_order_generation(18.9, 8);
metrics.record_order_generation(9.2, 3);
// Verify no panics occurred
}
#[test]
fn test_record_error() {
let metrics = TradingAgentMetrics::new();
// Record various error types
metrics.record_error("universe_selection_failed");
metrics.record_error("asset_selection_timeout");
metrics.record_error("allocation_constraint_violation");
metrics.record_error("order_generation_failed");
metrics.record_error("database_connection_error");
// Verify no panics occurred
}
#[test]
fn test_metrics_export() {
let metrics = TradingAgentMetrics::new();
// Record various operations
metrics.record_universe_selection(100.0, 150);
metrics.record_asset_selection(50.0, 25);
metrics.record_allocation(75.0, 1_000_000.0);
metrics.record_order_generation(10.0, 5);
metrics.record_error("test_error");
// Export metrics to Prometheus text format
let encoder = TextEncoder::new();
let metric_families = prometheus::gather();
let mut buffer = vec![];
encoder.encode(&metric_families, &mut buffer).unwrap();
let output = String::from_utf8(buffer).unwrap();
// Verify key metrics are present in output
assert!(output.contains("trading_agent"));
// Verify specific metric names exist (if they follow naming conventions)
// Note: Actual metric names depend on implementation
}
#[test]
fn test_concurrent_metric_recording() {
use std::sync::Arc;
use std::thread;
let metrics = Arc::new(TradingAgentMetrics::new());
let mut handles = vec![];
// Spawn multiple threads recording metrics concurrently
for i in 0..10 {
let metrics_clone = Arc::clone(&metrics);
let handle = thread::spawn(move || {
for j in 0..100 {
let duration = (i * 100 + j) as f64;
metrics_clone.record_universe_selection(duration, i * 10 + j);
metrics_clone.record_asset_selection(duration / 2.0, i * 5 + j);
metrics_clone.record_allocation(duration * 1.5, (i * 100000 + j * 1000) as f64);
metrics_clone.record_order_generation(duration / 10.0, i + j);
}
});
handles.push(handle);
}
// Wait for all threads to complete
for handle in handles {
handle.join().unwrap();
}
// Verify no panics or data races occurred
}
#[test]
fn test_histogram_buckets() {
let metrics = TradingAgentMetrics::new();
// Test various duration values to ensure histogram buckets work
let test_durations = vec![
0.001, // 1μs
0.01, // 10μs
0.1, // 100μs
1.0, // 1ms
10.0, // 10ms
100.0, // 100ms
1000.0, // 1s
5000.0, // 5s
];
for duration in test_durations {
metrics.record_universe_selection(duration, 100);
metrics.record_asset_selection(duration, 50);
metrics.record_allocation(duration, 1_000_000.0);
metrics.record_order_generation(duration, 5);
}
// Verify no panics occurred
}
#[test]
fn test_gauge_updates() {
let metrics = TradingAgentMetrics::new();
// Test that gauges update correctly (not just increment)
metrics.record_universe_selection(100.0, 150);
metrics.record_universe_selection(100.0, 200); // Should update gauge to 200
metrics.record_universe_selection(100.0, 100); // Should update gauge to 100
metrics.record_asset_selection(50.0, 25);
metrics.record_asset_selection(50.0, 50); // Should update gauge to 50
metrics.record_allocation(75.0, 1_000_000.0);
metrics.record_allocation(75.0, 2_000_000.0); // Should update gauge to 2M
// Verify no panics occurred
}
#[test]
fn test_zero_values() {
let metrics = TradingAgentMetrics::new();
// Test edge case with zero values
metrics.record_universe_selection(0.0, 0);
metrics.record_asset_selection(0.0, 0);
metrics.record_allocation(0.0, 0.0);
metrics.record_order_generation(0.0, 0);
// Verify no panics occurred
}
#[test]
fn test_large_values() {
let metrics = TradingAgentMetrics::new();
// Test edge case with large values
metrics.record_universe_selection(99999.0, u64::MAX);
metrics.record_asset_selection(99999.0, u64::MAX);
metrics.record_allocation(99999.0, f64::MAX / 2.0); // Avoid overflow
metrics.record_order_generation(99999.0, u64::MAX);
// Verify no panics occurred
}
#[test]
fn test_error_type_variety() {
let metrics = TradingAgentMetrics::new();
// Test various error type strings
let error_types = vec![
"timeout",
"database_error",
"validation_failed",
"resource_exhausted",
"permission_denied",
"internal_error",
"network_failure",
"constraint_violation",
];
for error_type in error_types {
metrics.record_error(error_type);
}
// Test edge cases separately
metrics.record_error(""); // Empty string edge case
let long_string = "a".repeat(256);
metrics.record_error(&long_string); // Long string edge case
// Verify no panics occurred
}
#[test]
fn test_metrics_independence() {
// Create multiple independent metrics instances
let metrics1 = TradingAgentMetrics::new();
let metrics2 = TradingAgentMetrics::new();
// Record to first instance
metrics1.record_universe_selection(100.0, 150);
metrics1.record_error("error1");
// Record to second instance
metrics2.record_asset_selection(50.0, 25);
metrics2.record_error("error2");
// Both should work independently without interference
// Note: In practice, Prometheus metrics are global, but instances should be safe to create
}
#[test]
fn test_realistic_workflow() {
let metrics = TradingAgentMetrics::new();
// Simulate a realistic trading agent workflow
// 1. Universe selection
metrics.record_universe_selection(125.5, 150);
// 2. Asset selection (subset of universe)
metrics.record_asset_selection(45.2, 25);
// 3. Portfolio allocation
metrics.record_allocation(78.5, 1_000_000.0);
// 4. Order generation
metrics.record_order_generation(12.4, 5);
// 5. Another cycle
metrics.record_universe_selection(135.2, 145);
metrics.record_asset_selection(48.9, 28);
metrics.record_allocation(82.1, 1_050_000.0);
metrics.record_order_generation(15.7, 6);
// 6. Error occurs
metrics.record_error("market_data_stale");
// Verify complete workflow executes without panics
}
#[test]
fn test_metrics_after_error() {
let metrics = TradingAgentMetrics::new();
// Record normal operations
metrics.record_universe_selection(100.0, 150);
// Record error
metrics.record_error("test_error");
// Verify metrics can still be recorded after error
metrics.record_asset_selection(50.0, 25);
metrics.record_allocation(75.0, 1_000_000.0);
metrics.record_order_generation(10.0, 5);
// Another error
metrics.record_error("another_error");
// Continue recording
metrics.record_universe_selection(110.0, 160);
// Verify no panics occurred
}