use super::*;
use std::sync::Arc;
#[tokio::test]
async fn test_budget_system_initialization() {
let (manager, alert_manager) = init_budget_system();
assert_eq!(manager.budget_count(), 0);
assert!(alert_manager.is_enabled().await);
}
#[tokio::test]
async fn test_budget_system_with_custom_config() {
let manager_config = BudgetManagerConfig {
enabled: true,
default_soft_limit_percentage: 0.75,
block_on_exceeded: true,
auto_reset_enabled: false,
reset_check_interval_secs: 300,
};
let alert_config = AlertConfig {
enabled: true,
soft_limit_percentage: 0.75,
warning_thresholds: vec![0.9],
max_history_size: 500,
duplicate_suppression_secs: 1800,
};
let (manager, alert_manager) = init_budget_system_with_config(manager_config, alert_config);
assert!(manager.is_enabled().await);
let config = alert_manager.get_config().await;
assert_eq!(config.soft_limit_percentage, 0.75);
}
#[tokio::test]
async fn test_end_to_end_budget_workflow() {
let manager = Arc::new(BudgetManager::new());
let alert_manager = Arc::new(BudgetAlertManager::new());
let config = BudgetConfig::new("User 1 Budget", 100.0)
.with_reset_period(ResetPeriod::Monthly)
.with_currency(Currency::USD);
let budget = manager
.create_budget(BudgetScope::User("user-1".to_string()), config)
.await
.unwrap();
assert_eq!(budget.name, "User 1 Budget");
assert_eq!(budget.max_budget, 100.0);
assert_eq!(budget.soft_limit, 80.0);
assert_eq!(budget.status(), BudgetStatus::Ok);
let result1 = manager
.record_spend(&BudgetScope::User("user-1".to_string()), 30.0)
.await
.unwrap();
assert_eq!(result1.current_spend, 30.0);
assert_eq!(result1.new_status, BudgetStatus::Ok);
assert!(!result1.should_alert_soft_limit);
let result2 = manager
.record_spend(&BudgetScope::User("user-1".to_string()), 51.0)
.await
.unwrap();
assert_eq!(result2.current_spend, 81.0);
assert_eq!(result2.new_status, BudgetStatus::Warning);
assert!(result2.should_alert_soft_limit);
let budget_now = manager
.get_budget(&BudgetScope::User("user-1".to_string()))
.unwrap();
alert_manager
.process_spend_result(&result2, &budget_now)
.await;
let alerts = alert_manager.get_all_alerts().await;
assert_eq!(alerts.len(), 1);
assert_eq!(alerts[0].alert_type, BudgetAlertType::SoftLimitReached);
let result3 = manager
.record_spend(&BudgetScope::User("user-1".to_string()), 20.0)
.await
.unwrap();
assert_eq!(result3.current_spend, 101.0);
assert_eq!(result3.new_status, BudgetStatus::Exceeded);
assert!(result3.should_alert_exceeded);
let check_result = manager
.check_spend(&BudgetScope::User("user-1".to_string()), 1.0)
.await;
assert!(!check_result.allowed);
manager
.reset_budget(&BudgetScope::User("user-1".to_string()))
.await
.unwrap();
let after_reset = manager
.get_budget(&BudgetScope::User("user-1".to_string()))
.unwrap();
assert_eq!(after_reset.current_spend, 0.0);
assert_eq!(after_reset.status(), BudgetStatus::Ok);
}
#[tokio::test]
async fn test_multiple_budget_scopes() {
let manager = BudgetManager::new();
manager
.create_budget(BudgetScope::Global, BudgetConfig::new("Global", 10000.0))
.await
.unwrap();
manager
.create_budget(
BudgetScope::Team("team-1".to_string()),
BudgetConfig::new("Team 1", 1000.0),
)
.await
.unwrap();
manager
.create_budget(
BudgetScope::User("user-1".to_string()),
BudgetConfig::new("User 1", 100.0),
)
.await
.unwrap();
manager
.create_budget(
BudgetScope::ApiKey("sk-123...".to_string()),
BudgetConfig::new("API Key 1", 50.0),
)
.await
.unwrap();
manager
.create_budget(
BudgetScope::Provider("openai".to_string()),
BudgetConfig::new("OpenAI Provider", 5000.0),
)
.await
.unwrap();
manager
.create_budget(
BudgetScope::Model("gpt-4".to_string()),
BudgetConfig::new("GPT-4 Model", 2000.0),
)
.await
.unwrap();
assert_eq!(manager.budget_count(), 6);
let user_budgets = manager.list_budgets_filtered(Some("user"), None);
assert_eq!(user_budgets.len(), 1);
let provider_budgets = manager.list_budgets_filtered(Some("provider"), None);
assert_eq!(provider_budgets.len(), 1);
}
#[cfg(feature = "gateway")]
#[tokio::test]
async fn test_budget_recorder() {
let manager = Arc::new(BudgetManager::new());
manager
.create_budget(BudgetScope::Global, BudgetConfig::new("Global", 10000.0))
.await
.unwrap();
manager
.create_budget(
BudgetScope::User("user-1".to_string()),
BudgetConfig::new("User 1", 100.0),
)
.await
.unwrap();
manager
.create_budget(
BudgetScope::Model("gpt-4".to_string()),
BudgetConfig::new("GPT-4", 1000.0),
)
.await
.unwrap();
let recorder = BudgetRecorder::new(Arc::clone(&manager));
recorder
.record_request_spend(Some("user-1"), None, None, Some("gpt-4"), None, 0.05)
.await;
assert_eq!(manager.get_current_spend(&BudgetScope::Global), 0.05);
assert_eq!(
manager.get_current_spend(&BudgetScope::User("user-1".to_string())),
0.05
);
assert_eq!(
manager.get_current_spend(&BudgetScope::Model("gpt-4".to_string())),
0.05
);
}
#[tokio::test]
async fn test_budget_summary() {
let manager = BudgetManager::new();
manager
.create_budget(
BudgetScope::User("user-1".to_string()),
BudgetConfig::new("User 1", 100.0),
)
.await
.unwrap();
manager
.create_budget(
BudgetScope::User("user-2".to_string()),
BudgetConfig::new("User 2", 100.0),
)
.await
.unwrap();
manager
.create_budget(
BudgetScope::User("user-3".to_string()),
BudgetConfig::new("User 3", 100.0),
)
.await
.unwrap();
manager
.record_spend(&BudgetScope::User("user-1".to_string()), 20.0)
.await;
manager
.record_spend(&BudgetScope::User("user-2".to_string()), 85.0)
.await; manager
.record_spend(&BudgetScope::User("user-3".to_string()), 110.0)
.await;
let summary = manager.get_summary();
assert_eq!(summary.total_budgets, 3);
assert_eq!(summary.total_allocated, 300.0);
assert_eq!(summary.total_spent, 215.0);
assert_eq!(summary.total_remaining, 85.0);
assert_eq!(summary.ok_count, 1);
assert_eq!(summary.warning_count, 1);
assert_eq!(summary.exceeded_count, 1);
}
#[tokio::test]
async fn test_alert_workflow() {
let manager = Arc::new(BudgetManager::new());
let alert_manager = Arc::new(BudgetAlertManager::new());
manager
.create_budget(BudgetScope::Global, BudgetConfig::new("Global", 100.0))
.await
.unwrap();
let result = manager
.record_spend(&BudgetScope::Global, 85.0)
.await
.unwrap();
let budget = manager.get_budget(&BudgetScope::Global).unwrap();
alert_manager.process_spend_result(&result, &budget).await;
let stats = alert_manager.get_alert_stats().await;
assert_eq!(stats.warning_count, 1);
assert_eq!(stats.soft_limit_alerts, 1);
assert_eq!(stats.unacknowledged, 1);
let alerts = alert_manager.get_all_alerts().await;
for alert in &alerts {
alert_manager.acknowledge_alert(&alert.id).await;
}
let stats_after = alert_manager.get_alert_stats().await;
assert_eq!(stats_after.unacknowledged, 0);
}
#[tokio::test]
async fn test_concurrent_budget_operations() {
use std::sync::Arc;
use tokio::task;
let manager = Arc::new(BudgetManager::new());
manager
.create_budget(BudgetScope::Global, BudgetConfig::new("Global", 10000.0))
.await
.unwrap();
let mut handles = vec![];
for _i in 0..10 {
let manager_clone = Arc::clone(&manager);
let handle = task::spawn(async move {
for _j in 0..100 {
manager_clone.record_spend(&BudgetScope::Global, 1.0).await;
}
});
handles.push(handle);
}
for handle in handles {
handle.await.unwrap();
}
let spend = manager.get_current_spend(&BudgetScope::Global);
assert_eq!(spend, 1000.0);
}
#[tokio::test]
async fn test_budget_scope_parsing() {
let scopes = vec![
BudgetScope::User("user-123".to_string()),
BudgetScope::Team("team-456".to_string()),
BudgetScope::ApiKey("sk-abc123".to_string()),
BudgetScope::Provider("openai".to_string()),
BudgetScope::Model("gpt-4-turbo".to_string()),
BudgetScope::Global,
];
for scope in scopes {
let key = scope.to_key();
let parsed = BudgetScope::from_key(&key);
assert_eq!(parsed, Some(scope));
}
}
#[tokio::test]
async fn test_budget_disabled() {
let config = BudgetManagerConfig {
enabled: false,
..Default::default()
};
let manager = BudgetManager::with_config(config);
manager
.create_budget(BudgetScope::Global, BudgetConfig::new("Global", 100.0))
.await
.unwrap();
manager.record_spend(&BudgetScope::Global, 150.0).await;
let result = manager.check_spend(&BudgetScope::Global, 50.0).await;
assert!(result.allowed); }
#[tokio::test]
async fn test_budget_without_blocking() {
let config = BudgetManagerConfig {
block_on_exceeded: false,
..Default::default()
};
let manager = BudgetManager::with_config(config);
manager
.create_budget(BudgetScope::Global, BudgetConfig::new("Global", 100.0))
.await
.unwrap();
manager.record_spend(&BudgetScope::Global, 150.0).await;
let result = manager.check_spend(&BudgetScope::Global, 50.0).await;
assert!(result.allowed);
assert_eq!(result.status, BudgetStatus::Exceeded);
}