use super::support::*;
#[test]
fn index_resolves_cross_file_local_python_imports() {
let Some(env) = StandaloneEnv::from_env() else {
eprintln!(
"skipping cross-file local-import resolution; set GCODE_GRAPH_STANDALONE_DATABASE_URL, GCODE_GRAPH_STANDALONE_FALKOR_HOST, and GCODE_GRAPH_STANDALONE_FALKOR_PORT"
);
return;
};
let project = tempfile::tempdir().expect("temp project");
fs::create_dir_all(project.path().join(".gobby")).expect("create .gobby");
fs::write(
project.path().join("a.py"),
"def recluster_project_entities():\n pass\n\n\nclass Service:\n pass\n",
)
.expect("write a.py");
fs::write(
project.path().join("b.py"),
"from a import recluster_project_entities\n\
from a import Service as Svc\n\n\n\
def caller():\n recluster_project_entities()\n Svc()\n",
)
.expect("write b.py");
fs::write(
project.path().join(".gobby/gcode.json"),
serde_json::json!({
"id": LOCAL_IMPORT_PROJECT_ID,
"name": "graph-standalone-local-import",
"created_at": "2026-06-15T00:00:00Z"
})
.to_string(),
)
.expect("write gcode identity");
let mut conn = Client::connect(&env.database_url, NoTls).expect("connect PostgreSQL");
let _cleanup = ProjectCleanup::new(&env.database_url, LOCAL_IMPORT_PROJECT_ID);
let indexed = run_gcode(&env, project.path(), &["index", "--full"]);
assert_success(indexed, "gcode index --full");
let recluster_id = required_symbol_id(
&mut conn,
LOCAL_IMPORT_PROJECT_ID,
"a.py",
"recluster_project_entities",
);
let service_id = required_symbol_id(&mut conn, LOCAL_IMPORT_PROJECT_ID, "a.py", "Service");
assert_eq!(
resolved_call_target(
&mut conn,
LOCAL_IMPORT_PROJECT_ID,
"b.py",
"recluster_project_entities"
),
Some(recluster_id),
"cross-file function call did not resolve to the canonical symbol"
);
assert_eq!(
resolved_call_target(&mut conn, LOCAL_IMPORT_PROJECT_ID, "b.py", "Service"),
Some(service_id),
"aliased cross-file constructor did not resolve to the class symbol"
);
assert_eq!(
pending_local_import_count(&mut conn, LOCAL_IMPORT_PROJECT_ID),
0,
"no local_import rows should survive a completed index run"
);
let rebuilt = json_command(&env, project.path(), &["graph", "rebuild"]);
assert_eq!(rebuilt["success"], true);
assert_caller_present(
&env,
project.path(),
"recluster_project_entities",
"caller",
"after rebuild",
);
let blast = json_command(
&env,
project.path(),
&["blast-radius", "recluster_project_entities"],
);
assert_blast_radius_reports_affected_callers(&blast);
let sync = run_gcode(
&env,
project.path(),
&["graph", "sync-file", "--file", "b.py"],
);
assert_success(sync, "graph sync-file b.py");
assert_caller_present(
&env,
project.path(),
"recluster_project_entities",
"caller",
"after sync-file",
);
}
#[test]
fn index_resolves_cross_package_local_go_calls() {
let Some(env) = StandaloneEnv::from_env() else {
eprintln!(
"skipping cross-package Go local-call resolution; set GCODE_GRAPH_STANDALONE_DATABASE_URL, GCODE_GRAPH_STANDALONE_FALKOR_HOST, and GCODE_GRAPH_STANDALONE_FALKOR_PORT"
);
return;
};
let project = tempfile::tempdir().expect("temp project");
fs::create_dir_all(project.path().join(".gobby")).expect("create .gobby");
fs::create_dir_all(project.path().join("pkg/store")).expect("create pkg/store");
fs::create_dir_all(project.path().join("cmd/app")).expect("create cmd/app");
fs::write(project.path().join("go.mod"), "module example.com/app\n").expect("write go.mod");
fs::write(
project.path().join("pkg/store/store.go"),
"package store\n\ntype Record struct{}\n",
)
.expect("write store.go");
fs::write(
project.path().join("pkg/store/query.go"),
"package store\n\nfunc Load() {}\n",
)
.expect("write query.go");
fs::write(
project.path().join("cmd/app/main.go"),
"package main\n\nimport \"example.com/app/pkg/store\"\n\nfunc Run() {\n store.Load()\n}\n",
)
.expect("write main.go");
fs::write(
project.path().join(".gobby/gcode.json"),
serde_json::json!({
"id": GO_LOCAL_PROJECT_ID,
"name": "graph-standalone-go-local",
"created_at": "2026-06-15T00:00:00Z"
})
.to_string(),
)
.expect("write gcode identity");
let mut conn = Client::connect(&env.database_url, NoTls).expect("connect PostgreSQL");
let _cleanup = ProjectCleanup::new(&env.database_url, GO_LOCAL_PROJECT_ID);
let indexed = run_gcode(&env, project.path(), &["index", "--full"]);
assert_success(indexed, "gcode index --full");
let load_id = required_symbol_id(&mut conn, GO_LOCAL_PROJECT_ID, "pkg/store/query.go", "Load");
assert_eq!(
resolved_call_target(&mut conn, GO_LOCAL_PROJECT_ID, "cmd/app/main.go", "Load"),
Some(load_id.clone()),
"cross-package Go selector call did not resolve to the canonical symbol"
);
assert_eq!(
pending_local_import_count(&mut conn, GO_LOCAL_PROJECT_ID),
0,
"no local_import rows should survive a completed index run"
);
let rebuilt = json_command(&env, project.path(), &["graph", "rebuild"]);
assert_eq!(rebuilt["success"], true);
assert_caller_present(&env, project.path(), "Load", "Run", "after rebuild");
let mut graph = phantom_graph_client(&env);
assert_eq!(
phantom_call_target_count(&mut graph, GO_LOCAL_PROJECT_ID),
0,
"no CALLS target may lack a DEFINES edge after rebuild"
);
assert!(
resolved_target_is_defined_and_called(&mut graph, GO_LOCAL_PROJECT_ID, &load_id),
"the resolved Go target must be a defined, called CodeSymbol"
);
let blast = json_command(&env, project.path(), &["blast-radius", "Load"]);
assert_blast_radius_reports_affected_callers(&blast);
let sync = run_gcode(
&env,
project.path(),
&["graph", "sync-file", "--file", "cmd/app/main.go"],
);
assert_success(sync, "graph sync-file cmd/app/main.go");
assert_caller_present(&env, project.path(), "Load", "Run", "after sync-file");
assert_eq!(
phantom_call_target_count(&mut graph, GO_LOCAL_PROJECT_ID),
0,
"sync-file must not introduce a phantom CALLS target"
);
}
#[test]
fn index_resolves_cross_file_local_java_calls() {
let Some(env) = StandaloneEnv::from_env() else {
eprintln!(
"skipping cross-file Java local-call resolution; set GCODE_GRAPH_STANDALONE_DATABASE_URL, GCODE_GRAPH_STANDALONE_FALKOR_HOST, and GCODE_GRAPH_STANDALONE_FALKOR_PORT"
);
return;
};
let project = tempfile::tempdir().expect("temp project");
fs::create_dir_all(project.path().join(".gobby")).expect("create .gobby");
fs::create_dir_all(project.path().join("src/main/java/app/svc")).expect("create app/svc");
fs::write(
project.path().join("src/main/java/app/svc/Service.java"),
"package app.svc;\n\nclass Service {\n static void start() {}\n}\n",
)
.expect("write Service.java");
fs::write(
project.path().join("src/main/java/app/Main.java"),
"package app;\n\nimport app.svc.Service;\n\nclass Main {\n void run() {\n Service.start();\n new Service();\n }\n}\n",
)
.expect("write Main.java");
fs::write(
project.path().join(".gobby/gcode.json"),
serde_json::json!({
"id": JAVA_LOCAL_PROJECT_ID,
"name": "graph-standalone-java-local",
"created_at": "2026-06-15T00:00:00Z"
})
.to_string(),
)
.expect("write gcode identity");
let mut conn = Client::connect(&env.database_url, NoTls).expect("connect PostgreSQL");
let _cleanup = ProjectCleanup::new(&env.database_url, JAVA_LOCAL_PROJECT_ID);
let indexed = run_gcode(&env, project.path(), &["index", "--full"]);
assert_success(indexed, "gcode index --full");
let service_file = "src/main/java/app/svc/Service.java";
let main_file = "src/main/java/app/Main.java";
let start_id = required_symbol_id(&mut conn, JAVA_LOCAL_PROJECT_ID, service_file, "start");
let service_id = required_symbol_id(&mut conn, JAVA_LOCAL_PROJECT_ID, service_file, "Service");
assert_eq!(
resolved_call_target(&mut conn, JAVA_LOCAL_PROJECT_ID, main_file, "start"),
Some(start_id.clone()),
"Java static member call did not resolve to the canonical method"
);
assert_eq!(
resolved_call_target(&mut conn, JAVA_LOCAL_PROJECT_ID, main_file, "Service"),
Some(service_id.clone()),
"Java constructor call did not resolve to the canonical class"
);
assert_eq!(
pending_local_import_count(&mut conn, JAVA_LOCAL_PROJECT_ID),
0,
"no local_import rows should survive a completed index run"
);
let rebuilt = json_command(&env, project.path(), &["graph", "rebuild"]);
assert_eq!(rebuilt["success"], true);
assert_caller_present(&env, project.path(), "start", "run", "after rebuild");
assert_caller_present(&env, project.path(), "Service", "run", "after rebuild");
let mut graph = phantom_graph_client(&env);
assert_eq!(
phantom_call_target_count(&mut graph, JAVA_LOCAL_PROJECT_ID),
0,
"no CALLS target may lack a DEFINES edge after rebuild"
);
assert!(
resolved_target_is_defined_and_called(&mut graph, JAVA_LOCAL_PROJECT_ID, &start_id),
"the resolved Java method target must be a defined, called CodeSymbol"
);
assert!(
resolved_target_is_defined_and_called(&mut graph, JAVA_LOCAL_PROJECT_ID, &service_id),
"the resolved Java class target must be a defined, called CodeSymbol"
);
let blast = json_command(&env, project.path(), &["blast-radius", "start"]);
assert_blast_radius_reports_affected_callers(&blast);
let sync = run_gcode(
&env,
project.path(),
&["graph", "sync-file", "--file", main_file],
);
assert_success(sync, "graph sync-file Main.java");
assert_caller_present(&env, project.path(), "start", "run", "after sync-file");
assert_eq!(
phantom_call_target_count(&mut graph, JAVA_LOCAL_PROJECT_ID),
0,
"sync-file must not introduce a phantom CALLS target"
);
}