rustauth-axum 0.3.0

Axum integration for RustAuth.
Documentation
mod common;

use axum::http::{header, Method, StatusCode, Version};
use common::*;
use rustauth::RustAuth;
use rustauth_axum::{handle, RustAuthAxumExt, RustAuthAxumOptions};
use tower::ServiceExt;

#[tokio::test]
async fn borrowed_handle_preserves_response_status_version_headers_body_and_query(
) -> Result<(), Box<dyn std::error::Error>> {
    let auth = RustAuth::builder()
        .secret(SECRET)
        .async_endpoint(response_contract_endpoint("/contract"))
        .build()
        .await?;

    let response = handle(
        &auth,
        RustAuthAxumOptions::default(),
        request(Method::GET, "/api/auth/contract?next=%2Fhome", "", None)?,
    )
    .await;

    assert_eq!(response.status(), StatusCode::CREATED);
    assert_eq!(response.version(), Version::HTTP_2);
    assert_eq!(body_text(response).await?, "query=next=%2Fhome");
    Ok(())
}

#[tokio::test]
async fn axum_adapter_preserves_duplicate_response_headers(
) -> Result<(), Box<dyn std::error::Error>> {
    let app = RustAuth::builder()
        .secret(SECRET)
        .async_endpoint(response_contract_endpoint("/contract"))
        .build()
        .await?
        .mount_at_base_path(RustAuthAxumOptions::default())?;

    let response = app
        .oneshot(request(Method::GET, "/api/auth/contract", "", None)?)
        .await?;

    let cookies = response
        .headers()
        .get_all(header::SET_COOKIE)
        .iter()
        .map(|value| value.to_str())
        .collect::<Result<Vec<_>, _>>()?;
    let test_headers = response
        .headers()
        .get_all("x-rustauth-test")
        .iter()
        .map(|value| value.to_str())
        .collect::<Result<Vec<_>, _>>()?;

    assert_eq!(cookies.len(), 2);
    assert!(cookies.iter().any(|value| value.starts_with("a=1;")));
    assert!(cookies.iter().any(|value| value.starts_with("b=2;")));
    assert_eq!(test_headers, vec!["one", "two"]);
    Ok(())
}

#[tokio::test]
async fn axum_adapter_preserves_response_extensions() -> Result<(), Box<dyn std::error::Error>> {
    let app = RustAuth::builder()
        .secret(SECRET)
        .async_endpoint(response_contract_endpoint("/contract"))
        .build()
        .await?
        .mount_at_base_path(RustAuthAxumOptions::default())?;

    let response = app
        .oneshot(request(Method::GET, "/api/auth/contract", "", None)?)
        .await?;

    assert_eq!(
        response.extensions().get::<ResponseExtensionMarker>(),
        Some(&ResponseExtensionMarker("response-contract"))
    );
    Ok(())
}

#[tokio::test]
async fn axum_adapter_preserves_empty_response_bodies() -> Result<(), Box<dyn std::error::Error>> {
    let app = RustAuth::builder()
        .secret(SECRET)
        .async_endpoint(empty_response_endpoint("/empty"))
        .build()
        .await?
        .mount_at_base_path(RustAuthAxumOptions::default())?;

    let response = app
        .oneshot(request(Method::GET, "/api/auth/empty", "", None)?)
        .await?;

    assert_eq!(response.status(), StatusCode::NO_CONTENT);
    assert_eq!(body_text(response).await?, "");
    Ok(())
}