227a408985
Harden axum response and root fingerprint tests with scraper-based HTML parsing instead of raw rendered-string equality. req: test/005
134 lines
4.6 KiB
Rust
134 lines
4.6 KiB
Rust
use axum::http::{header, HeaderMap};
|
|
use axum::response::IntoResponse;
|
|
use scraper::{Html, Selector};
|
|
use slhx_axum::{
|
|
runtime_js, DispatchRejection, EffectResponse, HandlerRegistry, InteractionForm,
|
|
InteractionFormRejection, PageMode, PageRequest, PageResponse, SLHX_CONTENT_TYPE,
|
|
SLHX_FINGERPRINT_HEADER, SLHX_PARTIAL_HEADER, SLHX_RUNTIME_CONTENT_TYPE, SLHX_TITLE_HEADER,
|
|
};
|
|
use slhx_core::{push, BuildFingerprint, Slot};
|
|
|
|
fn selector(value: &str) -> Selector {
|
|
Selector::parse(value).expect("test selector parses")
|
|
}
|
|
|
|
#[test]
|
|
fn page_mode_detects_partial_header() {
|
|
let mut headers = HeaderMap::new();
|
|
assert_eq!(PageMode::from_headers(&headers), PageMode::Full);
|
|
|
|
headers.insert(SLHX_PARTIAL_HEADER, "true".parse().unwrap());
|
|
assert_eq!(PageMode::from_headers(&headers), PageMode::Partial);
|
|
}
|
|
|
|
#[test]
|
|
fn page_request_wraps_full_pages_and_leaves_partials_unwrapped() {
|
|
// req: test/005
|
|
let full = PageRequest { mode: PageMode::Full }.page(
|
|
"<main data-page=\"docs\">Docs</main>",
|
|
|content| format!("<html><body data-shell=\"docs\">{content}</body></html>"),
|
|
);
|
|
assert_eq!(full.mode, PageMode::Full);
|
|
let full_document = Html::parse_document(&full.html);
|
|
assert_eq!(
|
|
full_document
|
|
.select(&selector("body[data-shell=\"docs\"] main[data-page=\"docs\"]"))
|
|
.count(),
|
|
1
|
|
);
|
|
|
|
let partial = PageRequest { mode: PageMode::Partial }.page(
|
|
"<main data-page=\"docs\">Docs</main>",
|
|
|content| format!("<html><body data-shell=\"docs\">{content}</body></html>"),
|
|
);
|
|
assert_eq!(partial.mode, PageMode::Partial);
|
|
let partial_fragment = Html::parse_fragment(&partial.html);
|
|
assert_eq!(partial_fragment.select(&selector("main[data-page=\"docs\"]")).count(), 1);
|
|
assert_eq!(partial_fragment.select(&selector("body[data-shell=\"docs\"]")).count(), 0);
|
|
}
|
|
|
|
#[test]
|
|
fn partial_page_response_sets_partial_and_title_headers() {
|
|
let response = PageResponse::partial("<main>Docs</main>")
|
|
.title("Docs")
|
|
.into_response();
|
|
|
|
assert_eq!(response.headers()[header::CONTENT_TYPE], "text/html; charset=utf-8");
|
|
assert_eq!(response.headers()[SLHX_PARTIAL_HEADER], "true");
|
|
assert_eq!(response.headers()[SLHX_TITLE_HEADER], "Docs");
|
|
}
|
|
|
|
#[test]
|
|
fn effect_response_is_wire_batch_with_fingerprint_header() {
|
|
let response = EffectResponse::new(push("/docs"), BuildFingerprint(99)).into_response();
|
|
|
|
assert_eq!(response.headers()[header::CONTENT_TYPE], SLHX_CONTENT_TYPE);
|
|
assert_eq!(response.headers()[SLHX_FINGERPRINT_HEADER], "99");
|
|
}
|
|
|
|
#[test]
|
|
fn interaction_form_parses_handle_and_fields() {
|
|
let form = InteractionForm::parse_urlencoded(b"__h=42&title=Hello+World&tag=a&tag=b%2Fc")
|
|
.unwrap();
|
|
|
|
assert_eq!(form.handle_id, 42);
|
|
assert_eq!(form.value("title"), Some("Hello World"));
|
|
assert_eq!(form.values("tag").collect::<Vec<_>>(), ["a", "b/c"]);
|
|
}
|
|
|
|
#[test]
|
|
fn interaction_form_requires_numeric_handle() {
|
|
assert_eq!(
|
|
InteractionForm::parse_urlencoded(b"title=Hello").unwrap_err(),
|
|
InteractionFormRejection::MissingHandle
|
|
);
|
|
assert_eq!(
|
|
InteractionForm::parse_urlencoded(b"__h=nope").unwrap_err(),
|
|
InteractionFormRejection::InvalidHandle
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn interaction_form_preserves_hidden_csrf_fields_for_extractors() {
|
|
// req: auth/004
|
|
let form = InteractionForm::parse_urlencoded(b"__h=42&csrf_token=abc123&title=Hello")
|
|
.unwrap();
|
|
|
|
assert_eq!(form.handle_id, 42);
|
|
assert_eq!(form.value("csrf_token"), Some("abc123"));
|
|
assert!(form.fields().iter().any(|(name, _)| name == "csrf_token"));
|
|
}
|
|
|
|
#[test]
|
|
fn handler_registry_dispatches_by_numeric_handle_id() {
|
|
let title = Slot::<String>::new(7);
|
|
let registry = HandlerRegistry::new(BuildFingerprint(123)).register(42, move |form| {
|
|
title.text(form.value("title").unwrap_or(""))
|
|
});
|
|
|
|
let response = registry
|
|
.dispatch(InteractionForm::new(42, [("title".into(), "Hello".into())]))
|
|
.unwrap();
|
|
|
|
assert_eq!(response.batch.fingerprint, BuildFingerprint(123));
|
|
assert_eq!(response.batch.ops, vec![title.text("Hello")]);
|
|
}
|
|
|
|
#[test]
|
|
fn handler_registry_rejects_unknown_handle_ids() {
|
|
let registry = HandlerRegistry::new(BuildFingerprint(123));
|
|
|
|
assert_eq!(
|
|
registry.dispatch(InteractionForm::new(9, [])).unwrap_err(),
|
|
DispatchRejection::UnknownHandle(9)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn runtime_js_response_serves_embedded_runtime() {
|
|
let response = runtime_js().into_response();
|
|
|
|
assert_eq!(response.headers()[header::CONTENT_TYPE], SLHX_RUNTIME_CONTENT_TYPE);
|
|
assert!(response.headers().contains_key(header::CACHE_CONTROL));
|
|
}
|