Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a0217e-8c4c-7383-be6b-f50a6e485306
138 lines
4.2 KiB
Python
138 lines
4.2 KiB
Python
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):
|
|
calls.append((field, positive, negative))
|
|
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,
|
|
)
|
|
|
|
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)
|