import copy from pathlib import Path from types import SimpleNamespace import pytest from secrets_engine import cli from secrets_engine.catalog import validate_entry from secrets_engine.config import Config from secrets_engine.errors import PolicyGuardError from secrets_engine.routing import route_lane from secrets_engine.verify import VerifyResult from tests.test_catalog import VALID def _entry(): data = copy.deepcopy(VALID) data["fields"] = ["api_token", "webhook_secret"] return validate_entry(data) class ReadinessClient: def __init__(self, presence): self.presence = presence self.requested_fields = None def is_reachable(self): return True def read_policy(self, _name): return "path \"secret/data/test/team/thing\" {}" def approle_exists(self, _name): return True def kv_fields_present(self, _mount, _path, fields): self.requested_fields = list(fields) return dict(self.presence) def test_route_requires_every_declared_field_and_names_only_missing_fields(tmp_path): client = ReadinessClient({"api_token": True, "webhook_secret": False}) result = route_lane(_entry(), hub_url="", repo_root=tmp_path, client=client) assert client.requested_fields == ["api_token", "webhook_secret"] assert result.value_present is False assert result.ready is False assert result.missing_fields == ["webhook_secret"] assert result.missing == "provisioned secret fields: webhook_secret" assert "--field webhook_secret" in result.next_command def test_route_is_ready_only_when_every_declared_field_is_present(tmp_path): client = ReadinessClient({"api_token": True, "webhook_secret": True}) result = route_lane(_entry(), hub_url="", repo_root=tmp_path, client=client) assert result.value_present is True assert result.missing_fields == [] assert result.ready is True def _config(tmp_path: Path) -> Config: return Config( catalog_dir=tmp_path, policy_dir=tmp_path, evidence_dir=tmp_path / "evidence", hub_url="", bao_addr="http://127.0.0.1:8200", topic_id="test-topic", ) def test_verify_defaults_to_every_declared_field_and_one_path_denial( tmp_path, monkeypatch ): entry = _entry() calls = [] records = [] monkeypatch.setattr(cli, "get_entry", lambda *_args: entry) monkeypatch.setattr(cli, "_require_lane_approval", lambda *_args: None) monkeypatch.setattr(cli.OpenBaoClient, "resolve", lambda *_args, **_kwargs: object()) def fake_verify( _client, _entry, field, *, positive, negative, unrelated_token=None ): calls.append((field, positive, negative)) assert unrelated_token is None check = "positive" if positive else "negative" return [VerifyResult(check, True, {"field": field, "reason": "test"})] monkeypatch.setattr(cli, "run_verification", fake_verify) monkeypatch.setattr( cli, "_writer", lambda _cfg: SimpleNamespace(record=lambda *args, **kwargs: records.append((args, kwargs))), ) args = SimpleNamespace( catalog_id=entry.id, bootstrap_token_file=None, field=None, positive=False, negative=False, negative_token_file=None, ) assert cli.cmd_verify(_config(tmp_path), args) == 0 assert calls == [ ("api_token", True, False), ("webhook_secret", True, False), ("api_token", False, True), ] assert len(records) == 3 def test_live_destroy_fails_before_approval_or_backend_until_action_contract( tmp_path, monkeypatch ): entry = _entry() monkeypatch.setattr(cli, "get_entry", lambda *_args: entry) monkeypatch.setattr( cli, "_require_lane_approval", lambda *_args: pytest.fail("coarse lane approval must not authorize destroy"), ) monkeypatch.setattr( cli.OpenBaoClient, "resolve", lambda *_args, **_kwargs: pytest.fail("backend must not be reached"), ) args = SimpleNamespace( catalog_id=entry.id, operation="destroy", dry_run=False, confirm_destroy=entry.id, bootstrap_token_file=None, ) with pytest.raises(PolicyGuardError, match="exact-action destruction approval"): cli.cmd_lifecycle(_config(tmp_path), args)