use std::path::PathBuf;
use std::time::Duration;
use yosh_plugin_api::pattern::CommandPattern;
use yosh_plugin_api::{CAP_COMMANDS_EXEC, CAP_HOOK_ON_CD, CAP_IO, CAP_VARIABLES_WRITE};
use yosh_plugin_manager::runner::{HookCall, invoke_exec, invoke_hook, load_plugin};
use yosh_plugin_manager::test_host::TestState;
fn wasm() -> Option<PathBuf> {
let p = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../target/wasm32-wasip2/release/test_plugin.wasm");
if p.exists() { Some(p) } else { None }
}
#[test]
fn case_1_run_exec_happy_path() {
let Some(w) = wasm() else { return };
let s = TestState {
caps: CAP_IO,
..Default::default()
};
let loaded = load_plugin(&w, s, Duration::from_secs(5)).expect("load");
let outcome = invoke_exec(loaded, "test_cmd", &["x".into()]);
assert_eq!(outcome.exit_code, Some(0));
assert!(String::from_utf8_lossy(&outcome.stdout).starts_with("test_cmd args="));
}
#[test]
fn case_2_hook_on_cd_records_var() {
let Some(w) = wasm() else { return };
let s = TestState {
caps: CAP_HOOK_ON_CD | CAP_VARIABLES_WRITE | CAP_IO,
..Default::default()
};
let loaded = load_plugin(&w, s, Duration::from_secs(5)).expect("load");
let outcome = invoke_hook(
loaded,
HookCall::OnCd {
old: "/tmp".into(),
new: "/home".into(),
},
);
assert!(outcome.error.is_none());
}
#[test]
fn case_3_insufficient_cap_denied() {
let Some(w) = wasm() else { return };
let mut s = TestState {
caps: CAP_IO,
..Default::default()
};
s.vars.insert("X".into(), "y".into());
let loaded = load_plugin(&w, s, Duration::from_secs(5)).expect("load");
let outcome = invoke_exec(loaded, "echo_var", &["X".into()]);
assert_eq!(outcome.exit_code, Some(2));
}
#[test]
fn case_4_allowed_exec_pattern_runs_echo() {
let Some(w) = wasm() else { return };
let s = TestState {
caps: CAP_IO | CAP_COMMANDS_EXEC,
allow_exec: vec![CommandPattern::parse("echo:*").unwrap()],
..Default::default()
};
let loaded = load_plugin(&w, s, Duration::from_secs(5)).expect("load");
let outcome = invoke_exec(loaded, "run-echo", &["hi".into()]);
assert_eq!(outcome.exit_code, Some(0));
assert_eq!(outcome.stdout, b"hi\n");
assert_eq!(outcome.exec_log.len(), 1);
}
#[test]
fn case_5_timeout_on_slow_plugin_pre_prompt() {
let slow = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../target/wasm32-wasip2/release/slow_plugin.wasm");
if !slow.exists() {
return;
}
let s = TestState {
caps: yosh_plugin_api::CAP_HOOK_PRE_PROMPT,
..Default::default()
};
let start = std::time::Instant::now();
let loaded = load_plugin(&slow, s, Duration::from_millis(200)).expect("load");
let outcome = invoke_hook(loaded, HookCall::PrePrompt);
let elapsed = start.elapsed();
assert_eq!(outcome.error_kind, Some("timeout"));
assert!(
elapsed < Duration::from_secs(15),
"timeout interrupt did not fire: {:?}",
elapsed
);
}
#[test]
fn case_6_test_runner_parses_passing_scenario() {
let Some(_w) = wasm() else { return };
let scenario =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/scenarios/echo_var_pass.toml");
let reports = yosh_plugin_manager::scenario::run_dir(&scenario, None);
assert_eq!(reports.len(), 1);
assert!(reports[0].passed(), "report: {:?}", reports[0]);
}
#[test]
fn case_7_test_runner_reports_failure_with_step_index() {
let Some(_w) = wasm() else { return };
let scenario =
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/scenarios/vars_set_fail.toml");
let reports = yosh_plugin_manager::scenario::run_dir(&scenario, None);
assert_eq!(reports.len(), 1);
assert!(!reports[0].passed());
}
#[test]
fn case_8_unknown_expect_key_rejected_at_parse() {
use yosh_plugin_manager::scenario;
let tmp = tempfile::NamedTempFile::new().unwrap();
std::fs::write(
tmp.path(),
r#"
plugin = "x.wasm"
[[step]]
call = "exec"
args = ["y"]
[step.expect]
unknown_key = "boom"
"#,
)
.unwrap();
let err = scenario::parse(tmp.path()).unwrap_err();
assert!(err.contains("unknown") || err.contains("unknown_key"));
}