Files
smirk-backend-core/tests/premium.rs
T
jwinterm dec9994e06 fix(config): fail closed on POW_REQUIRED without FEATURE_POW; harden premium key check
- Reject boot when POW_REQUIRED (or the per-pubkey PoW list) is set while
  FEATURE_POW is off: the gate would silently never apply and /capabilities
  would report pow_required:false — a signup endpoint the operator believes
  is protected but isn't.
- Validate the premium PAYMENT_API_KEY through the same require_secret path
  the pay-to-register flow uses, so a placeholder key cannot reach production.
- Advertise the premium-post write policy in MessagingCapability and
  regenerate openapi.json to match.
- Give the premium integration fixtures unique per-run ids so the suite is
  re-runnable against a persistent local test database.
2026-07-02 19:47:38 -04:00

158 lines
4.8 KiB
Rust

//! Premium tier — L1 integration (the payment-critical DB layer + feature gate).
//!
//! Self-skips when `TEST_DATABASE_URL` is unset (see `tests/common`). The pure
//! admission logic is unit-tested in `infra::relay::policy`; these cover the parts
//! that only surface against a real database: single-use activation, stacking,
//! cross-user binding, membership-by-expiry, and the endpoint feature gate.
mod common;
use axum::http::StatusCode;
use serde_json::json;
use smirk_backend_core::models::db::NewUser;
use uuid::Uuid;
fn new_user_with_npub(npub: Option<String>) -> NewUser {
NewUser {
username: None,
pubkey_hash: Some(format!("pk-{}", Uuid::new_v4())),
nostr_pubkey: npub,
wallet_birthday: None,
seed_fingerprint: None,
xmr_start_height: None,
wow_start_height: None,
}
}
#[tokio::test]
async fn activate_premium_is_single_use_and_stacks() {
let app = require_app!();
let db = &app.state.db;
let user_id = app.create_user().await;
// Unique per run so the suite is re-runnable against a persistent test DB.
let inv_a = format!("inv-a-{}", Uuid::new_v4());
let inv_b = format!("inv-b-{}", Uuid::new_v4());
db.insert_premium_invoice(&inv_a, user_id, "btcpay", "quarter", 90, "5", "USD")
.await
.unwrap();
db.insert_premium_invoice(&inv_b, user_id, "btcpay", "quarter", 90, "5", "USD")
.await
.unwrap();
assert_eq!(
db.count_unconsumed_premium_invoices(user_id).await.unwrap(),
2
);
// First activation grants a future expiry.
let until1 = db
.activate_premium(&inv_a, user_id, 90)
.await
.unwrap()
.expect("first activation grants");
assert!(until1 > chrono::Utc::now());
// Re-activating the SAME invoice is a no-op (single-use) — no double grant.
assert!(db
.activate_premium(&inv_a, user_id, 90)
.await
.unwrap()
.is_none());
// A second invoice STACKS from the current expiry.
let until2 = db
.activate_premium(&inv_b, user_id, 90)
.await
.unwrap()
.expect("second activation grants");
assert!(until2 > until1, "second plan extends the window");
assert_eq!(db.get_premium_until(user_id).await.unwrap(), Some(until2));
assert_eq!(
db.count_unconsumed_premium_invoices(user_id).await.unwrap(),
0
);
}
#[tokio::test]
async fn activate_premium_rejects_cross_user_invoice() {
let app = require_app!();
let db = &app.state.db;
let owner = app.create_user().await;
let attacker = app.create_user().await;
let inv_x = format!("inv-x-{}", Uuid::new_v4());
db.insert_premium_invoice(&inv_x, owner, "btcpay", "quarter", 90, "5", "USD")
.await
.unwrap();
// Another user cannot activate the owner's invoice; it stays unconsumed.
assert!(db
.activate_premium(&inv_x, attacker, 90)
.await
.unwrap()
.is_none());
assert_eq!(
db.count_unconsumed_premium_invoices(owner).await.unwrap(),
1
);
assert!(db.get_premium_until(attacker).await.unwrap().is_none());
}
#[tokio::test]
async fn is_premium_npub_respects_membership_and_expiry() {
let app = require_app!();
let db = &app.state.db;
let npub = format!("{}{}", Uuid::new_v4().simple(), Uuid::new_v4().simple());
let user = db
.create_user(new_user_with_npub(Some(npub.clone())))
.await
.unwrap();
// Not premium until granted.
assert!(!db.is_premium_npub(&npub).await.unwrap());
db.extend_premium(user.id, 90).await.unwrap();
assert!(db.is_premium_npub(&npub).await.unwrap());
// An unrelated npub is never premium.
assert!(!db.is_premium_npub(&"b".repeat(64)).await.unwrap());
}
#[tokio::test]
async fn premium_endpoints_gated_off_by_default() {
let app = require_app!();
let (_uid, access, _refresh) = app.mint_session().await;
// Premium disabled in the default test config → invoice/activate are 400.
let (s, _) = app
.request(
"POST",
"/api/v1/premium/invoice",
Some(&access),
Some(json!({ "plan": "quarter" })),
)
.await;
assert_eq!(s, StatusCode::BAD_REQUEST);
let (s, _) = app
.request(
"POST",
"/api/v1/premium/activate",
Some(&access),
Some(json!({ "invoice_id": "x" })),
)
.await;
assert_eq!(s, StatusCode::BAD_REQUEST);
// Status is always readable → not premium.
let (s, body) = app
.request("GET", "/api/v1/premium/status", Some(&access), None)
.await;
assert_eq!(s, StatusCode::OK);
assert_eq!(body["active"], false);
// No token → 401.
let (s, _) = app
.request("GET", "/api/v1/premium/status", None, None)
.await;
assert_eq!(s, StatusCode::UNAUTHORIZED);
}