use crate::interactive::session_activation::{HOST_LANE_FACT, bypass_line, denied_segment};
#[test]
fn the_bypass_activation_line_states_the_lane_when_composed() {
let line = bypass_line(&[], false, true, &[]);
assert!(
line.contains(HOST_LANE_FACT),
"the composed line states the lane fact: {line}"
);
}
#[test]
fn the_uncomposed_activation_line_keeps_its_exact_bytes() {
let line = bypass_line(&[], false, false, &[]);
assert!(
!line.contains(HOST_LANE_FACT),
"the uncomposed line states no lane fact: {line}"
);
let staged = bypass_line(&["python3".to_owned()], false, false, &[]);
assert!(
!staged.contains(HOST_LANE_FACT),
"the staged-interpreter variant states no lane fact either: {staged}"
);
}
#[test]
fn the_activation_line_states_the_denied_names_when_nonempty() {
let denied = denied_segment(&["curl".to_owned(), "ssh".to_owned()]);
assert!(
denied.contains("curl") && denied.contains("ssh"),
"the denied segment names the list: {denied}"
);
assert!(
denied_segment(&[]).is_empty(),
"an empty list keeps today's bytes: no segment"
);
}
#[test]
fn bypass_allows_every_host_ask_and_structural_refusals_still_refuse() {
use saya_agent::{ApprovalDecision, ApprovalPolicy, SessionPolicy};
let tool = crate::interactive::session_definitions::run_command();
let policy = SessionPolicy::new(ApprovalPolicy::Bypass);
assert_eq!(
policy.resolve(&tool.effect, None),
ApprovalDecision::Allow,
"bypass allows every host ask without a grant"
);
assert!(
saya_harness::host::HostCommand::new("bin/npm", Vec::<String>::new()).is_err(),
"a path-shaped name refuses"
);
let config = saya_harness::host::HostConfig::new(
"/nonexistent-path-for-h3-red",
std::path::PathBuf::from("/tmp"),
std::time::Duration::from_secs(600),
)
.expect("the config builds");
assert!(
config.resolve("npm").is_err(),
"a name not on the passed PATH refuses"
);
assert!(config.narrow_timeout(0).is_err(), "timeout 0 refuses");
assert!(
config.narrow_timeout(3600).is_err(),
"a timeout over the ceiling refuses"
);
let denied = crate::interactive::session_deny::denied_call_program(
"run_command",
&serde_json::json!({"program": "curl"}),
&["curl".to_owned()],
);
assert_eq!(
denied.as_deref(),
Some("curl"),
"a denied name refuses under bypass"
);
let refusal = crate::interactive::allow_refusal::composition_refusal(
"command:npm",
&crate::approval_facts::ApprovalFacts::default(),
);
assert!(
refusal
.as_deref()
.unwrap_or_default()
.contains("no workspace root is bound"),
"lane-off refuses the host ask: {refusal:?}"
);
assert!(
!refusal
.as_deref()
.unwrap_or_default()
.contains("--host-commands"),
"no deleted flag in the refusal: {refusal:?}"
);
}
#[test]
fn a_denied_name_still_refuses_under_ask_and_bypass() {
use saya_agent::{ApprovalDecision, ApprovalPolicy, SessionPolicy};
for mode in [ApprovalPolicy::Ask, ApprovalPolicy::Bypass] {
let denied = crate::interactive::session_deny::denied_call_program(
"run_command",
&serde_json::json!({"program": "curl"}),
&["curl".to_owned()],
);
assert_eq!(
denied.as_deref(),
Some("curl"),
"a denied name refuses under {mode:?}"
);
let policy = SessionPolicy::new(mode);
let gate =
crate::interactive::session_deny::SessionDeny::from_names(vec!["curl".to_owned()])
.expect("a bare name builds");
assert!(
gate.contains("curl"),
"the deny gate fires before {mode:?}'s own decision"
);
let _ = policy;
}
let open = crate::interactive::session_deny::denied_call_program(
"run_command",
&serde_json::json!({"program": "make"}),
&["curl".to_owned()],
);
assert!(open.is_none(), "a name outside the list is not refused");
let _ = ApprovalDecision::Ask;
}
#[test]
fn runs_still_refuse_command_scopes() {
use crate::commands::run::scopes::{Surface, parse};
for token in ["command:npm", "command:bash"] {
let Err(error) = parse(&[token.to_owned()], Surface::Run) else {
panic!("a run never gets this lane: {token}");
};
assert!(
error.contains("not available on runs, by design"),
"the run refusal is policy, pinned: {error}"
);
assert!(
error.contains("unconfined host execution"),
"the refusal says what the scope names: {error}"
);
}
}
#[test]
fn the_status_header_carries_the_host_segment_and_the_denied_names() {
let mut state = crate::SessionState::new("s1", Some(String::from("analytics")), "qwen");
state.host_composed = true;
state.denied_programs = vec!["curl".to_owned()];
let line = crate::interactive::session_prompt::status_line(&state);
assert!(
line.contains("host:unsandboxed"),
"the status header gains the host: segment: {line}"
);
assert!(
line.contains("curl"),
"the status header lists denied names: {line}"
);
}
#[test]
fn the_help_parity_suite_extends_to_the_token_and_both_flags() {
use clap::CommandFactory as _;
let root_help = crate::Cli::command().render_help().to_string();
assert!(
!root_help.contains("--host-commands"),
"the root help no longer names the deleted flag"
);
assert!(
root_help.contains("--deny"),
"the root help names the deny flag: {root_help}"
);
let allow_help = crate::slash::command_help("allow").expect("/allow has per-command help");
assert!(
allow_help.contains("command:<program>"),
"the /allow help names the new token: {allow_help}"
);
assert!(
allow_help.contains("allowed programs may still invoke it"),
"the /allow help states the deny list's not-bounded clause: {allow_help}"
);
}