mod common;
use common::sqry_bin;
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use tempfile::TempDir;
fn write_same_name_fixture(root: &std::path::Path) {
fs::create_dir_all(root.join("src")).unwrap();
fs::write(
root.join("Cargo.toml"),
r#"[package]
name = "db18_cli_same_name_frontier"
version = "0.0.1"
edition = "2024"
[lib]
path = "src/lib.rs"
"#,
)
.unwrap();
fs::write(
root.join("src/lib.rs"),
r"pub struct AlphaMarker;
impl AlphaMarker {
pub fn helper() {}
}
pub fn caller_a() {
AlphaMarker::helper();
}
pub fn root_a() {
caller_a();
}
pub struct BetaMarker;
impl BetaMarker {
pub fn helper() {}
}
pub fn caller_b() {
BetaMarker::helper();
}
pub fn root_b() {
caller_b();
}
",
)
.unwrap();
}
fn write_simple_callers_fixture(root: &std::path::Path) {
fs::write(
root.join("lib.rs"),
r"
pub fn helper() -> i32 {
42
}
pub fn fetch() -> i32 {
helper()
}
pub fn process() -> i32 {
helper()
}
",
)
.unwrap();
}
fn index(root: &std::path::Path) {
Command::new(sqry_bin())
.arg("index")
.arg(root)
.assert()
.success();
}
#[test]
fn cli_direct_callers_graph_eval_direction() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("direct-callers")
.arg("helper")
.assert()
.success()
.stdout(predicate::str::contains("fetch"))
.stdout(predicate::str::contains("process"));
}
#[test]
fn cli_direct_callers_segment_aware_union_semantic() {
let temp = TempDir::new().unwrap();
write_same_name_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("direct-callers")
.arg("AlphaMarker::helper")
.output()
.expect("command failed");
assert!(output.status.success(), "command failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("caller_a"),
"caller_a must be in direct-callers(AlphaMarker::helper) — \
it is a direct caller of the alpha-side helper, stdout = {stdout}"
);
assert!(
stdout.contains("caller_b"),
"caller_b must ALSO be in direct-callers(AlphaMarker::helper) \
— sqry-db's method-segment fallback unions callers across \
nodes with the same simple method name (this is the DB18 \
behavior shift aligning CLI with MCP), stdout = {stdout}"
);
let parsed: serde_json::Value = serde_json::from_str(&stdout).unwrap();
let callers = parsed["callers"].as_array().unwrap();
for caller in callers {
let name = caller["name"].as_str().unwrap_or("");
assert!(
name != "helper",
"helper must not appear as its own caller, got caller = {caller:?}"
);
}
}
#[test]
fn cli_direct_callers_json_schema_stable() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("direct-callers")
.arg("helper")
.output()
.expect("command failed");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
assert!(parsed.get("symbol").is_some(), "missing 'symbol'");
assert!(parsed.get("callers").is_some(), "missing 'callers'");
assert!(parsed.get("total").is_some(), "missing 'total'");
assert!(parsed.get("truncated").is_some(), "missing 'truncated'");
let callers = parsed["callers"].as_array().unwrap();
assert!(!callers.is_empty(), "expected at least one caller");
for caller in callers {
for field in ["name", "qualified_name", "kind", "file", "line", "language"] {
assert!(
caller.get(field).is_some(),
"caller row missing field '{field}': {caller:?}"
);
}
}
}
#[test]
fn cli_direct_callees_graph_eval_direction() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("direct-callees")
.arg("fetch")
.assert()
.success()
.stdout(predicate::str::contains("helper"));
}
#[test]
fn cli_direct_callees_from_unique_caller_stays_anchored() {
let temp = TempDir::new().unwrap();
write_same_name_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("direct-callees")
.arg("caller_a")
.output()
.expect("command failed");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("helper"),
"direct-callees(caller_a) must include helper (the alpha-side \
inherent method), stdout = {stdout}"
);
assert!(
!stdout.contains("caller_b"),
"caller_b must NOT appear in direct-callees(caller_a) — \
caller_b is not called by caller_a and a regression that \
broadened through a shared name would leak it in, stdout = {stdout}"
);
}
#[test]
fn cli_direct_callees_json_schema_stable() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("direct-callees")
.arg("fetch")
.output()
.expect("command failed");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
assert!(parsed.get("symbol").is_some(), "missing 'symbol'");
assert!(parsed.get("callees").is_some(), "missing 'callees'");
assert!(parsed.get("total").is_some(), "missing 'total'");
assert!(parsed.get("truncated").is_some(), "missing 'truncated'");
assert!(
parsed["total"].is_number(),
"'total' must be a number, got {:?}",
parsed["total"]
);
assert!(
parsed["truncated"].is_boolean(),
"'truncated' must be a boolean, got {:?}",
parsed["truncated"]
);
let callees = parsed["callees"].as_array().unwrap();
assert!(!callees.is_empty(), "expected at least one callee");
for callee in callees {
for field in ["name", "qualified_name", "kind", "file", "language"] {
assert!(
callee.get(field).is_some(),
"callee row missing string field '{field}': {callee:?}"
);
assert!(
callee[field].is_string(),
"callee field '{field}' must be a string: {callee:?}"
);
}
assert!(
callee.get("line").is_some(),
"callee row missing 'line': {callee:?}"
);
assert!(
callee["line"].is_number(),
"callee 'line' must be a number: {callee:?}"
);
}
}
#[test]
fn cli_call_chain_depth_same_name_frontier_invariant() {
let temp = TempDir::new().unwrap();
write_same_name_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("call-chain-depth")
.arg("caller_a")
.output()
.expect("command failed");
assert!(output.status.success(), "command failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
let results = parsed["results"]
.as_array()
.expect("top-level 'results' must be an array");
assert!(
!results.is_empty(),
"call-chain-depth(caller_a) must return at least one result, stdout = {stdout}"
);
let has_caller_a = results
.iter()
.any(|r| r["symbol"].as_str().is_some_and(|s| s.contains("caller_a")));
assert!(
has_caller_a,
"caller_a must appear in call-chain-depth results as the seeded symbol, stdout = {stdout}"
);
assert!(
!stdout.contains("BetaMarker"),
"BetaMarker must NOT appear in call-chain-depth(caller_a) — \
DB18 frontier regression freeze, stdout = {stdout}"
);
assert!(
!stdout.contains("caller_b"),
"caller_b must NOT appear in call-chain-depth(caller_a) — \
it belongs to the disjoint beta-side chain, stdout = {stdout}"
);
assert!(
!stdout.contains("root_b"),
"root_b must NOT appear in call-chain-depth(caller_a), stdout = {stdout}"
);
}
#[test]
fn cli_call_chain_depth_json_schema_stable() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("call-chain-depth")
.arg("helper")
.output()
.expect("command failed");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
assert!(parsed.get("results").is_some(), "missing 'results'");
assert!(parsed.get("count").is_some(), "missing 'count'");
assert!(
parsed["count"].is_number(),
"'count' must be a number, got {:?}",
parsed["count"]
);
let results = parsed["results"]
.as_array()
.expect("'results' must be an array");
assert!(!results.is_empty(), "expected at least one result entry");
for item in results {
assert!(
item.get("symbol").is_some() && item["symbol"].is_string(),
"result item missing string 'symbol': {item:?}"
);
assert!(
item.get("language").is_some() && item["language"].is_string(),
"result item missing string 'language': {item:?}"
);
assert!(
item.get("depth").is_some() && item["depth"].is_number(),
"result item missing number 'depth': {item:?}"
);
}
}
#[test]
fn cli_dependency_tree_same_name_frontier_invariant() {
let temp = TempDir::new().unwrap();
write_same_name_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("dependency-tree")
.arg("caller_a")
.output()
.expect("command failed");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let has_alpha_side = stdout.contains("caller_a") || stdout.contains("helper");
assert!(
has_alpha_side,
"dependency-tree(caller_a) must include at least one alpha-side symbol \
(caller_a or helper); an empty or broken traversal would fail this, \
stdout = {stdout}"
);
assert!(
!stdout.contains("BetaMarker"),
"BetaMarker symbols must NOT leak into dependency-tree(caller_a), stdout = {stdout}"
);
assert!(
!stdout.contains("caller_b"),
"caller_b must NOT leak into dependency-tree(caller_a), stdout = {stdout}"
);
}
#[test]
fn cli_dependency_tree_json_schema_stable() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("dependency-tree")
.arg("fetch")
.output()
.expect("command failed");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
assert!(
parsed.get("nodes").is_some() && parsed["nodes"].is_array(),
"missing array 'nodes'"
);
assert!(
parsed.get("edges").is_some() && parsed["edges"].is_array(),
"missing array 'edges'"
);
assert!(
parsed.get("node_count").is_some() && parsed["node_count"].is_number(),
"missing number 'node_count'"
);
assert!(
parsed.get("edge_count").is_some() && parsed["edge_count"].is_number(),
"missing number 'edge_count'"
);
let nodes = parsed["nodes"].as_array().unwrap();
assert!(!nodes.is_empty(), "expected at least one node");
let first_node = &nodes[0];
for field in ["id", "name", "language"] {
assert!(
first_node.get(field).is_some() && first_node[field].is_string(),
"node missing string field '{field}': {first_node:?}"
);
}
let edges = parsed["edges"].as_array().unwrap();
if !edges.is_empty() {
let first_edge = &edges[0];
for field in ["from", "to", "kind"] {
assert!(
first_edge.get(field).is_some() && first_edge[field].is_string(),
"edge missing string field '{field}': {first_edge:?}"
);
}
}
}
#[test]
fn cli_impact_same_name_frontier_invariant() {
let temp = TempDir::new().unwrap();
write_same_name_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("impact")
.arg("AlphaMarker::helper")
.arg("--path")
.arg(temp.path())
.arg("--json")
.output()
.expect("command failed");
assert!(output.status.success(), "command failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("caller_a"),
"impact(AlphaMarker::helper) must include caller_a, stdout = {stdout}"
);
assert!(
!stdout.contains("caller_b"),
"caller_b must NOT leak into impact(AlphaMarker::helper) — \
DB18 frontier regression freeze, stdout = {stdout}"
);
}
#[test]
fn cli_impact_json_schema_stable() {
let temp = TempDir::new().unwrap();
write_same_name_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("impact")
.arg("AlphaMarker::helper")
.arg("--path")
.arg(temp.path())
.arg("--json")
.output()
.expect("command failed");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
assert!(
parsed.get("symbol").is_some() && parsed["symbol"].is_string(),
"missing string 'symbol'"
);
assert!(
parsed.get("direct").is_some() && parsed["direct"].is_array(),
"missing array 'direct'"
);
assert!(parsed.get("stats").is_some(), "missing object 'stats'");
let stats = &parsed["stats"];
for field in [
"direct_count",
"indirect_count",
"total_affected",
"affected_files_count",
"max_depth",
] {
assert!(
stats.get(field).is_some() && stats[field].is_number(),
"stats missing number field '{field}': {stats:?}"
);
}
let direct = parsed["direct"].as_array().unwrap();
assert!(!direct.is_empty(), "expected at least one direct dependent");
let first = &direct[0];
for field in ["name", "qualified_name", "kind", "file", "relation"] {
assert!(
first.get(field).is_some() && first[field].is_string(),
"direct item missing string field '{field}': {first:?}"
);
}
assert!(
first.get("line").is_some() && first["line"].is_number(),
"direct item missing number 'line': {first:?}"
);
assert!(
first.get("depth").is_some() && first["depth"].is_number(),
"direct item missing number 'depth': {first:?}"
);
}
fn write_unused_fixture(root: &std::path::Path) {
fs::create_dir_all(root.join("src")).unwrap();
fs::write(
root.join("Cargo.toml"),
r#"[package]
name = "db18_cli_unused"
version = "0.0.1"
edition = "2024"
[lib]
path = "src/lib.rs"
"#,
)
.unwrap();
fs::write(
root.join("src/lib.rs"),
r"pub fn entry_point() -> i32 {
41
}
fn definitely_unused_orphan() -> i32 {
7
}
",
)
.unwrap();
}
#[test]
fn cli_unused_routes_through_sqry_db() {
let temp = TempDir::new().unwrap();
write_unused_fixture(temp.path());
index(temp.path());
Command::new(sqry_bin())
.arg("unused")
.arg("--scope")
.arg("all")
.arg(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("definitely_unused_orphan"));
}
#[test]
fn cli_unused_scope_values_unchanged() {
let temp = TempDir::new().unwrap();
write_unused_fixture(temp.path());
index(temp.path());
for scope in &["public", "private", "function", "struct", "all"] {
Command::new(sqry_bin())
.arg("unused")
.arg("--scope")
.arg(scope)
.arg(temp.path())
.assert()
.success();
}
}
#[test]
fn cli_unused_post_filter_completeness_with_lang_filter() {
let temp = TempDir::new().unwrap();
write_unused_fixture(temp.path());
index(temp.path());
Command::new(sqry_bin())
.arg("unused")
.arg("--scope")
.arg("all")
.arg("--lang")
.arg("rust")
.arg(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("definitely_unused_orphan"));
}
#[test]
fn cli_unused_json_schema_stable() {
let temp = TempDir::new().unwrap();
write_unused_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("--json")
.arg("unused")
.arg("--scope")
.arg("all")
.arg(temp.path())
.output()
.expect("command failed");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
let groups = parsed
.as_array()
.expect("output must be a JSON array of file groups");
assert!(
!groups.is_empty(),
"expected at least one file group (the orphan function must be present)"
);
let first_group = &groups[0];
assert!(
first_group.get("file").is_some() && first_group["file"].is_string(),
"group missing string 'file': {first_group:?}"
);
assert!(
first_group.get("count").is_some() && first_group["count"].is_number(),
"group missing number 'count': {first_group:?}"
);
assert!(
first_group.get("symbols").is_some() && first_group["symbols"].is_array(),
"group missing array 'symbols': {first_group:?}"
);
let symbols = first_group["symbols"].as_array().unwrap();
assert!(!symbols.is_empty(), "group must have at least one symbol");
let first_sym = &symbols[0];
for field in [
"name",
"qualified_name",
"kind",
"file",
"language",
"visibility",
] {
assert!(
first_sym.get(field).is_some() && first_sym[field].is_string(),
"symbol missing string field '{field}': {first_sym:?}"
);
}
assert!(
first_sym.get("line").is_some() && first_sym["line"].is_number(),
"symbol missing number 'line': {first_sym:?}"
);
}
fn write_three_cycle_fixture(root: &std::path::Path) {
fs::create_dir_all(root.join("src")).unwrap();
fs::write(
root.join("Cargo.toml"),
r#"[package]
name = "db19_cli_three_cycle"
version = "0.0.1"
edition = "2024"
[lib]
path = "src/lib.rs"
"#,
)
.unwrap();
fs::write(
root.join("src/lib.rs"),
r"pub fn node_alpha() {
node_beta();
}
pub fn node_beta() {
node_gamma();
}
pub fn node_gamma() {
node_alpha();
}
",
)
.unwrap();
}
#[test]
fn cli_cycles_detects_three_node_scc() {
let temp = TempDir::new().unwrap();
write_three_cycle_fixture(temp.path());
index(temp.path());
Command::new(sqry_bin())
.arg("cycles")
.arg("--type")
.arg("calls")
.arg(temp.path())
.assert()
.success()
.stdout(predicate::str::contains("node_alpha"))
.stdout(predicate::str::contains("node_beta"))
.stdout(predicate::str::contains("node_gamma"));
}
#[test]
fn cli_cycles_respects_min_depth() {
let temp = TempDir::new().unwrap();
write_three_cycle_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("--json")
.arg("cycles")
.arg("--type")
.arg("calls")
.arg("--min-depth")
.arg("4")
.arg(temp.path())
.output()
.expect("command failed");
assert!(output.status.success(), "command failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
let cycles = parsed.as_array().expect("output must be an array");
assert!(
cycles.is_empty(),
"min-depth=4 on a 3-node SCC must return no cycles, got {cycles:?}"
);
}
#[test]
fn cli_cycles_json_schema_stable() {
let temp = TempDir::new().unwrap();
write_three_cycle_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("--json")
.arg("cycles")
.arg("--type")
.arg("calls")
.arg(temp.path())
.output()
.expect("command failed");
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
let cycles = parsed.as_array().expect("output must be array");
assert!(!cycles.is_empty(), "expected at least one cycle");
for cycle in cycles {
assert!(cycle.get("depth").is_some(), "missing 'depth'");
assert!(cycle.get("nodes").is_some(), "missing 'nodes'");
assert!(cycle["nodes"].as_array().is_some(), "'nodes' must be array");
}
}
#[test]
fn cli_is_in_cycle_true_case() {
let temp = TempDir::new().unwrap();
write_three_cycle_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("is-in-cycle")
.arg("node_alpha")
.arg("--cycle-type")
.arg("calls")
.output()
.expect("command failed");
assert!(output.status.success(), "command failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
assert_eq!(
parsed["in_cycle"].as_bool(),
Some(true),
"node_alpha must be in_cycle=true, got {parsed:?}"
);
}
#[test]
fn cli_is_in_cycle_false_case() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("is-in-cycle")
.arg("helper")
.arg("--cycle-type")
.arg("calls")
.output()
.expect("command failed");
assert!(output.status.success(), "command failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value =
serde_json::from_str(&stdout).expect("output must be valid JSON");
assert_eq!(
parsed["in_cycle"].as_bool(),
Some(false),
"helper must be in_cycle=false, got {parsed:?}"
);
}
#[test]
fn cli_is_in_cycle_ambiguous_name_fails_strict() {
let temp = TempDir::new().unwrap();
write_same_name_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("json")
.arg("is-in-cycle")
.arg("helper")
.arg("--cycle-type")
.arg("calls")
.output()
.expect("command failed");
assert!(
!output.status.success(),
"is-in-cycle(helper) must exit non-zero when name is ambiguous \
(strict resolution policy). status = {:?}, stdout = {}",
output.status,
String::from_utf8_lossy(&output.stdout)
);
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
let combined = format!("{stdout}\n{stderr}");
assert!(
combined.contains("ambiguous") || combined.contains("candidates"),
"is-in-cycle(helper) error output must mention 'ambiguous' or \
'candidates'; combined = {combined}"
);
}
#[test]
fn cli_subgraph_same_name_frontier_invariant() {
let temp = TempDir::new().unwrap();
write_same_name_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("--json")
.arg("subgraph")
.arg("--path")
.arg(temp.path())
.arg("AlphaMarker::helper")
.output()
.expect("command failed");
assert!(output.status.success(), "command failed: {output:?}");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("caller_a") || stdout.contains("AlphaMarker"),
"subgraph(AlphaMarker::helper) must include an alpha-side symbol \
(caller_a or AlphaMarker); got empty/regressed output: {stdout}"
);
assert!(
!stdout.contains("caller_b"),
"caller_b must NOT leak into subgraph(AlphaMarker::helper), stdout = {stdout}"
);
}
#[test]
fn cli_visualize_same_name_frontier_invariant() {
let temp = TempDir::new().unwrap();
write_same_name_fixture(temp.path());
index(temp.path());
let out_file = temp.path().join("visualize.dot");
let output = Command::new(sqry_bin())
.arg("visualize")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg("graphviz")
.arg("--output-file")
.arg(&out_file)
.arg("callers:AlphaMarker::helper")
.output()
.expect("command failed");
assert!(output.status.success(), "command failed: {output:?}");
let rendered = fs::read_to_string(&out_file).unwrap_or_default();
assert!(
rendered.contains("caller_a") || rendered.contains("AlphaMarker"),
"visualize(callers:AlphaMarker::helper) must include an alpha-side \
symbol (caller_a or AlphaMarker) in the diagram; got empty/regressed \
output: {rendered}"
);
assert!(
!rendered.contains("caller_b"),
"caller_b must NOT leak into visualize(callers:AlphaMarker::helper), rendered = {rendered}"
);
}
#[test]
fn cli_visualize_format_switches_work() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
for format in &["graphviz", "mermaid", "d2"] {
let out_file = temp.path().join(format!("visualize.{format}"));
Command::new(sqry_bin())
.arg("visualize")
.arg("--path")
.arg(temp.path())
.arg("--format")
.arg(format)
.arg("--output-file")
.arg(&out_file)
.arg("callers:helper")
.assert()
.success();
let rendered = fs::read_to_string(&out_file).unwrap_or_default();
assert!(
!rendered.is_empty(),
"visualize --format {format} produced empty output"
);
}
}