use axum::{ body::Body, http::{Request, StatusCode}, }; use serde_json::Value; use tower::ServiceExt; // Bring in the crate modules via the library path // We test by spinning up the full app against an in-memory SQLite database. async fn build_test_app() -> axum::Router { let pool = sqlx::sqlite::SqlitePoolOptions::new() .connect("sqlite::memory:") .await .expect("in-memory sqlite"); sqlx::migrate!("./migrations") .run(&pool) .await .expect("migrations"); // Seed with test card + pack data let data_dir = "data"; openfut_core::build_app(pool, data_dir) .await .expect("app build") } #[tokio::test] async fn test_health_endpoint() { let app = build_test_app().await; let resp = app .oneshot( Request::builder() .uri("/health") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = axum::body::to_bytes(resp.into_body(), usize::MAX) .await .unwrap(); let json: Value = serde_json::from_slice(&body).unwrap(); assert_eq!(json["status"], "ok"); } #[tokio::test] async fn test_auth_local_creates_profile_and_club() { let app = build_test_app().await; let payload = serde_json::json!({ "username": "TestPlayer" }); let resp = app .oneshot( Request::builder() .method("POST") .uri("/auth/local") .header("content-type", "application/json") .body(Body::from(payload.to_string())) .unwrap(), ) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = axum::body::to_bytes(resp.into_body(), usize::MAX) .await .unwrap(); let json: Value = serde_json::from_slice(&body).unwrap(); assert_eq!(json["profile"]["username"], "TestPlayer"); assert_eq!(json["club"]["name"], "OpenFUT FC"); assert_eq!(json["club"]["coins"], 5000); } #[tokio::test] async fn test_profile_not_found_before_auth() { let app = build_test_app().await; let resp = app .oneshot( Request::builder() .uri("/profile") .body(Body::empty()) .unwrap(), ) .await .unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); } #[tokio::test] async fn test_match_result_awards_coins() { let app = build_test_app().await; // First create a profile let auth_resp = app .clone() .oneshot( Request::builder() .method("POST") .uri("/auth/local") .header("content-type", "application/json") .body(Body::from(r#"{"username":"MatchPlayer"}"#)) .unwrap(), ) .await .unwrap(); assert_eq!(auth_resp.status(), StatusCode::OK); // Submit a match win let payload = serde_json::json!({ "squad_id": "dummy-squad", "opponent_name": "AI Club", "goals_for": 3, "goals_against": 1, "mode": "squad_battles" }); let resp = app .oneshot( Request::builder() .method("POST") .uri("/matches/result") .header("content-type", "application/json") .body(Body::from(payload.to_string())) .unwrap(), ) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = axum::body::to_bytes(resp.into_body(), usize::MAX) .await .unwrap(); let json: Value = serde_json::from_slice(&body).unwrap(); assert_eq!(json["match_record"]["outcome"], "win"); assert!(json["coins_awarded"].as_i64().unwrap() > 0); } #[tokio::test] async fn test_sbc_list_returns_definitions() { let app = build_test_app().await; let resp = app .oneshot(Request::builder().uri("/sbc").body(Body::empty()).unwrap()) .await .unwrap(); assert_eq!(resp.status(), StatusCode::OK); let body = axum::body::to_bytes(resp.into_body(), usize::MAX) .await .unwrap(); let json: Value = serde_json::from_slice(&body).unwrap(); assert!(json["sbcs"].is_array()); }