use assert_cmd::Command;
use std::path::PathBuf;
use tempfile::TempDir;
fn fides_cmd() -> Command {
Command::cargo_bin("fidius").unwrap()
}
fn workspace_fidius_path() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../fidius")
}
#[test]
fn full_pipeline_scaffold_package_build_sign_load_call() {
let tmp = TempDir::new().unwrap();
let work_dir = tmp.path();
eprintln!("\n=== FULL PIPELINE E2E TEST ===");
eprintln!("Work dir: {}\n", work_dir.display());
eprintln!("Step 1: fidius init-interface test-api --trait Processor");
let fidius_path = workspace_fidius_path();
fides_cmd()
.args([
"init-interface",
"test-api",
"--trait",
"Processor",
"--path",
work_dir.to_str().unwrap(),
"--extension",
"testpkg",
])
.assert()
.success();
let fidius_toml = work_dir.join("test-api/fidius.toml");
assert!(fidius_toml.exists(), "fidius.toml should exist");
let fidius_toml_content = std::fs::read_to_string(&fidius_toml).unwrap();
assert!(
fidius_toml_content.contains("testpkg"),
"fidius.toml should contain extension"
);
let iface_cargo = work_dir.join("test-api/Cargo.toml");
std::fs::write(
&iface_cargo,
format!(
r#"[package]
name = "test-api"
version = "0.1.0"
edition = "2021"
[dependencies]
fidius = {{ path = "{}" }}
"#,
fidius_path.display(),
),
)
.unwrap();
eprintln!(" ✓ Interface crate scaffolded + patched for local deps\n");
eprintln!("Step 2: fidius init-plugin test-plugin --interface ./test-api --trait Processor");
let iface_dir = work_dir.join("test-api");
fides_cmd()
.args([
"init-plugin",
"test-plugin",
"--interface",
iface_dir.to_str().unwrap(),
"--trait",
"Processor",
"--path",
work_dir.to_str().unwrap(),
])
.assert()
.success();
let plugin_cargo = work_dir.join("test-plugin/Cargo.toml");
std::fs::write(
&plugin_cargo,
format!(
r#"[package]
name = "test-plugin"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
test-api = {{ path = "{}" }}
fidius = {{ path = "{}" }}
"#,
iface_dir.display(),
fidius_path.display(),
),
)
.unwrap();
eprintln!(" ✓ Plugin crate scaffolded + patched for local deps\n");
eprintln!("Step 3: Write package.toml");
let package_toml = work_dir.join("test-plugin/package.toml");
std::fs::write(
&package_toml,
r#"[package]
name = "test-processor"
version = "0.1.0"
interface = "test-api"
interface_version = 1
extension = "testpkg"
[metadata]
category = "testing"
description = "E2E test plugin"
"#,
)
.unwrap();
eprintln!(" ✓ package.toml written\n");
eprintln!("Step 4: fidius keygen --out testkey");
let key_base = work_dir.join("testkey");
fides_cmd()
.args(["keygen", "--out", key_base.to_str().unwrap()])
.assert()
.success();
let secret_key = format!("{}.secret", key_base.display());
let public_key = format!("{}.public", key_base.display());
eprintln!(" ✓ Keypair generated\n");
eprintln!("Step 5: fidius package validate");
let plugin_dir = work_dir.join("test-plugin");
fides_cmd()
.args(["package", "validate", plugin_dir.to_str().unwrap()])
.assert()
.success()
.stdout(predicates::str::contains("test-processor"));
eprintln!(" ✓ Package validated\n");
let mut build_args = vec!["package", "build", plugin_dir.to_str().unwrap()];
if cfg!(debug_assertions) {
build_args.push("--debug");
}
let profile_label = if cfg!(debug_assertions) {
"debug"
} else {
"release"
};
eprintln!("Step 6: fidius package build ({profile_label})");
fides_cmd()
.args(&build_args)
.assert()
.success()
.stdout(predicates::str::contains("Build successful"));
eprintln!(" ✓ Package built\n");
eprintln!("Step 7: fidius package sign");
fides_cmd()
.args([
"package",
"sign",
"--key",
&secret_key,
plugin_dir.to_str().unwrap(),
])
.assert()
.success();
eprintln!(" ✓ Package signed\n");
eprintln!("Step 8: fidius package pack (custom .testpkg extension)");
let fid_path = work_dir.join("test-processor-0.1.0.testpkg");
fides_cmd()
.args([
"package",
"pack",
plugin_dir.to_str().unwrap(),
"--output",
fid_path.to_str().unwrap(),
])
.assert()
.success()
.stdout(predicates::str::contains("Packed:"));
assert!(fid_path.exists(), ".fid archive should exist");
assert!(
fid_path.metadata().unwrap().len() > 0,
".fid should be non-empty"
);
eprintln!(" ✓ Package packed: {}\n", fid_path.display());
eprintln!("Step 9: fidius package unpack");
let unpack_dest = work_dir.join("unpacked");
std::fs::create_dir(&unpack_dest).unwrap();
fides_cmd()
.args([
"package",
"unpack",
fid_path.to_str().unwrap(),
"--dest",
unpack_dest.to_str().unwrap(),
])
.assert()
.success()
.stdout(predicates::str::contains("Unpacked:"));
let unpacked_dir = unpack_dest.join("test-processor-0.1.0");
assert!(
unpacked_dir.join("package.toml").exists(),
"unpacked should have package.toml"
);
assert!(
unpacked_dir.join("package.sig").exists(),
"unpacked should have package.sig"
);
assert!(
unpacked_dir.join("src/lib.rs").exists(),
"unpacked should have source files"
);
eprintln!(" ✓ Package unpacked: {}\n", unpacked_dir.display());
eprintln!("Step 10: fidius package verify (unpacked)");
fides_cmd()
.args([
"package",
"verify",
"--key",
&public_key,
unpacked_dir.to_str().unwrap(),
])
.assert()
.success();
eprintln!(" ✓ Unpacked package signature verified\n");
eprintln!("Step 11: fidius package pack (unsigned — expect warning)");
std::fs::remove_file(unpacked_dir.join("package.sig")).unwrap();
let unsigned_fid = work_dir.join("unsigned.fid");
fides_cmd()
.args([
"package",
"pack",
unpacked_dir.to_str().unwrap(),
"--output",
unsigned_fid.to_str().unwrap(),
])
.assert()
.success()
.stderr(predicates::str::contains("warning: package is unsigned"));
eprintln!(" ✓ Unsigned warning emitted\n");
eprintln!("Step 12: Sign dylib + load via PluginHost + call method");
let profile_dir = if cfg!(debug_assertions) {
"debug"
} else {
"release"
};
let dylib_dir = plugin_dir.join("target").join(profile_dir);
let key_bytes: [u8; 32] = std::fs::read(&public_key).unwrap().try_into().unwrap();
let verifying_key = ed25519_dalek::VerifyingKey::from_bytes(&key_bytes).unwrap();
let dylib_name = if cfg!(target_os = "macos") {
"libtest_plugin.dylib"
} else if cfg!(target_os = "windows") {
"test_plugin.dll"
} else {
"libtest_plugin.so"
};
let dylib_path = dylib_dir.join(dylib_name);
fides_cmd()
.args(["sign", "--key", &secret_key, dylib_path.to_str().unwrap()])
.assert()
.success();
let host = fidius_host::PluginHost::builder()
.search_path(&dylib_dir)
.require_signature(true)
.trusted_keys(&[verifying_key])
.build()
.unwrap();
let loaded = host.load("MyProcessor").unwrap();
assert_eq!(loaded.info.name, "MyProcessor");
assert_eq!(loaded.info.interface_name, "Processor");
let loaded_name = loaded.info.name.clone();
let loaded_iface = loaded.info.interface_name.clone();
let handle = fidius_host::PluginHandle::from_loaded(loaded);
let input = ("hello".to_string(),);
let result: String = handle.call_method(0, &input).unwrap();
assert_eq!(result, "processed: hello");
eprintln!(
" ✓ Plugin loaded: {} (interface: {})",
loaded_name, loaded_iface
);
eprintln!(" ✓ call_method(0, \"hello\") returned: {:?}", result);
eprintln!("\n=== ALL STEPS PASSED ===\n");
}