"""Unit tests for src/my_deepagent/governance.py.""" from __future__ import annotations import json import os import stat from pathlib import Path from unittest.mock import patch import pytest from my_deepagent.errors import MyDeepAgentError from my_deepagent.governance import consent_path, has_consent, record_consent, require_consent def test_has_consent_false_when_empty(tmp_path: Path) -> None: assert has_consent(tmp_path) is False def test_has_consent_true_after_record(tmp_path: Path) -> None: record_consent(tmp_path) assert has_consent(tmp_path) is True def test_consent_file_path(tmp_path: Path) -> None: expected = tmp_path / "governance-accepted.json" assert consent_path(tmp_path) == expected def test_record_consent_creates_valid_json(tmp_path: Path) -> None: record_consent(tmp_path) content = consent_path(tmp_path).read_text() data = json.loads(content) assert "accepted_at" in data assert "T" in data["accepted_at"] # ISO format def test_record_consent_file_mode_600(tmp_path: Path) -> None: record_consent(tmp_path) file_stat = consent_path(tmp_path).stat() mode = stat.S_IMODE(file_stat.st_mode) assert mode == 0o600 def test_record_consent_atomic_uses_os_replace(tmp_path: Path) -> None: replace_calls: list[tuple[object, object]] = [] original_replace = os.replace def spy_replace(src: object, dst: object) -> None: replace_calls.append((src, dst)) original_replace(src, dst) # type: ignore[arg-type] with patch("my_deepagent.governance.os.replace", spy_replace): record_consent(tmp_path) assert len(replace_calls) == 1 src_path, dst_path = replace_calls[0] assert str(src_path).endswith(".tmp") assert str(dst_path) == str(consent_path(tmp_path)) def test_require_consent_raises_when_no_consent(tmp_path: Path) -> None: with pytest.raises(MyDeepAgentError) as exc_info: require_consent(tmp_path) assert exc_info.value.code == "governance_not_accepted" def test_require_consent_passes_when_consent_exists(tmp_path: Path) -> None: record_consent(tmp_path) require_consent(tmp_path) # should not raise