use axum::body::Body; use axum::http::{Request, StatusCode}; use axum::routing::post; use axum::Router; use ed25519_dalek::SigningKey; use sqlx::sqlite::SqlitePoolOptions; use tower::ServiceExt; use wiremock::matchers::{any, method, path_regex}; use wiremock::{Mock, MockServer, ResponseTemplate}; use clean_copy_server::crypto; use clean_copy_server::handlers::activate; use clean_copy_server::AppState; fn payment_intent_json(id: &str, status: &str) -> serde_json::Value { serde_json::json!({ "id": id, "object": "payment_intent", "status": status, "amount": 2900, "amount_capturable": 0, "amount_received": 0, "currency": "usd", "capture_method": "automatic", "confirmation_method": "automatic", "created": 1700000000, "livemode": false, "metadata": {}, "payment_method_types": ["card"], }) } async fn setup_app(mock_url: &str) -> (Router, SigningKey) { let pool = SqlitePoolOptions::new() .connect("sqlite::memory:") .await .unwrap(); sqlx::migrate!().run(&pool).await.unwrap(); let key = SigningKey::from_bytes(&[42u8; 32]); let stripe_client = stripe::Client::from_url(mock_url, "sk_test_fake"); let state = AppState { config: clean_copy_server::config::Config { stripe_secret_key: "sk_test_fake".to_string(), stripe_price_id: String::new(), signing_key_hex: hex::encode(key.to_bytes()), database_url: "sqlite::memory:".to_string(), release_dir: "/tmp/releases".to_string(), static_dir: "./static".to_string(), port: 3000, app_url: "http://localhost:1420".to_string(), }, db: pool, signing_key: key.clone(), stripe_client, }; let app = Router::new() .route("/api/activate", post(activate::activate)) .with_state(state); (app, key) } #[tokio::test] async fn test_activate_new_license_with_mock_stripe() { let mock_server = MockServer::start().await; // Match ANY request to see what the Stripe client actually sends Mock::given(any()) .respond_with( ResponseTemplate::new(200).set_body_json(payment_intent_json("pi_test_123", "succeeded")), ) .expect(1) .mount(&mock_server) .await; let (app, key) = setup_app(&mock_server.uri()).await; let req_body = serde_json::json!({ "license_key": "pi_test_123", "hwid": "hwid_device_1" }); let response = app .oneshot( Request::builder() .method("POST") .uri("/api/activate") .header("content-type", "application/json") .body(Body::from(serde_json::to_string(&req_body).unwrap())) .unwrap(), ) .await .unwrap(); assert_eq!(response.status(), StatusCode::OK); let body: serde_json::Value = axum::body::to_bytes(response.into_body(), usize::MAX) .await .map(|b| serde_json::from_slice(&b).unwrap()) .unwrap(); let token = body["license_token"].as_str().unwrap(); assert!(!token.is_empty()); let pub_key = key.verifying_key().to_bytes(); let payload = crypto::verify_license(token, &pub_key, "hwid_device_1").unwrap(); assert_eq!(payload.license_key, "pi_test_123"); assert_eq!(payload.device_count, Some(1)); } #[tokio::test] async fn test_activate_payment_not_succeeded() { let mock_server = MockServer::start().await; Mock::given(any()) .respond_with( ResponseTemplate::new(200) .set_body_json(payment_intent_json("pi_pending", "requires_payment_method")), ) .expect(1) .mount(&mock_server) .await; let (app, _key) = setup_app(&mock_server.uri()).await; let req_body = serde_json::json!({ "license_key": "pi_pending", "hwid": "hwid_device_1" }); let response = app .oneshot( Request::builder() .method("POST") .uri("/api/activate") .header("content-type", "application/json") .body(Body::from(serde_json::to_string(&req_body).unwrap())) .unwrap(), ) .await .unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); let body: serde_json::Value = axum::body::to_bytes(response.into_body(), usize::MAX) .await .map(|b| serde_json::from_slice(&b).unwrap()) .unwrap(); assert!(body["error"] .as_str() .unwrap() .contains("Payment has not been completed")); } #[tokio::test] async fn test_activate_payment_not_found() { let mock_server = MockServer::start().await; Mock::given(method("GET")) .and(path_regex(r"/v1/payment_intents/pi_nonexistent")) .respond_with(ResponseTemplate::new(404).set_body_json(serde_json::json!({ "error": { "type": "invalid_request_error", "message": "No such PaymentIntent: 'pi_nonexistent'", } }))) .expect(1) .mount(&mock_server) .await; let (app, _key) = setup_app(&mock_server.uri()).await; let req_body = serde_json::json!({ "license_key": "pi_nonexistent", "hwid": "hwid_device_1" }); let response = app .oneshot( Request::builder() .method("POST") .uri("/api/activate") .header("content-type", "application/json") .body(Body::from(serde_json::to_string(&req_body).unwrap())) .unwrap(), ) .await .unwrap(); assert_eq!(response.status(), StatusCode::BAD_REQUEST); let body: serde_json::Value = axum::body::to_bytes(response.into_body(), usize::MAX) .await .map(|b| serde_json::from_slice(&b).unwrap()) .unwrap(); assert!(body["error"] .as_str() .unwrap() .contains("Payment not found")); } #[tokio::test] async fn test_activate_missing_fields() { let mock_server = MockServer::start().await; let (app, _key) = setup_app(&mock_server.uri()).await; let req_body = serde_json::json!({ "license_key": "", "hwid": "hwid_device_1" }); let response = app .oneshot( Request::builder() .method("POST") .uri("/api/activate") .header("content-type", "application/json") .body(Body::from(serde_json::to_string(&req_body).unwrap())) .unwrap(), ) .await .unwrap(); assert_eq!(response.status(), StatusCode::UNPROCESSABLE_ENTITY); } #[tokio::test] async fn test_activate_idempotent_same_device() { let mock_server = MockServer::start().await; Mock::given(any()) .respond_with( ResponseTemplate::new(200).set_body_json(payment_intent_json("pi_test_123", "succeeded")), ) .expect(2) .mount(&mock_server) .await; let (app, key) = setup_app(&mock_server.uri()).await; let req_body = serde_json::json!({ "license_key": "pi_test_123", "hwid": "hwid_device_1" }); let response1 = app .clone() .oneshot( Request::builder() .method("POST") .uri("/api/activate") .header("content-type", "application/json") .body(Body::from(serde_json::to_string(&req_body).unwrap())) .unwrap(), ) .await .unwrap(); assert_eq!(response1.status(), StatusCode::OK); let response2 = app .oneshot( Request::builder() .method("POST") .uri("/api/activate") .header("content-type", "application/json") .body(Body::from(serde_json::to_string(&req_body).unwrap())) .unwrap(), ) .await .unwrap(); assert_eq!(response2.status(), StatusCode::OK); let body2: serde_json::Value = axum::body::to_bytes(response2.into_body(), usize::MAX) .await .map(|b| serde_json::from_slice(&b).unwrap()) .unwrap(); let pub_key = key.verifying_key().to_bytes(); let payload = crypto::verify_license( body2["license_token"].as_str().unwrap(), &pub_key, "hwid_device_1", ) .unwrap(); assert_eq!(payload.device_count, Some(1)); }