use std::path::Path;
use std::process::Command;
mod common;
fn write_fixture(root: &Path) {
std::fs::create_dir_all(root).unwrap();
std::fs::write(
root.join("package.json"),
"{\"name\":\"resolve-fixture\",\"version\":\"1.0.0\",\"dependencies\":{\"express\":\"^4.0.0\"}}\n",
)
.unwrap();
std::fs::write(
root.join("tsconfig.json"),
"{\"compilerOptions\":{\"module\":\"commonjs\",\"target\":\"es2020\",\"esModuleInterop\":true}}\n",
)
.unwrap();
std::fs::write(
root.join("server.ts"),
r#"import express from "express";
import { db } from "./db";
const app = express();
/** Fetch the user rows through the shared db client. */
export async function handleList(): Promise<Array<{ id: string }>> {
const rows = await db.users.findMany();
return rows;
}
app.get("/api/users", handleList);
export default app;
"#,
)
.unwrap();
std::fs::write(
root.join("db.ts"),
r#"export const db = {
users: {
findMany: async () => [] as Array<{ id: string }>,
},
};
"#,
)
.unwrap();
}
fn bench_json(root: &Path, corpus: &Path, gt: &Path, extra: &[&str]) -> serde_json::Value {
let mut args: Vec<String> = vec![
"bench".into(),
"atlas".into(),
"--json".into(),
"--corpus".into(),
corpus.to_str().unwrap().into(),
"--ground-truth".into(),
gt.to_str().unwrap().into(),
];
args.extend(extra.iter().map(|s| s.to_string()));
let out = Command::new(common::scc())
.args(&args)
.current_dir(root)
.output()
.expect("scc bench atlas runs");
assert!(
out.status.success(),
"`scc bench atlas` failed: {}",
String::from_utf8_lossy(&out.stderr)
);
serde_json::from_slice(&out.stdout).expect("bench JSON output")
}
#[test]
fn resolve_seeds_behavior_flows_and_reports_resolved_calls() {
let tsserver_available = Command::new("typescript-language-server")
.arg("--version")
.output()
.map(|o| o.status.success())
.unwrap_or(false);
if !tsserver_available {
eprintln!("typescript-language-server not installed; skipping resolve assertions");
}
let tmp = tempfile::TempDir::new().unwrap();
let fixture_src = tmp.path().join("fixture-src");
write_fixture(&fixture_src);
let gt = tmp.path().join("ground-truth");
std::fs::create_dir_all(>).unwrap();
std::fs::write(
gt.join("resolve-fixture.md"),
"## architecture\n- root\n## entrypoints\n- handleList\n## behavior\n- handleList\n## state_authority\n- s\n## contracts\n- GET /api/users\n## tests\n- t\n",
)
.unwrap();
let corpus = tmp.path().join("corpus");
std::fs::create_dir_all(&corpus).unwrap();
std::fs::create_dir_all(corpus.join("resolve-fixture")).unwrap();
common::copy_tree(&fixture_src, &corpus.join("resolve-fixture"));
let corpus_nor = tmp.path().join("corpus-nor");
std::fs::create_dir_all(&corpus_nor).unwrap();
std::fs::create_dir_all(corpus_nor.join("resolve-fixture")).unwrap();
common::copy_tree(&fixture_src, &corpus_nor.join("resolve-fixture"));
let json = bench_json(tmp.path(), &corpus, >, &[]);
assert_eq!(json["scored"].as_u64(), Some(1), "{json}");
let repo = json["repos"]
.as_array()
.unwrap()
.iter()
.find(|r| r["repo"] == "resolve-fixture")
.unwrap();
let backend_missing = repo["backends_missing"]
.as_array()
.map(|a| a.iter().any(|b| b == "tsserver"))
.unwrap_or(false);
let resolve_failed = repo["resolve_error"].is_string();
if tsserver_available && !backend_missing && !resolve_failed {
assert!(
repo["resolved_calls"].as_u64().unwrap() > 0,
"resolved_calls must be reported: {repo}"
);
assert!(
repo["behavior"].as_f64().unwrap() > 0.0,
"behavior layer must match the resolved flow step: {repo}"
);
} else {
eprintln!(
"resolve assertions skipped (tsserver available={tsserver_available}, backends_missing={backend_missing}, resolve_error={}); resolved_calls={}",
repo["resolve_error"], repo["resolved_calls"]
);
}
let fg = Command::new(common::scc())
.args(["export", "flow-graphs.json"])
.current_dir(corpus.join("resolve-fixture"))
.output()
.expect("scc export flow-graphs.json runs");
assert!(
fg.status.success(),
"export failed: {}",
String::from_utf8_lossy(&fg.stderr)
);
let graphs: serde_json::Value = serde_json::from_slice(&fg.stdout).expect("flow-graphs JSON");
let route = graphs
.as_array()
.unwrap()
.iter()
.find(|g| g["name"] == "get-/api/users")
.unwrap_or_else(|| panic!("route flow missing: {graphs}"));
let ops: Vec<&str> = route["nodes"]
.as_array()
.unwrap()
.iter()
.map(|n| n["operation"].as_str().unwrap())
.collect();
assert!(
ops.iter().any(|o| o.ends_with("handleList")),
"flow starts at the handler op: {ops:?}"
);
assert!(
ops.iter().any(|o| o.ends_with("/db") || o.ends_with("db")),
"flow reaches the resolved callee: {ops:?}"
);
let resolved_edge = route["edges"]
.as_array()
.unwrap()
.iter()
.any(|e| e["provenance"] == "RESOLVED");
if tsserver_available && !backend_missing && !resolve_failed {
assert!(resolved_edge, "route flow has a RESOLVED edge: {route}");
}
let json = bench_json(tmp.path(), &corpus_nor, >, &["--no-resolve"]);
let repo = json["repos"]
.as_array()
.unwrap()
.iter()
.find(|r| r["repo"] == "resolve-fixture")
.unwrap();
assert_eq!(
repo["resolved_calls"].as_u64().unwrap(),
0,
"--no-resolve must skip the semantic backends: {repo}"
);
}