diff --git a/castle-api/tests/test_deployment_roundtrip.py b/castle-api/tests/test_deployment_roundtrip.py index 8e4bbb2..55f669e 100644 --- a/castle-api/tests/test_deployment_roundtrip.py +++ b/castle-api/tests/test_deployment_roundtrip.py @@ -78,3 +78,34 @@ class TestProgramEditSafety: assert m["description"] == "renamed" # change applied assert m["source"].endswith("wired-in") # source preserved assert m["commands"]["lint"] == [["make", "lint"]] # commands preserved + + +def test_deployment_edit_leaves_castle_yaml_globals_untouched(client, castle_root): + """The regression that bit us: a config-editor deployment edit must NOT rewrite + castle.yaml globals (role/secrets). Scoped writes guarantee this.""" + import yaml + + cy = castle_root / "castle.yaml" + data = yaml.safe_load(cy.read_text()) + data["role"] = "authority" + data["secrets"] = {"backend": "openbao", "addr": "https://v:8200"} + cy.write_text(yaml.safe_dump(data)) + before = cy.read_text() + + resp = client.put("/config/deployments/test-svc", json={"config": {"reach": "off"}}) + assert resp.status_code == 200 + assert cy.read_text() == before, "deployment edit rewrote castle.yaml globals" + + +def test_program_edit_leaves_castle_yaml_globals_untouched(client, castle_root): + import yaml + + cy = castle_root / "castle.yaml" + data = yaml.safe_load(cy.read_text()) + data["role"] = "authority" + cy.write_text(yaml.safe_dump(data)) + before = cy.read_text() + + resp = client.put("/config/programs/wired-in", json={"config": {"version": "9.9.9"}}) + assert resp.status_code == 200 + assert cy.read_text() == before, "program edit rewrote castle.yaml globals" diff --git a/castle-api/tests/test_secrets_overrides.py b/castle-api/tests/test_secrets_overrides.py index 83998bf..3623cb3 100644 --- a/castle-api/tests/test_secrets_overrides.py +++ b/castle-api/tests/test_secrets_overrides.py @@ -22,3 +22,12 @@ def test_set_override_rejected_on_file_backend(client: TestClient) -> None: def test_get_missing_override_is_404(client: TestClient) -> None: assert client.get("/secrets/overrides/primer/NOPE").status_code == 404 + + +def test_secrets_info_reports_backend(client: TestClient) -> None: + r = client.get("/secrets/info") + assert r.status_code == 200 + body = r.json() + assert body["backend"] == "file" # conftest forces file in tests + assert body["writable"] is True + assert "role" in body diff --git a/core/tests/test_fleet_role.py b/core/tests/test_fleet_role.py index e0a55b3..02e3ef6 100644 --- a/core/tests/test_fleet_role.py +++ b/core/tests/test_fleet_role.py @@ -86,6 +86,24 @@ def test_write_deployment_file_leaves_globals_untouched(castle_root: Path) -> No assert cy.read_text() == before # globals byte-identical — nothing touched them +def test_write_program_file_leaves_globals_untouched(castle_root: Path) -> None: + """Scoped program write must not rewrite castle.yaml globals either.""" + from castle_core.config import load_config, write_program_file + + cy = castle_root / "castle.yaml" + data = yaml.safe_load(cy.read_text()) + data["role"] = "authority" + data["secrets"] = {"backend": "openbao"} + cy.write_text(yaml.safe_dump(data)) + before = cy.read_text() + + config = load_config(castle_root) + name = next(iter(config.programs)) + write_program_file(config, name) + + assert cy.read_text() == before + + def test_registry_role_round_trip(tmp_path: Path) -> None: reg = NodeRegistry( node=NodeConfig(hostname="civil", role="authority"), deployed={}