use crate::brain::commands::UserCommand;
use crate::brain::skills::{Skill, SkillSource};
use crate::channels::commands::{ChannelCommand, gate_user_command, match_user_command_inner};
fn deploy_command() -> UserCommand {
UserCommand {
name: "/deploy".to_string(),
description: "Ship the current branch".to_string(),
action: "prompt".to_string(),
prompt: "Deploy the current branch to production.".to_string(),
}
}
fn status_command() -> UserCommand {
UserCommand {
name: "/notice".to_string(),
description: "Canned notice".to_string(),
action: "system".to_string(),
prompt: "Maintenance window is open.".to_string(),
}
}
fn audit_skill() -> Skill {
Skill {
name: "security-audit".to_string(),
slash_name: "/security-audit".to_string(),
description: "Audit the repo".to_string(),
body: "Run a full security audit and report findings.".to_string(),
review_gate: false,
source: SkillSource::User,
}
}
fn assert_owner_refusal(cmd: ChannelCommand, what: &str) {
match cmd {
ChannelCommand::UnknownCommand(msg) => assert!(
msg.to_lowercase().contains("owner"),
"{what}: denial should read as owner-only, got: {msg}"
),
_ => panic!("{what} must be refused for a non-owner"),
}
}
#[test]
fn a_user_command_is_refused_for_a_non_owner() {
let matched = match_user_command_inner("/deploy", &[deploy_command()], &[]);
assert!(
matches!(matched, ChannelCommand::UserPrompt(_)),
"fixture must match"
);
assert_owner_refusal(gate_user_command(matched, false), "/deploy");
}
#[test]
fn a_system_action_command_is_refused_for_a_non_owner() {
let matched = match_user_command_inner("/notice", &[status_command()], &[]);
assert!(
matches!(matched, ChannelCommand::UserSystem(_)),
"fixture must match"
);
assert_owner_refusal(gate_user_command(matched, false), "/notice");
}
#[test]
fn a_skill_slug_is_refused_for_a_non_owner() {
let matched = match_user_command_inner("/security-audit", &[], &[audit_skill()]);
assert!(
!matches!(matched, ChannelCommand::UnknownCommand(_)),
"fixture must match a skill"
);
assert_owner_refusal(gate_user_command(matched, false), "/security-audit");
}
#[test]
fn arguments_do_not_bypass_the_gate() {
let matched = match_user_command_inner("/deploy staging --force", &[deploy_command()], &[]);
assert_owner_refusal(gate_user_command(matched, false), "/deploy with args");
}
#[test]
fn the_owner_is_unaffected() {
for (text, cmds, skills) in [
("/deploy", vec![deploy_command()], vec![]),
("/notice", vec![status_command()], vec![]),
("/security-audit", vec![], vec![audit_skill()]),
] {
let matched = match_user_command_inner(text, &cmds, &skills);
let gated = gate_user_command(matched, true);
assert!(
!matches!(gated, ChannelCommand::UnknownCommand(_)),
"{text} must still work for the owner"
);
}
}
#[test]
fn an_unmatched_slash_is_not_turned_into_an_owner_refusal() {
let matched = match_user_command_inner("/no-such-thing", &[deploy_command()], &[audit_skill()]);
let gated = gate_user_command(matched, false);
match gated {
ChannelCommand::UnknownCommand(msg) => assert!(
!msg.to_lowercase().contains("owner"),
"an unmatched slash must not answer with the owner refusal, or the \
refusal itself enumerates what exists: {msg}"
),
_ => panic!("unmatched slash should stay UnknownCommand"),
}
}
#[test]
fn the_refusal_is_indistinguishable_from_a_built_in_refusal() {
let matched = match_user_command_inner("/deploy", &[deploy_command()], &[]);
match gate_user_command(matched, false) {
ChannelCommand::UnknownCommand(msg) => {
assert_eq!(msg, "🔒 Owner-only command.");
}
_ => panic!("expected an owner refusal"),
}
}