#![cfg(feature = "constructors-wasm")]
use std::path::PathBuf;
use std::sync::OnceLock;
use cloacina::packaging::provider_bundle::{bundle_providers, ProviderRef};
use cloacina::registry::loader::grants::GrantSpec;
use cloacina::registry::loader::{load_constructor_node, set_provider_search_path};
use cloacina::Context;
fn consumer_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../examples/constructor-contract/provider-consumer-fixture")
}
fn provider_search_root() -> &'static PathBuf {
static ROOT: OnceLock<(tempfile::TempDir, PathBuf)> = OnceLock::new();
&ROOT
.get_or_init(|| {
let work = tempfile::TempDir::new().unwrap();
let bundled = bundle_providers(
&consumer_dir(),
&[ProviderRef::parse("cloacina-provider-fs")],
work.path(),
false,
)
.expect("bundle cloacina-provider-fs from the consumer's Cargo graph");
assert_eq!(bundled.len(), 1);
let providers = work.path().join("providers");
set_provider_search_path(&providers);
let data = work.path().join("data");
std::fs::create_dir_all(&data).unwrap();
std::fs::write(data.join("secret.txt"), "packaged secret").unwrap();
(work, data)
})
.1
}
fn fs_ro_grant(dir: &std::path::Path) -> GrantSpec {
GrantSpec::from_lists(
vec![],
vec![],
vec![format!("ro:{}", dir.display())],
vec![],
vec![],
)
}
#[tokio::test]
async fn bundled_provider_node_resolves_by_name_and_runs_with_a_grant() {
let data = provider_search_root();
let secret = data.join("secret.txt");
let task = load_constructor_node(
"reader",
"cloacina-provider-fs@0.1.0",
"read_file",
vec![(
"path".to_string(),
serde_json::json!(secret.display().to_string()),
)],
vec![],
fs_ro_grant(data),
)
.expect("resolve read_file from the bundled provider");
assert_eq!(task.id(), "reader", "the node id is the author-chosen id");
let out = task
.execute(Context::new())
.await
.expect("the bundled constructor node runs");
assert_eq!(
out.get("contents"),
Some(&serde_json::json!("packaged secret")),
"read_file read the granted file through the bundled+sandboxed component"
);
}
#[tokio::test]
async fn bundled_provider_node_fails_closed_without_a_grant() {
let data = provider_search_root();
let secret = data.join("secret.txt");
let task = load_constructor_node(
"reader_denied",
"cloacina-provider-fs@0.1.0",
"read_file",
vec![(
"path".to_string(),
serde_json::json!(secret.display().to_string()),
)],
vec![],
GrantSpec::from_lists(vec![], vec![], vec![], vec![], vec![]),
)
.expect("resolve read_file (load succeeds; the DENIAL is at execute)");
let err = task
.execute(Context::new())
.await
.expect_err("a read with no fs grant must fail closed");
let msg = format!("{err}");
assert!(
msg.contains("read") || msg.to_lowercase().contains("pre-opened") || msg.contains("secret"),
"error should reflect the denied sandbox read, got: {msg}"
);
}
#[tokio::test]
async fn unknown_member_of_bundled_provider_fails_closed() {
let _ = provider_search_root();
let err = load_constructor_node(
"nope",
"cloacina-provider-fs@0.1.0",
"delete_file", vec![("path".to_string(), serde_json::json!("/x"))],
vec![],
GrantSpec::from_lists(vec![], vec![], vec![], vec![], vec![]),
)
.err()
.expect("an unknown member must fail closed");
let msg = format!("{err}");
assert!(
msg.contains("delete_file") && msg.contains("read_file"),
"error should name the missing member + the available ones, got: {msg}"
);
}