#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "tests and benches use unwrap and expect to keep fixture setup concise"
)]
#[path = "common/mod.rs"]
mod common;
use std::process::Command;
use common::{fallow_bin, parse_json};
fn telemetry_command(args: &[&str]) -> common::CommandOutput {
let home = tempfile::tempdir().expect("temp home");
let mut cmd = Command::new(fallow_bin());
cmd.env_remove("CI")
.env_remove("GITHUB_ACTIONS")
.env_remove("GITLAB_CI")
.env_remove("DO_NOT_TRACK")
.env_remove("FALLOW_TELEMETRY")
.env_remove("FALLOW_TELEMETRY_DEBUG")
.env_remove("FALLOW_TELEMETRY_DISABLED")
.env_remove("FALLOW_AGENT_SOURCE")
.env("HOME", home.path())
.env("XDG_CONFIG_HOME", home.path().join(".config"))
.env("APPDATA", home.path().join("AppData"))
.env("RUST_LOG", "")
.env("NO_COLOR", "1");
for arg in args {
cmd.arg(arg);
}
let output = cmd.output().expect("failed to run fallow binary");
common::CommandOutput {
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
code: output.status.code().unwrap_or(-1),
}
}
#[test]
fn telemetry_status_json_is_parseable() {
let output = telemetry_command(&["--format", "json", "--quiet", "telemetry", "status"]);
assert_eq!(
output.code, 0,
"telemetry status should exit 0: {}",
output.stderr
);
let json = parse_json(&output);
assert_eq!(json["telemetry"]["state"].as_str(), Some("off"));
assert_eq!(json["telemetry"]["source"].as_str(), Some("default"));
}
#[test]
fn non_telemetry_subcommands_still_dispatch_normally() {
let output = telemetry_command(&["--format", "json", "--quiet", "explain", "unused-exports"]);
assert_eq!(output.code, 0, "explain should exit 0: {}", output.stderr);
let json = parse_json(&output);
assert_eq!(json["id"].as_str(), Some("fallow/unused-export"));
}
#[test]
fn telemetry_inspect_preserves_command_stdout_json() {
let mut cmd = Command::new(fallow_bin());
let home = tempfile::tempdir().expect("temp home");
cmd.env("FALLOW_TELEMETRY", "inspect")
.env("FALLOW_AGENT_SOURCE", "claude-code")
.env("HOME", home.path())
.env("XDG_CONFIG_HOME", home.path().join(".config"))
.env("APPDATA", home.path().join("AppData"))
.env("RUST_LOG", "")
.env("NO_COLOR", "1")
.arg("--parent-run")
.arg("../repo/main")
.args(["--format", "json", "--quiet"]);
let raw = cmd.output().expect("failed to run fallow binary");
let output = common::CommandOutput {
stdout: String::from_utf8_lossy(&raw.stdout).to_string(),
stderr: String::from_utf8_lossy(&raw.stderr).to_string(),
code: raw.status.code().unwrap_or(-1),
};
assert_eq!(output.code, 0, "analysis should exit 0: {}", output.stderr);
let json = parse_json(&output);
assert_eq!(json["kind"].as_str(), Some("combined"));
assert_eq!(json["schema_version"].as_u64(), Some(7));
let event_start = output
.stderr
.find('{')
.expect("inspect stderr should contain telemetry JSON");
let event: serde_json::Value = serde_json::from_str(&output.stderr[event_start..])
.expect("inspect stderr should contain valid telemetry JSON");
assert_eq!(event["invocation_context"].as_str(), Some("agent"));
assert_eq!(event["agent_source"].as_str(), Some("claude_code"));
assert_eq!(event.get("parent_run"), None);
}
#[test]
fn invalid_explicit_agent_source_is_ignored() {
let mut cmd = Command::new(fallow_bin());
let home = tempfile::tempdir().expect("temp home");
cmd.env("FALLOW_TELEMETRY", "inspect")
.env("FALLOW_AGENT_SOURCE", "private-agent-x")
.env("HOME", home.path())
.env("XDG_CONFIG_HOME", home.path().join(".config"))
.env("APPDATA", home.path().join("AppData"))
.env("RUST_LOG", "")
.env("NO_COLOR", "1")
.args(["--format", "json", "--quiet"]);
let raw = cmd.output().expect("failed to run fallow binary");
let output = common::CommandOutput {
stdout: String::from_utf8_lossy(&raw.stdout).to_string(),
stderr: String::from_utf8_lossy(&raw.stderr).to_string(),
code: raw.status.code().unwrap_or(-1),
};
assert_eq!(output.code, 0, "analysis should exit 0: {}", output.stderr);
let event_start = output
.stderr
.find('{')
.expect("inspect stderr should contain telemetry JSON");
let event: serde_json::Value = serde_json::from_str(&output.stderr[event_start..])
.expect("inspect stderr should contain valid telemetry JSON");
assert_ne!(event["agent_source"].as_str(), Some("private-agent-x"));
assert!(event.get("agent_source").is_none());
}
fn inspect_event(
root: &std::path::Path,
args: &[&str],
extra_env: &[(&str, &str)],
) -> serde_json::Value {
let home = tempfile::tempdir().expect("temp home");
let mut cmd = Command::new(fallow_bin());
cmd.env_remove("CI")
.env_remove("GITHUB_ACTIONS")
.env_remove("GITLAB_CI")
.env_remove("DO_NOT_TRACK")
.env_remove("FALLOW_TELEMETRY_DISABLED")
.env_remove("FALLOW_AGENT_SOURCE")
.env_remove("FALLOW_INTEGRATION_SURFACE")
.env_remove("FALLOW_MCP_TOOL")
.env("FALLOW_TELEMETRY", "inspect")
.env("HOME", home.path())
.env("XDG_CONFIG_HOME", home.path().join(".config"))
.env("APPDATA", home.path().join("AppData"))
.env("RUST_LOG", "")
.env("NO_COLOR", "1")
.args(["--root", &root.to_string_lossy()])
.args(args);
for (key, value) in extra_env {
cmd.env(key, value);
}
let raw = cmd.output().expect("failed to run fallow binary");
let stderr = String::from_utf8_lossy(&raw.stderr).to_string();
let event_start = stderr.find('{').unwrap_or_else(|| {
panic!("inspect stderr should contain telemetry JSON; stderr was: {stderr}")
});
serde_json::from_str(&stderr[event_start..])
.expect("inspect stderr should contain valid telemetry JSON")
}
fn write_duplicated_project(dir: &std::path::Path) {
let src = dir.join("src");
std::fs::create_dir_all(&src).expect("create src");
std::fs::write(
dir.join("package.json"),
"{\n \"name\": \"dup-fixture\"\n}\n",
)
.expect("write package.json");
let block = " let total = 0;\n let count = 0;\n for (const item of items) {\n total = total + item * 2;\n count = count + 1;\n }\n const average = total / Math.max(count, 1);\n const doubled = average * 2;\n const adjusted = doubled + count - 1;\n return adjusted + total + count;\n";
for name in ["a", "b"] {
std::fs::write(
src.join(format!("{name}.ts")),
format!("export function compute_{name}(items: number[]): number {{\n{block}}}\n"),
)
.expect("write source file");
}
}
#[test]
fn dupes_with_duplication_sets_findings_present_despite_success_outcome() {
let dir = tempfile::tempdir().expect("temp project");
write_duplicated_project(dir.path());
let event = inspect_event(dir.path(), &["dupes", "--format", "json", "--quiet"], &[]);
assert_eq!(event["workflow"].as_str(), Some("dupes"));
assert_eq!(event["outcome"].as_str(), Some("success"));
assert_eq!(
event["findings_present"].as_bool(),
Some(true),
"dupes with real duplication must report findings_present=true"
);
}
#[test]
fn dupes_on_clean_project_sets_findings_present_false() {
let dir = tempfile::tempdir().expect("temp project");
let src = dir.path().join("src");
std::fs::create_dir_all(&src).expect("create src");
std::fs::write(
dir.path().join("package.json"),
"{\n \"name\": \"clean\"\n}\n",
)
.expect("write package.json");
std::fs::write(src.join("only.ts"), "export const value = 41 + 1;\n").expect("write source");
let event = inspect_event(dir.path(), &["dupes", "--format", "json", "--quiet"], &[]);
assert_eq!(event["workflow"].as_str(), Some("dupes"));
assert_eq!(
event["findings_present"].as_bool(),
Some(false),
"a genuinely clean dupes run must report findings_present=false"
);
}
#[test]
fn admin_command_emits_no_findings_present_key() {
let dir = tempfile::tempdir().expect("temp project");
std::fs::write(dir.path().join("package.json"), "{\n \"name\": \"x\"\n}\n")
.expect("write package.json");
let event = inspect_event(dir.path(), &["explain", "unused-exports"], &[]);
assert_eq!(event["workflow"].as_str(), Some("explain"));
assert!(
event.get("findings_present").is_none(),
"commands that run no analysis must omit findings_present"
);
}
#[test]
fn mcp_surface_override_tags_event_with_tool() {
let dir = tempfile::tempdir().expect("temp project");
std::fs::write(dir.path().join("package.json"), "{\n \"name\": \"x\"\n}\n")
.expect("write package.json");
let event = inspect_event(
dir.path(),
&["dupes", "--format", "json", "--quiet"],
&[
("FALLOW_INTEGRATION_SURFACE", "mcp"),
("FALLOW_MCP_TOOL", "find_dupes"),
],
);
assert_eq!(
event["integration_surface"].as_str(),
Some("mcp"),
"the surface override must re-tag the event as mcp instead of cli_json"
);
assert_eq!(event["mcp_tool"].as_str(), Some("find_dupes"));
}
#[test]
fn off_allowlist_mcp_tool_is_dropped() {
let dir = tempfile::tempdir().expect("temp project");
std::fs::write(dir.path().join("package.json"), "{\n \"name\": \"x\"\n}\n")
.expect("write package.json");
let event = inspect_event(
dir.path(),
&["dupes", "--format", "json", "--quiet"],
&[
("FALLOW_INTEGRATION_SURFACE", "mcp"),
("FALLOW_MCP_TOOL", "/etc/passwd"),
],
);
assert_eq!(event["integration_surface"].as_str(), Some("mcp"));
assert!(
event.get("mcp_tool").is_none(),
"an off-allowlist FALLOW_MCP_TOOL value must be dropped, never echoed"
);
}