Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/monetize

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")
}