use assert_cmd::Command;
use predicates::str::contains;
use std::path::Path;
use tempfile::TempDir;
const JAVA_FIXTURE: &str = "tests/fixtures/java-simple";
fn copy_dir_all(src: &Path, dest: &Path) {
std::fs::create_dir_all(dest).unwrap();
for entry in std::fs::read_dir(src).unwrap() {
let entry = entry.unwrap();
let src_path = entry.path();
let dest_path = dest.join(entry.file_name());
if src_path.is_dir() {
copy_dir_all(&src_path, &dest_path);
} else {
std::fs::copy(&src_path, &dest_path).unwrap();
}
}
}
fn setup_java_fixture() -> TempDir {
let dir = TempDir::new().unwrap();
let fixture = Path::new(JAVA_FIXTURE);
copy_dir_all(fixture, dir.path());
dir
}
fn sc_init(dir: &Path) -> assert_cmd::assert::Assert {
Command::cargo_bin("scope")
.unwrap()
.arg("init")
.current_dir(dir)
.assert()
}
fn sc_index_full(dir: &Path) -> assert_cmd::assert::Assert {
Command::cargo_bin("scope")
.unwrap()
.args(["index", "--full"])
.current_dir(dir)
.assert()
}
fn indexed_java_fixture_db() -> (rusqlite::Connection, TempDir) {
let dir = setup_java_fixture();
sc_init(dir.path()).success();
sc_index_full(dir.path()).success();
let db_path = dir.path().join(".scope").join("graph.db");
let conn = rusqlite::Connection::open(&db_path).unwrap();
(conn, dir)
}
#[test]
fn test_init_detects_java_from_pom_xml() {
let dir = TempDir::new().unwrap();
std::fs::write(
dir.path().join("pom.xml"),
"<project><modelVersion>4.0.0</modelVersion></project>",
)
.unwrap();
sc_init(dir.path()).success().stdout(contains("Java"));
}
#[test]
fn test_init_detects_java_from_build_gradle() {
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("build.gradle"), "apply plugin: 'java'").unwrap();
sc_init(dir.path()).success().stdout(contains("Java"));
}
#[test]
fn test_init_detects_java_from_build_gradle_kts() {
let dir = TempDir::new().unwrap();
std::fs::write(dir.path().join("build.gradle.kts"), "plugins { java }").unwrap();
sc_init(dir.path()).success().stdout(contains("Java"));
}
#[test]
fn test_init_detects_java_from_src_main_java_dir() {
let dir = TempDir::new().unwrap();
std::fs::create_dir_all(dir.path().join("src/main/java")).unwrap();
sc_init(dir.path()).success().stdout(contains("Java"));
}
#[test]
fn test_index_full_on_java_fixture() {
let dir = setup_java_fixture();
sc_init(dir.path()).success();
sc_index_full(dir.path())
.success()
.stderr(contains("files"))
.stderr(contains("symbols"));
let graph_db = dir.path().join(".scope").join("graph.db");
assert!(graph_db.exists(), "graph.db should exist after indexing");
assert!(
graph_db.metadata().unwrap().len() > 0,
"graph.db should not be empty"
);
}
#[test]
fn test_index_detects_java_classes() {
let (conn, _dir) = indexed_java_fixture_db();
let symbol_exists = |name: &str, kind: &str| -> bool {
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM symbols WHERE name = ?1 AND kind = ?2",
rusqlite::params![name, kind],
|row| row.get(0),
)
.unwrap();
count > 0
};
assert!(
symbol_exists("PaymentService", "class"),
"PaymentService class should be indexed"
);
assert!(
symbol_exists("OrderController", "class"),
"OrderController class should be indexed"
);
assert!(
symbol_exists("Logger", "class"),
"Logger class should be indexed"
);
assert!(
symbol_exists("PaymentException", "class"),
"PaymentException class should be indexed"
);
}
#[test]
fn test_index_detects_java_interfaces() {
let (conn, _dir) = indexed_java_fixture_db();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM symbols WHERE name = 'IPaymentClient' AND kind = 'interface'",
[],
|row| row.get(0),
)
.unwrap();
assert!(count > 0, "IPaymentClient interface should be indexed");
}
#[test]
fn test_index_detects_java_enums() {
let (conn, _dir) = indexed_java_fixture_db();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM symbols WHERE name = 'PaymentResult' AND kind = 'enum'",
[],
|row| row.get(0),
)
.unwrap();
assert!(count > 0, "PaymentResult enum should be indexed");
}
#[test]
fn test_index_detects_java_methods() {
let (conn, _dir) = indexed_java_fixture_db();
let symbol_exists = |name: &str, kind: &str| -> bool {
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM symbols WHERE name = ?1 AND kind = ?2",
rusqlite::params![name, kind],
|row| row.get(0),
)
.unwrap();
count > 0
};
assert!(
symbol_exists("processPayment", "method"),
"processPayment method should be indexed"
);
assert!(
symbol_exists("refund", "method"),
"refund method should be indexed"
);
assert!(
symbol_exists("createOrder", "method"),
"createOrder method should be indexed"
);
assert!(
symbol_exists("info", "method"),
"info method should be indexed"
);
}
#[test]
fn test_index_detects_java_import_edges() {
let (conn, _dir) = indexed_java_fixture_db();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM edges WHERE kind = 'imports'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
count > 0,
"Java fixture should have 'imports' edges; got {count}"
);
}
#[test]
fn test_index_detects_java_call_edges() {
let (conn, _dir) = indexed_java_fixture_db();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM edges WHERE kind = 'calls'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
count > 0,
"Java fixture should have 'calls' edges; got {count}"
);
}
#[test]
fn test_index_detects_java_implements_edges() {
let (conn, _dir) = indexed_java_fixture_db();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM edges WHERE kind = 'implements'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
count > 0,
"Java fixture should have 'implements' edges (PaymentService implements IPaymentClient); got {count}"
);
}
#[test]
fn test_index_detects_java_extends_edges() {
let (conn, _dir) = indexed_java_fixture_db();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM edges WHERE kind = 'extends'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
count > 0,
"Java fixture should have 'extends' edges (PaymentException extends Exception); got {count}"
);
}
#[test]
fn test_index_java_metadata_has_access_modifiers() {
let (conn, _dir) = indexed_java_fixture_db();
let metadata: String = conn
.query_row(
"SELECT metadata FROM symbols WHERE name = 'PaymentService' AND kind = 'class' LIMIT 1",
[],
|row| row.get(0),
)
.unwrap();
assert!(
metadata.contains("\"access\":\"public\""),
"PaymentService should have public access; got: {metadata}"
);
}
#[test]
fn test_index_java_metadata_has_annotations() {
let (conn, _dir) = indexed_java_fixture_db();
let metadata: String = conn
.query_row(
"SELECT metadata FROM symbols WHERE name = 'refund' AND kind = 'method' LIMIT 1",
[],
|row| row.get(0),
)
.unwrap();
assert!(
metadata.contains("Deprecated"),
"refund method should have @Deprecated annotation; got: {metadata}"
);
}
#[test]
fn test_index_java_metadata_has_static() {
let (conn, _dir) = indexed_java_fixture_db();
let metadata: String = conn
.query_row(
"SELECT metadata FROM symbols WHERE name = 'getTransactionCount' AND kind = 'method' LIMIT 1",
[],
|row| row.get(0),
)
.unwrap();
assert!(
metadata.contains("\"is_static\":true"),
"getTransactionCount should be marked as static; got: {metadata}"
);
}
#[test]
fn test_index_java_metadata_has_synchronized() {
let (conn, _dir) = indexed_java_fixture_db();
let metadata: String = conn
.query_row(
"SELECT metadata FROM symbols WHERE name = 'refund' AND kind = 'method' LIMIT 1",
[],
|row| row.get(0),
)
.unwrap();
assert!(
metadata.contains("\"is_synchronized\":true"),
"refund should be marked as synchronized; got: {metadata}"
);
}
#[test]
fn test_index_java_metadata_package_access_default() {
let (conn, _dir) = indexed_java_fixture_db();
let metadata: String = conn
.query_row(
"SELECT metadata FROM symbols WHERE name = 'getTransactionCount' AND kind = 'method' LIMIT 1",
[],
|row| row.get(0),
)
.unwrap();
assert!(
metadata.contains("\"access\":\"package\""),
"getTransactionCount should have package access; got: {metadata}"
);
}
#[test]
fn test_index_java_symbol_count_is_reasonable() {
let (conn, _dir) = indexed_java_fixture_db();
let total: i64 = conn
.query_row("SELECT COUNT(*) FROM symbols", [], |row| row.get(0))
.unwrap();
assert!(
total >= 15,
"expected at least 15 symbols from Java fixture; got {total}"
);
}
#[test]
fn test_sketch_java_class() {
let dir = setup_java_fixture();
sc_init(dir.path()).success();
sc_index_full(dir.path()).success();
Command::cargo_bin("scope")
.unwrap()
.args(["sketch", "PaymentService"])
.current_dir(dir.path())
.assert()
.success()
.stdout(contains("PaymentService"));
}
#[test]
fn test_sketch_java_class_json() {
let dir = setup_java_fixture();
sc_init(dir.path()).success();
sc_index_full(dir.path()).success();
let output = Command::cargo_bin("scope")
.unwrap()
.args(["sketch", "PaymentService", "--json"])
.current_dir(dir.path())
.output()
.unwrap();
assert!(output.status.success());
let json: serde_json::Value =
serde_json::from_slice(&output.stdout).expect("Output should be valid JSON");
assert_eq!(json["command"], "sketch");
}
#[test]
fn test_sketch_java_shows_annotations_on_methods() {
let dir = setup_java_fixture();
sc_init(dir.path()).success();
sc_index_full(dir.path()).success();
let output = Command::cargo_bin("scope")
.unwrap()
.args(["sketch", "PaymentService"])
.current_dir(dir.path())
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("@Override"),
"Sketch should show @Override annotation on processPayment. Got:\n{stdout}"
);
assert!(
stdout.contains("@Deprecated"),
"Sketch should show @Deprecated annotation on refund. Got:\n{stdout}"
);
}
#[test]
fn test_refs_finds_java_callers() {
let dir = setup_java_fixture();
sc_init(dir.path()).success();
sc_index_full(dir.path()).success();
Command::cargo_bin("scope")
.unwrap()
.args(["refs", "processPayment"])
.current_dir(dir.path())
.assert()
.success();
}
#[test]
fn test_index_detects_java_enum_variants() {
let (conn, _dir) = indexed_java_fixture_db();
let variants: Vec<String> = {
let mut stmt = conn
.prepare("SELECT name FROM symbols WHERE kind = 'variant' ORDER BY name")
.unwrap();
stmt.query_map([], |row| row.get(0))
.unwrap()
.filter_map(|r| r.ok())
.collect()
};
assert!(
variants.contains(&"SUCCESS".to_string()),
"SUCCESS variant should be indexed; found: {variants:?}"
);
assert!(
variants.contains(&"FAILED".to_string()),
"FAILED variant should be indexed; found: {variants:?}"
);
assert!(
variants.contains(&"PENDING".to_string()),
"PENDING variant should be indexed; found: {variants:?}"
);
}
#[test]
fn test_java_enum_variant_has_parent_id() {
let (conn, _dir) = indexed_java_fixture_db();
let parent_name: String = conn
.query_row(
"SELECT p.name FROM symbols v
JOIN symbols p ON v.parent_id = p.id
WHERE v.name = 'SUCCESS' AND v.kind = 'variant'
LIMIT 1",
[],
|row| row.get(0),
)
.unwrap();
assert_eq!(
parent_name, "PaymentResult",
"SUCCESS variant should have PaymentResult as parent"
);
}
#[test]
fn test_java_this_method_call_edge_detected() {
let (conn, _dir) = indexed_java_fixture_db();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM edges
WHERE (to_id = 'calculateFee' OR to_id LIKE '%::calculateFee') AND kind = 'calls'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
count > 0,
"this.calculateFee() call should generate a 'calls' edge with to_id='calculateFee'; got {count}"
);
}
#[test]
fn test_java_super_method_call_edge_detected() {
let (conn, _dir) = indexed_java_fixture_db();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM edges e
JOIN symbols s ON e.to_id = s.id
WHERE s.name = 'validate' AND e.kind = 'calls'",
[],
|row| row.get(0),
)
.unwrap_or(0);
let total_calls: i64 = conn
.query_row(
"SELECT COUNT(*) FROM edges WHERE kind = 'calls'",
[],
|row| row.get(0),
)
.unwrap();
let _ = count;
assert!(
total_calls > 0,
"Java fixture should have 'calls' edges including super.method() calls; got {total_calls}"
);
}
#[test]
fn test_java_switch_case_variant_ref_edge_detected() {
let (conn, _dir) = indexed_java_fixture_db();
let count: i64 = conn
.query_row(
"SELECT COUNT(*) FROM edges WHERE kind = 'references'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
count > 0,
"Java switch case labels should generate 'references' edges; got {count}"
);
let success_ref: i64 = conn
.query_row(
"SELECT COUNT(*) FROM edges WHERE kind = 'references' AND to_id = 'SUCCESS'",
[],
|row| row.get(0),
)
.unwrap();
assert!(
success_ref > 0,
"switch case `case SUCCESS:` should generate a 'references' edge with to_id='SUCCESS'; got {success_ref}"
);
}