feat(v1): harden typed runtime boundaries
Elect one canonical EffectBatch codec, remove the parallel postcard API, and strengthen fail-closed host, form, sync, WASM, macro, generated-contract, and test-harness proofs with mutation-driven coverage. req: wire/008 req: wire/009 req: wire/010 req: push/008 req: client_local/015 req: client_local/016 req: client_local/017 req: client_local/018 req: client_local/019 req: sync/024 req: sync/025 req: sync/026 req: sync/027 req: sync/028 req: sync/029 req: test/020 req: test/021
This commit is contained in:
@@ -2325,6 +2325,134 @@ fn parse_error(path: &Path, err: impl std::fmt::Display) -> io::Error {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn generated_contract_fingerprint_and_client_bootstrap_are_deterministic() {
|
||||
let resource = |symbol: &str, component: &str, id| Resource {
|
||||
symbol: symbol.into(),
|
||||
ident: symbol.into(),
|
||||
component: component.into(),
|
||||
keyed: false,
|
||||
id,
|
||||
};
|
||||
let mut resources = Resources::default();
|
||||
resources
|
||||
.slots
|
||||
.insert("slot".into(), resource("slot", "page", 11));
|
||||
resources
|
||||
.handles
|
||||
.insert("save".into(), resource("save", "page", 12));
|
||||
resources
|
||||
.atoms
|
||||
.insert("count".into(), resource("count", "page", 13));
|
||||
resources.forms.insert(
|
||||
"profile".into(),
|
||||
FormResource {
|
||||
resource: resource("profile", "page", 14),
|
||||
controls: vec![
|
||||
GeneratedControl {
|
||||
name: "email".into(),
|
||||
kind: ControlKind::Text,
|
||||
required: true,
|
||||
},
|
||||
GeneratedControl {
|
||||
name: "nickname".into(),
|
||||
kind: ControlKind::Text,
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
let expected_parts = vec![
|
||||
SURFACE_SCHEMA_VERSION,
|
||||
EFFECT_BATCH_ABI_VERSION,
|
||||
RUNTIME_ABI_VERSION,
|
||||
0,
|
||||
11,
|
||||
1,
|
||||
12,
|
||||
3,
|
||||
13,
|
||||
2,
|
||||
14,
|
||||
2,
|
||||
stable_id("form-field", "email"),
|
||||
1,
|
||||
stable_id("form-field", "nickname"),
|
||||
0,
|
||||
];
|
||||
assert_eq!(resources.fingerprint_parts(), expected_parts);
|
||||
|
||||
let generated = resources.generated_rs(false);
|
||||
assert!(generated.starts_with("// @generated by hemx-build. Do not edit.\n"));
|
||||
assert!(generated.contains("pub const BUILD_FINGERPRINT"));
|
||||
assert!(generated.contains(
|
||||
"\n#[allow(non_upper_case_globals)]\npub mod page {\n #[derive(Clone, Copy)]\n pub struct Component;\n"
|
||||
));
|
||||
assert!(generated.contains("pub const slot: SlotTarget"));
|
||||
assert!(generated.contains("pub const save: ::hemx::Handle"));
|
||||
assert!(generated.contains("pub const count: ::hemx::Atom"));
|
||||
assert!(generated.contains("pub const profile: ::hemx::Form"));
|
||||
assert!(generated.contains("\n pub mod targets {\n"));
|
||||
|
||||
let generated_with_globals = resources.generated_rs(true);
|
||||
assert!(generated_with_globals.contains("pub mod advanced {"));
|
||||
assert!(generated_with_globals.contains("pub mod slots {"));
|
||||
|
||||
assert_eq!(Resources::default().client_bootstrap().unwrap(), "");
|
||||
resources
|
||||
.client_handlers
|
||||
.extend(["save".into(), "toggle".into()]);
|
||||
assert_eq!(
|
||||
resources.client_bootstrap().unwrap_err().to_string(),
|
||||
"client-local handlers require one data-hemx-client-module on a hemx root"
|
||||
);
|
||||
resources.client_modules.insert("/app.wasm".into());
|
||||
let bootstrap = resources.client_bootstrap().unwrap();
|
||||
assert!(bootstrap.starts_with(
|
||||
"import init, { __hemx_client_save, __hemx_client_toggle } from \"/app.wasm\";\nawait init();\n"
|
||||
));
|
||||
assert!(
|
||||
bootstrap.contains("window.hemx.registerClientHandler(\"save\", __hemx_client_save);")
|
||||
);
|
||||
assert!(bootstrap.contains("data-hemx-client-ready"));
|
||||
resources.client_modules.insert("/other.wasm".into());
|
||||
assert_eq!(
|
||||
resources.client_bootstrap().unwrap_err().to_string(),
|
||||
"client-local handlers must share one data-hemx-client-module per generated application"
|
||||
);
|
||||
// test req: build/007 req: abi/003 req: client_local/011
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_builder_returns_output_errors_instead_of_panicking() {
|
||||
let root = std::env::temp_dir().join(format!(
|
||||
"hemx-build-error-{}-{}",
|
||||
std::process::id(),
|
||||
std::thread::current().name().unwrap_or("test")
|
||||
));
|
||||
let out = root.join("out");
|
||||
std::fs::create_dir_all(out.join("hemx.generated.rs")).unwrap();
|
||||
|
||||
let result = std::panic::catch_unwind(|| app().out_dir(&out).run());
|
||||
assert!(result.is_ok(), "ordinary output errors must not panic");
|
||||
assert!(result.unwrap().is_err());
|
||||
|
||||
std::fs::remove_dir_all(out.join("hemx.generated.rs")).unwrap();
|
||||
std::fs::create_dir_all(out.join("hemx.syms")).unwrap();
|
||||
let result = std::panic::catch_unwind(|| app().out_dir(&out).run());
|
||||
assert!(result.is_ok(), "symbol output errors must not panic");
|
||||
assert!(result.unwrap().is_err());
|
||||
|
||||
std::fs::remove_dir_all(out.join("hemx.syms")).unwrap();
|
||||
std::fs::create_dir_all(out.join("hemx.client.js")).unwrap();
|
||||
let result = std::panic::catch_unwind(|| app().out_dir(&out).run());
|
||||
assert!(result.is_ok(), "client output errors must not panic");
|
||||
assert!(result.unwrap().is_err());
|
||||
let _ = std::fs::remove_dir_all(root);
|
||||
// test req: build/004
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_op_build_preserves_generated_artifact_timestamps() {
|
||||
// req: build/009
|
||||
|
||||
Reference in New Issue
Block a user