mod common;
use common::sqry_bin;
use assert_cmd::Command;
use std::fs;
use tempfile::TempDir;
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 global_json_before_graph_threads_into_direct_callers_renderer() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
let output = Command::new(sqry_bin())
.arg("--json")
.arg("graph")
.arg("--path")
.arg(temp.path())
.arg("direct-callers")
.arg("helper")
.output()
.expect("command failed");
assert!(
output.status.success(),
"command failed: stdout={}, stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&stdout)
.unwrap_or_else(|e| panic!("expected JSON output, got: {stdout}\nparse error: {e}"));
assert_eq!(parsed["symbol"], "helper");
assert!(parsed["callers"].is_array());
}
#[test]
fn global_json_after_subcommand_threads_into_direct_callers_renderer() {
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("direct-callers")
.arg("helper")
.arg("--json")
.output()
.expect("command failed");
assert!(
output.status.success(),
"command failed: stdout={}, stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&stdout)
.unwrap_or_else(|e| panic!("expected JSON output, got: {stdout}\nparse error: {e}"));
assert_eq!(parsed["symbol"], "helper");
assert!(parsed["callers"].is_array());
}
#[test]
fn explicit_format_json_still_produces_json() {
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(),
"command failed: stdout={}, stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let stdout = String::from_utf8_lossy(&output.stdout);
let parsed: serde_json::Value = serde_json::from_str(&stdout)
.unwrap_or_else(|e| panic!("expected JSON output, got: {stdout}\nparse error: {e}"));
assert_eq!(parsed["symbol"], "helper");
}
fn assert_all_three_orders_produce_json<F>(
fixture_root: &std::path::Path,
subcommand_args: &[&str],
assert_json: F,
) where
F: Fn(&serde_json::Value, &str),
{
let order_1 = {
let mut cmd = Command::new(sqry_bin());
cmd.arg("graph").arg("--path").arg(fixture_root);
for arg in subcommand_args {
cmd.arg(arg);
}
cmd.arg("--json").output().expect("command failed")
};
assert!(
order_1.status.success(),
"order 1 (`graph <sub> ... --json`) failed: stdout={}, stderr={}",
String::from_utf8_lossy(&order_1.stdout),
String::from_utf8_lossy(&order_1.stderr),
);
let stdout_1 = String::from_utf8_lossy(&order_1.stdout).to_string();
let parsed_1: serde_json::Value = serde_json::from_str(&stdout_1)
.unwrap_or_else(|e| panic!("order 1 expected JSON, got: {stdout_1}\nparse error: {e}"));
assert_json(&parsed_1, "order 1 (`graph <sub> ... --json`)");
let order_2 = {
let mut cmd = Command::new(sqry_bin());
cmd.arg("--json")
.arg("graph")
.arg("--path")
.arg(fixture_root);
for arg in subcommand_args {
cmd.arg(arg);
}
cmd.output().expect("command failed")
};
assert!(
order_2.status.success(),
"order 2 (`--json graph <sub> ...`) failed: stdout={}, stderr={}",
String::from_utf8_lossy(&order_2.stdout),
String::from_utf8_lossy(&order_2.stderr),
);
let stdout_2 = String::from_utf8_lossy(&order_2.stdout).to_string();
let parsed_2: serde_json::Value = serde_json::from_str(&stdout_2)
.unwrap_or_else(|e| panic!("order 2 expected JSON, got: {stdout_2}\nparse error: {e}"));
assert_json(&parsed_2, "order 2 (`--json graph <sub> ...`)");
let order_3 = {
let mut cmd = Command::new(sqry_bin());
cmd.arg("graph")
.arg("--path")
.arg(fixture_root)
.arg("--format")
.arg("json");
for arg in subcommand_args {
cmd.arg(arg);
}
cmd.output().expect("command failed")
};
assert!(
order_3.status.success(),
"order 3 (`graph --format json <sub> ...`) failed: stdout={}, stderr={}",
String::from_utf8_lossy(&order_3.stdout),
String::from_utf8_lossy(&order_3.stderr),
);
let stdout_3 = String::from_utf8_lossy(&order_3.stdout).to_string();
let parsed_3: serde_json::Value = serde_json::from_str(&stdout_3)
.unwrap_or_else(|e| panic!("order 3 expected JSON, got: {stdout_3}\nparse error: {e}"));
assert_json(&parsed_3, "order 3 (`graph --format json <sub> ...`)");
}
#[test]
fn provenance_honors_all_three_invocation_orders() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
assert_all_three_orders_produce_json(
temp.path(),
&["provenance", "helper"],
|parsed, label| {
assert!(
parsed.get("fact_epoch").is_some(),
"{label}: provenance JSON should expose `fact_epoch`, got {parsed}"
);
assert!(
parsed.get("nodes").is_some(),
"{label}: provenance JSON should expose `nodes`, got {parsed}"
);
},
);
}
#[test]
fn resolve_honors_all_three_invocation_orders() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
assert_all_three_orders_produce_json(temp.path(), &["resolve", "helper"], |parsed, label| {
assert!(
parsed.is_object() || parsed.is_array(),
"{label}: resolve output should be a JSON value, got {parsed}"
);
});
}
#[test]
fn status_honors_all_three_invocation_orders() {
let temp = TempDir::new().unwrap();
write_simple_callers_fixture(temp.path());
index(temp.path());
assert_all_three_orders_produce_json(temp.path(), &["status"], |parsed, label| {
assert!(
parsed.is_object(),
"{label}: status output should be a JSON object, got {parsed}"
);
});
}
#[test]
fn provenance_conflicting_format_text_and_json_errors_loudly() {
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("text")
.arg("provenance")
.arg("helper")
.arg("--json")
.output()
.expect("command failed");
assert!(
!output.status.success(),
"expected nonzero exit, got success. stdout={}, stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let combined = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
assert!(
combined.contains("--json") && combined.contains("--format"),
"diagnostic must name both flags: {combined}"
);
}
#[test]
fn conflicting_format_text_and_json_errors_loudly() {
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("text")
.arg("direct-callers")
.arg("helper")
.arg("--json")
.output()
.expect("command failed");
assert!(
!output.status.success(),
"expected nonzero exit, got success. stdout={}, stderr={}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
let combined = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
assert!(
combined.contains("--json"),
"diagnostic must name --json: {combined}"
);
assert!(
combined.contains("--format"),
"diagnostic must name --format: {combined}"
);
}