openauth-core 0.0.4

Core types and primitives for OpenAuth.
Documentation
use http::{Method, Request, StatusCode};
use openauth_core::api::{response, ApiRequest, AsyncAuthEndpoint, AuthEndpoint, AuthRouter};
use openauth_core::context::{create_auth_context, AuthContext};
use openauth_core::error::OpenAuthError;
use openauth_core::options::OpenAuthOptions;

fn sync_endpoint() -> AuthEndpoint {
    AuthEndpoint {
        path: "/sync".to_owned(),
        method: Method::GET,
        handler: |_context: &AuthContext, _request: ApiRequest| {
            response(StatusCode::OK, b"SYNC".to_vec())
        },
    }
}

fn async_endpoint(path: &str) -> AsyncAuthEndpoint {
    AsyncAuthEndpoint::new(
        path.to_owned(),
        Method::GET,
        |_context: &AuthContext, _request: ApiRequest| {
            Box::pin(async move { response(StatusCode::OK, b"ASYNC".to_vec()) })
        },
    )
}

#[tokio::test]
async fn handle_async_runs_async_endpoint() -> Result<(), Box<dyn std::error::Error>> {
    let context = create_auth_context(OpenAuthOptions {
        secret: Some("secret-a-at-least-32-chars-long!!".to_owned()),
        ..OpenAuthOptions::default()
    })?;
    let router =
        AuthRouter::with_async_endpoints(context, Vec::new(), vec![async_endpoint("/async")])?;

    let response = router
        .handle_async(
            Request::builder()
                .method(Method::GET)
                .uri("http://localhost:3000/api/auth/async")
                .body(Vec::new())?,
        )
        .await?;

    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(response.body(), b"ASYNC");
    Ok(())
}

#[tokio::test]
async fn handle_async_can_run_existing_sync_endpoints() -> Result<(), Box<dyn std::error::Error>> {
    let context = create_auth_context(OpenAuthOptions {
        secret: Some("secret-a-at-least-32-chars-long!!".to_owned()),
        ..OpenAuthOptions::default()
    })?;
    let router = AuthRouter::with_async_endpoints(context, vec![sync_endpoint()], Vec::new())?;

    let response = router
        .handle_async(
            Request::builder()
                .method(Method::GET)
                .uri("http://localhost:3000/api/auth/sync")
                .body(Vec::new())?,
        )
        .await?;

    assert_eq!(response.status(), StatusCode::OK);
    assert_eq!(response.body(), b"SYNC");
    Ok(())
}

#[test]
fn sync_handle_rejects_async_only_endpoint() -> Result<(), Box<dyn std::error::Error>> {
    let context = create_auth_context(OpenAuthOptions {
        secret: Some("secret-a-at-least-32-chars-long!!".to_owned()),
        ..OpenAuthOptions::default()
    })?;
    let router =
        AuthRouter::with_async_endpoints(context, Vec::new(), vec![async_endpoint("/async")])?;

    let error = match router.handle(
        Request::builder()
            .method(Method::GET)
            .uri("http://localhost:3000/api/auth/async")
            .body(Vec::new())?,
    ) {
        Ok(_) => return Err("sync handle unexpectedly accepted async endpoint".into()),
        Err(error) => error,
    };

    assert_eq!(
        error,
        OpenAuthError::Api("async endpoint requires AuthRouter::handle_async".to_owned())
    );
    Ok(())
}

#[test]
fn async_endpoint_conflicts_with_sync_endpoint() -> Result<(), Box<dyn std::error::Error>> {
    let context = create_auth_context(OpenAuthOptions {
        secret: Some("secret-a-at-least-32-chars-long!!".to_owned()),
        ..OpenAuthOptions::default()
    })?;

    let result = AuthRouter::with_async_endpoints(
        context,
        vec![AuthEndpoint {
            path: "/same".to_owned(),
            method: Method::GET,
            handler: |_context: &AuthContext, _request: ApiRequest| {
                response(StatusCode::OK, Vec::new())
            },
        }],
        vec![async_endpoint("/same")],
    );

    assert!(result.is_err());
    Ok(())
}