mod common;
use common::sqry_bin;
use assert_cmd::Command;
use predicates::prelude::*;
use std::fs;
use std::path::Path;
use tempfile::TempDir;
fn sqry_cmd() -> Command {
Command::new(sqry_bin())
}
fn copy_dir_recursive(src: &Path, dst: &Path) {
fs::create_dir_all(dst).expect("create destination directory");
for entry in fs::read_dir(src).expect("read source directory") {
let entry = entry.expect("read dir entry");
let src_path = entry.path();
let dst_path = dst.join(entry.file_name());
if src_path.is_dir() {
copy_dir_recursive(&src_path, &dst_path);
} else {
fs::copy(&src_path, &dst_path).expect("copy file");
}
}
}
fn setup_fixture(fixture_name: &str) -> TempDir {
let dir = TempDir::new().expect("create temp dir");
let src = Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.expect("workspace root")
.join("test-fixtures/e2e-scenarios")
.join(fixture_name);
copy_dir_recursive(&src, dir.path());
dir
}
#[test]
fn scenario_index_then_query() {
let project = setup_fixture("multi-lang");
sqry_cmd()
.arg("index")
.arg(project.path())
.assert()
.success();
assert!(
project.path().join(".sqry/graph/snapshot.sqry").exists(),
"graph snapshot must exist after index"
);
sqry_cmd()
.arg("query")
.arg("kind:function")
.arg(project.path())
.assert()
.success()
.stdout(predicate::str::contains("process").or(predicate::str::contains("helper")));
sqry_cmd()
.arg("query")
.arg("kind:class")
.arg(project.path())
.assert()
.success()
.stdout(
predicate::str::contains("RequestHandler").or(predicate::str::contains("Formatter")),
);
}
#[test]
fn scenario_incremental_reindex() {
let project = setup_fixture("multi-lang");
sqry_cmd()
.arg("index")
.arg(project.path())
.assert()
.success();
let lib_path = project.path().join("src/lib.rs");
let mut content = fs::read_to_string(&lib_path).expect("read lib.rs");
content.push_str("\npub fn extra() -> i32 { 99 }\n");
fs::write(&lib_path, &content).expect("write lib.rs");
sqry_cmd()
.arg("index")
.arg("--force")
.arg(project.path())
.assert()
.success();
sqry_cmd()
.arg("query")
.arg("name:extra")
.arg(project.path())
.assert()
.success()
.stdout(predicate::str::contains("extra"));
}
#[test]
fn scenario_multi_format_output() {
let project = setup_fixture("multi-lang");
sqry_cmd()
.arg("index")
.arg(project.path())
.assert()
.success();
let json_out = sqry_cmd()
.arg("--json")
.arg("query")
.arg("kind:function")
.arg(project.path())
.output()
.expect("run sqry --json query");
assert!(json_out.status.success(), "JSON query must succeed");
let stdout_str = String::from_utf8_lossy(&json_out.stdout);
serde_json::from_str::<serde_json::Value>(&stdout_str)
.expect("--json output must be valid JSON");
sqry_cmd()
.arg("--csv")
.arg("--headers")
.arg("query")
.arg("kind:function")
.arg(project.path())
.assert()
.success()
.stdout(
predicate::str::contains("name")
.or(predicate::str::contains("kind"))
.or(predicate::str::contains("file")),
);
sqry_cmd()
.arg("query")
.arg("kind:function")
.arg(project.path())
.assert()
.success()
.stdout(predicate::str::contains("process").or(predicate::str::contains("helper")));
}
#[test]
fn scenario_graph_analysis() {
let project = setup_fixture("multi-lang");
sqry_cmd()
.arg("index")
.arg(project.path())
.assert()
.success();
let nodes_out = sqry_cmd()
.arg("graph")
.arg("--path")
.arg(project.path())
.arg("--format")
.arg("json")
.arg("nodes")
.output()
.expect("run sqry graph nodes");
assert!(nodes_out.status.success(), "graph nodes must succeed");
let nodes_json: serde_json::Value =
serde_json::from_slice(&nodes_out.stdout).expect("graph nodes output must be JSON");
assert!(
nodes_json.get("count").is_some(),
"graph nodes JSON must have 'count'"
);
let count = nodes_json["count"].as_u64().unwrap_or(0);
assert!(count > 0, "indexed project must have at least one node");
let edges_out = sqry_cmd()
.arg("graph")
.arg("--path")
.arg(project.path())
.arg("--format")
.arg("json")
.arg("edges")
.output()
.expect("run sqry graph edges");
assert!(edges_out.status.success(), "graph edges must succeed");
let edges_json: serde_json::Value =
serde_json::from_slice(&edges_out.stdout).expect("graph edges output must be JSON");
assert!(
edges_json.get("count").is_some(),
"graph edges JSON must have 'count'"
);
}
#[test]
fn scenario_config_workflow() {
let project = setup_fixture("multi-lang");
sqry_cmd()
.arg("config")
.arg("init")
.arg("--path")
.arg(project.path())
.assert()
.success()
.stdout(predicate::str::contains("Config initialized"));
sqry_cmd()
.arg("config")
.arg("show")
.arg("--path")
.arg(project.path())
.assert()
.success()
.stdout(
predicate::str::contains("Schema version")
.or(predicate::str::contains("Limits"))
.or(predicate::str::contains("max_results")),
);
let json_out = sqry_cmd()
.arg("config")
.arg("show")
.arg("--path")
.arg(project.path())
.arg("--json")
.output()
.expect("run config show --json");
assert!(json_out.status.success(), "config show --json must succeed");
let config_json: serde_json::Value =
serde_json::from_slice(&json_out.stdout).expect("config show --json must be valid JSON");
assert_eq!(config_json["schema_version"], 1, "schema_version must be 1");
}
#[test]
fn scenario_error_handling_nonexistent_path() {
sqry_cmd()
.arg("index")
.arg("/nonexistent/e2e-scenario-path-that-will-never-exist")
.assert()
.failure()
.stderr(
predicate::str::contains("Error")
.or(predicate::str::contains("error"))
.or(predicate::str::contains("failed"))
.or(predicate::str::contains("not found"))
.or(predicate::str::contains("No such")),
);
}
#[test]
fn scenario_error_handling_empty_dir() {
let empty = TempDir::new().expect("create empty temp dir");
let status = sqry_cmd()
.arg("index")
.arg(empty.path())
.output()
.expect("run sqry index on empty dir");
assert!(
status.status.code().is_some(),
"process must exit cleanly, not via signal"
);
}
#[test]
fn scenario_search_workflows() {
let project = setup_fixture("multi-lang");
sqry_cmd()
.arg("index")
.arg(project.path())
.assert()
.success();
sqry_cmd()
.arg("query")
.arg("name:process AND kind:function")
.arg(project.path())
.assert()
.success()
.stdout(predicate::str::contains("process"));
sqry_cmd()
.arg("query")
.arg("kind:class")
.arg(project.path())
.assert()
.success()
.stdout(
predicate::str::contains("RequestHandler").or(predicate::str::contains("Formatter")),
);
sqry_cmd()
.arg("query")
.arg("kind:function OR kind:class")
.arg(project.path())
.assert()
.success()
.stdout(predicate::str::is_empty().not());
}
#[test]
fn scenario_analyze_workflow() {
let project = setup_fixture("multi-lang");
sqry_cmd()
.arg("index")
.arg(project.path())
.assert()
.success();
sqry_cmd()
.arg("analyze")
.arg(project.path())
.assert()
.success();
let analysis_dir = project.path().join(".sqry/analysis");
assert!(
analysis_dir.exists(),
"analysis directory must exist after sqry analyze"
);
}