use std::sync::Once;
use axum::http::Request;
use axum::{
body::Body,
http::{StatusCode, header},
};
use http_body_util::BodyExt;
use sloc_web::make_test_router;
use tower::ServiceExt;
const BANNER: &str = r#"Restricted & <monitored> "system""#;
fn enable_banner() {
static INIT: Once = Once::new();
INIT.call_once(|| unsafe { std::env::set_var("SLOC_CONSENT_BANNER", BANNER) });
}
async fn body_string(resp: axum::response::Response) -> String {
let bytes = resp.into_body().collect().await.unwrap().to_bytes();
String::from_utf8_lossy(&bytes).into_owned()
}
#[tokio::test]
async fn browser_navigation_without_cookie_gets_the_consent_page() {
enable_banner();
let resp = make_test_router()
.oneshot(
Request::get("/")
.header(header::ACCEPT, "text/html")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let body = body_string(resp).await;
assert!(
body.contains("Notice and Consent"),
"consent interstitial expected"
);
assert!(body.contains("Restricted & <monitored> "system""));
assert!(body.contains("/auth/consent?next="));
}
#[tokio::test]
async fn request_with_consent_cookie_passes_through() {
enable_banner();
let resp = make_test_router()
.oneshot(
Request::get("/")
.header(header::ACCEPT, "text/html")
.header(header::COOKIE, "sloc_consent=1")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
let body = body_string(resp).await;
assert!(
!body.contains("Notice and Consent"),
"an acknowledged session must not be re-gated"
);
}
#[tokio::test]
async fn exempt_path_is_not_gated() {
enable_banner();
let resp = make_test_router()
.oneshot(
Request::get("/healthz")
.header(header::ACCEPT, "text/html")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::OK);
assert!(!body_string(resp).await.contains("Notice and Consent"));
}
#[tokio::test]
async fn non_html_request_is_not_gated() {
enable_banner();
let resp = make_test_router()
.oneshot(
Request::get("/")
.header(header::ACCEPT, "application/json")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert!(!body_string(resp).await.contains("Notice and Consent"));
}
#[tokio::test]
async fn non_get_method_is_not_gated() {
enable_banner();
let resp = make_test_router()
.oneshot(
Request::post("/analyze")
.header(header::ACCEPT, "text/html")
.header(header::CONTENT_TYPE, "application/x-www-form-urlencoded")
.body(Body::from("path=."))
.unwrap(),
)
.await
.unwrap();
assert!(!body_string(resp).await.contains("Notice and Consent"));
}
#[tokio::test]
async fn accepting_consent_sets_cookie_and_redirects_to_safe_next() {
enable_banner();
let resp = make_test_router()
.oneshot(
Request::get("/auth/consent?next=/scan")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::FOUND);
let cookie = resp
.headers()
.get(header::SET_COOKIE)
.and_then(|v| v.to_str().ok())
.unwrap_or_default();
assert!(
cookie.contains("sloc_consent=1"),
"acknowledgement cookie set"
);
let loc = resp
.headers()
.get(header::LOCATION)
.and_then(|v| v.to_str().ok())
.unwrap_or_default();
assert_eq!(loc, "/scan", "returns the user to their original path");
}
#[tokio::test]
async fn accepting_consent_rejects_offsite_next() {
enable_banner();
let resp = make_test_router()
.oneshot(
Request::get("/auth/consent?next=http://evil.example/x")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(resp.status(), StatusCode::FOUND);
let loc = resp
.headers()
.get(header::LOCATION)
.and_then(|v| v.to_str().ok())
.unwrap_or_default();
assert_eq!(loc, "/", "offsite redirect target must be neutralised");
}