mod common;
use common::run_ok;
use std::path::Path;
#[test]
fn same_dir_modules_split_on_low_cohesion() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("repo");
write_split_fixture(&dir);
run_ok(&dir, &["index", "--quiet"]);
let atlas = run_ok(&dir, &["atlas"]);
assert!(atlas.contains("SRC/CHECKOUT"), "checkout module component: {atlas}");
assert!(atlas.contains("SRC/PRICING"), "pricing module component: {atlas}");
assert!(
!atlas.contains("SRC\nImplementation: src"),
"no directory-blob component: {atlas}"
);
}
#[test]
fn cross_dir_modules_merge_on_call_and_state_weight() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("repo");
write_merge_fixture(&dir);
run_ok(&dir, &["index", "--quiet"]);
let atlas = run_ok(&dir, &["atlas"]);
assert!(
atlas.contains("AUTH+USERS"),
"cross-dir merge into one component: {atlas}"
);
let idx = atlas.find("AUTH+USERS").unwrap();
let window = &atlas[idx..idx + 600];
assert!(window.contains("auth"), "auth dir kept as prior: {window}");
assert!(window.contains("users"), "users dir kept as prior: {window}");
}
#[test]
fn library_architecture_comes_from_exports() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("repo");
write_library_fixture(&dir);
run_ok(&dir, &["index", "--quiet"]);
let atlas = run_ok(&dir, &["atlas"]);
assert!(atlas.contains("ARCHETYPE: library_sdk"), "library archetype: {atlas}");
assert!(
atlas.contains("LIB\nPurpose:") || atlas.contains("LIB\nImplementation:"),
"export-driven component: {atlas}"
);
assert!(!atlas.contains("LIB/IMPL_A"), "no impl_a shell: {atlas}");
assert!(!atlas.contains("LIB/IMPL_B"), "no impl_b shell: {atlas}");
assert!(atlas.contains("LIB/CONTRACTS"), "interface module: {atlas}");
}
#[test]
fn clustering_is_deterministic_across_recompiles() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("repo");
write_split_fixture(&dir);
run_ok(&dir, &["index", "--quiet"]);
let atlas1 = run_ok(&dir, &["atlas"]);
run_ok(&dir, &["index", "--quiet"]);
let atlas2 = run_ok(&dir, &["atlas"]);
let norm = |a: &str| {
a.lines()
.filter(|l| !l.starts_with("model_epoch_generations:"))
.collect::<Vec<_>>()
.join("\n")
};
assert_eq!(norm(&atlas1), norm(&atlas2), "atlas output must be deterministic");
assert!(atlas1.contains("SRC/CHECKOUT"), "first run: {atlas1}");
assert!(atlas2.contains("SRC/CHECKOUT"), "second run: {atlas2}");
}
#[test]
fn split_and_merge_coexist_in_one_repo() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("repo");
write_split_fixture(&dir);
std::fs::create_dir_all(dir.join("checkout")).unwrap();
std::fs::create_dir_all(dir.join("cart")).unwrap();
std::fs::write(
dir.join("checkout/service.py"),
"import sqlite3\n\ndef start_checkout(user):\n conn = sqlite3.connect('shop.db')\n conn.execute('INSERT INTO checkouts (user) VALUES (?)', (user,))\n conn.commit()\n conn.close()\n",
)
.unwrap();
std::fs::write(
dir.join("cart/service.py"),
"import sqlite3\nfrom checkout.service import start_checkout\n\ndef add_to_cart(item):\n conn = sqlite3.connect('shop.db')\n conn.execute('INSERT INTO cart (item) VALUES (?)', (item,))\n conn.commit()\n conn.close()\n start_checkout('guest')\n",
)
.unwrap();
run_ok(&dir, &["index", "--quiet"]);
let atlas = run_ok(&dir, &["atlas"]);
assert!(atlas.contains("SRC/CHECKOUT"), "split side: {atlas}");
assert!(atlas.contains("SRC/PRICING"), "split side: {atlas}");
assert!(atlas.contains("CART+CHECKOUT"), "merge side: {atlas}");
}
#[test]
fn flat_library_package_splits_into_unrelated_modules() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("repo");
write_flat_package_fixture(&dir);
run_ok(&dir, &["index", "--quiet"]);
let comps = run_ok(&dir, &["components"]);
let lines: Vec<&str> = comps.lines().filter(|l| !l.is_empty()).collect();
assert!(
lines.len() >= 2,
"flat package must split into 2+ components: {comps}"
);
for n in ["src/core.py", "src/parser.py", "src/termui.py"] {
assert!(lines.contains(&n), "module component {n}: {comps}");
}
assert!(
!lines.contains(&"src"),
"no fused package blob: {comps}"
);
}
#[test]
fn single_link_chaining_is_blocked() {
let tmp = tempfile::TempDir::new().unwrap();
let dir = tmp.path().join("repo");
write_chain_fixture(&dir);
run_ok(&dir, &["index", "--quiet"]);
let comps = run_ok(&dir, &["components"]);
let lines: Vec<&str> = comps.lines().filter(|l| !l.is_empty()).collect();
assert!(
lines.len() >= 2,
"chained clusters must not collapse into one component: {comps}"
);
assert!(
lines.contains(&"a+b+c"),
"the first three links cohere (avg 3 >= 2.4): {comps}"
);
assert!(
lines.contains(&"d"),
"the tail link must NOT chain on a single edge: {comps}"
);
}
fn write_split_fixture(dir: &Path) {
std::fs::create_dir_all(dir.join("src/checkout")).unwrap();
std::fs::create_dir_all(dir.join("src/pricing")).unwrap();
std::fs::write(
dir.join("src/checkout/cart.py"),
"def add_item(item: str) -> None:\n pass\n",
)
.unwrap();
std::fs::write(
dir.join("src/pricing/tax.py"),
"def compute_tax(amount: float) -> float:\n return amount * 0.2\n",
)
.unwrap();
std::fs::write(dir.join("README.md"), "# split fixture\n").unwrap();
}
fn write_merge_fixture(dir: &Path) {
std::fs::create_dir_all(dir.join("auth")).unwrap();
std::fs::create_dir_all(dir.join("users")).unwrap();
std::fs::write(
dir.join("auth/session.py"),
"import sqlite3\nfrom users.api import get_user\n\ndef create_session(user_id: int) -> str:\n conn = sqlite3.connect('app.db')\n conn.execute('INSERT INTO sessions (user_id) VALUES (?)', (user_id,))\n conn.commit()\n conn.close()\n get_user(user_id)\n return 'tok'\n",
)
.unwrap();
std::fs::write(
dir.join("users/api.py"),
"import sqlite3\n\ndef get_user(user_id: int) -> dict:\n conn = sqlite3.connect('app.db')\n conn.execute('INSERT INTO access_log (user_id) VALUES (?)', (user_id,))\n conn.commit()\n conn.close()\n return {'id': user_id}\n",
)
.unwrap();
std::fs::write(dir.join("README.md"), "# merge fixture\n").unwrap();
}
fn write_library_fixture(dir: &Path) {
std::fs::create_dir_all(dir.join("lib/contracts")).unwrap();
std::fs::create_dir_all(dir.join("lib/impl_a")).unwrap();
std::fs::create_dir_all(dir.join("lib/impl_b")).unwrap();
std::fs::write(
dir.join("lib/contracts/base.ts"),
"export interface iface {\n run(): void;\n}\n",
)
.unwrap();
std::fs::write(
dir.join("lib/impl_a/a.ts"),
"import { iface } from '../contracts/base';\n\nexport class ImplA implements iface {\n run(): void {}\n}\n",
)
.unwrap();
std::fs::write(
dir.join("lib/impl_b/b.ts"),
"import { iface } from '../contracts/base';\n\nexport class ImplB implements iface {\n run(): void {}\n}\n",
)
.unwrap();
std::fs::write(dir.join("README.md"), "# library fixture\n").unwrap();
}
fn write_flat_package_fixture(dir: &Path) {
std::fs::create_dir_all(dir.join("src")).unwrap();
std::fs::write(
dir.join("package.json"),
"{\n \"name\": \"flatlib\",\n \"workspaces\": [\"src\"]\n}\n",
)
.unwrap();
std::fs::write(
dir.join("src/core.py"),
"def core_fn() -> str:\n return 'core'\n",
)
.unwrap();
std::fs::write(
dir.join("src/parser.py"),
"def parse_arg(raw: str) -> str:\n return raw.strip()\n",
)
.unwrap();
std::fs::write(
dir.join("src/termui.py"),
"def render_line(text: str) -> str:\n return f'[{text}]'\n",
)
.unwrap();
std::fs::write(dir.join("README.md"), "# flat package fixture\n").unwrap();
}
fn write_chain_fixture(dir: &Path) {
for d in ["a", "b", "c", "d"] {
std::fs::create_dir_all(dir.join(d)).unwrap();
}
std::fs::write(
dir.join("a/x.py"),
"import sqlite3\nfrom b.x import _b_run\n\ndef _a_run(user: str) -> str:\n cursor = sqlite3.connect('db1.db')\n cursor.execute('INSERT INTO a (user) VALUES (?)', (user,))\n cursor.commit()\n cursor.close()\n _b_run(user)\n return 'a'\n",
)
.unwrap();
std::fs::write(
dir.join("b/x.py"),
"import sqlite3\nfrom c.x import _c_run\n\ndef _b_run(user: str) -> str:\n cursor = sqlite3.connect('db1.db')\n cursor.execute('INSERT INTO b (user) VALUES (?)', (user,))\n cursor.commit()\n cursor.close()\n engine = sqlite3.connect('db2.db')\n engine.execute('INSERT INTO b (user) VALUES (?)', (user,))\n engine.commit()\n engine.close()\n _c_run(user)\n return 'b'\n",
)
.unwrap();
std::fs::write(
dir.join("c/x.py"),
"import sqlite3\nfrom d.x import _d_run\n\ndef _c_run(user: str) -> str:\n engine = sqlite3.connect('db2.db')\n engine.execute('INSERT INTO c (user) VALUES (?)', (user,))\n engine.commit()\n engine.close()\n pool = sqlite3.connect('db3.db')\n pool.execute('INSERT INTO c (user) VALUES (?)', (user,))\n pool.commit()\n pool.close()\n _d_run(user)\n return 'c'\n",
)
.unwrap();
std::fs::write(
dir.join("d/x.py"),
"import sqlite3\n\ndef _d_run(user: str) -> str:\n pool = sqlite3.connect('db3.db')\n pool.execute('INSERT INTO d (user) VALUES (?)', (user,))\n pool.commit()\n pool.close()\n return 'd'\n",
)
.unwrap();
std::fs::write(dir.join("README.md"), "# chain fixture\n").unwrap();
}