Lines
93.85 %
Functions
100 %
Branches
//! P4 A1 invariant tests.
//!
//! Locks in the structural guarantees of the universal-result
//! convention so a future refactor can't silently regress them:
//! - Every registered `HostFnSpec` declares its result/params through
//! `WasmType` alone — no per-shape boolean flags survive on the
//! struct (verified by absence at compile time).
//! - Host fns returning typed GC refs (Ratio / Commodity / StringRef /
//! PairRef / EntityRef) declare their import signature with the
//! matching abstract heap-type so wasmtime's `Rooted<...>` /
//! `WasmTy::valtype()` mapping holds.
//! The "drift detector" test that used to live here retired when the
//! registry source-of-truth moved into
//! =doc/scripting/native_reference.org= (tangled by `rpc/build.rs`).
//! There's nothing to drift against — the org IS the source.
use nomiscript::{EntityKind, HostFnSpec, PairElement, WasmType};
use rpc::natives::all_compiler_specs;
/// Verifies every typed-result spec maps through the universal-result
/// convention to a non-empty wasm import signature. Each `WasmType`
/// variant is exercised at least once across the registry, so this
/// catches a future variant addition that forgets its handling in
/// `host_import_return_type`.
#[test]
fn every_wasm_type_variant_appears_in_registry() {
let specs = all_compiler_specs();
let mut seen_i32 = false;
let mut seen_ratio = false;
let mut seen_commodity = false;
let mut seen_string = false;
let mut seen_pair = false;
let mut seen_entity = false;
for spec in &specs {
match spec.result {
Some(WasmType::I32) => seen_i32 = true,
// Bool is produced by comparison/predicate operators, not by the
// typed-entity native registry this test surveys.
Some(WasmType::Bool) => {}
Some(WasmType::Ratio) => seen_ratio = true,
Some(WasmType::Commodity) => seen_commodity = true,
Some(WasmType::StringRef) => seen_string = true,
Some(WasmType::PairRef(_)) => seen_pair = true,
Some(WasmType::EntityRef(_)) => seen_entity = true,
Some(WasmType::Closure(_)) => {}
Some(WasmType::AnyRef) => {}
None => {}
}
assert!(seen_i32, "registry has no I32-returning native");
assert!(seen_ratio, "registry has no Ratio-returning native");
assert!(seen_commodity, "registry has no Commodity-returning native");
assert!(seen_string, "registry has no StringRef-returning native");
assert!(seen_pair, "registry has no PairRef-returning native");
assert!(seen_entity, "registry has no EntityRef-returning native");
/// `account-balance` is the canonical commodity-bearing native — it
/// must surface a `Commodity` return so callers can compose with `+`,
/// `convert-commodity`, etc. Locks in the P3b/1b migration.
fn account_balance_returns_commodity() {
let spec = specs
.iter()
.find(|s| s.nomi_name == "ACCOUNT-BALANCE")
.expect("ACCOUNT-BALANCE missing from registry");
assert_eq!(spec.result, Some(WasmType::Commodity));
assert_eq!(spec.params, vec![WasmType::StringRef]);
/// `convert-commodity` is the cross-currency bridge — takes a typed
/// `Commodity` value and a target uuid string, returns a `Commodity`
/// in the target. Locks in the P3b/1d signature.
fn convert_commodity_signature_holds() {
.find(|s| s.nomi_name == "CONVERT-COMMODITY")
.expect("CONVERT-COMMODITY missing from registry");
assert_eq!(
spec.params,
vec![WasmType::Commodity, WasmType::StringRef],
"convert-commodity must take (commodity, string)"
);
/// Builder methods on `HostFnSpec` should cover the universal-result
/// convention end-to-end: chain `returns(...)` + `with_params(...)`
/// produces a structurally valid spec the registry can consume.
fn host_fn_spec_builder_round_trip() {
let spec = HostFnSpec::new("my-fn", "nomi", "my_fn")
.with_params(vec![WasmType::I32, WasmType::StringRef])
.returns(WasmType::PairRef(PairElement::Entity(EntityKind::Account)));
assert_eq!(spec.nomi_name, "MY-FN");
assert_eq!(spec.import_module, "nomi");
assert_eq!(spec.import_name, "my_fn");
assert_eq!(spec.params, vec![WasmType::I32, WasmType::StringRef]);
assert!(matches!(
spec.result,
Some(WasmType::PairRef(PairElement::Entity(EntityKind::Account)))
));
/// Sanity check: list-X natives must return `PairRef(...)`. The shape
/// is structural — if `list-accounts` started returning `StringRef`,
/// every `(car (list-accounts))` composition would silently break,
/// since the typed CAR helper only accepts `PairRef`.
fn list_natives_return_pair_ref() {
if spec.nomi_name.starts_with("LIST-") {
assert!(
matches!(spec.result, Some(WasmType::PairRef(_))),
"{} must return PairRef, got {:?}",
spec.nomi_name,
spec.result