provisioning-platform/tests/architecture/solid_boundaries_test.rs

115 lines
3.4 KiB
Rust

//! SOLID Architecture Boundary Tests
//!
//! Scans the source tree to detect violations of the service separation
//! constraints defined in the architecture plan.
//!
//! Run: `cargo test --test solid_boundaries`
use std::path::{Path, PathBuf};
fn workspace_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn collect_files(root: &Path, ext: &str) -> Vec<PathBuf> {
let mut files = Vec::new();
collect_recursive(root, ext, &mut files);
files
}
fn collect_recursive(dir: &Path, ext: &str, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
// Skip target, .git, test directories that contain expected violations for testing
let name = path.file_name().unwrap_or_default().to_string_lossy();
if matches!(name.as_ref(), "target" | ".git" | "node_modules") {
continue;
}
collect_recursive(&path, ext, out);
} else if path.extension().and_then(|e| e.to_str()) == Some(ext) {
out.push(path);
}
}
}
fn file_contains_any(path: &Path, patterns: &[&str]) -> bool {
let Ok(content) = std::fs::read_to_string(path) else {
return false;
};
patterns.iter().any(|p| content.contains(p))
}
fn is_within(path: &Path, segment: &str) -> bool {
path.to_string_lossy().contains(segment)
}
#[test]
fn no_provider_imports_outside_orchestrator() {
let root = workspace_root();
let provider_patterns = ["hcloud_api", "hcloud::"];
let mut violations = Vec::new();
for rs in collect_files(&root, "rs") {
if is_within(&rs, "orchestrator") || is_within(&rs, "tests/architecture") {
continue;
}
if file_contains_any(&rs, &provider_patterns) {
violations.push(rs.to_string_lossy().to_string());
}
}
assert!(
violations.is_empty(),
"SOLID violation — provider imports outside orchestrator:\n{}",
violations.join("\n")
);
}
#[test]
fn no_ssh_imports_outside_orchestrator() {
let root = workspace_root();
let ssh_patterns = ["russh::", "ssh2::"];
let mut violations = Vec::new();
for rs in collect_files(&root, "rs") {
if is_within(&rs, "orchestrator") || is_within(&rs, "tests/architecture") {
continue;
}
if file_contains_any(&rs, &ssh_patterns) {
violations.push(rs.to_string_lossy().to_string());
}
}
assert!(
violations.is_empty(),
"SOLID violation — SSH imports outside orchestrator:\n{}",
violations.join("\n")
);
}
#[test]
fn no_direct_surrealdb_in_nushell_cli() {
let root = workspace_root();
let surreal_patterns = ["surrealdb", "ws://localhost:8000", "Surreal::new"];
let mut violations = Vec::new();
for nu in collect_files(&root, "nu") {
// Allow platform service scripts to reference surrealdb
if is_within(&nu, "platform/") || is_within(&nu, "tests/") {
continue;
}
if file_contains_any(&nu, &surreal_patterns) {
violations.push(nu.to_string_lossy().to_string());
}
}
assert!(
violations.is_empty(),
"SOLID violation — direct SurrealDB access in Nushell CLI:\n{}",
violations.join("\n")
);
}