//! STRICT TDD - Billing Manager Tests //! These tests define the complete billing system behavior before implementation use chrono::{DateTime, Utc}; use std::collections::HashMap; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use tokio::time; use uuid::Uuid; use rtx_platform::billing::{ AggregationPeriod, AlertThreshold, BillingAlert, BillingConfig, BillingManager, CostCalculator, Invoice, InvoiceGenerator, PricingTier, ResourceType, UsageMeter, UsageRecord, }; use rtx_platform::{PlatformConfig, PlatformResult}; fn create_test_billing_config() -> BillingConfig { BillingConfig { pricing_tiers: vec![ PricingTier { resource_type: ResourceType::Compute, tiers: vec![ (0.0, 1000.0, 0.10), // $0.10/hour for first 1000 hours (1000.0, 5000.0, 0.08), // $0.08/hour for next 4000 hours (5000.0, f64::INFINITY, 0.06), // $0.06/hour for 5000+ hours ], }, PricingTier { resource_type: ResourceType::Storage, tiers: vec![ (0.0, 1024.0, 0.023), // $0.023/GB-month for first TB (1024.0, f64::INFINITY, 0.021), // $0.021/GB-month for 1TB+ ], }, PricingTier { resource_type: ResourceType::NetworkEgress, tiers: vec![ (0.0, 100.0, 0.0), // First 100GB free (100.0, 1024.0, 0.09), // $0.09/GB for next 924GB (1024.0, f64::INFINITY, 0.085), // $0.085/GB for 1TB+ ], }, ], aggregation_period: AggregationPeriod::Hourly, alert_thresholds: vec![AlertThreshold { tenant_id: "default".to_string(), monthly_limit: 1000.0, warning_threshold: 0.8, critical_threshold: 0.95, }], invoice_generation_day: 1, late_fee_rate: 0.015, // 1.5% monthly late fee } } fn create_test_platform_config() -> PlatformConfig { PlatformConfig { database_url: "postgresql://test:test@localhost/billing_test".to_string(), redis_urls: vec!["redis://localhost:6379".to_string()], kafka_brokers: vec!["localhost:9092".to_string()], metrics_endpoint: "localhost:9090".to_string(), regions: HashMap::new(), sla_targets: rtx_platform::slo::SlaTargets::default(), } } #[tokio::test] async fn test_billing_manager_creation() -> PlatformResult<()> { let config = create_test_platform_config(); let billing_config = create_test_billing_config(); let billing_manager = BillingManager::new(&config, billing_config).await?; assert!(billing_manager.is_healthy().await?); Ok(()) } #[tokio::test] async fn test_usage_meter_records_compute_usage() -> PlatformResult<()> { let config = create_test_platform_config(); let billing_config = create_test_billing_config(); let mut billing_manager = BillingManager::new(&config, billing_config).await?; billing_manager.start().await?; let tenant_id = Uuid::new_v4(); let region = "us-west-2".to_string(); // Record compute usage let usage_record = UsageRecord { id: Uuid::new_v4(), tenant_id, region: region.clone(), resource_type: ResourceType::Compute, quantity: 2.5, // 2.5 GPU hours timestamp: Utc::now(), metadata: HashMap::from([ ("instance_type".to_string(), "h100-8x".to_string()), ("region".to_string(), region), ]), }; billing_manager.record_usage(usage_record.clone()).await?; // Verify usage was recorded let recorded_usage = billing_manager .get_usage_for_tenant(tenant_id, Utc::now().date_naive()) .await?; assert_eq!(recorded_usage.len(), 1); assert_eq!(recorded_usage[0].resource_type, ResourceType::Compute); assert_eq!(recorded_usage[0].quantity, 2.5); billing_manager.shutdown().await?; Ok(()) } #[tokio::test] async fn test_cost_calculation_tiered_pricing() -> PlatformResult<()> { let config = create_test_platform_config(); let billing_config = create_test_billing_config(); let billing_manager = BillingManager::new(&config, billing_config).await?; let cost_calculator = billing_manager.cost_calculator(); // Test tiered compute pricing let usage_records = vec![ UsageRecord { id: Uuid::new_v4(), tenant_id: Uuid::new_v4(), region: "us-west-2".to_string(), resource_type: ResourceType::Compute, quantity: 500.0, // First tier: 500 * $0.10 = $50.00 timestamp: Utc::now(), metadata: HashMap::new(), }, UsageRecord { id: Uuid::new_v4(), tenant_id: Uuid::new_v4(), region: "us-west-2".to_string(), resource_type: ResourceType::Compute, quantity: 1500.0, // Tiered: 1000 * $0.10 + 500 * $0.08 = $140.00 timestamp: Utc::now(), metadata: HashMap::new(), }, ]; let total_cost = cost_calculator.calculate_cost(&usage_records).await?; // Expected: $50.00 (500 * $0.10) + $140.00 (1000 * $0.10 + 500 * $0.08) = $190.00 assert_eq!((total_cost * 100.0).round() / 100.0, 190.0); Ok(()) } #[tokio::test] async fn test_cost_calculation_free_tier() -> PlatformResult<()> { let config = create_test_platform_config(); let billing_config = create_test_billing_config(); let billing_manager = BillingManager::new(&config, billing_config).await?; let cost_calculator = billing_manager.cost_calculator(); // Test free network egress tier let usage_records = vec![UsageRecord { id: Uuid::new_v4(), tenant_id: Uuid::new_v4(), region: "us-west-2".to_string(), resource_type: ResourceType::NetworkEgress, quantity: 50.0, // First 100GB free timestamp: Utc::now(), metadata: HashMap::new(), }]; let total_cost = cost_calculator.calculate_cost(&usage_records).await?; // Should be $0.00 for free tier assert_eq!(total_cost, 0.0); Ok(()) } #[tokio::test] async fn test_real_time_billing_alerts() -> PlatformResult<()> { let config = create_test_platform_config(); let mut billing_config = create_test_billing_config(); let tenant_id = Uuid::new_v4(); billing_config.alert_thresholds[0].tenant_id = tenant_id.to_string(); billing_config.alert_thresholds[0].monthly_limit = 100.0; // Low limit for testing let mut billing_manager = BillingManager::new(&config, billing_config).await?; billing_manager.start().await?; // Record usage that exceeds warning threshold (80% of $100 = $80) let high_usage = UsageRecord { id: Uuid::new_v4(), tenant_id, region: "us-west-2".to_string(), resource_type: ResourceType::Compute, quantity: 850.0, // 850 * $0.10 = $85.00 (exceeds warning) timestamp: Utc::now(), metadata: HashMap::new(), }; billing_manager.record_usage(high_usage).await?; // Wait for alert processing time::sleep(Duration::from_millis(100)).await; let alerts = billing_manager.get_active_alerts(tenant_id).await?; assert!(!alerts.is_empty()); assert_eq!(alerts[0].alert_type, BillingAlert::Warning); assert_eq!(alerts[0].tenant_id, tenant_id); billing_manager.shutdown().await?; Ok(()) } #[tokio::test] async fn test_usage_aggregation_across_regions() -> PlatformResult<()> { let config = create_test_platform_config(); let billing_config = create_test_billing_config(); let mut billing_manager = BillingManager::new(&config, billing_config).await?; billing_manager.start().await?; let tenant_id = Uuid::new_v4(); // Record usage across multiple regions let usage_records = vec![ UsageRecord { id: Uuid::new_v4(), tenant_id, region: "us-west-2".to_string(), resource_type: ResourceType::Compute, quantity: 100.0, timestamp: Utc::now(), metadata: HashMap::new(), }, UsageRecord { id: Uuid::new_v4(), tenant_id, region: "us-east-1".to_string(), resource_type: ResourceType::Compute, quantity: 150.0, timestamp: Utc::now(), metadata: HashMap::new(), }, UsageRecord { id: Uuid::new_v4(), tenant_id, region: "eu-west-1".to_string(), resource_type: ResourceType::Compute, quantity: 200.0, timestamp: Utc::now(), metadata: HashMap::new(), }, ]; for record in usage_records { billing_manager.record_usage(record).await?; } let aggregated_usage = billing_manager .get_aggregated_usage(tenant_id, AggregationPeriod::Daily) .await?; // Should aggregate to 450.0 total across all regions let total_compute = aggregated_usage .get(&ResourceType::Compute) .expect("Compute usage should be present"); assert_eq!(*total_compute, 450.0); billing_manager.shutdown().await?; Ok(()) } #[tokio::test] async fn test_invoice_generation_monthly() -> PlatformResult<()> { let config = create_test_platform_config(); let billing_config = create_test_billing_config(); let mut billing_manager = BillingManager::new(&config, billing_config).await?; billing_manager.start().await?; let tenant_id = Uuid::new_v4(); // Record usage for the month let usage_record = UsageRecord { id: Uuid::new_v4(), tenant_id, region: "us-west-2".to_string(), resource_type: ResourceType::Compute, quantity: 1000.0, // $100.00 timestamp: Utc::now(), metadata: HashMap::new(), }; billing_manager.record_usage(usage_record).await?; // Generate invoice let invoice = billing_manager .generate_invoice(tenant_id, Utc::now().date_naive()) .await?; assert_eq!(invoice.tenant_id, tenant_id); assert_eq!((invoice.total_amount * 100.0).round() / 100.0, 100.0); assert_eq!(invoice.line_items.len(), 1); assert_eq!(invoice.line_items[0].resource_type, ResourceType::Compute); billing_manager.shutdown().await?; Ok(()) } #[tokio::test] async fn test_invoice_late_fees() -> PlatformResult<()> { let config = create_test_platform_config(); let billing_config = create_test_billing_config(); let mut billing_manager = BillingManager::new(&config, billing_config).await?; billing_manager.start().await?; let tenant_id = Uuid::new_v4(); // Create overdue invoice (30+ days old) let old_date = Utc::now().date_naive() - chrono::Duration::days(35); let mut invoice = Invoice { id: Uuid::new_v4(), tenant_id, billing_period_start: old_date, billing_period_end: old_date + chrono::Duration::days(30), generated_at: DateTime::from_naive_utc_and_offset( old_date.and_hms_opt(0, 0, 0).unwrap(), Utc, ), due_date: old_date + chrono::Duration::days(30), total_amount: 100.0, line_items: vec![], status: rtx_platform::billing::InvoiceStatus::Outstanding, late_fees: 0.0, }; billing_manager.store_invoice(&invoice).await?; // Process late fees billing_manager.process_late_fees().await?; // Retrieve updated invoice let updated_invoice = billing_manager.get_invoice(invoice.id).await?; // Should have 1.5% late fee let expected_late_fee = 100.0 * 0.015; assert_eq!( (updated_invoice.late_fees * 1000.0).round() / 1000.0, expected_late_fee ); billing_manager.shutdown().await?; Ok(()) } #[tokio::test] async fn test_billing_precision_tracking() -> PlatformResult<()> { let config = create_test_platform_config(); let billing_config = create_test_billing_config(); let mut billing_manager = BillingManager::new(&config, billing_config).await?; billing_manager.start().await?; let tenant_id = Uuid::new_v4(); // Record precise usage with fractional values let precision_usage = UsageRecord { id: Uuid::new_v4(), tenant_id, region: "us-west-2".to_string(), resource_type: ResourceType::Storage, quantity: 1536.7832, // Precise GB-months timestamp: Utc::now(), metadata: HashMap::new(), }; billing_manager.record_usage(precision_usage).await?; let cost_calculator = billing_manager.cost_calculator(); let usage_records = billing_manager .get_usage_for_tenant(tenant_id, Utc::now().date_naive()) .await?; let total_cost = cost_calculator.calculate_cost(&usage_records).await?; // Verify precision is maintained in calculations // First 1024GB at $0.023 + 512.7832GB at $0.021 let expected_cost = (1024.0 * 0.023) + (512.7832 * 0.021); let cost_diff = (total_cost - expected_cost).abs(); assert!( cost_diff < 0.0001, "Cost calculation should be precise to 4 decimal places" ); billing_manager.shutdown().await?; Ok(()) } #[tokio::test] async fn test_concurrent_usage_recording() -> PlatformResult<()> { let config = create_test_platform_config(); let billing_config = create_test_billing_config(); let mut billing_manager = BillingManager::new(&config, billing_config).await?; billing_manager.start().await?; let tenant_id = Uuid::new_v4(); // Spawn multiple concurrent usage recording tasks let mut handles = vec![]; for i in 0..100 { let billing_manager_clone = billing_manager.clone(); let handle = tokio::spawn(async move { let usage_record = UsageRecord { id: Uuid::new_v4(), tenant_id, region: "us-west-2".to_string(), resource_type: ResourceType::Compute, quantity: i as f64 * 0.1, timestamp: Utc::now(), metadata: HashMap::new(), }; billing_manager_clone.record_usage(usage_record).await }); handles.push(handle); } // Wait for all tasks to complete for handle in handles { handle.await.unwrap()?; } let usage_records = billing_manager .get_usage_for_tenant(tenant_id, Utc::now().date_naive()) .await?; // All 100 records should be stored assert_eq!(usage_records.len(), 100); billing_manager.shutdown().await?; Ok(()) }