use std::fs;
use std::path::Path;
use std::process::Command;
use tempfile::TempDir;
fn boundary_cmd() -> Command {
Command::new(env!("CARGO_BIN_EXE_boundary"))
}
fn shallow_clone(url: &str) -> Option<TempDir> {
let dir = tempfile::tempdir().expect("failed to create temp dir");
let status = Command::new("git")
.args(["clone", "--depth", "1", "--quiet", url, "."])
.current_dir(dir.path())
.status();
match status {
Ok(s) if s.success() => Some(dir),
Ok(s) => {
println!("git clone exited with {s} — skipping smoke test");
None
}
Err(e) => {
println!("git clone failed ({e}) — skipping smoke test");
None
}
}
}
fn analyze_json(path: &Path) -> serde_json::Value {
let output = boundary_cmd()
.args(["analyze", &path.to_string_lossy(), "--format", "json"])
.output()
.expect("failed to run boundary");
let stdout = String::from_utf8_lossy(&output.stdout);
serde_json::from_str(stdout.trim())
.unwrap_or_else(|e| panic!("invalid JSON from boundary: {e}\noutput: {stdout}"))
}
#[test]
#[ignore = "requires network"]
fn go_hexagonal_infrastructure_has_adapters() {
let Some(dir) = shallow_clone("https://github.com/RanchoCooper/go-hexagonal") else {
return;
};
let result = analyze_json(dir.path());
let by_layer = &result["metrics"]["components_by_layer"];
let infra = by_layer["infrastructure"].as_u64().unwrap_or(0);
assert!(
infra >= 5,
"expected >= 5 infrastructure components (MySQL/PostgreSQL/Redis adapters), got {infra}"
);
}
#[test]
#[ignore = "requires network"]
fn go_hexagonal_domain_has_ports() {
let Some(dir) = shallow_clone("https://github.com/RanchoCooper/go-hexagonal") else {
return;
};
let result = analyze_json(dir.path());
let by_kind = &result["metrics"]["components_by_kind"];
let ports = by_kind["port"].as_u64().unwrap_or(0);
assert!(
ports >= 2,
"expected >= 2 port interfaces in domain layer (IExampleRepo, IExampleCacheRepo at minimum), got {ports}"
);
}
#[test]
#[ignore = "requires network"]
fn go_hexagonal_infrastructure_has_repositories() {
let Some(dir) = shallow_clone("https://github.com/RanchoCooper/go-hexagonal") else {
return;
};
let result = analyze_json(dir.path());
let by_kind = &result["metrics"]["components_by_kind"];
let repos = by_kind["repository"].as_u64().unwrap_or(0);
assert!(
repos >= 2,
"expected >= 2 repository adapters (mysql.ExampleRepo, postgre.ExampleRepo), got {repos}"
);
}
#[test]
#[ignore = "requires network"]
fn go_hexagonal_interface_coverage_nonzero() {
let Some(dir) = shallow_clone("https://github.com/RanchoCooper/go-hexagonal") else {
return;
};
let result = analyze_json(dir.path());
let score = result["score"]
.as_object()
.expect("score should be present");
let coverage = score
.get("interface_coverage")
.and_then(|v| v.as_f64())
.unwrap_or(0.0);
assert!(
coverage > 0.0,
"interface_coverage must be > 0 when ports and repositories exist; got {coverage}"
);
}
#[test]
#[ignore = "requires network"]
fn go_hexagonal_pattern_detected() {
let Some(dir) = shallow_clone("https://github.com/RanchoCooper/go-hexagonal") else {
return;
};
let result = analyze_json(dir.path());
let top_confidence = result["pattern_detection"]["top_confidence"]
.as_f64()
.unwrap_or(0.0);
let top_pattern = result["pattern_detection"]["top_pattern"]
.as_str()
.unwrap_or("unknown");
assert!(
top_confidence >= 0.5,
"expected ddd-hexagonal pattern with >= 50% confidence, got '{top_pattern}' at {top_confidence:.2}"
);
}
#[test]
#[ignore = "requires network"]
fn go_hexagonal_no_domain_to_infra_violations() {
let Some(dir) = shallow_clone("https://github.com/RanchoCooper/go-hexagonal") else {
return;
};
let result = analyze_json(dir.path());
let violations = result["violations"].as_array().cloned().unwrap_or_default();
let domain_to_infra: Vec<_> = violations
.iter()
.filter(|v| {
let kind = v["kind"].as_object();
kind.map(|k| k.contains_key("LayerBoundary"))
.unwrap_or(false)
&& v.to_string().contains("domain")
&& v.to_string().contains("infrastructure")
})
.collect();
assert!(
domain_to_infra.is_empty(),
"domain layer must not import from infrastructure; found {} violation(s): {:?}",
domain_to_infra.len(),
domain_to_infra
);
}
fn write_ts_boundary_config(dir: &Path) {
let config = r#"
[project]
languages = ["typescript"]
[layers]
domain = ["**/domain/**"]
application = ["**/commands/**", "**/queries/**"]
infrastructure = ["**/database/**"]
cross_cutting = ["**/libs/**", "**/configs/**", "**/dtos/**", "**/mappers/**"]
"#;
fs::write(dir.join(".boundary.toml"), config.trim_start())
.expect("failed to write .boundary.toml");
}
#[test]
#[ignore = "requires network"]
fn ts_ddd_hexagon_domain_has_components() {
let Some(dir) = shallow_clone("https://github.com/Sairyss/domain-driven-hexagon") else {
return;
};
write_ts_boundary_config(dir.path());
let result = analyze_json(dir.path());
let by_layer = &result["metrics"]["components_by_layer"];
let domain = by_layer["domain"].as_u64().unwrap_or(0);
assert!(
domain >= 1,
"expected >= 1 domain component (value objects / entities in domain/); got {domain}"
);
}
#[test]
#[ignore = "requires network"]
fn ts_ddd_hexagon_infrastructure_has_repositories() {
let Some(dir) = shallow_clone("https://github.com/Sairyss/domain-driven-hexagon") else {
return;
};
write_ts_boundary_config(dir.path());
let result = analyze_json(dir.path());
let by_layer = &result["metrics"]["components_by_layer"];
let infra = by_layer["infrastructure"].as_u64().unwrap_or(0);
assert!(
infra >= 1,
"expected >= 1 infrastructure component (UserRepository in database/); got {infra}"
);
}
#[test]
#[ignore = "requires network"]
fn ts_ddd_hexagon_structural_presence_nonzero() {
let Some(dir) = shallow_clone("https://github.com/Sairyss/domain-driven-hexagon") else {
return;
};
write_ts_boundary_config(dir.path());
let result = analyze_json(dir.path());
let by_layer = &result["metrics"]["components_by_layer"];
let classified: u64 = ["domain", "application", "infrastructure", "presentation"]
.iter()
.map(|l| by_layer[l].as_u64().unwrap_or(0))
.sum();
assert!(
classified >= 2,
"expected >= 2 classified components across all layers; got {classified}"
);
}