#![allow(clippy::disallowed_methods)]
use std::path::PathBuf;
use std::process::{Command, Output};
const AGENT_TOKEN_ENV: &str = "TFTIO_AGENT_TOKEN";
const AGENT_TOKEN_EXPECTED_ENV: &str = "TFTIO_AGENT_TOKEN_EXPECTED";
fn bin_path() -> String {
if let Some(value) = std::env::vars().find_map(|(key, value)| {
if key.starts_with("CARGO_BIN_EXE_asana") {
Some(value)
} else {
None
}
}) {
return value;
}
let exe_name = if cfg!(windows) {
"asana-cli.exe"
} else {
"asana-cli"
};
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut dir: &std::path::Path = &manifest_dir;
loop {
for profile in ["debug", "release"] {
let candidate = dir.join("target").join(profile).join(exe_name);
if candidate.exists() {
return candidate.to_string_lossy().into_owned();
}
}
match dir.parent() {
Some(parent) => dir = parent,
None => break,
}
}
panic!(
"asana-cli binary not found: neither CARGO_BIN_EXE_asana* was set \
nor was a target/debug or target/release build present above {}",
manifest_dir.display()
);
}
fn run_agent_command(args: &[&str]) -> Output {
Command::new(bin_path())
.args(args)
.env(AGENT_TOKEN_ENV, "shared-test-token")
.env(AGENT_TOKEN_EXPECTED_ENV, "shared-test-token")
.output()
.expect("failed to execute asana-cli binary")
}
fn run_plain(args: &[&str]) -> Output {
Command::new(bin_path())
.args(args)
.env_remove(AGENT_TOKEN_ENV)
.env_remove(AGENT_TOKEN_EXPECTED_ENV)
.output()
.expect("failed to execute asana-cli binary")
}
#[test]
fn agent_surface_help_lists_only_declared_capabilities() {
let output = run_agent_command(&["--agent-help"]);
assert!(
output.status.success(),
"agent help failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
for capability in [
"manage-config",
"manage-tasks",
"manage-projects",
"manage-sections",
"manage-tags",
"manage-custom-fields",
"manage-workspaces",
"manage-users",
] {
assert!(
stdout.contains(capability),
"missing capability {capability} from output: {stdout}"
);
}
for hidden in [
"- doctor",
"- update",
"- manpage",
"- completions",
"- version",
"- license",
"command doctor",
"command update",
"command manpage",
"command completions",
"command version",
"command license",
] {
assert!(
!stdout.contains(hidden),
"unexpected hidden entry {hidden} in output: {stdout}"
);
}
}
#[test]
fn agent_surface_skill_manage_tasks_is_capability_scoped() {
let output = run_agent_command(&["--agent-skill", "manage-tasks"]);
assert!(
output.status.success(),
"agent skill failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("manage-tasks"));
assert!(stdout.contains("- task"));
assert!(
!stdout.contains("manage-projects"),
"unexpected extra capability: {stdout}"
);
assert!(
!stdout.contains("- project"),
"unexpected extra command path: {stdout}"
);
}
#[test]
fn agent_surface_examples_parse_as_valid_invocations() {
let placeholders: &[(&str, &str)] = &[
("<TASK2>", "T2"),
("<TASK>", "T1"),
("<TAG>", "TAG1"),
("<USER>", "U1"),
("<PROJECT>", "P1"),
("<SECTION>", "S1"),
("<FIELD>", "F1"),
("<WORKSPACE>", "W1"),
("<COMMENT>", "C1"),
("<ATTACHMENT>", "A1"),
("<PATH>", "/tmp/x"),
("<NAME>", "Name"),
("<TEXT>", "text"),
("<BODY>", "body"),
("<PAT>", "pat"),
("<GID>", "G1"),
];
for capability in [
"manage-config",
"manage-tasks",
"manage-projects",
"manage-sections",
"manage-tags",
"manage-custom-fields",
"manage-workspaces",
"manage-users",
] {
let described = run_plain(&["meta", "agent", "describe", capability]);
assert!(
described.status.success(),
"describe {capability} failed: {}",
String::from_utf8_lossy(&described.stderr)
);
let body = String::from_utf8_lossy(&described.stdout);
let mut in_examples = false;
let mut tested = 0usize;
for raw in body.lines() {
if raw.starts_with("examples:") {
in_examples = true;
continue;
}
if in_examples
&& let Some(prefix) = raw.chars().next()
&& prefix.is_ascii_alphabetic()
&& raw.contains(':')
{
in_examples = false;
}
if !in_examples {
continue;
}
let Some(stripped) = raw.strip_prefix("- asana-cli ") else {
continue;
};
let mut cooked = stripped.to_string();
for (placeholder, value) in placeholders {
cooked = cooked.replace(placeholder, value);
}
let mut tokens: Vec<String> = cooked.split_whitespace().map(str::to_string).collect();
tokens.push("--help".to_string());
let token_refs: Vec<&str> = tokens.iter().map(String::as_str).collect();
let probe = run_plain(&token_refs);
assert!(
probe.status.success(),
"example for {capability} failed to parse: `asana-cli {cooked}`\nstderr: {}",
String::from_utf8_lossy(&probe.stderr)
);
tested += 1;
}
assert!(
tested > 0,
"{capability} declared no examples (or parser failed to find any)"
);
}
}
#[test]
fn agent_surface_hidden_top_level_commands_parse_as_nonexistent_in_agent_mode() {
for args in [&["doctor"][..], &["update"][..]] {
let output = run_agent_command(args);
assert!(
!output.status.success(),
"command should be hidden but succeeded: {}",
args.join(" ")
);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
stderr.contains("unrecognized subcommand"),
"expected clap unknown-subcommand error for {}: {stderr}",
args.join(" ")
);
assert!(
!stderr.contains("Did you mean"),
"hidden command error leaked suggestion for {}: {stderr}",
args.join(" ")
);
}
}