#[cfg(feature = "axum")]
use axum::{
Json, Router,
extract::{Form, Query},
response::{IntoResponse, Redirect},
routing::{get, post},
};
#[cfg(feature = "axum")]
use base64::Engine;
#[cfg(feature = "axum")]
use serde::Deserialize;
#[cfg(feature = "axum")]
use serde_json::json;
#[cfg(feature = "axum")]
#[derive(Deserialize)]
pub struct AuthQuery {
pub client_id: String,
pub redirect_uri: String,
pub response_type: String,
pub scope: Option<String>,
pub state: Option<String>,
}
#[cfg(feature = "axum")]
#[derive(Deserialize)]
pub struct TokenForm {
pub client_id: String,
pub client_secret: String,
pub code: String,
pub grant_type: String,
pub redirect_uri: String,
}
#[cfg(feature = "axum")]
pub fn mock_router() -> Router {
Router::new()
.route("/auth", get(authorize_handler))
.route("/token", post(token_handler))
.route("/userinfo", get(userinfo_handler))
.route("/.well-known/openid-configuration", get(discovery_handler))
}
#[cfg(feature = "axum")]
async fn authorize_handler(Query(params): Query<AuthQuery>) -> impl IntoResponse {
let mut redirect = format!("{}?code=mock_auth_code_12345", params.redirect_uri);
if let Some(state) = params.state {
redirect = format!("{}&state={}", redirect, state);
}
Redirect::temporary(&redirect)
}
#[cfg(feature = "axum")]
async fn token_handler(Form(form): Form<TokenForm>) -> impl IntoResponse {
if form.code != "mock_auth_code_12345" {
return Json(json!({
"error": "invalid_grant",
"error_description": "The provided authorization code is invalid."
}));
}
let header = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(b"{\"alg\":\"none\"}");
let payload = base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(
b"{\"sub\":\"mock_user_999\",\"name\":\"Mock User\",\"email\":\"mock@example.com\",\"email_verified\":true}"
);
let id_token = format!("{}.{}.", header, payload);
Json(json!({
"access_token": "mock_access_token_abcde",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "mock_refresh_token_fghij",
"id_token": id_token
}))
}
#[cfg(feature = "axum")]
async fn userinfo_handler() -> impl IntoResponse {
Json(json!({
"sub": "mock_user_999",
"name": "Mock User",
"email": "mock@example.com",
"email_verified": true,
"picture": "https://mock.provider/avatar.png"
}))
}
#[cfg(feature = "axum")]
async fn discovery_handler() -> impl IntoResponse {
Json(json!({
"issuer": "http://localhost:8080",
"authorization_endpoint": "http://localhost:8080/auth",
"token_endpoint": "http://localhost:8080/token",
"userinfo_endpoint": "http://localhost:8080/userinfo",
"jwks_uri": "http://localhost:8080/jwks",
"response_types_supported": ["code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256", "none"]
}))
}
#[cfg(all(test, feature = "axum"))]
mod tests {
use super::*;
use axum::response::IntoResponse;
#[tokio::test]
async fn test_token_handler_invalid_code() {
let form = TokenForm {
client_id: "test".to_string(),
client_secret: "test".to_string(),
code: "invalid_code_here".to_string(),
grant_type: "authorization_code".to_string(),
redirect_uri: "http://test".to_string(),
};
let response = token_handler(axum::extract::Form(form))
.await
.into_response();
let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.expect("Failed to read response body bytes");
let json: serde_json::Value =
serde_json::from_slice(&body_bytes).expect("Failed to parse response body as JSON");
assert_eq!(json["error"], "invalid_grant");
}
#[tokio::test]
async fn test_mock_router_discovery() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let app = mock_router();
let response = app
.oneshot(
Request::builder()
.uri("/.well-known/openid-configuration")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), axum::http::StatusCode::OK);
let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
let json: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap();
assert_eq!(json["issuer"], "http://localhost:8080");
assert_eq!(json["authorization_endpoint"], "http://localhost:8080/auth");
}
#[tokio::test]
async fn test_mock_router_userinfo() {
use axum::{body::Body, http::Request};
use tower::ServiceExt;
let app = mock_router();
let response = app
.oneshot(
Request::builder()
.uri("/userinfo")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), axum::http::StatusCode::OK);
let body_bytes = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
let json: serde_json::Value = serde_json::from_slice(&body_bytes).unwrap();
assert_eq!(json["sub"], "mock_user_999");
assert_eq!(json["email"], "mock@example.com");
}
}