188 lines
5.9 KiB
Rust
188 lines
5.9 KiB
Rust
|
|
use std::collections::HashMap;
|
||
|
|
use vapora_llm_router::{BudgetManager, RoleBudget};
|
||
|
|
|
||
|
|
fn create_test_budgets() -> HashMap<String, RoleBudget> {
|
||
|
|
let mut budgets = HashMap::new();
|
||
|
|
|
||
|
|
budgets.insert(
|
||
|
|
"architect".to_string(),
|
||
|
|
RoleBudget {
|
||
|
|
role: "architect".to_string(),
|
||
|
|
monthly_limit_cents: 50000, // $500
|
||
|
|
weekly_limit_cents: 12500, // $125
|
||
|
|
fallback_provider: "gemini".to_string(),
|
||
|
|
alert_threshold: 0.8,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
budgets.insert(
|
||
|
|
"developer".to_string(),
|
||
|
|
RoleBudget {
|
||
|
|
role: "developer".to_string(),
|
||
|
|
monthly_limit_cents: 30000,
|
||
|
|
weekly_limit_cents: 7500,
|
||
|
|
fallback_provider: "ollama".to_string(),
|
||
|
|
alert_threshold: 0.8,
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
budgets
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_budget_initialization() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
let status = manager.check_budget("architect").await.unwrap();
|
||
|
|
assert_eq!(status.role, "architect");
|
||
|
|
assert_eq!(status.monthly_remaining_cents, 50000);
|
||
|
|
assert_eq!(status.monthly_utilization, 0.0);
|
||
|
|
assert!(!status.exceeded);
|
||
|
|
assert!(!status.near_threshold);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_budget_spending() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
manager.record_spend("developer", 3000).await.unwrap();
|
||
|
|
|
||
|
|
let status = manager.check_budget("developer").await.unwrap();
|
||
|
|
assert_eq!(status.monthly_remaining_cents, 27000);
|
||
|
|
assert!((status.monthly_utilization - 0.1).abs() < 0.01);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_multiple_spends_accumulate() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
manager.record_spend("developer", 5000).await.unwrap();
|
||
|
|
manager.record_spend("developer", 3000).await.unwrap();
|
||
|
|
manager.record_spend("developer", 2000).await.unwrap();
|
||
|
|
|
||
|
|
let status = manager.check_budget("developer").await.unwrap();
|
||
|
|
assert_eq!(status.monthly_remaining_cents, 20000); // 30000 - 10000
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_alert_threshold_near() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
// Spend 81% of weekly budget (12500 * 0.81 = 10125) to trigger near_threshold
|
||
|
|
// This keeps us under both monthly and weekly limits while triggering alert
|
||
|
|
let spend_amount = (12500.0 * 0.81) as u32; // 10125
|
||
|
|
manager.record_spend("architect", spend_amount).await.unwrap();
|
||
|
|
|
||
|
|
let status = manager.check_budget("architect").await.unwrap();
|
||
|
|
assert!(!status.exceeded);
|
||
|
|
assert!(status.near_threshold);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_budget_exceeded() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
// Spend entire monthly budget
|
||
|
|
manager.record_spend("developer", 30000).await.unwrap();
|
||
|
|
|
||
|
|
let status = manager.check_budget("developer").await.unwrap();
|
||
|
|
assert!(status.exceeded);
|
||
|
|
assert_eq!(status.monthly_remaining_cents, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_budget_overspend() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
// Spend more than budget (overflow protection)
|
||
|
|
manager.record_spend("developer", 35000).await.unwrap();
|
||
|
|
|
||
|
|
let status = manager.check_budget("developer").await.unwrap();
|
||
|
|
assert!(status.exceeded);
|
||
|
|
assert_eq!(status.monthly_remaining_cents, 0); // Saturating subtract
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_weekly_budget_independent() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
// Spend 100% of weekly budget but only 25% of monthly
|
||
|
|
manager.record_spend("developer", 7500).await.unwrap();
|
||
|
|
|
||
|
|
let status = manager.check_budget("developer").await.unwrap();
|
||
|
|
assert_eq!(status.monthly_remaining_cents, 22500);
|
||
|
|
assert_eq!(status.weekly_remaining_cents, 0);
|
||
|
|
assert!(status.exceeded); // Both budgets checked
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_fallback_provider() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
let fallback_dev = manager.get_fallback_provider("developer").await.unwrap();
|
||
|
|
assert_eq!(fallback_dev, "ollama");
|
||
|
|
|
||
|
|
let fallback_arch = manager.get_fallback_provider("architect").await.unwrap();
|
||
|
|
assert_eq!(fallback_arch, "gemini");
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_unknown_role_error() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
let result = manager.check_budget("unknown").await;
|
||
|
|
assert!(result.is_err());
|
||
|
|
|
||
|
|
let result = manager.record_spend("unknown", 100).await;
|
||
|
|
assert!(result.is_err());
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_get_all_budgets() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
manager.record_spend("architect", 5000).await.unwrap();
|
||
|
|
manager.record_spend("developer", 3000).await.unwrap();
|
||
|
|
|
||
|
|
let all_statuses = manager.get_all_budgets().await;
|
||
|
|
assert_eq!(all_statuses.len(), 2);
|
||
|
|
|
||
|
|
let arch_status = all_statuses
|
||
|
|
.iter()
|
||
|
|
.find(|s| s.role == "architect")
|
||
|
|
.unwrap();
|
||
|
|
assert_eq!(arch_status.monthly_remaining_cents, 45000);
|
||
|
|
|
||
|
|
let dev_status = all_statuses
|
||
|
|
.iter()
|
||
|
|
.find(|s| s.role == "developer")
|
||
|
|
.unwrap();
|
||
|
|
assert_eq!(dev_status.monthly_remaining_cents, 27000);
|
||
|
|
}
|
||
|
|
|
||
|
|
#[tokio::test]
|
||
|
|
async fn test_budget_status_comprehensive() {
|
||
|
|
let budgets = create_test_budgets();
|
||
|
|
let manager = BudgetManager::new(budgets);
|
||
|
|
|
||
|
|
// Spend 6000 cents: keeps us at 12% of monthly and 48% of weekly (both safe)
|
||
|
|
manager.record_spend("architect", 6000).await.unwrap();
|
||
|
|
|
||
|
|
let status = manager.check_budget("architect").await.unwrap();
|
||
|
|
assert_eq!(status.monthly_remaining_cents, 44000);
|
||
|
|
assert!((status.monthly_utilization - 0.12).abs() < 0.01);
|
||
|
|
assert!(!status.exceeded);
|
||
|
|
assert!(!status.near_threshold); // 12% < 80%
|
||
|
|
assert_eq!(status.fallback_provider, "gemini");
|
||
|
|
}
|