595 lines
23 KiB
Rust
595 lines
23 KiB
Rust
use std::sync::Arc;
|
|
|
|
use chrono::Duration;
|
|
use warpui::{App, ModelHandle};
|
|
|
|
use crate::server::server_api::team::MockTeamClient;
|
|
use crate::server::server_api::workspace::MockWorkspaceClient;
|
|
use crate::server::server_api::ServerApiProvider;
|
|
use crate::workspaces::{
|
|
user_workspaces::UserWorkspaces,
|
|
workspace::{
|
|
AiOverages, ByoApiKeyPolicy, CustomerType, EnterpriseCreditsAutoReloadPolicy,
|
|
EnterprisePayAsYouGoPolicy, PurchaseAddOnCreditsPolicy, Workspace, WorkspaceUid,
|
|
},
|
|
};
|
|
|
|
use ai::api_keys::ApiKeyManager;
|
|
use warp_core::features::FeatureFlag;
|
|
|
|
use super::*;
|
|
|
|
fn create_test_workspace() -> (WorkspaceUid, Workspace) {
|
|
let server_id: crate::server::ids::ServerId = 1_i64.into();
|
|
let uid = WorkspaceUid::from(server_id);
|
|
let workspace = Workspace::from_local_cache(uid, "Test Workspace".to_string(), None);
|
|
(uid, workspace)
|
|
}
|
|
|
|
fn add_user_workspaces_with_workspace(app: &mut App, workspace: Workspace) {
|
|
app.add_singleton_model(|ctx| {
|
|
UserWorkspaces::mock(
|
|
Arc::new(MockTeamClient::new()),
|
|
Arc::new(MockWorkspaceClient::new()),
|
|
vec![workspace],
|
|
ctx,
|
|
)
|
|
});
|
|
}
|
|
|
|
fn add_request_usage_model(app: &mut App) -> ModelHandle<AIRequestUsageModel> {
|
|
app.add_singleton_model(|_| ServerApiProvider::new_for_test());
|
|
app.update(|ctx| {
|
|
warpui_extras::secure_storage::register_noop("test", ctx);
|
|
ctx.add_singleton_model(ApiKeyManager::new);
|
|
});
|
|
app.add_singleton_model(|ctx| {
|
|
AIRequestUsageModel::new_for_test(ServerApiProvider::as_ref(ctx).get_ai_client(), ctx)
|
|
})
|
|
}
|
|
|
|
#[test]
|
|
fn test_request_limit_info() {
|
|
App::test((), |mut app| async move {
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
request_usage_model.update(&mut app, |request_usage_model, _ctx| {
|
|
request_usage_model.request_limit_info = RequestLimitInfo {
|
|
limit: 200,
|
|
num_requests_used_since_refresh: 39,
|
|
next_refresh_time: ServerTimestamp::new(Utc::now() + Duration::days(1)),
|
|
is_unlimited: false,
|
|
request_limit_refresh_duration: RequestLimitRefreshDuration::Monthly,
|
|
is_unlimited_voice: false,
|
|
voice_request_limit: 100,
|
|
voice_requests_used_since_last_refresh: 0,
|
|
is_unlimited_codebase_indices: false,
|
|
max_codebase_indices: 3,
|
|
max_files_per_repo: 5000,
|
|
embedding_generation_batch_size: 100,
|
|
};
|
|
assert_eq!(200, request_usage_model.request_limit());
|
|
assert_eq!(39, request_usage_model.requests_used());
|
|
assert_eq!(161, request_usage_model.requests_remaining());
|
|
})
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_request_limit_info_with_limit() {
|
|
App::test((), |mut app| async move {
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
request_usage_model.update(&mut app, |request_usage_model, _ctx| {
|
|
request_usage_model.request_limit_info = RequestLimitInfo {
|
|
limit: 999999999,
|
|
num_requests_used_since_refresh: 39,
|
|
next_refresh_time: ServerTimestamp::new(Utc::now() + Duration::minutes(1)),
|
|
is_unlimited: false,
|
|
request_limit_refresh_duration: RequestLimitRefreshDuration::Monthly,
|
|
is_unlimited_voice: false,
|
|
voice_request_limit: 100,
|
|
voice_requests_used_since_last_refresh: 0,
|
|
is_unlimited_codebase_indices: false,
|
|
max_codebase_indices: 3,
|
|
max_files_per_repo: 5000,
|
|
embedding_generation_batch_size: 100,
|
|
};
|
|
assert_eq!(999999999, request_usage_model.request_limit());
|
|
assert_eq!(39, request_usage_model.requests_used());
|
|
assert_eq!(999999960, request_usage_model.requests_remaining());
|
|
})
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_request_limit_info_past_refresh_time() {
|
|
App::test((), |mut app| async move {
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
request_usage_model.update(&mut app, |request_usage_model, _ctx| {
|
|
request_usage_model.request_limit_info = RequestLimitInfo {
|
|
limit: 200,
|
|
num_requests_used_since_refresh: 39,
|
|
next_refresh_time: ServerTimestamp::new(Utc::now() - Duration::seconds(1)),
|
|
is_unlimited: false,
|
|
request_limit_refresh_duration: RequestLimitRefreshDuration::Monthly,
|
|
is_unlimited_voice: false,
|
|
voice_request_limit: 100,
|
|
voice_requests_used_since_last_refresh: 0,
|
|
is_unlimited_codebase_indices: false,
|
|
max_codebase_indices: 3,
|
|
max_files_per_repo: 5000,
|
|
embedding_generation_batch_size: 100,
|
|
};
|
|
assert_eq!(200, request_usage_model.request_limit());
|
|
assert_eq!(0, request_usage_model.requests_used());
|
|
assert_eq!(200, request_usage_model.requests_remaining());
|
|
})
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_request_limit_info_is_unlimited_true() {
|
|
App::test((), |mut app| async move {
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
request_usage_model.update(&mut app, |request_usage_model, _ctx| {
|
|
request_usage_model.request_limit_info = RequestLimitInfo {
|
|
limit: 999999999,
|
|
num_requests_used_since_refresh: 39,
|
|
next_refresh_time: ServerTimestamp::new(Utc::now() + Duration::minutes(1)),
|
|
is_unlimited: true,
|
|
request_limit_refresh_duration: RequestLimitRefreshDuration::Monthly,
|
|
is_unlimited_voice: false,
|
|
voice_request_limit: 100,
|
|
voice_requests_used_since_last_refresh: 0,
|
|
is_unlimited_codebase_indices: false,
|
|
max_codebase_indices: 3,
|
|
max_files_per_repo: 5000,
|
|
embedding_generation_batch_size: 100,
|
|
};
|
|
assert_eq!(999999999, request_usage_model.request_limit());
|
|
assert_eq!(39, request_usage_model.requests_used());
|
|
assert_eq!(999999999, request_usage_model.requests_remaining());
|
|
})
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_true_with_remaining_requests() {
|
|
App::test((), |mut app| async move {
|
|
app.add_singleton_model(UserWorkspaces::default_mock);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// Some requests remaining, no bonus or overages needed.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 5);
|
|
assert!(model.has_any_ai_remaining(ctx));
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_buy_credits_banner_shows_with_only_ambient_bonus_credits() {
|
|
App::test((), |mut app| async move {
|
|
let (_uid, mut workspace) = create_test_workspace();
|
|
workspace
|
|
.billing_metadata
|
|
.tier
|
|
.purchase_add_on_credits_policy = Some(PurchaseAddOnCreditsPolicy { enabled: true });
|
|
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants = vec![BonusGrant {
|
|
created_at: Utc::now(),
|
|
cost_cents: 0,
|
|
expiration: Some(Utc::now() + chrono::Duration::days(7)),
|
|
grant_type: BonusGrantType::AmbientOnly,
|
|
reason: "ambient trial credits".to_string(),
|
|
user_facing_message: None,
|
|
request_credits_granted: 1000,
|
|
request_credits_remaining: 1000,
|
|
scope: BonusGrantScope::User,
|
|
}];
|
|
|
|
assert_eq!(
|
|
model.compute_buy_addon_credits_banner_display_state(ctx),
|
|
BuyCreditsBannerDisplayState::OutOfCredits,
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_buy_credits_banner_hidden_with_non_ambient_bonus_credits() {
|
|
App::test((), |mut app| async move {
|
|
let (_uid, mut workspace) = create_test_workspace();
|
|
workspace
|
|
.billing_metadata
|
|
.tier
|
|
.purchase_add_on_credits_policy = Some(PurchaseAddOnCreditsPolicy { enabled: true });
|
|
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants = vec![BonusGrant {
|
|
created_at: Utc::now(),
|
|
cost_cents: 0,
|
|
expiration: Some(Utc::now() + chrono::Duration::days(7)),
|
|
grant_type: BonusGrantType::Any,
|
|
reason: "standard bonus credits".to_string(),
|
|
user_facing_message: None,
|
|
request_credits_granted: 100,
|
|
request_credits_remaining: 100,
|
|
scope: BonusGrantScope::User,
|
|
}];
|
|
|
|
assert_eq!(
|
|
model.compute_buy_addon_credits_banner_display_state(ctx),
|
|
BuyCreditsBannerDisplayState::Hidden,
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_buy_credits_banner_shows_when_non_ambient_bonus_credits_are_depleted() {
|
|
App::test((), |mut app| async move {
|
|
let (_uid, mut workspace) = create_test_workspace();
|
|
workspace
|
|
.billing_metadata
|
|
.tier
|
|
.purchase_add_on_credits_policy = Some(PurchaseAddOnCreditsPolicy { enabled: true });
|
|
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants = vec![BonusGrant {
|
|
created_at: Utc::now(),
|
|
cost_cents: 0,
|
|
expiration: Some(Utc::now() + chrono::Duration::days(7)),
|
|
grant_type: BonusGrantType::Any,
|
|
reason: "depleted standard bonus credits".to_string(),
|
|
user_facing_message: None,
|
|
request_credits_granted: 100,
|
|
request_credits_remaining: 0,
|
|
scope: BonusGrantScope::User,
|
|
}];
|
|
|
|
assert_eq!(
|
|
model.compute_buy_addon_credits_banner_display_state(ctx),
|
|
BuyCreditsBannerDisplayState::OutOfCredits,
|
|
);
|
|
});
|
|
});
|
|
}
|
|
#[test]
|
|
fn test_has_any_ai_remaining_false_when_no_requests_or_bonus() {
|
|
App::test((), |mut app| async move {
|
|
app.add_singleton_model(UserWorkspaces::default_mock);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// At limit, no bonus credits and no overages.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
assert!(!model.has_any_ai_remaining(ctx));
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_true_with_user_bonus_credits() {
|
|
App::test((), |mut app| async move {
|
|
app.add_singleton_model(UserWorkspaces::default_mock);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests remaining.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
|
|
// User-level bonus credits remaining.
|
|
model.bonus_grants = vec![BonusGrant {
|
|
created_at: Utc::now(),
|
|
cost_cents: 0,
|
|
expiration: Some(Utc::now() + chrono::Duration::days(7)),
|
|
grant_type: BonusGrantType::Any,
|
|
reason: "test user bonus".to_string(),
|
|
user_facing_message: None,
|
|
request_credits_granted: 5,
|
|
request_credits_remaining: 5,
|
|
scope: BonusGrantScope::User,
|
|
}];
|
|
|
|
assert!(
|
|
model.has_any_ai_remaining(ctx),
|
|
"expected has_any_ai_remaining to be true when user bonus credits exist",
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_true_with_workspace_overages() {
|
|
App::test((), |mut app| async move {
|
|
// Create a workspace with overages enabled and remaining.
|
|
let (_uid, mut workspace) = create_test_workspace();
|
|
workspace.settings.usage_based_pricing_settings.enabled = true;
|
|
workspace
|
|
.settings
|
|
.usage_based_pricing_settings
|
|
.max_monthly_spend_cents = Some(1_000);
|
|
workspace.billing_metadata.ai_overages = Some(AiOverages {
|
|
current_monthly_request_cost_cents: 100,
|
|
current_monthly_requests_used: 100,
|
|
current_period_end: Utc::now() + chrono::Duration::days(7),
|
|
});
|
|
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests left and no bonus credits.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants.clear();
|
|
|
|
assert!(
|
|
model.has_any_ai_remaining(ctx),
|
|
"expected overages to count as remaining AI when standard requests are exhausted",
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_true_with_workspace_bonus_credits() {
|
|
App::test((), |mut app| async move {
|
|
let (uid, workspace) = create_test_workspace();
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests remaining.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
|
|
// Workspace-level bonus credits remaining.
|
|
model.bonus_grants = vec![BonusGrant {
|
|
created_at: Utc::now(),
|
|
cost_cents: 0,
|
|
expiration: Some(Utc::now() + chrono::Duration::days(7)),
|
|
grant_type: BonusGrantType::Any,
|
|
reason: "test workspace bonus".to_string(),
|
|
user_facing_message: None,
|
|
request_credits_granted: 5,
|
|
request_credits_remaining: 5,
|
|
scope: BonusGrantScope::Workspace(uid),
|
|
}];
|
|
|
|
assert!(
|
|
model.has_any_ai_remaining(ctx),
|
|
"expected has_any_ai_remaining to be true when workspace bonus credits exist",
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_true_with_payg_enabled() {
|
|
App::test((), |mut app| async move {
|
|
// Create a workspace with pay-as-you-go enabled.
|
|
let (_uid, mut workspace) = create_test_workspace();
|
|
workspace.billing_metadata.customer_type = CustomerType::Enterprise;
|
|
workspace
|
|
.billing_metadata
|
|
.tier
|
|
.enterprise_pay_as_you_go_policy = Some(EnterprisePayAsYouGoPolicy { enabled: true });
|
|
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests remaining, no bonus credits.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants.clear();
|
|
|
|
assert!(
|
|
model.has_any_ai_remaining(ctx),
|
|
"expected has_any_ai_remaining to be true when pay-as-you-go is enabled",
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_true_with_enterprise_auto_reload() {
|
|
App::test((), |mut app| async move {
|
|
// Create a workspace with enterprise auto-reload enabled.
|
|
let (_uid, mut workspace) = create_test_workspace();
|
|
workspace.billing_metadata.customer_type = CustomerType::Enterprise;
|
|
workspace
|
|
.billing_metadata
|
|
.tier
|
|
.enterprise_credits_auto_reload_policy =
|
|
Some(EnterpriseCreditsAutoReloadPolicy { enabled: true });
|
|
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests remaining, no bonus credits.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants.clear();
|
|
|
|
assert!(
|
|
model.has_any_ai_remaining(ctx),
|
|
"expected has_any_ai_remaining to be true when enterprise auto-reload is enabled",
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_false_with_workspace_no_pricing_no_overages_no_credits() {
|
|
App::test((), |mut app| async move {
|
|
// Create a workspace with no tier pricing (default).
|
|
let (_uid, workspace) = create_test_workspace();
|
|
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests remaining, no bonus credits.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants.clear();
|
|
|
|
assert!(
|
|
!model.has_any_ai_remaining(ctx),
|
|
"expected has_any_ai_remaining to be false with no pricing, no overages, no credits",
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_false_both_payg_and_autoreload_disabled() {
|
|
App::test((), |mut app| async move {
|
|
// Create a workspace with policies but payg disabled and auto-reload disabled.
|
|
let (_uid, mut workspace) = create_test_workspace();
|
|
workspace
|
|
.billing_metadata
|
|
.tier
|
|
.enterprise_pay_as_you_go_policy = Some(EnterprisePayAsYouGoPolicy { enabled: false });
|
|
workspace
|
|
.billing_metadata
|
|
.tier
|
|
.enterprise_credits_auto_reload_policy =
|
|
Some(EnterpriseCreditsAutoReloadPolicy { enabled: false });
|
|
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests remaining, no bonus credits.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants.clear();
|
|
|
|
assert!(
|
|
!model.has_any_ai_remaining(ctx),
|
|
"expected has_any_ai_remaining to be false with policies but payg and auto-reload disabled",
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_true_with_byok_enabled_and_key_provided() {
|
|
App::test((), |mut app| async move {
|
|
// Create a workspace with BYOK (Bring Your Own Key) enabled.
|
|
let (_uid, mut workspace) = create_test_workspace();
|
|
workspace.billing_metadata.tier.byo_api_key_policy =
|
|
Some(ByoApiKeyPolicy { enabled: true });
|
|
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
ApiKeyManager::handle(&app).update(&mut app, |manager, ctx| {
|
|
manager.set_openai_key(Some("test-key".to_string()), ctx);
|
|
});
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests remaining, no bonus credits.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants.clear();
|
|
|
|
assert!(
|
|
model.has_any_ai_remaining(ctx),
|
|
"expected has_any_ai_remaining to be true when BYOK is enabled and a key is provided",
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_false_with_byok_enabled_but_no_key() {
|
|
App::test((), |mut app| async move {
|
|
// Create a workspace with BYOK enabled but no key provided.
|
|
let (_uid, mut workspace) = create_test_workspace();
|
|
workspace.billing_metadata.tier.byo_api_key_policy =
|
|
Some(ByoApiKeyPolicy { enabled: true });
|
|
|
|
add_user_workspaces_with_workspace(&mut app, workspace);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests remaining, no bonus credits.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants.clear();
|
|
|
|
assert!(
|
|
!model.has_any_ai_remaining(ctx),
|
|
"expected has_any_ai_remaining to be false when BYOK is enabled but no key is provided",
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_true_with_byo_key_and_no_workspace() {
|
|
App::test((), |mut app| async move {
|
|
let _guard = FeatureFlag::SoloUserByok.override_enabled(true);
|
|
|
|
// No workspace — user is not on a team.
|
|
app.add_singleton_model(UserWorkspaces::default_mock);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
ApiKeyManager::handle(&app).update(&mut app, |manager, ctx| {
|
|
manager.set_openai_key(Some("test-key".to_string()), ctx);
|
|
});
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests remaining, no bonus credits.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
model.bonus_grants.clear();
|
|
|
|
assert!(
|
|
model.has_any_ai_remaining(ctx),
|
|
"expected has_any_ai_remaining to be true when user has a BYO key but no workspace",
|
|
);
|
|
});
|
|
});
|
|
}
|
|
|
|
#[test]
|
|
fn test_has_any_ai_remaining_false_with_only_ambient_bonus_credits() {
|
|
App::test((), |mut app| async move {
|
|
app.add_singleton_model(UserWorkspaces::default_mock);
|
|
let request_usage_model = add_request_usage_model(&mut app);
|
|
|
|
request_usage_model.update(&mut app, |model, ctx| {
|
|
// No standard requests remaining.
|
|
model.request_limit_info = RequestLimitInfo::new_for_test(10, 10);
|
|
|
|
// Only ambient-only bonus credits.
|
|
model.bonus_grants = vec![BonusGrant {
|
|
created_at: Utc::now(),
|
|
cost_cents: 0,
|
|
expiration: Some(Utc::now() + chrono::Duration::days(7)),
|
|
grant_type: BonusGrantType::AmbientOnly,
|
|
reason: "ambient trial credits".to_string(),
|
|
user_facing_message: None,
|
|
request_credits_granted: 1000,
|
|
request_credits_remaining: 1000,
|
|
scope: BonusGrantScope::User,
|
|
}];
|
|
|
|
assert!(
|
|
!model.has_any_ai_remaining(ctx),
|
|
"expected has_any_ai_remaining to be false when only ambient-only bonus credits exist",
|
|
);
|
|
});
|
|
});
|
|
}
|