import importlib.util import sys import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[2] CHECKER = ROOT / "scripts" / "check-rust-boundaries.py" def load_checker_module(): spec = importlib.util.spec_from_file_location("check_rust_boundaries", CHECKER) module = importlib.util.module_from_spec(spec) assert spec.loader is not None sys.modules[spec.name] = module spec.loader.exec_module(module) return module def package(root: Path, name: str, rel_dir: str, dependencies: list[str] | None = None) -> dict: dependencies = dependencies or [] return { "id": f"path+file://{root / rel_dir}#{name}", "name": name, "manifest_path": str(root / rel_dir / "Cargo.toml"), "dependencies": [{"name": dependency} for dependency in dependencies], } def dependency(name: str, kind: str | None = None) -> dict: return {"name": name, "kind": kind} def metadata(packages: list[dict], root: Path | None = None) -> dict: root = root or Path("/tmp/crank") return { "workspace_root": str(root), "workspace_members": [pkg["id"] for pkg in packages], "packages": packages, } class RustBoundaryCheckTests(unittest.TestCase): def setUp(self) -> None: self.checker = load_checker_module() self.root = Path("/tmp/crank") def test_allows_apps_to_depend_on_workspace_crates(self) -> None: packages = [ package(self.root, "admin-api", "apps/admin-api", ["crank-runtime"]), package(self.root, "crank-runtime", "crates/crank-runtime", ["crank-core"]), package(self.root, "crank-core", "crates/crank-core"), ] violations = self.checker.find_violations(metadata(packages, self.root)) self.assertEqual(violations, []) def test_rejects_core_dependency_on_runtime(self) -> None: packages = [ package(self.root, "crank-core", "crates/crank-core", ["crank-runtime"]), package(self.root, "crank-runtime", "crates/crank-runtime"), ] violations = self.checker.find_violations(metadata(packages, self.root)) self.assertEqual(len(violations), 1) self.assertEqual(violations[0].source, "crank-core") self.assertEqual(violations[0].dependency, "crank-runtime") def test_rejects_workspace_crate_dependency_on_app(self) -> None: packages = [ package(self.root, "crank-runtime", "crates/crank-runtime", ["admin-api"]), package(self.root, "admin-api", "apps/admin-api"), ] violations = self.checker.find_violations(metadata(packages, self.root)) self.assertEqual(len(violations), 1) self.assertIn("must not depend on apps", violations[0].reason) def test_rejects_registry_dependency_on_adapter(self) -> None: packages = [ package(self.root, "crank-registry", "crates/crank-registry", ["crank-adapter-rest"]), package(self.root, "crank-adapter-rest", "crates/crank-adapter-rest"), ] violations = self.checker.find_violations(metadata(packages, self.root)) self.assertEqual(len(violations), 1) self.assertEqual(violations[0].source, "crank-registry") self.assertEqual(violations[0].dependency, "crank-adapter-rest") def test_allows_apps_to_depend_on_observability(self) -> None: packages = [ package( self.root, "admin-api", "apps/admin-api", ["crank-observability"], ), package( self.root, "crank-observability", "crates/crank-observability", ), ] violations = self.checker.find_violations(metadata(packages, self.root)) self.assertEqual(violations, []) def test_rejects_observability_dependency_on_workspace_crates(self) -> None: for dependency in ("crank-core", "crank-registry", "crank-runtime"): packages = [ package( self.root, "crank-observability", "crates/crank-observability", [dependency], ), package(self.root, dependency, f"crates/{dependency}"), ] violations = self.checker.find_violations(metadata(packages, self.root)) self.assertEqual(len(violations), 1, dependency) self.assertEqual(violations[0].source, "crank-observability") self.assertEqual(violations[0].dependency, dependency) def test_allows_observability_and_product_crates_to_depend_on_metrics_contract(self) -> None: packages = [ package( self.root, "crank-observability", "crates/crank-observability", ["crank-metrics"], ), package( self.root, "crank-runtime", "crates/crank-runtime", ["crank-metrics"], ), package(self.root, "crank-metrics", "crates/crank-metrics"), ] violations = self.checker.find_violations(metadata(packages, self.root)) self.assertEqual(violations, []) def test_rejects_metrics_contract_dependency_on_workspace_crates(self) -> None: packages = [ package( self.root, "crank-metrics", "crates/crank-metrics", ["crank-core"], ), package(self.root, "crank-core", "crates/crank-core"), ] violations = self.checker.find_violations(metadata(packages, self.root)) self.assertEqual(len(violations), 1) self.assertEqual(violations[0].source, "crank-metrics") self.assertEqual(violations[0].dependency, "crank-core") def test_rejects_domain_and_runtime_dependencies_on_observability(self) -> None: for source in ("crank-core", "crank-registry", "crank-runtime"): packages = [ package( self.root, source, f"crates/{source}", ["crank-observability"], ), package( self.root, "crank-observability", "crates/crank-observability", ), ] violations = self.checker.find_violations(metadata(packages, self.root)) self.assertEqual(len(violations), 1, source) self.assertEqual(violations[0].source, source) self.assertEqual(violations[0].dependency, "crank-observability") def test_rejects_direct_production_metrics_dependency_outside_contract(self) -> None: app = package(self.root, "admin-api", "apps/admin-api") app["dependencies"] = [dependency("metrics")] violations = self.checker.find_violations(metadata([app], self.root)) self.assertEqual(len(violations), 1) self.assertEqual(violations[0].dependency, "metrics") def test_allows_metrics_as_test_only_dependency(self) -> None: app = package(self.root, "admin-api", "apps/admin-api") app["dependencies"] = [dependency("metrics", "dev")] violations = self.checker.find_violations(metadata([app], self.root)) self.assertEqual(violations, []) if __name__ == "__main__": unittest.main()