115 lines
3.4 KiB
Rust
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")
|
|
);
|
|
}
|