test(axum): close package mutation gate
Exercise every state, typed-form, async, and result registration family; replace fallible numeric headers with typed HeaderValue construction; and classify only validated equivalent multipart/header mutants. req: axum_integration/003 req: derive_handler/005 req: multipart/003 req: test/020 req: test/021 req: test/022
This commit is contained in:
+9
-11
@@ -1347,10 +1347,10 @@ impl IntoResponse for PageResponse {
|
||||
.headers_mut()
|
||||
.insert(HEMX_PARTIAL_HEADER, HeaderValue::from_static("true"));
|
||||
}
|
||||
if let Some(fingerprint) = self.fingerprint.and_then(fingerprint_header) {
|
||||
if let Some(fingerprint) = self.fingerprint {
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(HEMX_FINGERPRINT_HEADER, fingerprint);
|
||||
.insert(HEMX_FINGERPRINT_HEADER, fingerprint_header(fingerprint));
|
||||
}
|
||||
if let Some(title) = self
|
||||
.title
|
||||
@@ -1380,8 +1380,8 @@ fn html_with_root_fingerprint(mut html: String, fingerprint: BuildFingerprint) -
|
||||
html
|
||||
}
|
||||
|
||||
fn fingerprint_header(fingerprint: BuildFingerprint) -> Option<HeaderValue> {
|
||||
HeaderValue::from_str(&fingerprint.0.to_string()).ok()
|
||||
fn fingerprint_header(fingerprint: BuildFingerprint) -> HeaderValue {
|
||||
HeaderValue::from(fingerprint.0)
|
||||
}
|
||||
|
||||
impl IntoResponse for EffectResponse {
|
||||
@@ -1392,11 +1392,10 @@ impl IntoResponse for EffectResponse {
|
||||
header::CONTENT_TYPE,
|
||||
HeaderValue::from_static(HEMX_CONTENT_TYPE),
|
||||
);
|
||||
if let Some(fingerprint) = fingerprint_header(self.batch.fingerprint) {
|
||||
response
|
||||
.headers_mut()
|
||||
.insert(HEMX_FINGERPRINT_HEADER, fingerprint);
|
||||
}
|
||||
response.headers_mut().insert(
|
||||
HEMX_FINGERPRINT_HEADER,
|
||||
fingerprint_header(self.batch.fingerprint),
|
||||
);
|
||||
response
|
||||
}
|
||||
}
|
||||
@@ -1442,8 +1441,7 @@ impl IntoResponse for RuntimeJs {
|
||||
);
|
||||
response.headers_mut().insert(
|
||||
header::CONTENT_LENGTH,
|
||||
HeaderValue::from_str(hemx_js::RUNTIME_JS.len().to_string().as_str())
|
||||
.expect("runtime length is a valid header value"),
|
||||
HeaderValue::from(hemx_js::RUNTIME_JS.len() as u64),
|
||||
);
|
||||
response
|
||||
}
|
||||
|
||||
+150
-1
@@ -13,7 +13,7 @@ use hemx_axum::{
|
||||
InteractionRequest, IntoHandlerFailure, PageMode, PageRequest, PageResponse, HEMX_CONTENT_TYPE,
|
||||
HEMX_FINGERPRINT_HEADER, HEMX_PARTIAL_HEADER, HEMX_RUNTIME_CONTENT_TYPE, HEMX_TITLE_HEADER,
|
||||
};
|
||||
use hemx_core::{push, BuildFingerprint, Handle, IntoEffect, SafeHtml, Slot};
|
||||
use hemx_core::{push, BuildFingerprint, Effect, Handle, IntoEffect, SafeHtml, Slot};
|
||||
use scraper::{Html, Selector};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use tower::ServiceExt;
|
||||
@@ -865,6 +865,155 @@ async fn registry_contains_registered_sync_and_async_handles_and_async_falls_bac
|
||||
// test req: axum_integration/003 req: public_api/001
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn state_result_registration_paths_preserve_state_form_and_failures() {
|
||||
let sync = Handle::<()>::new(30);
|
||||
let typed = Handle::<()>::new(31);
|
||||
let asynchronous = Handle::<()>::new(32);
|
||||
let typed_async = Handle::<()>::new(33);
|
||||
let typed_plain = Handle::<()>::new(34);
|
||||
let typed_state_plain = Handle::<()>::new(37);
|
||||
let typed_async_plain = Handle::<()>::new(35);
|
||||
let state_async_plain = Handle::<()>::new(36);
|
||||
let registry = interactions(BuildFingerprint(88))
|
||||
.on_state_result(sync, String::from("sync"), |state: State<String>| {
|
||||
Ok::<_, HandlerBoom>(Slot::<String>::new(1).text(state.0))
|
||||
})
|
||||
.on_state_form_result(
|
||||
typed,
|
||||
String::from("typed"),
|
||||
|state: State<String>, Form(form): Form<OpenProject>| {
|
||||
Err::<Effect, _>(HandlerBoom).map_err(|error| {
|
||||
let _ = (state, form);
|
||||
error
|
||||
})
|
||||
},
|
||||
)
|
||||
.on_state_async_result(
|
||||
asynchronous,
|
||||
String::from("async"),
|
||||
|state: State<String>| async move {
|
||||
Ok::<_, HandlerBoom>(Slot::<String>::new(1).text(state.0))
|
||||
},
|
||||
)
|
||||
.on_form(typed_plain, |Form(form): Form<OpenProject>| {
|
||||
Slot::<String>::new(1).text(format!("typed:{}", form.project_id.0))
|
||||
})
|
||||
.on_state_form(
|
||||
typed_state_plain,
|
||||
String::from("typed-state"),
|
||||
|state: State<String>, Form(form): Form<OpenProject>| {
|
||||
Slot::<String>::new(1).text(format!("{}:{}", state.0, form.project_id.0))
|
||||
},
|
||||
)
|
||||
.on_form_async(
|
||||
typed_async_plain,
|
||||
|Form(form): Form<OpenProject>| async move {
|
||||
Slot::<String>::new(1).text(format!("typed-plain-async:{}", form.project_id.0))
|
||||
},
|
||||
)
|
||||
.on_state_async(
|
||||
state_async_plain,
|
||||
String::from("state-async"),
|
||||
|state: State<String>| async move { Slot::<String>::new(1).text(state.0) },
|
||||
)
|
||||
.on_state_form_async_result(
|
||||
typed_async,
|
||||
String::from("typed-async"),
|
||||
|state: State<String>, Form(form): Form<OpenProject>| async move {
|
||||
Ok::<_, HandlerBoom>(
|
||||
Slot::<String>::new(1).text(format!("{}:{}", state.0, form.project_id.0)),
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
for handle in [
|
||||
sync,
|
||||
typed,
|
||||
asynchronous,
|
||||
typed_async,
|
||||
typed_plain,
|
||||
typed_state_plain,
|
||||
typed_async_plain,
|
||||
state_async_plain,
|
||||
] {
|
||||
assert!(registry.contains(handle.id().id));
|
||||
}
|
||||
assert!(registry
|
||||
.dispatch(InteractionForm::for_handle(sync, []))
|
||||
.is_ok());
|
||||
let invalid_form = registry
|
||||
.dispatch(InteractionForm::for_handle(
|
||||
typed,
|
||||
[(String::from("project_id"), String::from("invalid"))],
|
||||
))
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
invalid_form,
|
||||
DispatchRejection::InvalidForm {
|
||||
handle_id: typed.id().id,
|
||||
message: "invalid form field `project_id`".into(),
|
||||
}
|
||||
);
|
||||
let error = registry
|
||||
.dispatch(InteractionForm::for_handle(
|
||||
typed,
|
||||
[(String::from("project_id"), String::from("7"))],
|
||||
))
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
error,
|
||||
DispatchRejection::HandlerError(HandlerFailure::internal("database unavailable"))
|
||||
);
|
||||
assert!(registry
|
||||
.dispatch_async(InteractionForm::for_handle(asynchronous, []))
|
||||
.await
|
||||
.is_ok());
|
||||
let invalid_plain = registry
|
||||
.dispatch(InteractionForm::for_handle(
|
||||
typed_plain,
|
||||
[(String::from("project_id"), String::from("invalid"))],
|
||||
))
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
invalid_plain,
|
||||
DispatchRejection::InvalidForm { .. }
|
||||
));
|
||||
let invalid_state_plain = registry
|
||||
.dispatch(InteractionForm::for_handle(
|
||||
typed_state_plain,
|
||||
[(String::from("project_id"), String::from("invalid"))],
|
||||
))
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
invalid_state_plain,
|
||||
DispatchRejection::InvalidForm { .. }
|
||||
));
|
||||
assert!(registry
|
||||
.dispatch_async(InteractionForm::for_handle(
|
||||
typed_async_plain,
|
||||
[(String::from("project_id"), String::from("8"))],
|
||||
))
|
||||
.await
|
||||
.is_ok());
|
||||
assert!(registry
|
||||
.dispatch_async(InteractionForm::for_handle(state_async_plain, []))
|
||||
.await
|
||||
.is_ok());
|
||||
let response = registry
|
||||
.dispatch_async(InteractionForm::for_handle(
|
||||
typed_async,
|
||||
[(String::from("project_id"), String::from("9"))],
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
response.batch.ops,
|
||||
vec![Slot::<String>::new(1).text("typed-async:9")]
|
||||
);
|
||||
// test req: axum_integration/003 req: derive_handler/005
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn interactions_reject_unknown_handle_ids() {
|
||||
let request = InteractionRequest::from(InteractionForm::new(9, []));
|
||||
|
||||
Reference in New Issue
Block a user