use crate::billing::{BillingError, LimitValue, UsageMetric}; use std::collections::HashMap; use std::sync::Arc; use tokio::sync::RwLock; use uuid::Uuid; pub struct QuotaManager { usage_cache: Arc>>, alert_thresholds: Vec, } #[derive(Debug, Clone)] pub struct OrganizationQuotas { pub organization_id: Uuid, pub plan_id: String, pub limits: QuotaLimits, pub usage: QuotaUsage, pub period_start: chrono::DateTime, pub period_end: chrono::DateTime, } #[derive(Debug, Clone)] pub struct QuotaLimits { pub messages_per_day: LimitValue, pub storage_bytes: LimitValue, pub api_calls_per_day: LimitValue, pub bots: LimitValue, pub users: LimitValue, pub kb_documents: LimitValue, pub apps: LimitValue, } #[derive(Debug, Clone, Default)] pub struct QuotaUsage { pub messages_today: u64, pub storage_bytes: u64, pub api_calls_today: u64, pub bots: u64, pub users: u64, pub kb_documents: u64, pub apps: u64, pub last_reset: Option>, } #[derive(Debug, Clone)] pub struct QuotaCheckResult { pub allowed: bool, pub metric: UsageMetric, pub current: u64, pub limit: Option, pub remaining: Option, pub percentage_used: f64, pub alerts: Vec, } #[derive(Debug, Clone)] pub struct QuotaAlert { pub metric: UsageMetric, pub threshold: f64, pub current_percentage: f64, pub message: String, } #[derive(Debug, Clone)] pub enum QuotaAction { Allow, Warn { message: String, percentage: f64 }, Block { message: String }, } impl QuotaManager { pub fn new() -> Self { Self { usage_cache: Arc::new(RwLock::new(HashMap::new())), alert_thresholds: vec![80.0, 90.0, 100.0], } } pub fn with_thresholds(thresholds: Vec) -> Self { Self { usage_cache: Arc::new(RwLock::new(HashMap::new())), alert_thresholds: thresholds, } } pub async fn set_quotas(&self, quotas: OrganizationQuotas) { let mut cache = self.usage_cache.write().await; cache.insert(quotas.organization_id, quotas); } pub async fn get_quotas(&self, organization_id: Uuid) -> Option { let cache = self.usage_cache.read().await; cache.get(&organization_id).cloned() } pub async fn check_quota( &self, organization_id: Uuid, metric: UsageMetric, increment: u64, ) -> Result { let cache = self.usage_cache.read().await; let quotas = cache .get(&organization_id) .ok_or(BillingError::SubscriptionNotFound)?; let (current, limit) = self.get_metric_values(quotas, metric); let new_value = current + increment; let (allowed, remaining, percentage) = match limit { LimitValue::Unlimited => (true, None, 0.0), LimitValue::Limited(max) => { let allowed = new_value <= max; let remaining = if new_value > max { 0 } else { max - new_value }; let percentage = (new_value as f64 / max as f64) * 100.0; (allowed, Some(remaining), percentage) } }; let alerts = self.generate_alerts(metric, percentage); Ok(QuotaCheckResult { allowed, metric, current: new_value, limit: limit.value(), remaining, percentage_used: percentage, alerts, }) } pub async fn increment_usage( &self, organization_id: Uuid, metric: UsageMetric, amount: u64, ) -> Result { let check_result = self.check_quota(organization_id, metric, amount).await?; if check_result.allowed { let mut cache = self.usage_cache.write().await; if let Some(quotas) = cache.get_mut(&organization_id) { self.apply_increment(&mut quotas.usage, metric, amount); } } Ok(check_result) } pub async fn decrement_usage( &self, organization_id: Uuid, metric: UsageMetric, amount: u64, ) -> Result<(), BillingError> { let mut cache = self.usage_cache.write().await; let quotas = cache .get_mut(&organization_id) .ok_or(BillingError::SubscriptionNotFound)?; self.apply_decrement(&mut quotas.usage, metric, amount); Ok(()) } pub async fn set_usage( &self, organization_id: Uuid, metric: UsageMetric, value: u64, ) -> Result<(), BillingError> { let mut cache = self.usage_cache.write().await; let quotas = cache .get_mut(&organization_id) .ok_or(BillingError::SubscriptionNotFound)?; self.set_metric_value(&mut quotas.usage, metric, value); Ok(()) } pub async fn reset_daily_quotas(&self, organization_id: Uuid) -> Result<(), BillingError> { let mut cache = self.usage_cache.write().await; let quotas = cache .get_mut(&organization_id) .ok_or(BillingError::SubscriptionNotFound)?; quotas.usage.messages_today = 0; quotas.usage.api_calls_today = 0; quotas.usage.last_reset = Some(chrono::Utc::now()); Ok(()) } pub async fn reset_all_daily_quotas(&self) { let mut cache = self.usage_cache.write().await; let now = chrono::Utc::now(); for quotas in cache.values_mut() { quotas.usage.messages_today = 0; quotas.usage.api_calls_today = 0; quotas.usage.last_reset = Some(now); } } pub async fn get_usage_summary(&self, organization_id: Uuid) -> Result { let cache = self.usage_cache.read().await; let quotas = cache .get(&organization_id) .ok_or(BillingError::SubscriptionNotFound)?; let metrics = vec![ UsageMetric::Messages, UsageMetric::StorageBytes, UsageMetric::ApiCalls, UsageMetric::Bots, UsageMetric::Users, UsageMetric::KbDocuments, UsageMetric::Apps, ]; let items: Vec = metrics .into_iter() .map(|metric| { let (current, limit) = self.get_metric_values(quotas, metric); let (remaining, percentage) = match limit { LimitValue::Unlimited => (None, 0.0), LimitValue::Limited(max) => { let remaining = if current > max { 0 } else { max - current }; let percentage = (current as f64 / max as f64) * 100.0; (Some(remaining), percentage) } }; UsageSummaryItem { metric, current, limit: limit.value(), remaining, percentage_used: percentage, is_unlimited: limit.is_unlimited(), } }) .collect(); Ok(UsageSummary { organization_id, plan_id: quotas.plan_id.clone(), items, period_start: quotas.period_start, period_end: quotas.period_end, }) } pub async fn check_action(&self, organization_id: Uuid, metric: UsageMetric) -> QuotaAction { match self.check_quota(organization_id, metric, 1).await { Ok(result) => { if !result.allowed { QuotaAction::Block { message: format!( "Quota exceeded for {:?}. Current: {}, Limit: {:?}", metric, result.current, result.limit ), } } else if result.percentage_used >= 90.0 { QuotaAction::Warn { message: format!( "Approaching quota limit for {:?}. {}% used.", metric, result.percentage_used as u32 ), percentage: result.percentage_used, } } else { QuotaAction::Allow } } Err(_) => QuotaAction::Block { message: "Unable to verify quota".to_string(), }, } } fn get_metric_values(&self, quotas: &OrganizationQuotas, metric: UsageMetric) -> (u64, LimitValue) { match metric { UsageMetric::Messages => (quotas.usage.messages_today, quotas.limits.messages_per_day), UsageMetric::StorageBytes => (quotas.usage.storage_bytes, quotas.limits.storage_bytes), UsageMetric::ApiCalls => (quotas.usage.api_calls_today, quotas.limits.api_calls_per_day), UsageMetric::Bots => (quotas.usage.bots, quotas.limits.bots), UsageMetric::Users => (quotas.usage.users, quotas.limits.users), UsageMetric::KbDocuments => (quotas.usage.kb_documents, quotas.limits.kb_documents), UsageMetric::Apps => (quotas.usage.apps, quotas.limits.apps), } } fn apply_increment(&self, usage: &mut QuotaUsage, metric: UsageMetric, amount: u64) { match metric { UsageMetric::Messages => usage.messages_today += amount, UsageMetric::StorageBytes => usage.storage_bytes += amount, UsageMetric::ApiCalls => usage.api_calls_today += amount, UsageMetric::Bots => usage.bots += amount, UsageMetric::Users => usage.users += amount, UsageMetric::KbDocuments => usage.kb_documents += amount, UsageMetric::Apps => usage.apps += amount, } } fn apply_decrement(&self, usage: &mut QuotaUsage, metric: UsageMetric, amount: u64) { match metric { UsageMetric::Messages => usage.messages_today = usage.messages_today.saturating_sub(amount), UsageMetric::StorageBytes => usage.storage_bytes = usage.storage_bytes.saturating_sub(amount), UsageMetric::ApiCalls => usage.api_calls_today = usage.api_calls_today.saturating_sub(amount), UsageMetric::Bots => usage.bots = usage.bots.saturating_sub(amount), UsageMetric::Users => usage.users = usage.users.saturating_sub(amount), UsageMetric::KbDocuments => usage.kb_documents = usage.kb_documents.saturating_sub(amount), UsageMetric::Apps => usage.apps = usage.apps.saturating_sub(amount), } } fn set_metric_value(&self, usage: &mut QuotaUsage, metric: UsageMetric, value: u64) { match metric { UsageMetric::Messages => usage.messages_today = value, UsageMetric::StorageBytes => usage.storage_bytes = value, UsageMetric::ApiCalls => usage.api_calls_today = value, UsageMetric::Bots => usage.bots = value, UsageMetric::Users => usage.users = value, UsageMetric::KbDocuments => usage.kb_documents = value, UsageMetric::Apps => usage.apps = value, } } fn generate_alerts(&self, metric: UsageMetric, percentage: f64) -> Vec { self.alert_thresholds .iter() .filter(|&&threshold| percentage >= threshold) .map(|&threshold| QuotaAlert { metric, threshold, current_percentage: percentage, message: self.alert_message(metric, threshold, percentage), }) .collect() } fn alert_message(&self, metric: UsageMetric, threshold: f64, current: f64) -> String { let metric_name = match metric { UsageMetric::Messages => "messages", UsageMetric::StorageBytes => "storage", UsageMetric::ApiCalls => "API calls", UsageMetric::Bots => "bots", UsageMetric::Users => "users", UsageMetric::KbDocuments => "KB documents", UsageMetric::Apps => "apps", }; if current >= 100.0 { format!("You have reached your {} quota limit.", metric_name) } else { format!( "You have used {}% of your {} quota (threshold: {}%).", current as u32, metric_name, threshold as u32 ) } } } impl Default for QuotaManager { fn default() -> Self { Self::new() } } #[derive(Debug, Clone)] pub struct UsageSummary { pub organization_id: Uuid, pub plan_id: String, pub items: Vec, pub period_start: chrono::DateTime, pub period_end: chrono::DateTime, } #[derive(Debug, Clone)] pub struct UsageSummaryItem { pub metric: UsageMetric, pub current: u64, pub limit: Option, pub remaining: Option, pub percentage_used: f64, pub is_unlimited: bool, } pub struct QuotaMiddleware { quota_manager: Arc, } impl QuotaMiddleware { pub fn new(quota_manager: Arc) -> Self { Self { quota_manager } } pub async fn check_and_increment( &self, organization_id: Uuid, metric: UsageMetric, ) -> Result { self.quota_manager.increment_usage(organization_id, metric, 1).await } pub async fn check_storage( &self, organization_id: Uuid, bytes: u64, ) -> Result { self.quota_manager.check_quota(organization_id, UsageMetric::StorageBytes, bytes).await } pub async fn add_storage( &self, organization_id: Uuid, bytes: u64, ) -> Result { self.quota_manager.increment_usage(organization_id, UsageMetric::StorageBytes, bytes).await } pub async fn remove_storage( &self, organization_id: Uuid, bytes: u64, ) -> Result<(), BillingError> { self.quota_manager.decrement_usage(organization_id, UsageMetric::StorageBytes, bytes).await } } pub async fn daily_quota_reset_job(quota_manager: Arc) { loop { let now = chrono::Utc::now(); let tomorrow = (now + chrono::Duration::days(1)) .date_naive() .and_hms_opt(0, 0, 0) .map(|dt| chrono::DateTime::::from_naive_utc_and_offset(dt, chrono::Utc)); if let Some(next_reset) = tomorrow { let duration = next_reset - now; if let Ok(std_duration) = duration.to_std() { tokio::time::sleep(std_duration).await; } } quota_manager.reset_all_daily_quotas().await; tracing::info!("Daily quotas reset completed"); } } #[cfg(test)] mod tests { use super::*; use crate::billing::LimitValue; fn create_test_quotas(org_id: Uuid, plan_id: &str) -> OrganizationQuotas { let now = chrono::Utc::now(); OrganizationQuotas { organization_id: org_id, plan_id: plan_id.to_string(), limits: QuotaLimits { messages_per_day: LimitValue::Limited(100), storage_bytes: LimitValue::Limited(1024 * 1024 * 100), api_calls_per_day: LimitValue::Limited(1000), bots: LimitValue::Limited(5), users: LimitValue::Limited(10), kb_documents: LimitValue::Limited(50), apps: LimitValue::Limited(10), }, usage: QuotaUsage::default(), period_start: now, period_end: now + chrono::Duration::days(30), } } fn create_unlimited_quotas(org_id: Uuid) -> OrganizationQuotas { let now = chrono::Utc::now(); OrganizationQuotas { organization_id: org_id, plan_id: "enterprise".to_string(), limits: QuotaLimits { messages_per_day: LimitValue::Unlimited, storage_bytes: LimitValue::Unlimited, api_calls_per_day: LimitValue::Unlimited, bots: LimitValue::Unlimited, users: LimitValue::Unlimited, kb_documents: LimitValue::Unlimited, apps: LimitValue::Unlimited, }, usage: QuotaUsage::default(), period_start: now, period_end: now + chrono::Duration::days(30), } } #[tokio::test] async fn test_set_and_get_quotas() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas.clone()).await; let retrieved = manager.get_quotas(org_id).await; assert!(retrieved.is_some()); let retrieved = retrieved.unwrap(); assert_eq!(retrieved.organization_id, org_id); assert_eq!(retrieved.plan_id, "business"); } #[tokio::test] async fn test_get_quotas_nonexistent() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let result = manager.get_quotas(org_id).await; assert!(result.is_none()); } #[tokio::test] async fn test_check_quota_within_limit() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; let result = manager .check_quota(org_id, UsageMetric::Messages, 50) .await; assert!(result.is_ok()); let check = result.unwrap(); assert!(check.allowed); assert_eq!(check.current, 50); assert_eq!(check.limit, Some(100)); assert_eq!(check.remaining, Some(50)); assert_eq!(check.percentage_used, 50.0); } #[tokio::test] async fn test_check_quota_at_limit() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; let result = manager .check_quota(org_id, UsageMetric::Messages, 100) .await; assert!(result.is_ok()); let check = result.unwrap(); assert!(check.allowed); assert_eq!(check.remaining, Some(0)); assert_eq!(check.percentage_used, 100.0); } #[tokio::test] async fn test_check_quota_exceeds_limit() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; let result = manager .check_quota(org_id, UsageMetric::Messages, 101) .await; assert!(result.is_ok()); let check = result.unwrap(); assert!(!check.allowed); assert_eq!(check.remaining, Some(0)); } #[tokio::test] async fn test_check_quota_unlimited() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_unlimited_quotas(org_id); manager.set_quotas(quotas).await; let result = manager .check_quota(org_id, UsageMetric::Messages, 1_000_000) .await; assert!(result.is_ok()); let check = result.unwrap(); assert!(check.allowed); assert_eq!(check.limit, None); assert_eq!(check.remaining, None); assert_eq!(check.percentage_used, 0.0); } #[tokio::test] async fn test_check_quota_subscription_not_found() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let result = manager .check_quota(org_id, UsageMetric::Messages, 1) .await; assert!(result.is_err()); assert!(matches!(result.unwrap_err(), BillingError::SubscriptionNotFound)); } #[tokio::test] async fn test_increment_usage() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; let result = manager .increment_usage(org_id, UsageMetric::Messages, 10) .await; assert!(result.is_ok()); assert!(result.unwrap().allowed); let quotas = manager.get_quotas(org_id).await.unwrap(); assert_eq!(quotas.usage.messages_today, 10); } #[tokio::test] async fn test_increment_usage_blocked_when_exceeded() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; let result = manager .increment_usage(org_id, UsageMetric::Messages, 150) .await; assert!(result.is_ok()); assert!(!result.unwrap().allowed); let quotas = manager.get_quotas(org_id).await.unwrap(); assert_eq!(quotas.usage.messages_today, 0); } #[tokio::test] async fn test_decrement_usage() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; manager.increment_usage(org_id, UsageMetric::Bots, 3).await.unwrap(); let result = manager.decrement_usage(org_id, UsageMetric::Bots, 1).await; assert!(result.is_ok()); let quotas = manager.get_quotas(org_id).await.unwrap(); assert_eq!(quotas.usage.bots, 2); } #[tokio::test] async fn test_decrement_usage_saturating() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; let result = manager.decrement_usage(org_id, UsageMetric::Bots, 100).await; assert!(result.is_ok()); let quotas = manager.get_quotas(org_id).await.unwrap(); assert_eq!(quotas.usage.bots, 0); } #[tokio::test] async fn test_set_usage() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; let result = manager.set_usage(org_id, UsageMetric::StorageBytes, 5000).await; assert!(result.is_ok()); let quotas = manager.get_quotas(org_id).await.unwrap(); assert_eq!(quotas.usage.storage_bytes, 5000); } #[tokio::test] async fn test_reset_daily_quotas() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; manager.increment_usage(org_id, UsageMetric::Messages, 50).await.unwrap(); manager.increment_usage(org_id, UsageMetric::ApiCalls, 100).await.unwrap(); manager.increment_usage(org_id, UsageMetric::Bots, 2).await.unwrap(); let result = manager.reset_daily_quotas(org_id).await; assert!(result.is_ok()); let quotas = manager.get_quotas(org_id).await.unwrap(); assert_eq!(quotas.usage.messages_today, 0); assert_eq!(quotas.usage.api_calls_today, 0); assert_eq!(quotas.usage.bots, 2); assert!(quotas.usage.last_reset.is_some()); } #[tokio::test] async fn test_reset_all_daily_quotas() { let manager = QuotaManager::new(); let org_id1 = Uuid::new_v4(); let org_id2 = Uuid::new_v4(); manager.set_quotas(create_test_quotas(org_id1, "business")).await; manager.set_quotas(create_test_quotas(org_id2, "personal")).await; manager.increment_usage(org_id1, UsageMetric::Messages, 50).await.unwrap(); manager.increment_usage(org_id2, UsageMetric::Messages, 30).await.unwrap(); manager.reset_all_daily_quotas().await; let q1 = manager.get_quotas(org_id1).await.unwrap(); let q2 = manager.get_quotas(org_id2).await.unwrap(); assert_eq!(q1.usage.messages_today, 0); assert_eq!(q2.usage.messages_today, 0); } #[tokio::test] async fn test_get_usage_summary() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; manager.increment_usage(org_id, UsageMetric::Messages, 50).await.unwrap(); manager.increment_usage(org_id, UsageMetric::Bots, 2).await.unwrap(); let result = manager.get_usage_summary(org_id).await; assert!(result.is_ok()); let summary = result.unwrap(); assert_eq!(summary.organization_id, org_id); assert_eq!(summary.plan_id, "business"); assert_eq!(summary.items.len(), 7); let messages_item = summary.items.iter().find(|i| i.metric == UsageMetric::Messages).unwrap(); assert_eq!(messages_item.current, 50); assert_eq!(messages_item.limit, Some(100)); assert_eq!(messages_item.remaining, Some(50)); assert_eq!(messages_item.percentage_used, 50.0); assert!(!messages_item.is_unlimited); } #[tokio::test] async fn test_check_action_allow() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; let action = manager.check_action(org_id, UsageMetric::Messages).await; assert!(matches!(action, QuotaAction::Allow)); } #[tokio::test] async fn test_check_action_warn() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; manager.set_usage(org_id, UsageMetric::Messages, 91).await.unwrap(); let action = manager.check_action(org_id, UsageMetric::Messages).await; assert!(matches!(action, QuotaAction::Warn { .. })); } #[tokio::test] async fn test_check_action_block() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; manager.set_usage(org_id, UsageMetric::Messages, 100).await.unwrap(); let action = manager.check_action(org_id, UsageMetric::Messages).await; assert!(matches!(action, QuotaAction::Block { .. })); } #[tokio::test] async fn test_alerts_generated_at_thresholds() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; let result = manager.check_quota(org_id, UsageMetric::Messages, 85).await.unwrap(); assert_eq!(result.alerts.len(), 1); assert_eq!(result.alerts[0].threshold, 80.0); let result = manager.check_quota(org_id, UsageMetric::Messages, 95).await.unwrap(); assert_eq!(result.alerts.len(), 2); let result = manager.check_quota(org_id, UsageMetric::Messages, 100).await.unwrap(); assert_eq!(result.alerts.len(), 3); } #[tokio::test] async fn test_quota_middleware_check_and_increment() { let manager = Arc::new(QuotaManager::new()); let middleware = QuotaMiddleware::new(manager.clone()); let org_id = Uuid::new_v4(); manager.set_quotas(create_test_quotas(org_id, "business")).await; let result = middleware.check_and_increment(org_id, UsageMetric::ApiCalls).await; assert!(result.is_ok()); assert!(result.unwrap().allowed); let quotas = manager.get_quotas(org_id).await.unwrap(); assert_eq!(quotas.usage.api_calls_today, 1); } #[tokio::test] async fn test_quota_middleware_storage_operations() { let manager = Arc::new(QuotaManager::new()); let middleware = QuotaMiddleware::new(manager.clone()); let org_id = Uuid::new_v4(); manager.set_quotas(create_test_quotas(org_id, "business")).await; let check = middleware.check_storage(org_id, 1000).await; assert!(check.is_ok()); assert!(check.unwrap().allowed); let add = middleware.add_storage(org_id, 1000).await; assert!(add.is_ok()); let quotas = manager.get_quotas(org_id).await.unwrap(); assert_eq!(quotas.usage.storage_bytes, 1000); let remove = middleware.remove_storage(org_id, 500).await; assert!(remove.is_ok()); let quotas = manager.get_quotas(org_id).await.unwrap(); assert_eq!(quotas.usage.storage_bytes, 500); } #[test] fn test_quota_usage_default() { let usage = QuotaUsage::default(); assert_eq!(usage.messages_today, 0); assert_eq!(usage.storage_bytes, 0); assert_eq!(usage.api_calls_today, 0); assert_eq!(usage.bots, 0); assert_eq!(usage.users, 0); assert_eq!(usage.kb_documents, 0); assert_eq!(usage.apps, 0); assert!(usage.last_reset.is_none()); } #[tokio::test] async fn test_all_metric_types() { let manager = QuotaManager::new(); let org_id = Uuid::new_v4(); let quotas = create_test_quotas(org_id, "business"); manager.set_quotas(quotas).await; let metrics = vec![ (UsageMetric::Messages, 10), (UsageMetric::StorageBytes, 1000), (UsageMetric::ApiCalls, 5), (UsageMetric::Bots, 1), (UsageMetric::Users, 2), (UsageMetric::KbDocuments, 3), (UsageMetric::Apps, 1), ]; for (metric, amount) in metrics { let result = manager.increment_usage(org_id, metric, amount).await; assert!(result.is_ok(), "Failed for metric {:?}", metric); assert!(result.unwrap().allowed, "Not allowed for metric {:?}", metric); } let quotas = manager.get_quotas(org_id).await.unwrap(); assert_eq!(quotas.usage.messages_today, 10); assert_eq!(quotas.usage.storage_bytes, 1000); assert_eq!(quotas.usage.api_calls_today, 5); assert_eq!(quotas.usage.bots, 1); assert_eq!(quotas.usage.users, 2); assert_eq!(quotas.usage.kb_documents, 3); assert_eq!(quotas.usage.apps, 1); } }