fraiseql-auth 2.19.0

Authentication, authorization, and session management for FraiseQL
//! Tests for the local-password HTTP routes (#367).
#![allow(clippy::unwrap_used, clippy::panic)] // Reason: test code, panics are acceptable
use super::*;

/// axum validates path-capture syntax inside `Router::route`, so a bad
/// literal panics here in `cargo test` rather than at first server boot
/// (the repo's axum-bump checklist). `connect_lazy` needs no live database:
/// the router is built, never driven.
#[tokio::test]
async fn local_password_router_constructs() {
    let pool =
        sqlx::PgPool::connect_lazy("postgres://unused/unused").expect("lazy pool needs no server");
    let accounts = Arc::new(crate::InMemoryAccountStore::new());
    let state = LocalPasswordRouteState {
        authenticator: Arc::new(LocalPasswordAuthenticator::new(pool.clone(), accounts)),
        session_store: Arc::new(crate::PostgresSessionStore::new(pool)),
        rate_limiters: Some(Arc::new(crate::RateLimiters::default())),
    };
    let _router = local_password_routes(Arc::new(state));
}

/// The verification routes act on the [`SessionSubject`] the server's authentication layer
/// resolved, and refuse a request that carries none: mounted without that layer, they
/// serve no one rather than an account they were never told. The refusal comes before any
/// store is touched, which is why a lazy pool serves here.
#[tokio::test]
async fn email_verification_refuses_a_request_with_no_session() {
    use axum::{body::Body, http::Request};
    use tower::ServiceExt as _;

    let pool =
        sqlx::PgPool::connect_lazy("postgres://unused/unused").expect("lazy pool needs no server");
    let accounts = Arc::new(crate::InMemoryAccountStore::new());
    let router = email_verification_routes(Arc::new(EmailVerificationRouteState {
        authenticator: Arc::new(LocalPasswordAuthenticator::new(pool, accounts)),
        rate_limiters: None,
    }));
    for (path, body) in [
        ("/auth/v1/email/verify/start", "{}"),
        ("/auth/v1/email/verify/confirm", r#"{"token":"whatever"}"#),
    ] {
        let request = Request::builder()
            .method("POST")
            .uri(path)
            .header("content-type", "application/json")
            .extension(ConnectInfo(SocketAddr::from(([10, 0, 0, 1], 4000))))
            .body(Body::from(body))
            .unwrap();
        let response = router.clone().oneshot(request).await.unwrap();
        assert_eq!(response.status(), StatusCode::UNAUTHORIZED, "{path} without a session");
    }
}