crates/monetize-product-wasm/tests/common/mod.rs
//! Shared fixtures: a scratch directory, the REAL built toy guest, a signer,
//! and a `[products.toy]` block in the shape the factory is built from.
//!
//! **The toy module is required, never skipped.** A test that quietly passed
//! because there was nothing to load would be the `NOT RUN → true` shape this
//! constellation keeps finding; it fails naming the build command instead.
#![allow(dead_code)]
use std::path::{Path, PathBuf};
use monetize_product::{GuestSpec, ProductBuild};
pub const BUILD_CMD: &str = "cargo xtask build-guests";
pub fn scratch(name: &str) -> PathBuf {
let p = std::env::temp_dir().join(format!("monetize-guest-{name}-{}-{:?}", std::process::id(), std::thread::current().id()));
let _ = std::fs::remove_dir_all(&p);
std::fs::create_dir_all(&p).expect("scratch dir");
p
}
/// The toy guest as `cargo xtask build-guests` left it.
pub fn built_toy() -> PathBuf {
built_guest("monetize_guest_toy")
}
/// Any guest `cargo xtask build-guests` built, by its module file stem.
pub fn built_guest(stem: &str) -> PathBuf {
let target = std::env::var("CARGO_TARGET_DIR").map(PathBuf::from).unwrap_or_else(|_| Path::new(env!("CARGO_MANIFEST_DIR")).join("../../target"));
let p = target.join(format!("wasm32-unknown-unknown/release/{stem}.wasm"));
assert!(p.is_file(), "{} has not been built. This test loads the REAL guest; it does not skip.\nBuild it with:\n {BUILD_CMD}", p.display());
p
}
/// A copy of a built guest in a fresh directory under the name it is pinned by.
pub fn guest_copy(dir: &Path, stem: &str, as_name: &str) -> PathBuf {
let dst = dir.join(as_name);
std::fs::copy(built_guest(stem), &dst).expect("copy the guest");
dst
}
/// A copy of the toy guest in a fresh directory, so a test can sign, tamper or
/// mis-pin it without touching the build output.
pub fn toy_copy(dir: &Path) -> PathBuf {
let dst = dir.join("toy.wasm");
std::fs::copy(built_toy(), &dst).expect("copy the toy");
dst
}
pub fn digest_of(path: &Path) -> String {
guest_host::digest_hex(&std::fs::read(path).unwrap())
}
/// `[products.toy]` with the module SIGNED (its own root, [`vouched`]), pinned by digest, and
/// the test kind's typed `endpoint` door granted.
pub fn toy_spec(module: &Path) -> GuestSpec {
GuestSpec { module: module.to_path_buf(), digest: Some(digest_of(module)), trust_roots: vouched(module), grants: vec!["endpoint".into()], ..Default::default() }
}
/// **Sign `module` as a release does** (owner 2026-10-17: every guest monetize runs is
/// signed): a CA of its own, written beside it (`<module>.root.der`), its `.p7s` beside it.
/// Answers the roots a block names.
pub fn vouched(module: &Path) -> Vec<PathBuf> {
use guest_host::verify::sign::{self, dev};
let (ca_key, ca_der) = dev::mint_ca("test-plugin-root").unwrap();
let mut root = module.as_os_str().to_owned();
root.push(".root.der");
let root = PathBuf::from(root);
std::fs::write(&root, &ca_der).unwrap();
let signer = dev::new_ed25519_signer(&ca_key, &ca_der, "test-plugin-signing").unwrap();
sign::sign_module_file(module, &signer).unwrap();
vec![root]
}
pub fn build<'a>(id: &'a str, spec: &'a GuestSpec) -> ProductBuild<'a> {
ProductBuild { id, endpoint: "127.0.0.1:1", identity_key: None, appliance_key: None, bearer_token: None, api_key_env: None, restoring_from_twin: None, tls_ca: None, tls_client: None, guest: Some(spec) }
}
/// A CA and a leaf signer, the CA's DER written to `dir/root.der`.
pub fn mint_signer(dir: &Path, cn: &str) -> (PathBuf, guest_host::verify::sign::Ed25519Signer) {
use guest_host::verify::sign::dev;
let (ca_key, ca_der) = dev::mint_ca(&format!("{cn}-ca")).unwrap();
let root = dir.join(format!("{cn}-root.der"));
std::fs::write(&root, &ca_der).unwrap();
(root, dev::new_ed25519_signer(&ca_key, &ca_der, cn).unwrap())
}
/// A WAT guest speaking the convention with a canned manifest, whose
/// `plugin_call` runs `body` and then answers `{"Ok":"x"}`.
pub fn wat_guest(manifest_json: &str, abi_min: u32, abi_max: u32, body: &str) -> Vec<u8> {
let manifest = manifest_json.replace('"', "\\\"");
let src = format!(
r#"(module
(memory (export "memory") 4)
(data (i32.const 1024) "{manifest}")
(data (i32.const 4096) "{{\"Ok\":\"x\"}}")
(global $bump (mut i32) (i32.const 8192))
(func (export "alloc") (param $n i32) (result i32)
(local $p i32)
(local.set $p (global.get $bump))
(global.set $bump (i32.add (global.get $bump) (local.get $n)))
(local.get $p))
(func (export "dealloc") (param i32 i32))
(func (export "result_len") (result i32) (i32.const {mlen}))
(func (export "plugin_abi") (result i64) (i64.const {abi}))
(func (export "plugin_manifest") (result i32) (i32.const 1024))
(func (export "plugin_call") (param i32 i32 i32) (result i64)
{body}
(i64.or (i64.shl (i64.const 4096) (i64.const 32)) (i64.const 10)))
)"#,
mlen = manifest_json.len(),
abi = ((abi_min as u64) << 32) | abi_max as u64,
);
wat::parse_str(src).expect("valid wat")
}