Private
Public Access
1
0

test: add CRL integration and unit tests (PR 6 of 6)
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 6s
CI Pipeline / Clippy Lints (push) Successful in 52s
CI Pipeline / Rust Unit Tests (push) Successful in 1m10s
CI Pipeline / Security Audit (push) Successful in 6s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 16s
CI Pipeline / Build .deb & Release (push) Has been skipped

Co-authored-by: Draco Lunaris <331325+Draco-Lunaris@users.noreply.github.com>
This commit is contained in:
Draco-Lunaris-Echo
2026-06-05 17:26:20 -05:00
committed by GitHub
parent 5ab3532833
commit 899fd4a79a
5 changed files with 611 additions and 0 deletions

View File

@ -44,3 +44,6 @@ sha2 = { workspace = true }
jsonwebtoken = { workspace = true }
url = { workspace = true }
urlencoding = "2"
[dev-dependencies]
tempfile = "3"

View File

@ -59,3 +59,204 @@ async fn get_crl(State(state): State<AppState>) -> impl IntoResponse {
},
}
}
#[cfg(test)]
mod tests {
use super::*;
use axum::body::Body;
use axum::http::{Request, StatusCode};
use axum::Router;
use dashmap::DashMap;
use pm_auth::rbac::AuthConfig;
use pm_core::config::AppConfig;
use sqlx::PgPool;
use std::sync::Arc;
use tokio::sync::Mutex;
use tower::ServiceExt;
/// Helper: create a test AppState with a real CA and database pool.
/// Returns None if TEST_DATABASE_URL is not set (tests are skipped).
async fn setup_app_state() -> Option<(PgPool, AppState)> {
let db_url = std::env::var("TEST_DATABASE_URL").ok()?;
let pool = PgPool::connect(&db_url).await.ok()?;
// Run migrations to ensure schema is up to date.
sqlx::migrate!("../../migrations").run(&pool).await.ok()?;
// Create a temp directory for the CA.
let tmp_dir = tempfile::tempdir().ok()?;
let ca_dir = tmp_dir.path().to_path_buf();
let ca = pm_ca::CertAuthority::init(&ca_dir, &pool).await.ok()?;
let config = Arc::new(AppConfig::default());
use crate::routes::sso::OidcCache;
let state = AppState {
db: pool.clone(),
config,
signing_key_pem: String::new(),
auth_config: Arc::new(AuthConfig::new(String::new(), &[], &[])),
ws_tickets: Arc::new(DashMap::new()),
sso_sessions: Arc::new(DashMap::new()),
sso_handoffs: Arc::new(DashMap::new()),
oidc_cache: Arc::new(Mutex::new(OidcCache::default())),
ca: Arc::new(ca),
approved_enrollments: Arc::new(DashMap::new()),
};
Some((pool, state))
}
/// Build an Axum app with just the PKI routes for testing.
fn test_app(state: AppState) -> Router {
Router::new().nest("/api/v1", router()).with_state(state)
}
#[tokio::test]
async fn crl_endpoint_returns_200_with_valid_pem() {
let Some((pool, state)) = setup_app_state().await else {
eprintln!("skipping: TEST_DATABASE_URL not set");
return;
};
let app = test_app(state);
let response = app
.oneshot(
Request::builder()
.uri("/api/v1/pki/crl.pem")
.body(Body::empty())
.unwrap(),
)
.await
.expect("request should succeed");
assert_eq!(
response.status(),
StatusCode::OK,
"CRL endpoint should return 200 OK"
);
let body = axum::body::to_bytes(response.into_body(), 10_000)
.await
.expect("body should be readable");
let body_str = String::from_utf8(body.to_vec()).expect("body should be UTF-8");
assert!(
body_str.contains("-----BEGIN X509 CRL-----"),
"Response should contain CRL PEM header"
);
assert!(
body_str.contains("-----END X509 CRL-----"),
"Response should contain CRL PEM footer"
);
pool.close().await;
}
#[tokio::test]
async fn crl_endpoint_returns_cache_control_header() {
let Some((pool, state)) = setup_app_state().await else {
eprintln!("skipping: TEST_DATABASE_URL not set");
return;
};
let app = test_app(state);
let response = app
.oneshot(
Request::builder()
.uri("/api/v1/pki/crl.pem")
.body(Body::empty())
.unwrap(),
)
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::OK);
let cache_control = response
.headers()
.get("cache-control")
.expect("Cache-Control header should be present");
assert_eq!(
cache_control.to_str().unwrap(),
"max-age=3600",
"Cache-Control should be max-age=3600"
);
pool.close().await;
}
#[tokio::test]
async fn crl_endpoint_works_without_authentication() {
let Some((pool, state)) = setup_app_state().await else {
eprintln!("skipping: TEST_DATABASE_URL not set");
return;
};
let app = test_app(state);
// Make request without any auth headers — CRL endpoint is public.
let response = app
.oneshot(
Request::builder()
.uri("/api/v1/pki/crl.pem")
.body(Body::empty())
.unwrap(),
)
.await
.expect("request should succeed");
// Should return 200, not 401 Unauthorized.
assert_eq!(
response.status(),
StatusCode::OK,
"CRL endpoint should be accessible without authentication"
);
pool.close().await;
}
#[tokio::test]
async fn crl_endpoint_returns_pem_content_type() {
let Some((pool, state)) = setup_app_state().await else {
eprintln!("skipping: TEST_DATABASE_URL not set");
return;
};
let app = test_app(state);
let response = app
.oneshot(
Request::builder()
.uri("/api/v1/pki/crl.pem")
.body(Body::empty())
.unwrap(),
)
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::OK);
let content_type = response
.headers()
.get("content-type")
.expect("Content-Type header should be present");
assert!(
content_type
.to_str()
.unwrap()
.contains("application/x-pem-file"),
"Content-Type should be application/x-pem-file, got: {:?}",
content_type
);
pool.close().await;
}
}