import json import subprocess import tempfile import unittest from pathlib import Path SCRIPT = Path(__file__).parents[2] / "scripts/check-runtime-config.py" def field(name: str, process: str = "shared", mode: str = "effective") -> dict: return { "env_name": name, "process": process, "mode": mode, "required": False, "default": "value", "sensitivity": "public", } class RuntimeConfigContractTests(unittest.TestCase): def fixture(self) -> Path: root = Path(tempfile.mkdtemp()) (root / "docs/schemas").mkdir(parents=True) (root / "deploy/community").mkdir(parents=True) contract = { "schema_version": 1, "fields": [ field("CRANK_SHARED"), field("CRANK_ADMIN", "admin_api"), field("CRANK_MCP", "mcp_server"), field("CRANK_OLD", mode="deprecated_no_effect"), ], "deployment_only_fields": ["COMPOSE_PROJECT_NAME"], } (root / "docs/schemas/runtime-config.schema.json").write_text( json.dumps(contract), encoding="utf-8" ) section = ( "# BEGIN GENERATED CRANK RUNTIME CONFIG\n" "CRANK_ADMIN=value\nCRANK_MCP=value\nCRANK_SHARED=value\n" "# END GENERATED CRANK RUNTIME CONFIG\n" ) for relative in ( ".env.example", "deploy/community/.env.example", "deploy/community/.env.images.example", ): (root / relative).write_text(section, encoding="utf-8") compose = ( "services:\n" " admin-api:\n environment:\n" " CRANK_SHARED: ${CRANK_SHARED:-value}\n" " CRANK_ADMIN: ${CRANK_ADMIN:-value}\n" " mcp-server:\n environment:\n" " CRANK_SHARED: ${CRANK_SHARED:-value}\n" " CRANK_MCP: ${CRANK_MCP:-value}\n" ) for relative in ( "docker-compose.yml", "deploy/community/docker-compose.yml", "deploy/community/docker-compose.images.yml", ): (root / relative).write_text(compose, encoding="utf-8") return root def run_check(self, root: Path) -> subprocess.CompletedProcess[str]: return subprocess.run( ["python3", str(SCRIPT), "--root", str(root)], text=True, capture_output=True, check=False, ) def test_valid_contract_passes(self) -> None: result = self.run_check(self.fixture()) self.assertEqual(result.returncode, 0, result.stderr) def test_missing_generated_field_fails_closed(self) -> None: root = self.fixture() path = root / ".env.example" path.write_text( path.read_text().replace("CRANK_MCP=value\n", ""), encoding="utf-8" ) result = self.run_check(root) self.assertNotEqual(result.returncode, 0) def test_compose_extra_or_wrong_process_field_fails(self) -> None: root = self.fixture() path = root / "docker-compose.yml" path.write_text( path.read_text().replace( "CRANK_ADMIN: ${CRANK_ADMIN:-value}", "CRANK_ADMIN: ${CRANK_ADMIN:-value}\n" " CRANK_MCP: ${CRANK_MCP:-value}", ), encoding="utf-8", ) result = self.run_check(root) self.assertNotEqual(result.returncode, 0) def test_deprecated_startup_bootstrap_password_may_be_absent_from_compose(self) -> None: root = self.fixture() schema_path = root / "docs/schemas/runtime-config.schema.json" schema = json.loads(schema_path.read_text(encoding="utf-8")) schema["fields"].append( { "env_name": "CRANK_BOOTSTRAP_ADMIN_PASSWORD", "process": "admin_api", "mode": "effective", "required": False, "default": None, "sensitivity": "secret", "compatibility": "deprecated startup-bootstrap password; use local bootstrap contract", } ) schema_path.write_text(json.dumps(schema), encoding="utf-8") for relative in ( ".env.example", "deploy/community/.env.example", "deploy/community/.env.images.example", ): path = root / relative path.write_text( path.read_text().replace( "CRANK_SHARED=value\n", "CRANK_SHARED=value\nCRANK_BOOTSTRAP_ADMIN_PASSWORD=\n", ), encoding="utf-8", ) result = self.run_check(root) self.assertEqual(result.returncode, 0, result.stderr) def test_divergent_inline_default_fails(self) -> None: root = self.fixture() path = root / "docker-compose.yml" path.write_text( path.read_text().replace("${CRANK_SHARED:-value}", "${CRANK_SHARED:-drift}", 1), encoding="utf-8", ) result = self.run_check(root) self.assertNotEqual(result.returncode, 0) self.assertIn("divergent Compose inline default", result.stderr) def test_wrong_interpolation_reference_fails(self) -> None: root = self.fixture() path = root / "docker-compose.yml" path.write_text( path.read_text().replace( "CRANK_ADMIN: ${CRANK_ADMIN:-value}", "CRANK_ADMIN: ${CRANK_SHARED:-value}", ), encoding="utf-8", ) result = self.run_check(root) self.assertNotEqual(result.returncode, 0) self.assertIn("wrong Compose interpolation reference", result.stderr) def test_required_and_empty_operator_forms_are_enforced(self) -> None: for required, expression in ( (True, "${CRANK_SHARED:-value}"), (False, "${CRANK_SHARED-value}"), (False, "${CRANK_SHARED:?required}"), ): with self.subTest(required=required, expression=expression): root = self.fixture() schema_path = root / "docs/schemas/runtime-config.schema.json" schema = json.loads(schema_path.read_text(encoding="utf-8")) schema["fields"][0]["required"] = required schema_path.write_text(json.dumps(schema), encoding="utf-8") compose_path = root / "docker-compose.yml" compose_path.write_text( compose_path.read_text().replace( "${CRANK_SHARED:-value}", expression, 1 ), encoding="utf-8", ) result = self.run_check(root) self.assertNotEqual(result.returncode, 0) def test_required_empty_aware_error_form_passes(self) -> None: root = self.fixture() schema_path = root / "docs/schemas/runtime-config.schema.json" schema = json.loads(schema_path.read_text(encoding="utf-8")) schema["fields"][0]["required"] = True schema_path.write_text(json.dumps(schema), encoding="utf-8") for relative in ( "docker-compose.yml", "deploy/community/docker-compose.yml", "deploy/community/docker-compose.images.yml", ): path = root / relative path.write_text( path.read_text().replace( "${CRANK_SHARED:-value}", "${CRANK_SHARED:?must be configured}" ), encoding="utf-8", ) result = self.run_check(root) self.assertEqual(result.returncode, 0, result.stderr) if __name__ == "__main__": unittest.main()