mod common;
use common::sqry_bin;
use assert_cmd::Command;
use serde_json::Value;
use std::fs;
use std::io;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
fn sqry_cmd() -> Command {
let path = sqry_bin();
Command::new(path)
}
fn index_project(path: &Path) {
sqry_cmd()
.arg("index")
.arg("--force")
.arg(path)
.assert()
.success();
}
fn copy_fixture_dir(relative: &str) -> TempDir {
let binding = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let root = binding.parent().expect("workspace root");
let source = root.join(relative);
assert!(
source.exists(),
"fixture directory {} not found",
source.display()
);
let temp = TempDir::new().expect("create temp dir");
copy_dir_all(&source, temp.path()).expect("copy fixture into temp dir");
temp
}
fn copy_dir_all(src: &Path, dst: &Path) -> io::Result<()> {
fs::create_dir_all(dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
let dest_path = dst.join(entry.file_name());
if ty.is_dir() {
copy_dir_all(&entry.path(), &dest_path)?;
} else if ty.is_file() {
fs::copy(entry.path(), dest_path)?;
}
}
Ok(())
}
#[test]
fn polyglot_project_extracts_nodes_from_multiple_languages() {
let project = copy_fixture_dir("tests/fixtures/polyglot_micro");
index_project(project.path());
let output = sqry_cmd()
.arg("graph")
.arg("--path")
.arg(project.path())
.arg("--format")
.arg("json")
.arg("stats")
.arg("--by-language")
.arg("--by-file")
.output()
.expect("run sqry graph stats");
assert!(
output.status.success(),
"graph stats command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stats: Value =
serde_json::from_slice(&output.stdout).expect("stats output should be valid JSON");
let language_summary = stats["nodes_by_language"]
.as_object()
.expect("nodes_by_language map missing");
assert!(
language_summary
.keys()
.any(|lang| lang.eq("Rust") || lang.eq("RUST")),
"expected Rust nodes in language summary, got {language_summary:?}"
);
assert!(
language_summary
.keys()
.any(|lang| lang.eq("JavaScript") || lang.eq("JAVASCRIPT") || lang.eq("Js")),
"expected JavaScript nodes in language summary, got {language_summary:?}"
);
assert!(
language_summary
.keys()
.any(|lang| lang.eq("Sql") || lang.eq("SQL")),
"expected SQL nodes in language summary, got {language_summary:?}"
);
assert!(
language_summary
.keys()
.any(|lang| lang.eq("Dart") || lang.eq("DART")),
"expected Dart nodes in language summary, got {language_summary:?}"
);
let dart_count = language_summary
.get("Dart")
.or_else(|| language_summary.get("DART"))
.and_then(serde_json::Value::as_u64)
.unwrap_or(0);
assert!(
dart_count >= 2,
"expected at least 2 Dart nodes (Counter + HelloWidget), found {dart_count}"
);
let file_summary = stats["nodes_by_file"]
.as_object()
.expect("nodes_by_file map missing");
assert!(
file_summary.keys().any(|file| std::path::Path::new(file)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("rs"))),
"expected Rust file (.rs) in nodes_by_file summary"
);
assert!(
file_summary.keys().any(|file| std::path::Path::new(file)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("js"))),
"expected JavaScript file (.js) in nodes_by_file summary"
);
assert!(
file_summary.keys().any(|file| std::path::Path::new(file)
.extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("sql"))),
"expected SQL file (.sql) in nodes_by_file summary"
);
let total_nodes = stats["node_count"]
.as_u64()
.expect("node_count should be a number");
assert!(
total_nodes >= 3,
"expected at least 3 total nodes (Rust+JS+SQL), got {total_nodes}"
);
}