diff --git a/PLAN.md b/PLAN.md index 0dde1ad..02d85bb 100644 --- a/PLAN.md +++ b/PLAN.md @@ -21,7 +21,7 @@ - [ ] **State:** In progress — the package-native capped xtask entry point is reachable, rejects unknown packages, propagates mutest failure, and mutation-tests `hemx-core`, `hemx-js`, and the full `hemx-test` package cleanly; full package closure remains. - **User value:** maintainers can run one bounded repository command and trust that meaningful Rust logic across every mutation-applicable library is either killed or explicitly justified. - **Build:** add a capped `hemx-xtask` mutation command that invokes `/opt/repositories/mutest`/`mutest` through package-native test targets rather than the broken workspace-wide example path; enumerate only current mutation-applicable library/proc-macro packages; finish adversarial tests or simplify code until every survivor is classified; keep equivalent, invariant-only, and infrastructure-inapplicable classifications inspectable and minimal; document the exact local release command in the existing readiness surface. -- **Blocked by:** none; broad survivors currently remain in `hemx-axum`, `hemx-build`, `hemx-derive`, and `hemx-lsp` outside already-clean focused contracts; the current `hemx-axum` frontier now proves page-mode, response constructors, form accessors/rejections, media-type limits, and multipart success/error semantics mutation-clean; registry and remaining response paths remain. +- **Blocked by:** none; broad survivors currently remain in `hemx-axum`, `hemx-build`, `hemx-derive`, and `hemx-lsp` outside already-clean focused contracts; the current `hemx-axum` frontier now proves page-mode, response constructors, form accessors/rejections, media-type limits, multipart success/error semantics, and sync/async registry dispatch mutation-clean; remaining response and integration paths remain. - **Proof:** the new xtask mutation command exits zero within its documented bound, covers each applicable package, emits no unexplained missed mutant, and a deliberate adjacent mutation makes it fail. `cargo run -p hemx-xtask -- test` remains green. req: test/020 req: test/021 ## 3. Elect and enforce the release license policy diff --git a/hemx-axum/tests/response.rs b/hemx-axum/tests/response.rs index fbb0238..d1ac677 100644 --- a/hemx-axum/tests/response.rs +++ b/hemx-axum/tests/response.rs @@ -19,6 +19,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; use tower::ServiceExt; static MUTATION_CALLS: AtomicUsize = AtomicUsize::new(0); +static BOUNDARY_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); async fn bounded_mutation(_: InteractionRequest) -> StatusCode { MUTATION_CALLS.fetch_add(1, Ordering::SeqCst); @@ -40,6 +41,9 @@ async fn multipart_mutation(request: InteractionRequest) -> StatusCode { #[tokio::test] async fn interaction_boundary_honors_media_type_and_host_body_limit() { // test req: security/003 + let _guard = BOUNDARY_TEST_LOCK + .lock() + .unwrap_or_else(|error| error.into_inner()); MUTATION_CALLS.store(0, Ordering::SeqCst); let app = Router::new() .route("/mutate", post(bounded_mutation)) @@ -100,6 +104,9 @@ async fn interaction_boundary_honors_media_type_and_host_body_limit() { #[tokio::test] async fn interaction_boundary_extracts_multipart_fields_and_files() { + let _guard = BOUNDARY_TEST_LOCK + .lock() + .unwrap_or_else(|error| error.into_inner()); let boundary = "hemx-boundary"; let body = concat!( "--hemx-boundary\r\n", @@ -134,6 +141,9 @@ async fn interaction_boundary_extracts_multipart_fields_and_files() { #[tokio::test] async fn interaction_boundary_skips_unnamed_parts_and_rejects_invalid_multipart() { + let _guard = BOUNDARY_TEST_LOCK + .lock() + .unwrap_or_else(|error| error.into_inner()); let accepted = concat!( "--b\r\n", "Content-Disposition: form-data; filename=\"ignored.txt\"\r\n\r\n", @@ -781,6 +791,32 @@ fn interactions_dispatch_by_checked_handle() { assert_eq!(response.batch.ops, vec![title.text("Hello")]); } +#[tokio::test] +async fn registry_contains_registered_sync_and_async_handles_and_async_falls_back_to_sync() { + let sync = Handle::<()>::new(21); + let asynchronous = Handle::<()>::new(22); + let registry = interactions(BuildFingerprint(55)) + .on(sync, |_| Slot::::new(1).text("sync")) + .on_async(asynchronous, |_| async { + Slot::::new(1).text("async") + }); + + assert!(registry.contains(sync.id().id)); + assert!(registry.contains(asynchronous.id().id)); + assert!(!registry.contains(99)); + + let sync_response = InteractionRequest::from(InteractionForm::for_handle(sync, [])) + .dispatch_async(registry) + .await + .unwrap(); + assert_eq!(sync_response.batch.fingerprint, BuildFingerprint(55)); + assert_eq!( + sync_response.batch.ops, + vec![Slot::::new(1).text("sync")] + ); + // test req: axum_integration/003 req: public_api/001 +} + #[test] fn interactions_reject_unknown_handle_ids() { let request = InteractionRequest::from(InteractionForm::new(9, []));