Files
clean-copy/server/tests/checkout_integration.rs
T

173 lines
4.3 KiB
Rust

use axum::body::Body;
use axum::http::{Request, StatusCode};
use axum::routing::get;
use axum::Router;
use ed25519_dalek::SigningKey;
use sqlx::sqlite::SqlitePoolOptions;
use tower::ServiceExt;
use wiremock::matchers::{method, path_regex};
use wiremock::{Mock, MockServer, ResponseTemplate};
use clean_copy_server::handlers::{activate, checkout};
use clean_copy_server::AppState;
async fn setup_app(mock_url: &str) -> Router {
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: "price_test_123".to_string(),
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:3000".to_string(),
},
db: pool,
signing_key: key,
stripe_client,
};
Router::new()
.route("/api/checkout", get(checkout::checkout))
.route("/api/activate", axum::routing::post(activate::activate))
.with_state(state)
}
fn checkout_session_json(id: &str, url: Option<&str>) -> serde_json::Value {
let mut json = serde_json::json!({
"id": id,
"object": "checkout.session",
"mode": "payment",
"automatic_tax": { "enabled": false },
"created": 1700000000,
"expires_at": 1700003600,
"custom_fields": [],
"custom_text": {
"after_submit": null,
"shipping_address": null,
"submit": null,
"terms_of_service_acceptance": null
},
"livemode": false,
"payment_method_types": ["card"],
"payment_status": "unpaid",
"shipping_options": [],
});
if let Some(u) = url {
json["url"] = serde_json::json!(u);
}
json
}
#[tokio::test]
async fn test_checkout_redirects_to_stripe() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path_regex(r"/v1/checkout/sessions"))
.respond_with(
ResponseTemplate::new(200).set_body_json(checkout_session_json(
"cs_test_123",
Some("https://checkout.stripe.com/pay/cs_test_123"),
)),
)
.expect(1)
.mount(&mock_server)
.await;
let app = setup_app(&mock_server.uri()).await;
let response = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/checkout")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::SEE_OTHER);
let location = response
.headers()
.get("location")
.unwrap()
.to_str()
.unwrap();
assert_eq!(location, "https://checkout.stripe.com/pay/cs_test_123");
}
#[tokio::test]
async fn test_checkout_stripe_error() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path_regex(r"/v1/checkout/sessions"))
.respond_with(ResponseTemplate::new(400).set_body_json(serde_json::json!({
"error": {
"type": "invalid_request_error",
"message": "No such price: 'price_bad'",
}
})))
.expect(1)
.mount(&mock_server)
.await;
let app = setup_app(&mock_server.uri()).await;
let response = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/checkout")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
#[tokio::test]
async fn test_checkout_no_url_in_response() {
let mock_server = MockServer::start().await;
Mock::given(method("POST"))
.and(path_regex(r"/v1/checkout/sessions"))
.respond_with(
ResponseTemplate::new(200).set_body_json(checkout_session_json("cs_test_no_url", None)),
)
.expect(1)
.mount(&mock_server)
.await;
let app = setup_app(&mock_server.uri()).await;
let response = app
.oneshot(
Request::builder()
.method("GET")
.uri("/api/checkout")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::INTERNAL_SERVER_ERROR);
}