//! A faux model runs real bash and curl with inherited conversation grants through the host hook. use anyhow::{Result, ensure}; use sausage::{ api::Api, cred::{Caller, Cred, grants::Grant, types::Subject}, world::World, }; use serde_json::json; use std::{ path::{Path, PathBuf}, sync::Arc, time::Duration, }; fn components() -> Result { let userland = Path::new(env!("CARGO_MANIFEST_DIR")).join("../userland"); let target = Path::new(env!("CARGO_TARGET_TMPDIR")).parent().unwrap(); if !userland.join("cwd-init/init.o").is_file() { ensure!( std::process::Command::new("nu") .args(["--no-config-file", "cwd-init/build.nu"]) .current_dir(&userland) .status()? .success(), "cwd fixture build failed" ); } ensure!( std::process::Command::new("cargo") .args([ "build", "--release", "--locked", "--target", "wasm32-wasip2", "-j", "2", "-p", "curl", "--target-dir" ]) .arg(target) .current_dir(&userland) .status()? .success(), "curl fixture build failed" ); let release = target.join("wasm32-wasip2/release"); if !release.join("brush.wasm").is_file() { ensure!( std::process::Command::new("nu") .arg("brush/build.nu") .current_dir(&userland) .status()? .success(), "shell fixture build failed" ); } Ok(release) } #[tokio::test(flavor = "current_thread")] async fn ancestry_reaches_the_real_hook() -> Result<()> { let components = components()?; let temp = tempfile::tempdir()?; let world = World::open(temp.path())?; let disk = temp.path().join("world"); std::fs::create_dir_all(disk.join("bin"))?; for (source, name) in [("curl.wasm", "curl.wasm"), ("brush.wasm", "sh.wasm")] { std::fs::copy(components.join(source), disk.join("bin").join(name))?; } let server = Arc::new( tiny_http::Server::http("127.0.0.1:0") .map_err(|_| anyhow::anyhow!("fixture bind failed"))?, ); let port = server.server_addr().to_ip().unwrap().port(); let cred = Arc::new(Cred::open(&world.host_dir)?); let caller = Caller::default(); let token = uuid::Uuid::new_v4().to_string(); let api = Arc::new(Api::open( cred.clone(), &world.host_dir, "localhost", "http://localhost:8081", )?); let runtime = Arc::new(sausage::process::Runtime::new_with_hooks( World { root: world.root.clone(), host_dir: world.host_dir.clone(), processes: world.processes.clone(), }, cred.clone(), sausage::process::default_script(), Some(api), )?); sausage::harness::run_module_with_credentials( World { root: world.root.clone(), host_dir: world.host_dir.clone(), processes: world.processes.clone(), }, runtime.clone(), cred.clone(), include_str!("../js/chain-test-bundle.js"), ) .await?; let records: serde_json::Value = serde_json::from_slice(&std::fs::read(disk.join("chain-ids"))?)?; for (path, subject) in [ ("/token", records["parent"]["id"].to_string()), ("/unrelated", records["unrelated"]["id"].to_string()), ] { let item = cred.create_item(serde_json::from_value(json!({"name":path,"rules":[{"scheme":"http","hosts":["127.0.0.1"],"port":port,"path_prefixes":[path]}],"transform_js":sausage::cred::summary::script("bearer")?,"fields":[{"name":"token","field_type":"text"}]}))?, &caller, cred.unlocked()?)?; cred.set_field( &item.id, &item.fields[0].field_id, &token, &caller, cred.unlocked()?, )?; cred.grant_create( Grant { id: String::new(), items: vec![item.id], subject: Subject::Conversation(subject), exp: None, max_uses: None, uses: 0, revoked: false, validator: None, note: None, }, &caller, cred.unlocked()?, )?; } let serving = server.clone(); let expected = format!("Bearer {token}"); let thread = std::thread::spawn(move || { let mut accepted = 0; while let Ok(request) = serving.recv() { let valid = request.headers().iter().any(|header| { header.field.equiv("Authorization") && header.value.as_str() == expected }); if valid { accepted += 1; } let _ = request.respond(tiny_http::Response::from_string(if valid { "accepted" } else { "missing" })); } accepted }); let source = format!( "globalThis.chainReady = true; globalThis.chainPort = {port};\n{}", include_str!("../js/chain-test-bundle.js") ); let result = tokio::time::timeout( Duration::from_secs(90), sausage::harness::run_module_with_credentials(world, runtime, cred, &source), ) .await; server.unblock(); let accepted = thread.join().unwrap(); result??; ensure!(accepted == 3, "unexpected number of injected requests"); Ok(()) }