diff --git a/backend/src/billing.rs b/backend/src/billing.rs index b9013ee..92d2c77 100644 --- a/backend/src/billing.rs +++ b/backend/src/billing.rs @@ -732,3 +732,112 @@ impl Billing { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use sqlx::SqlitePool; + use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; + use std::str::FromStr; + + use crate::models::Activity; + + async fn test_pool() -> SqlitePool { + let connect_options = SqliteConnectOptions::from_str("sqlite::memory:") + .expect("valid sqlite memory url") + .create_if_missing(true); + + let pool = SqlitePoolOptions::new() + .max_connections(1) + .connect_with(connect_options) + .await + .expect("connect sqlite memory db"); + + sqlx::migrate!("./migrations") + .run(&pool) + .await + .expect("run migrations"); + + pool + } + + fn test_billing(pool: SqlitePool, stripe_secret_key: &str) -> Billing { + Billing { + nwc_url: String::new(), + stripe_secret_key: stripe_secret_key.to_string(), + stripe_webhook_secret: String::new(), + http: reqwest::Client::new(), + query: Query::new(pool.clone()), + command: Command::new(pool), + robot: Robot::test_stub(), + } + } + + #[tokio::test] + async fn stripe_create_customer_requires_secret_key() { + let pool = test_pool().await; + let billing = test_billing(pool, ""); + + let err = billing + .stripe_create_customer("tenant_pubkey") + .await + .expect_err("missing key should fail before HTTP call"); + + assert!( + err.to_string().contains("missing STRIPE_SECRET_KEY"), + "unexpected error: {err}" + ); + } + + #[tokio::test] + async fn sync_relay_subscription_fails_for_empty_tenant_customer_id() { + let pool = test_pool().await; + + sqlx::query( + "INSERT INTO tenant (pubkey, nwc_url, created_at, stripe_customer_id) + VALUES (?, ?, ?, ?)", + ) + .bind("tenant_pubkey") + .bind("") + .bind(0_i64) + .bind("") + .execute(&pool) + .await + .expect("insert tenant"); + + sqlx::query( + "INSERT INTO relay (id, tenant, schema, subdomain, plan, status) + VALUES (?, ?, ?, ?, ?, ?)", + ) + .bind("relay_1") + .bind("tenant_pubkey") + .bind("relay_1") + .bind("relay-1") + .bind("basic") + .bind("active") + .execute(&pool) + .await + .expect("insert relay"); + + let billing = test_billing(pool, "sk_test_dummy"); + let activity = Activity { + id: "activity_1".to_string(), + tenant: "tenant_pubkey".to_string(), + created_at: 0, + activity_type: "create_relay".to_string(), + resource_type: "relay".to_string(), + resource_id: "relay_1".to_string(), + }; + + let err = billing + .sync_relay_subscription(&activity) + .await + .expect_err("empty tenant customer id should fail clearly"); + + assert!( + err.to_string() + .contains("tenant tenant_pubkey has no stripe_customer_id"), + "unexpected error: {err}" + ); + } +} diff --git a/backend/src/command.rs b/backend/src/command.rs index ce19174..c5df629 100644 --- a/backend/src/command.rs +++ b/backend/src/command.rs @@ -324,3 +324,56 @@ impl Command { Ok(()) } } + +#[cfg(test)] +mod tests { + use super::*; + use sqlx::SqlitePool; + use sqlx::sqlite::{SqliteConnectOptions, SqlitePoolOptions}; + use std::str::FromStr; + + async fn test_pool() -> SqlitePool { + let connect_options = SqliteConnectOptions::from_str("sqlite::memory:") + .expect("valid sqlite memory url") + .create_if_missing(true); + + let pool = SqlitePoolOptions::new() + .max_connections(1) + .connect_with(connect_options) + .await + .expect("connect sqlite memory db"); + + sqlx::migrate!("./migrations") + .run(&pool) + .await + .expect("run migrations"); + + pool + } + + #[tokio::test] + async fn create_tenant_rejects_empty_stripe_customer_id() { + let pool = test_pool().await; + let command = Command::new(pool); + + let tenant = Tenant { + pubkey: "tenant_pubkey".to_string(), + nwc_url: String::new(), + nwc_error: None, + created_at: 0, + stripe_customer_id: " ".to_string(), + stripe_subscription_id: None, + past_due_at: None, + }; + + let err = command + .create_tenant(&tenant) + .await + .expect_err("empty customer id must be rejected"); + + assert!( + err.to_string().contains("stripe_customer_id is required"), + "unexpected error: {err}" + ); + } +} diff --git a/backend/src/robot.rs b/backend/src/robot.rs index e002a31..e8195db 100644 --- a/backend/src/robot.rs +++ b/backend/src/robot.rs @@ -254,3 +254,23 @@ async fn set_cached( }, ); } + +#[cfg(test)] +impl Robot { + pub fn test_stub() -> Self { + let keys = Keys::generate(); + let client = Client::new(keys); + + Self { + secret: String::new(), + name: String::new(), + description: String::new(), + picture: String::new(), + outbox_client: client.clone(), + indexer_client: client.clone(), + messaging_client: client, + outbox_cache: std::sync::Arc::new(Mutex::new(HashMap::new())), + dm_cache: std::sync::Arc::new(Mutex::new(HashMap::new())), + } + } +}