- Exclude problematic markdown files from linting (existing legacy issues) - Make clippy check less aggressive (warnings only, not -D warnings) - Move cargo test to manual stage (too slow for pre-commit) - Exclude SVG files from end-of-file-fixer and trailing-whitespace - Add markdown linting exclusions for existing documentation This allows pre-commit hooks to run successfully on new code without blocking commits due to existing issues in legacy documentation files.
185 lines
5.8 KiB
Rust
185 lines
5.8 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");
|
|
}
|