Something went wrong. Try again.
Flatpak-style launcher for sandboxed WebAssembly tools. Install, verify, and run .wasm binaries with explicit capability grants. Single binary, local-first, zero telemetry. wasmbox.orbiter.website
wasm rust wasmbox
Something went wrong. Try again.
77 kB · 2390 lines
Rust
at dev
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391//! Integration tests for wasmbox CLI.//!//! These tests stand up a mock registry (wiremock), run the wasmbox binary//! against it, and verify the install → run → verify → update → remove flow.
use std::path::{Path, PathBuf};use std::process::{Command, Output};
use tempfile::TempDir;use wiremock::matchers::{method, path};use wiremock::{Mock, MockServer, ResponseTemplate};
// --- Helpers ---
fn wasmbox_bin() -> PathBuf { PathBuf::from(env!("CARGO_BIN_EXE_wasmbox"))}
fn fixture_path(name: &str) -> PathBuf { // tests/fixtures/ is at workspace root Path::new(env!("CARGO_MANIFEST_DIR")) .join("../../tests/fixtures") .join(name)}
fn fixture_bytes(name: &str) -> Vec<u8> { std::fs::read(fixture_path(name)).expect("read fixture")}
fn fixture_hash(name: &str) -> String { wasmbox_verify::hash_file(&fixture_path(name)).expect("hash fixture")}
/// Run wasmbox with --home pointing at a temp dir. Returns (Output, home_dir).fn run_wasmbox(home: &Path, args: &[&str]) -> Output { Command::new(wasmbox_bin()) .arg("--home") .arg(home) .args(args) .env("NO_COLOR", "1") .output() .expect("failed to execute wasmbox")}
/// Build a wasmbox.toml manifest string for a test tool.fn build_manifest(name: &str, version: &str, wasm_file: &str, hash: &str) -> String { format!( r#"[tool]name = "{name}"version = "{version}"description = "A test tool"author = "Test Author"license = "MIT"
[binary]wasm = "{wasm_file}"hash = "{hash}"
[capabilities]stdout = true
[ui]type = "cli""#, )}
/// Build a registry index.json response.fn build_index(entries: &[(&str, &str, &str, u64)]) -> String { let tools: Vec<String> = entries .iter() .map(|(name, version, hash, size)| { format!( r#" {{ "name": "{name}", "version": "{version}", "description": "A test tool", "hash": "{hash}", "size": {size}, "categories": ["test"] }}"#, ) }) .collect();
format!( r#"{{ "registry": "test-registry", "tools": [{} ]}}"#, tools.join(",\n"), )}
/// Set up a mock registry serving a single tool.async fn setup_mock_registry( server: &MockServer, tool_name: &str, version: &str, wasm_fixture: &str,) -> String { let wasm_bytes = fixture_bytes(wasm_fixture); let hash = fixture_hash(wasm_fixture); let manifest = build_manifest(tool_name, version, &format!("{tool_name}.wasm"), &hash); let index = build_index(&[(tool_name, version, &hash, wasm_bytes.len() as u64)]);
Mock::given(method("GET")) .and(path("/index.json")) .respond_with(ResponseTemplate::new(200).set_body_string(&index)) .mount(server) .await;
Mock::given(method("GET")) .and(path(format!("/tools/{tool_name}.json"))) .respond_with(ResponseTemplate::new(200).set_body_string(&manifest)) .mount(server) .await;
Mock::given(method("GET")) .and(path(format!("/tools/{tool_name}.wasm"))) .respond_with(ResponseTemplate::new(200).set_body_bytes(wasm_bytes)) .mount(server) .await;
hash}
/// Configure a registry URL in the wasmbox home.fn configure_registry(home: &Path, url: &str) { std::fs::create_dir_all(home).expect("create home"); let config = format!("[[registries]]\nurl = \"{url}\"\n"); std::fs::write(home.join("config.toml"), config).expect("write config");}
/// Install a tool and return the Output.fn install_tool(home: &Path, name: &str, registry_url: &str) -> Output { run_wasmbox(home, &["install", name, "--registry", registry_url])}
// --- Tests ---
#[tokio::test]async fn install_from_registry() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri());
let output = install_tool(home_path, "hello-tool", &server.uri());
assert!( output.status.success(), "install failed: {}", String::from_utf8_lossy(&output.stderr) );
// Verify files on disk let tool_dir = home_path.join("cache/hello-tool/0.1.0"); assert!(tool_dir.exists(), "tool directory not created"); assert!( tool_dir.join("wasmbox.toml").exists(), "manifest not written" ); assert!( tool_dir.join("hello-tool.wasm").exists(), "wasm not written" );}
#[tokio::test]async fn list_shows_installed_tool() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
let output = run_wasmbox(home_path, &["list", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let tools: serde_json::Value = serde_json::from_str(&stdout).expect("parse JSON"); let tools = tools.as_array().expect("should be array"); assert_eq!(tools.len(), 1); assert_eq!(tools[0]["name"], "hello-tool"); assert_eq!(tools[0]["version"], "0.1.0");}
#[tokio::test]async fn run_installed_tool() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Run with --allow stdout (tool outputs "Hello from WasmBox!") let output = run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
assert!( output.status.success(), "run failed: {}", String::from_utf8_lossy(&output.stderr) ); let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains("Hello from WasmBox!"), "expected hello output, got: {stdout}" );}
#[tokio::test]async fn run_with_sandbox_zero_capabilities() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Run with --sandbox: tool gets zero capabilities, no stdout let output = run_wasmbox(home_path, &["run", "hello-tool", "--sandbox"]);
assert!( output.status.success(), "sandbox run failed: {}", String::from_utf8_lossy(&output.stderr) ); let stdout = String::from_utf8_lossy(&output.stdout); assert!( !stdout.contains("Hello from WasmBox!"), "sandbox should suppress stdout, got: {stdout}" );}
#[tokio::test]async fn verify_installed_tool_passes() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
let output = run_wasmbox(home_path, &["verify", "hello-tool"]); assert!( output.status.success(), "verify failed: {}", String::from_utf8_lossy(&output.stderr) );
let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains("verified"), "expected 'verified' in output");}
#[tokio::test]async fn verify_tampered_binary_fails() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Tamper with the wasm binary let wasm_path = home_path.join("cache/hello-tool/0.1.0/hello-tool.wasm"); std::fs::write(&wasm_path, b"tampered content").unwrap();
let output = run_wasmbox(home_path, &["verify", "hello-tool"]);
assert!( !output.status.success(), "verify should fail on tampered binary" ); assert_eq!( output.status.code(), Some(3), "exit code should be 3 for verification failure" );}
#[tokio::test]async fn run_tampered_binary_fails() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Tamper with the wasm binary let wasm_path = home_path.join("cache/hello-tool/0.1.0/hello-tool.wasm"); std::fs::write(&wasm_path, b"tampered content").unwrap();
let output = run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
assert!( !output.status.success(), "run should fail on tampered binary" ); assert_eq!( output.status.code(), Some(3), "exit code should be 3 for verification failure" );}
#[tokio::test]async fn install_with_allow_all_stores_permissions() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri());
let output = run_wasmbox( home_path, &[ "install", "hello-tool", "--registry", &server.uri(), "--allow-all", ], ); assert!( output.status.success(), "install --allow-all failed: {}", String::from_utf8_lossy(&output.stderr) );
// Check permissions file exists and has the tool let perm_path = home_path.join("permissions.toml"); assert!(perm_path.exists(), "permissions.toml not created");
let output = run_wasmbox(home_path, &["permissions", "hello-tool", "show", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let perms: serde_json::Value = serde_json::from_str(&stdout).expect("parse perms JSON"); assert_eq!(perms["stdout"], true, "stdout should be granted");}
#[tokio::test]async fn run_without_tty_no_permissions_fails() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Run without --allow, --sandbox, or --allow-all. // Since tool requests stdout and there's no TTY for prompting, should fail. let output = run_wasmbox(home_path, &["run", "hello-tool"]);
assert!( !output.status.success(), "should fail without TTY or --allow" ); assert_eq!( output.status.code(), Some(2), "exit code should be 2 for permission denied" );}
#[tokio::test]async fn info_shows_tool_metadata() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
let output = run_wasmbox(home_path, &["info", "hello-tool", "--json"]); assert!( output.status.success(), "info failed: {}", String::from_utf8_lossy(&output.stderr) );
let stdout = String::from_utf8_lossy(&output.stdout); let info: serde_json::Value = serde_json::from_str(&stdout).expect("parse info JSON"); assert_eq!(info["name"], "hello-tool"); assert_eq!(info["version"], "0.1.0"); assert_eq!(info["author"], "Test Author");}
#[tokio::test]async fn search_finds_tool() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri());
let output = run_wasmbox(home_path, &["search", "hello", "--json"]); assert!( output.status.success(), "search failed: {}", String::from_utf8_lossy(&output.stderr) );
let stdout = String::from_utf8_lossy(&output.stdout); let results: serde_json::Value = serde_json::from_str(&stdout).expect("parse search JSON"); let results = results.as_array().expect("should be array"); assert!(!results.is_empty(), "search should find hello-tool"); assert_eq!(results[0]["name"], "hello-tool");}
#[tokio::test]async fn search_no_results() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri());
let output = run_wasmbox(home_path, &["search", "nonexistent", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let results: serde_json::Value = serde_json::from_str(&stdout).expect("parse search JSON"); let results = results.as_array().expect("should be array"); assert!( results.is_empty(), "search should return empty for nonexistent" );}
#[tokio::test]async fn remove_tool() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Verify installed assert!(home_path.join("cache/hello-tool/0.1.0").exists());
let output = run_wasmbox(home_path, &["remove", "hello-tool"]); assert!( output.status.success(), "remove failed: {}", String::from_utf8_lossy(&output.stderr) );
// Verify removed assert!( !home_path.join("cache/hello-tool").exists(), "tool directory should be removed" );}
#[tokio::test]async fn remove_nonexistent_tool_fails() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path.join("cache")).unwrap();
let output = run_wasmbox(home_path, &["remove", "nonexistent"]); assert!( !output.status.success(), "remove of nonexistent should fail" );}
#[tokio::test]async fn update_to_newer_version() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
// Install v0.1.0 with hello.wasm setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Reset mocks and set up v0.2.0 with echo.wasm (different binary + hash) server.reset().await; setup_mock_registry(&server, "hello-tool", "0.2.0", "echo.wasm").await;
let output = run_wasmbox(home_path, &["update", "hello-tool"]); assert!( output.status.success(), "update failed: {}", String::from_utf8_lossy(&output.stderr) );
// Both versions should exist (old kept for rollback) assert!( home_path.join("cache/hello-tool/0.1.0").exists(), "old version should be kept for rollback" ); assert!( home_path.join("cache/hello-tool/0.2.0").exists(), "new version should be installed" );}
#[tokio::test]async fn update_already_latest() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Update when already at latest let output = run_wasmbox(home_path, &["update", "hello-tool"]); assert!(output.status.success());
let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("up to date"), "should say already up to date, got: {stderr}" );}
#[tokio::test]async fn revoke_capability() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri());
// Install with pre-approved permissions run_wasmbox( home_path, &[ "install", "hello-tool", "--registry", &server.uri(), "--allow-all", ], );
// Revoke stdout let output = run_wasmbox(home_path, &["revoke", "hello-tool", "stdout"]); assert!( output.status.success(), "revoke failed: {}", String::from_utf8_lossy(&output.stderr) );
// Verify stdout is revoked let output = run_wasmbox(home_path, &["permissions", "hello-tool", "show", "--json"]); let stdout = String::from_utf8_lossy(&output.stdout); let perms: serde_json::Value = serde_json::from_str(&stdout).expect("parse perms JSON"); assert_eq!(perms["stdout"], false, "stdout should be revoked");}
#[tokio::test]async fn audit_shows_all_permissions() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri());
run_wasmbox( home_path, &[ "install", "hello-tool", "--registry", &server.uri(), "--allow-all", ], );
let output = run_wasmbox(home_path, &["audit", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let audit: serde_json::Value = serde_json::from_str(&stdout).expect("parse audit JSON"); assert!( audit.get("hello-tool").is_some(), "audit should show hello-tool permissions" );}
#[tokio::test]async fn hash_command() { let wasm_path = fixture_path("hello.wasm"); let home = TempDir::new().unwrap();
let output = run_wasmbox(home.path(), &["hash", wasm_path.to_str().unwrap()]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string(); assert!( stdout.starts_with("sha256:"), "hash should start with sha256:" ); assert_eq!( stdout.len(), 7 + 64, "hash should be sha256: + 64 hex chars" );}
#[tokio::test]async fn install_nonexistent_tool_fails() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
// Empty registry Mock::given(method("GET")) .and(path("/index.json")) .respond_with( ResponseTemplate::new(200).set_body_string(r#"{"registry":"test","tools":[]}"#), ) .mount(&server) .await;
configure_registry(home_path, &server.uri());
let output = install_tool(home_path, "nonexistent", &server.uri()); assert!( !output.status.success(), "install of nonexistent should fail" );}
#[tokio::test]async fn registry_add_list_remove() { let home = TempDir::new().unwrap(); let home_path = home.path();
// Add registry let output = run_wasmbox(home_path, &["registry", "add", "https://example.com"]); assert!(output.status.success());
// List registries let output = run_wasmbox(home_path, &["registry", "list"]); assert!(output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains("example.com"), "should list registry");
// Remove registry let output = run_wasmbox(home_path, &["registry", "remove", "https://example.com"]); assert!(output.status.success());
// Verify removed let output = run_wasmbox(home_path, &["registry", "list"]); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("no registries"), "should have no registries" );}
#[tokio::test]async fn run_local_file_with_sandbox() { let home = TempDir::new().unwrap(); let wasm_path = fixture_path("hello.wasm");
let output = run_wasmbox( home.path(), &["run", "--file", wasm_path.to_str().unwrap(), "--sandbox"], );
assert!( output.status.success(), "run --file failed: {}", String::from_utf8_lossy(&output.stderr) );}
#[tokio::test]async fn run_local_file_with_manifest_verifies_hash() { let home = TempDir::new().unwrap(); let home_path = home.path(); let wasm_path = fixture_path("hello.wasm"); let hash = fixture_hash("hello.wasm");
// Write a manifest that matches the wasm file let manifest = build_manifest("hello", "0.1.0", "hello.wasm", &hash); let manifest_path = home_path.join("test-manifest.toml"); std::fs::write(&manifest_path, manifest).unwrap();
let output = run_wasmbox( home_path, &[ "run", "--file", wasm_path.to_str().unwrap(), "--manifest", manifest_path.to_str().unwrap(), "--allow", "stdout", ], );
assert!( output.status.success(), "run with manifest failed: {}", String::from_utf8_lossy(&output.stderr) ); let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains("verified"), "should verify hash");}
#[tokio::test]async fn run_local_file_with_wrong_manifest_hash_fails() { let home = TempDir::new().unwrap(); let home_path = home.path(); let wasm_path = fixture_path("hello.wasm");
// Write a manifest with wrong hash let wrong_hash = "sha256:0000000000000000000000000000000000000000000000000000000000000000"; let manifest = build_manifest("hello", "0.1.0", "hello.wasm", wrong_hash); let manifest_path = home_path.join("test-manifest.toml"); std::fs::write(&manifest_path, manifest).unwrap();
let output = run_wasmbox( home_path, &[ "run", "--file", wasm_path.to_str().unwrap(), "--manifest", manifest_path.to_str().unwrap(), "--allow", "stdout", ], );
assert!(!output.status.success(), "should fail with wrong hash"); assert_eq!(output.status.code(), Some(3), "exit code should be 3");}
#[tokio::test]async fn install_verifies_hash_from_registry() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
let wasm_bytes = fixture_bytes("hello.wasm"); let wrong_hash = "sha256:0000000000000000000000000000000000000000000000000000000000000000";
// Registry says the hash is wrong — install should fail verification let manifest = build_manifest("hello-tool", "0.1.0", "hello-tool.wasm", wrong_hash); let index = build_index(&[("hello-tool", "0.1.0", wrong_hash, wasm_bytes.len() as u64)]);
Mock::given(method("GET")) .and(path("/index.json")) .respond_with(ResponseTemplate::new(200).set_body_string(&index)) .mount(&server) .await;
Mock::given(method("GET")) .and(path("/tools/hello-tool.json")) .respond_with(ResponseTemplate::new(200).set_body_string(&manifest)) .mount(&server) .await;
Mock::given(method("GET")) .and(path("/tools/hello-tool.wasm")) .respond_with(ResponseTemplate::new(200).set_body_bytes(wasm_bytes)) .mount(&server) .await;
configure_registry(home_path, &server.uri());
let output = install_tool(home_path, "hello-tool", &server.uri());
assert!( !output.status.success(), "install with bad hash should fail" ); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("verification failed"), "should mention verification failure, got: {stderr}" );}
#[tokio::test]async fn run_installed_tool_at_specific_version() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
// Install v0.1.0 with hello.wasm setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Install v0.2.0 with echo.wasm by doing an update server.reset().await; setup_mock_registry(&server, "hello-tool", "0.2.0", "echo.wasm").await; run_wasmbox(home_path, &["update", "hello-tool"]);
// Run specific version (v0.1.0 should output "Hello from WasmBox!") let output = run_wasmbox(home_path, &["run", "hello-tool@0.1.0", "--allow", "stdout"]); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); assert!( stdout.contains("Hello from WasmBox!"), "v0.1.0 should output hello, got: {stdout}" );}
#[tokio::test]async fn permissions_revoke_all() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); run_wasmbox( home_path, &[ "install", "hello-tool", "--registry", &server.uri(), "--allow-all", ], );
// Revoke all let output = run_wasmbox(home_path, &["permissions", "hello-tool", "revoke"]); assert!(output.status.success());
// Verify permissions are gone let output = run_wasmbox(home_path, &["permissions", "hello-tool", "show"]); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("no permissions"), "should say no permissions, got: {stderr}" );}
// =============================================================================// Edge Cases// =============================================================================
#[tokio::test]async fn run_corrupted_wasm_binary() { // A file that exists but isn't valid wasm should fail gracefully let home = TempDir::new().unwrap(); let home_path = home.path();
let bad_wasm = home_path.join("bad.wasm"); std::fs::write(&bad_wasm, b"this is not valid wasm").unwrap();
let output = run_wasmbox( home_path, &["run", "--file", bad_wasm.to_str().unwrap(), "--sandbox"], );
assert!(!output.status.success(), "corrupt wasm should fail"); assert_eq!( output.status.code(), Some(1), "should be general error, not verification" );}
#[tokio::test]async fn run_empty_wasm_file() { let home = TempDir::new().unwrap(); let home_path = home.path();
let empty_wasm = home_path.join("empty.wasm"); std::fs::write(&empty_wasm, b"").unwrap();
let output = run_wasmbox( home_path, &["run", "--file", empty_wasm.to_str().unwrap(), "--sandbox"], );
assert!(!output.status.success(), "empty wasm should fail");}
#[tokio::test]async fn run_missing_wasm_file() { let home = TempDir::new().unwrap();
let output = run_wasmbox( home.path(), &["run", "--file", "/nonexistent/path.wasm", "--sandbox"], );
assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("not found"), "should report file not found, got: {stderr}" );}
#[tokio::test]async fn run_tool_with_missing_cache_wasm() { // Install a tool, then delete the .wasm but leave the manifest let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Delete the wasm file let wasm_path = home_path.join("cache/hello-tool/0.1.0/hello-tool.wasm"); std::fs::remove_file(&wasm_path).unwrap();
let output = run_wasmbox(home_path, &["run", "hello-tool", "--sandbox"]); assert!(!output.status.success(), "should fail with missing wasm");}
#[tokio::test]async fn run_tool_with_missing_manifest() { // Install a tool, then delete the manifest but leave the .wasm let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Delete the manifest let manifest_path = home_path.join("cache/hello-tool/0.1.0/wasmbox.toml"); std::fs::remove_file(&manifest_path).unwrap();
let output = run_wasmbox(home_path, &["run", "hello-tool", "--sandbox"]); assert!( !output.status.success(), "should fail with missing manifest" );}
#[tokio::test]async fn corrupt_config_toml() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path).unwrap();
// Write garbage config std::fs::write(home_path.join("config.toml"), "{{{{ not valid toml").unwrap();
// Commands that read config should fail gracefully let output = run_wasmbox(home_path, &["search", "anything"]); assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.to_lowercase().contains("error"), "should report error for corrupt config, got: {stderr}" );}
#[tokio::test]async fn corrupt_permissions_toml() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Write garbage permissions std::fs::write(home_path.join("permissions.toml"), "not valid {{{{").unwrap();
// Audit should fail gracefully let output = run_wasmbox(home_path, &["audit"]); assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.to_lowercase().contains("error"), "should report error, got: {stderr}" );}
#[tokio::test]async fn corrupt_manifest_in_cache() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Corrupt the cached manifest let manifest_path = home_path.join("cache/hello-tool/0.1.0/wasmbox.toml"); std::fs::write(&manifest_path, "garbage {{{{ not toml").unwrap();
let output = run_wasmbox(home_path, &["run", "hello-tool", "--sandbox"]); assert!( !output.status.success(), "corrupt manifest should cause failure" );}
#[tokio::test]async fn run_nonexistent_tool() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path.join("cache")).unwrap();
let output = run_wasmbox(home_path, &["run", "nonexistent-tool", "--sandbox"]); assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("not found"), "should say tool not found, got: {stderr}" );}
#[tokio::test]async fn verify_nonexistent_tool() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path.join("cache")).unwrap();
let output = run_wasmbox(home_path, &["verify", "nonexistent-tool"]); assert!(!output.status.success());}
#[tokio::test]async fn info_nonexistent_tool() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path.join("cache")).unwrap();
let output = run_wasmbox(home_path, &["info", "nonexistent-tool"]); assert!(!output.status.success());}
#[tokio::test]async fn list_with_empty_cache() { let home = TempDir::new().unwrap();
let output = run_wasmbox(home.path(), &["list", "--json"]); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); let tools: serde_json::Value = serde_json::from_str(&stdout).expect("parse JSON"); assert_eq!(tools.as_array().unwrap().len(), 0);}
#[tokio::test]async fn list_with_no_cache_dir() { let home = TempDir::new().unwrap(); // Don't create any dirs — home exists from TempDir but no cache subdir
let output = run_wasmbox(home.path(), &["list"]); assert!(output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("no tools"), "should say no tools installed, got: {stderr}" );}
#[tokio::test]async fn audit_with_no_permissions_file() { let home = TempDir::new().unwrap();
let output = run_wasmbox(home.path(), &["audit"]); assert!(output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("no permissions"), "should report no permissions, got: {stderr}" );}
#[tokio::test]async fn registry_server_returns_500() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
Mock::given(method("GET")) .and(path("/index.json")) .respond_with(ResponseTemplate::new(500)) .mount(&server) .await;
configure_registry(home_path, &server.uri());
let output = install_tool(home_path, "any-tool", &server.uri()); assert!(!output.status.success());}
#[tokio::test]async fn registry_returns_invalid_json() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
Mock::given(method("GET")) .and(path("/index.json")) .respond_with(ResponseTemplate::new(200).set_body_string("not json at all")) .mount(&server) .await;
configure_registry(home_path, &server.uri());
let output = install_tool(home_path, "any-tool", &server.uri()); assert!(!output.status.success());}
#[tokio::test]async fn registry_returns_empty_index() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
Mock::given(method("GET")) .and(path("/index.json")) .respond_with( ResponseTemplate::new(200).set_body_string(r#"{"registry":"test","tools":[]}"#), ) .mount(&server) .await;
configure_registry(home_path, &server.uri());
let output = run_wasmbox(home_path, &["search", "anything", "--json"]); assert!(output.status.success()); let stdout = String::from_utf8_lossy(&output.stdout); let results: serde_json::Value = serde_json::from_str(&stdout).unwrap(); assert!(results.as_array().unwrap().is_empty());}
#[tokio::test]async fn install_when_registry_wasm_returns_404() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
let hash = fixture_hash("hello.wasm"); let index = build_index(&[("hello-tool", "0.1.0", &hash, 1000)]);
Mock::given(method("GET")) .and(path("/index.json")) .respond_with(ResponseTemplate::new(200).set_body_string(&index)) .mount(&server) .await;
// Wasm endpoint returns 404 Mock::given(method("GET")) .and(path("/tools/hello-tool.wasm")) .respond_with(ResponseTemplate::new(404)) .mount(&server) .await;
configure_registry(home_path, &server.uri());
let output = install_tool(home_path, "hello-tool", &server.uri()); assert!( !output.status.success(), "install with 404 wasm should fail" );}
#[tokio::test]async fn install_when_registry_manifest_returns_404() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
let wasm_bytes = fixture_bytes("hello.wasm"); let hash = fixture_hash("hello.wasm"); let index = build_index(&[("hello-tool", "0.1.0", &hash, wasm_bytes.len() as u64)]);
Mock::given(method("GET")) .and(path("/index.json")) .respond_with(ResponseTemplate::new(200).set_body_string(&index)) .mount(&server) .await;
Mock::given(method("GET")) .and(path("/tools/hello-tool.wasm")) .respond_with(ResponseTemplate::new(200).set_body_bytes(wasm_bytes)) .mount(&server) .await;
// Manifest endpoint returns 404 Mock::given(method("GET")) .and(path("/tools/hello-tool.json")) .respond_with(ResponseTemplate::new(404)) .mount(&server) .await;
configure_registry(home_path, &server.uri());
let output = install_tool(home_path, "hello-tool", &server.uri()); assert!( !output.status.success(), "install with 404 manifest should fail" );}
#[tokio::test]async fn no_registries_configured() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path).unwrap(); // Write empty config (no registries) std::fs::write(home_path.join("config.toml"), "").unwrap();
let output = run_wasmbox(home_path, &["search", "anything"]); assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("no registries"), "should report no registries, got: {stderr}" );}
#[tokio::test]async fn install_same_tool_twice() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri());
// First install let output = install_tool(home_path, "hello-tool", &server.uri()); assert!(output.status.success());
// Second install of same version — should succeed (overwrite) let output = install_tool(home_path, "hello-tool", &server.uri()); assert!(output.status.success());
// Tool should still work let output = run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]); assert!(output.status.success());}
#[tokio::test]async fn remove_specific_version() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
// Install v0.1.0 setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Install v0.2.0 server.reset().await; setup_mock_registry(&server, "hello-tool", "0.2.0", "echo.wasm").await; run_wasmbox(home_path, &["update", "hello-tool"]);
// Remove only v0.1.0 let output = run_wasmbox(home_path, &["remove", "hello-tool@0.1.0"]); assert!(output.status.success());
// v0.2.0 should still exist assert!(home_path.join("cache/hello-tool/0.2.0").exists()); assert!(!home_path.join("cache/hello-tool/0.1.0").exists());}
#[tokio::test]async fn hash_nonexistent_file() { let home = TempDir::new().unwrap();
let output = run_wasmbox(home.path(), &["hash", "/nonexistent/file.wasm"]); assert!(!output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("not found"), "should report not found, got: {stderr}" );}
#[tokio::test]async fn update_nonexistent_tool() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path.join("cache")).unwrap();
let output = run_wasmbox(home_path, &["update", "nonexistent-tool"]); assert!(!output.status.success());}
#[tokio::test]async fn run_with_no_args() { let home = TempDir::new().unwrap();
let output = run_wasmbox(home.path(), &["run"]); assert!(!output.status.success(), "run with no args should fail");}
#[tokio::test]async fn revoke_capability_on_tool_with_no_permissions() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path).unwrap();
let output = run_wasmbox(home_path, &["revoke", "nonexistent", "stdout"]); // Should succeed but report nothing to revoke let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("no ") || output.status.success(), "should handle missing permissions gracefully, got: {stderr}" );}
#[tokio::test]async fn registry_duplicate_add() { let home = TempDir::new().unwrap(); let home_path = home.path();
run_wasmbox(home_path, &["registry", "add", "https://example.com"]); let output = run_wasmbox(home_path, &["registry", "add", "https://example.com"]);
assert!(output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("already"), "should report already configured, got: {stderr}" );}
#[tokio::test]async fn registry_remove_nonexistent() { let home = TempDir::new().unwrap(); let home_path = home.path();
let output = run_wasmbox(home_path, &["registry", "remove", "https://not-there.com"]); assert!(output.status.success()); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("not found"), "should report not found, got: {stderr}" );}
// =============================================================================// Compliance: Run Log// =============================================================================
#[tokio::test]async fn run_creates_log_entry() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Run the tool run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
// Check run.log exists and has entries let log_path = home_path.join("run.log"); assert!(log_path.exists(), "run.log should be created");
let content = std::fs::read_to_string(&log_path).unwrap(); let lines: Vec<&str> = content.lines().collect(); // Should have 2 entries: pre-execution + post-execution assert!( lines.len() >= 2, "run.log should have at least 2 entries (pre+post), got {}", lines.len(), );
// Parse the last entry (post-execution) let last: serde_json::Value = serde_json::from_str(lines.last().unwrap()).unwrap(); assert_eq!(last["tool"], "hello-tool"); assert_eq!(last["version"], "0.1.0"); assert_eq!(last["hash_verified"], true); assert_eq!(last["exit_code"], 0); assert!(last["duration_ms"].as_u64().is_some()); assert_eq!(last["policy"], "no_policy");}
#[tokio::test]async fn run_logs_hash_failure() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Tamper with the binary let wasm_path = home_path.join("cache/hello-tool/0.1.0/hello-tool.wasm"); std::fs::write(&wasm_path, b"tampered").unwrap();
// Run — should fail run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
// Check run.log has the hash failure entry let log_path = home_path.join("run.log"); assert!( log_path.exists(), "run.log should be created even on hash failure" );
let content = std::fs::read_to_string(&log_path).unwrap(); let entry: serde_json::Value = serde_json::from_str(content.lines().next().unwrap()).unwrap(); assert_eq!(entry["hash_verified"], false); assert!(entry["exit_code"].is_null());}
#[tokio::test]async fn log_command_shows_entries() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Generate some log entries run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
let output = run_wasmbox(home_path, &["log", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let entries: serde_json::Value = serde_json::from_str(&stdout).expect("parse log JSON"); let entries = entries.as_array().expect("should be array"); assert!(!entries.is_empty(), "log should have entries");}
#[tokio::test]async fn log_filter_by_tool() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri()); run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
let output = run_wasmbox(home_path, &["log", "--tool", "hello-tool", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let entries: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap(); assert!(entries.iter().all(|e| e["tool"] == "hello-tool"));}
#[tokio::test]async fn log_last_n() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Run tool 3 times to generate multiple log entries run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]); run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]); run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
let output = run_wasmbox(home_path, &["log", "--last", "2", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let entries: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap(); assert_eq!(entries.len(), 2, "should only return last 2 entries");}
#[tokio::test]async fn log_stats() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
let output = run_wasmbox(home_path, &["log", "--stats", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let stats: serde_json::Value = serde_json::from_str(&stdout).expect("parse stats JSON"); assert!(stats["total_runs"].as_u64().unwrap() >= 2); // pre + post assert_eq!(stats["unique_tools"], 1); assert_eq!(stats["blocked"], 0);}
#[tokio::test]async fn log_rotate() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]); assert!(home_path.join("run.log").exists());
let output = run_wasmbox(home_path, &["log", "--rotate"]); assert!(output.status.success());
assert!( !home_path.join("run.log").exists(), "run.log should be rotated" ); assert!( home_path.join("run.log.1").exists(), "archived log should exist" );}
#[tokio::test]async fn log_export() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
let export_path = home_path.join("exported.jsonl"); let output = run_wasmbox( home_path, &["log", "--export", export_path.to_str().unwrap()], ); assert!(output.status.success()); assert!(export_path.exists(), "exported file should exist");}
#[tokio::test]async fn log_empty() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path).unwrap();
let output = run_wasmbox(home_path, &["log", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let entries: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap(); assert!(entries.is_empty());}
// =============================================================================// Compliance: Policy// =============================================================================
#[tokio::test]async fn policy_init_creates_file() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
let output = run_wasmbox( home_path, &[ "policy", "init", "--name", "Test Policy", "--approved-by", "test@example.com", "--enforcement", "warn", ], ); assert!( output.status.success(), "policy init failed: {}", String::from_utf8_lossy(&output.stderr), );
assert!( home_path.join("policy.toml").exists(), "policy.toml should be created" );
// Verify content let content = std::fs::read_to_string(home_path.join("policy.toml")).unwrap(); assert!(content.contains("Test Policy")); assert!(content.contains("test@example.com")); assert!(content.contains("hello-tool"));}
#[tokio::test]async fn policy_show() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
run_wasmbox( home_path, &[ "policy", "init", "--name", "Test", "--approved-by", "test@example.com", ], );
let output = run_wasmbox(home_path, &["policy", "show", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let policy: serde_json::Value = serde_json::from_str(&stdout).expect("parse policy JSON"); assert_eq!(policy["policy"]["name"], "Test"); assert!(policy["tools"]["hello-tool"].is_object());}
#[tokio::test]async fn policy_check() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
run_wasmbox( home_path, &[ "policy", "init", "--name", "Test", "--approved-by", "test@example.com", ], );
let output = run_wasmbox(home_path, &["policy", "check", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let results: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap(); assert_eq!(results.len(), 1); assert_eq!(results[0]["name"], "hello-tool"); assert_eq!(results[0]["status"], "approved");}
#[tokio::test]async fn policy_enforce_blocks_unapproved_tool() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
// Install hello-tool setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Create policy with hello-tool but set to enforce run_wasmbox( home_path, &[ "policy", "init", "--name", "Strict", "--approved-by", "test@example.com", "--enforcement", "enforce", ], );
// Remove hello-tool from policy run_wasmbox(home_path, &["policy", "remove", "hello-tool"]);
// Running hello-tool should be blocked let output = run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]); assert!(!output.status.success(), "should be blocked by policy"); assert_eq!(output.status.code(), Some(2), "exit code should be 2");
let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("BLOCKED") || stderr.contains("blocked"), "should mention blocked, got: {stderr}", );
// Check run.log has the blocked entry let log_path = home_path.join("run.log"); let content = std::fs::read_to_string(&log_path).unwrap(); let has_blocked = content.lines().any(|line| { let v: serde_json::Value = serde_json::from_str(line).unwrap_or_default(); v["policy"] == "blocked" }); assert!(has_blocked, "run.log should contain a blocked entry");}
#[tokio::test]async fn policy_warn_allows_unapproved_tool() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Create policy and remove tool, but in warn mode run_wasmbox( home_path, &[ "policy", "init", "--name", "Lenient", "--approved-by", "test@example.com", "--enforcement", "warn", ], ); run_wasmbox(home_path, &["policy", "remove", "hello-tool"]);
// Running should succeed (with warning) let output = run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]); assert!(output.status.success(), "warn mode should allow execution");
let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("WARNING"), "should show warning, got: {stderr}", );}
#[tokio::test]async fn policy_add_and_remove_tool() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
run_wasmbox( home_path, &[ "policy", "init", "--name", "Test", "--approved-by", "test@example.com", ], );
// Remove tool from policy let output = run_wasmbox(home_path, &["policy", "remove", "hello-tool"]); assert!(output.status.success());
// Re-add tool let output = run_wasmbox(home_path, &["policy", "add", "hello-tool"]); assert!(output.status.success());
// Check it's back let output = run_wasmbox(home_path, &["policy", "check", "--json"]); let stdout = String::from_utf8_lossy(&output.stdout); let results: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap(); assert_eq!(results[0]["status"], "approved");}
#[tokio::test]async fn policy_enforcement_modes() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path).unwrap();
run_wasmbox( home_path, &[ "policy", "init", "--name", "Test", "--approved-by", "test@example.com", ], );
// Set to enforce let output = run_wasmbox(home_path, &["policy", "enforce"]); assert!(output.status.success()); let content = std::fs::read_to_string(home_path.join("policy.toml")).unwrap(); assert!(content.contains(r#"enforcement = "enforce""#));
// Set to warn let output = run_wasmbox(home_path, &["policy", "warn"]); assert!(output.status.success()); let content = std::fs::read_to_string(home_path.join("policy.toml")).unwrap(); assert!(content.contains(r#"enforcement = "warn""#));
// Set to disabled let output = run_wasmbox(home_path, &["policy", "disable"]); assert!(output.status.success()); let content = std::fs::read_to_string(home_path.join("policy.toml")).unwrap(); assert!(content.contains(r#"enforcement = "disabled""#));}
#[tokio::test]async fn policy_diff_detects_changes() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
run_wasmbox( home_path, &[ "policy", "init", "--name", "Test", "--approved-by", "test@example.com", ], );
// Remove the tool from disk (but keep in policy) std::fs::remove_dir_all(home_path.join("cache/hello-tool")).unwrap();
let output = run_wasmbox(home_path, &["policy", "diff", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let changes: Vec<String> = serde_json::from_str(&stdout).unwrap(); assert!(!changes.is_empty(), "diff should detect removed tool"); assert!(changes[0].contains("REMOVED"));}
#[tokio::test]async fn policy_export() { let home = TempDir::new().unwrap(); let home_path = home.path(); std::fs::create_dir_all(home_path).unwrap();
run_wasmbox( home_path, &[ "policy", "init", "--name", "Export Test", "--approved-by", "test@example.com", ], );
let output = run_wasmbox(home_path, &["policy", "export"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("Export Test")); assert!(stdout.contains("test@example.com"));}
// =============================================================================// Compliance: Signed Audit Export// =============================================================================
#[tokio::test]async fn audit_export_json() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Run tool to generate log entries run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
let output = run_wasmbox(home_path, &["audit", "--export"]); assert!( output.status.success(), "audit export failed: {}", String::from_utf8_lossy(&output.stderr), );
let stdout = String::from_utf8_lossy(&output.stdout); let report: serde_json::Value = serde_json::from_str(&stdout).expect("parse audit JSON"); assert_eq!(report["report"]["type"], "wasmbox_compliance_audit"); assert!(report["tools"].as_array().is_some()); assert!(report["run_summary"]["total_executions"].as_u64().unwrap() >= 1); assert_eq!( report["compliance_checks"]["compliance_status"], "INCOMPLETE" );}
#[tokio::test]async fn audit_export_with_policy_passes() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Create policy run_wasmbox( home_path, &[ "policy", "init", "--name", "Test", "--approved-by", "test@example.com", "--enforcement", "enforce", ], );
// Run tool run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]);
let output = run_wasmbox(home_path, &["audit", "--export"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let report: serde_json::Value = serde_json::from_str(&stdout).unwrap(); assert_eq!(report["compliance_checks"]["compliance_status"], "PASS"); assert_eq!( report["compliance_checks"]["policy_enforcement_active"], true ); assert_eq!(report["policy"]["name"], "Test");}
#[tokio::test]async fn audit_export_markdown() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
let output = run_wasmbox(home_path, &["audit", "--export", "--format", "md"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("# WasmBox Compliance Audit Report"));}
#[tokio::test]async fn audit_export_csv() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
let output = run_wasmbox(home_path, &["audit", "--export", "--format", "csv"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); assert!(stdout.contains("name,version,hash")); assert!(stdout.contains("hello-tool"));}
#[tokio::test]async fn audit_init_key_and_sign() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Generate signing key let output = run_wasmbox(home_path, &["audit", "--init-key"]); assert!( output.status.success(), "init-key failed: {}", String::from_utf8_lossy(&output.stderr), ); assert!(home_path.join("audit_key.pem").exists()); assert!(home_path.join("audit_key.pub").exists());
// Export with signature let output = run_wasmbox(home_path, &["audit", "--export", "--sign"]); assert!( output.status.success(), "signed export failed: {}", String::from_utf8_lossy(&output.stderr), );
let stdout = String::from_utf8_lossy(&output.stdout); let report: serde_json::Value = serde_json::from_str(&stdout).unwrap(); assert!(report["signature"].is_object(), "should have signature"); assert_eq!(report["signature"]["algorithm"], "ed25519");
// Write report to file and verify let report_path = home_path.join("audit-report.json"); std::fs::write(&report_path, stdout.as_bytes()).unwrap();
let output = run_wasmbox( home_path, &["audit", "--verify", report_path.to_str().unwrap()], ); assert!( output.status.success(), "verify failed: {}", String::from_utf8_lossy(&output.stderr), ); let stderr = String::from_utf8_lossy(&output.stderr); assert!(stderr.contains("VALID"), "should say VALID, got: {stderr}");}
#[tokio::test]async fn audit_verify_tampered_report_fails() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// Generate key and signed report run_wasmbox(home_path, &["audit", "--init-key"]); let output = run_wasmbox(home_path, &["audit", "--export", "--sign"]); let stdout = String::from_utf8_lossy(&output.stdout);
// Tamper with the report let tampered = stdout.replace("INCOMPLETE", "PASS"); let report_path = home_path.join("tampered.json"); std::fs::write(&report_path, tampered).unwrap();
let output = run_wasmbox( home_path, &["audit", "--verify", report_path.to_str().unwrap()], ); assert!( !output.status.success(), "tampered report should fail verification" ); let stderr = String::from_utf8_lossy(&output.stderr); assert!( stderr.contains("INVALID") || stderr.contains("invalid"), "should say invalid, got: {stderr}", );}
#[tokio::test]async fn audit_backwards_compatible() { // The default audit command (no --export) should still work let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); run_wasmbox( home_path, &[ "install", "hello-tool", "--registry", &server.uri(), "--allow-all", ], );
let output = run_wasmbox(home_path, &["audit", "--json"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let audit: serde_json::Value = serde_json::from_str(&stdout).expect("parse audit JSON"); assert!( audit.get("hello-tool").is_some(), "backwards-compatible audit should show hello-tool", );}
// =============================================================================// Compliance: End-to-End Flow// =============================================================================
#[tokio::test]async fn full_compliance_flow() { let server = MockServer::start().await; let home = TempDir::new().unwrap(); let home_path = home.path();
// 1. Install tool setup_mock_registry(&server, "hello-tool", "0.1.0", "hello.wasm").await; configure_registry(home_path, &server.uri()); install_tool(home_path, "hello-tool", &server.uri());
// 2. Create policy let output = run_wasmbox( home_path, &[ "policy", "init", "--name", "Production Policy", "--approved-by", "compliance@company.com", "--enforcement", "enforce", ], ); assert!(output.status.success());
// 3. Run tool (should be approved since it was in policy from init) let output = run_wasmbox(home_path, &["run", "hello-tool", "--allow", "stdout"]); assert!(output.status.success());
// 4. Check log has approved entry let output = run_wasmbox(home_path, &["log", "--json"]); let stdout = String::from_utf8_lossy(&output.stdout); let entries: Vec<serde_json::Value> = serde_json::from_str(&stdout).unwrap(); let approved = entries.iter().any(|e| e["policy"] == "approved"); assert!(approved, "should have an approved log entry");
// 5. Generate key and signed audit run_wasmbox(home_path, &["audit", "--init-key"]); let output = run_wasmbox(home_path, &["audit", "--export", "--sign"]); assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout); let report: serde_json::Value = serde_json::from_str(&stdout).unwrap(); assert_eq!(report["compliance_checks"]["compliance_status"], "PASS"); assert_eq!(report["policy"]["name"], "Production Policy"); assert!(report["signature"].is_object());
// 6. Verify signed report let report_path = home_path.join("report.json"); std::fs::write(&report_path, stdout.as_bytes()).unwrap(); let output = run_wasmbox( home_path, &["audit", "--verify", report_path.to_str().unwrap()], ); assert!(output.status.success());}
// --- Session Recorder: via ---
#[tokio::test]async fn test_via_passes_through_and_logs() { let home = TempDir::new().unwrap(); let out = run_wasmbox(home.path(), &["via", "echo", "hello"]); assert!(out.status.success()); assert_eq!(String::from_utf8_lossy(&out.stdout), "hello\n");
let log = run_wasmbox(home.path(), &["log", "--json"]); let text = String::from_utf8_lossy(&log.stdout); assert!(text.contains("\"mode\": \"via\"")); assert!(text.contains("\"tool\": \"echo\""));}
#[tokio::test]async fn test_via_propagates_exit_code() { let home = TempDir::new().unwrap(); let out = run_wasmbox(home.path(), &["via", "false"]); assert_eq!(out.status.code(), Some(1));}
// --- Session Recorder: shell ---
#[tokio::test]async fn test_shell_init_generates_shims() { let home = TempDir::new().unwrap(); let out = run_wasmbox(home.path(), &["shell", "init", "--include", "git,jq"]); assert!(out.status.success());
let proxy_dir = home.path().join("proxy"); assert!(proxy_dir.join("git").is_file()); assert!(proxy_dir.join("jq").is_file()); assert!(!proxy_dir.join("wasmbox").exists());
let status = run_wasmbox(home.path(), &["shell", "status", "--json"]); let text = String::from_utf8_lossy(&status.stdout); assert!(text.contains("\"shim_count\""));}
// --- Session Recorder: wrap ---
#[tokio::test]async fn test_wrap_records_session() { let home = TempDir::new().unwrap(); let out = run_wasmbox(home.path(), &["wrap", "echo", "hello-agent"]); assert!(out.status.success()); let err = String::from_utf8_lossy(&out.stderr); assert!(err.contains("WasmBox Session Summary"));}
#[tokio::test]async fn test_wrap_missing_agent_friendly_error() { let home = TempDir::new().unwrap(); let out = run_wasmbox(home.path(), &["wrap", "definitely-not-installed-xyz"]); assert!(!out.status.success()); let err = String::from_utf8_lossy(&out.stderr).to_lowercase(); assert!(err.contains("not found") || err.contains("not installed"));}
// --- Session Recorder: log filters ---
#[tokio::test]async fn test_log_filter_by_mode() { let home = TempDir::new().unwrap(); run_wasmbox(home.path(), &["via", "echo", "one"]); run_wasmbox(home.path(), &["via", "true"]);
let out = run_wasmbox(home.path(), &["log", "--mode", "via", "--json"]); let text = String::from_utf8_lossy(&out.stdout); assert!(text.contains("\"tool\": \"echo\"")); assert!(text.contains("\"tool\": \"true\"")); assert!(!text.contains("via_started"));}
// --- Session Recorder: audit session export ---
#[tokio::test]async fn test_audit_session_export() { let home = TempDir::new().unwrap(); let sid = "test-sess-4f1e"; // Run a proxied command stamped with an explicit session ID. let via = Command::new(wasmbox_bin()) .arg("--home") .arg(home.path()) .args(["via", "echo", "hi"]) .env("NO_COLOR", "1") .env("WASMBOX_SESSION_ID", sid) .output() .expect("run via"); assert!(via.status.success());
let out = run_wasmbox(home.path(), &["audit", "--session", sid, "--export"]); assert!(out.status.success()); let report = String::from_utf8_lossy(&out.stdout); assert!(report.contains("session_audit")); assert!(report.contains(sid)); assert!(report.contains("\"tool\": \"echo\""));}
// --- Session Recorder: proxy modes ---
#[tokio::test]async fn test_via_enforce_mode_blocks_via_env() { let home = TempDir::new().unwrap(); let out = Command::new(wasmbox_bin()) .arg("--home") .arg(home.path()) .args(["via", "echo", "x"]) .env("NO_COLOR", "1") .env("WASMBOX_MODE", "enforce") .output() .expect("run via"); assert_eq!(out.status.code(), Some(126));}
// --- Session Recorder: end-to-end ---
#[tokio::test]async fn test_end_to_end_session_recording() { let home = TempDir::new().unwrap();
run_wasmbox(home.path(), &["shell", "init", "--include", "echo,true"]);
let agent = home.path().join("fake-agent.sh"); let wasmbox_bin = wasmbox_bin(); std::fs::write( &agent, format!( "#!/bin/sh\n\ {bin} --home {home} via echo step-one\n\ {bin} --home {home} via true\n", bin = wasmbox_bin.display(), home = home.path().display(), ), ) .unwrap(); #[cfg(unix)] { use std::os::unix::fs::PermissionsExt; let mut p = std::fs::metadata(&agent).unwrap().permissions(); p.set_mode(0o755); std::fs::set_permissions(&agent, p).unwrap(); }
let out = run_wasmbox(home.path(), &["wrap", agent.to_str().unwrap()]); assert!(out.status.success()); let err = String::from_utf8_lossy(&out.stderr); assert!(err.contains("WasmBox Session Summary")); assert!(err.contains("2 passthrough"));
let log = run_wasmbox(home.path(), &["log", "--mode", "via", "--json"]); let entries: serde_json::Value = serde_json::from_str(&String::from_utf8_lossy(&log.stdout)).unwrap(); let arr = entries.as_array().unwrap(); assert_eq!(arr.len(), 2); let sid = arr[0]["session_id"].as_str().unwrap(); assert!(arr.iter().all(|e| e["session_id"].as_str() == Some(sid)));}
#[tokio::test]async fn test_audit_session_sign_and_verify() { let home = TempDir::new().unwrap(); let sid = "verify-sess-1"; Command::new(wasmbox_bin()) .arg("--home") .arg(home.path()) .args(["via", "echo", "hi"]) .env("NO_COLOR", "1") .env("WASMBOX_SESSION_ID", sid) .output() .expect("run via");
run_wasmbox(home.path(), &["audit", "--init-key"]); let out = run_wasmbox(home.path(), &["audit", "--session", sid, "--export", "--sign"]); assert!(out.status.success());
let report_path = home.path().join("sess.json"); std::fs::write(&report_path, &out.stdout).unwrap();
let v = run_wasmbox(home.path(), &["audit", "--verify", report_path.to_str().unwrap()]); assert!(v.status.success(), "signed session report must verify"); assert!(String::from_utf8_lossy(&v.stderr).contains("VALID"));}