"""Tests for the pure, read-only predicates in _compliance_state.py.""" from __future__ import annotations from datetime import UTC, date, datetime import json from typing import TYPE_CHECKING from unittest.mock import patch import freedays from screen_locker._compliance_state import ( has_logged_today, is_early_bird_pending, is_scheduled_skip_today, is_sick_day_today, ) from screen_locker.tests._sick_helpers import mark_sick if TYPE_CHECKING: from pathlib import Path def _today() -> str: return datetime.now(tz=UTC).strftime("%Y-%m-%d") class TestIsScheduledSkipToday: """Mirrors test_scheduled_skip.py's coverage, for the standalone function.""" def test_empty_pool(self, tmp_path: Path) -> None: """An empty pool means today is not a free day.""" assert is_scheduled_skip_today() is False def test_today_marked(self, tmp_path: Path) -> None: """Today in the shared pool makes it a free day.""" freedays.mark( freedays.today(), paths=freedays.Paths.under(tmp_path / "freedays") ) assert is_scheduled_skip_today() is True def test_today_not_marked(self, tmp_path: Path) -> None: """A pool holding some other date is not today's free day.""" freedays.mark( date(2026, 12, 24), paths=freedays.Paths.under(tmp_path / "freedays"), ) assert is_scheduled_skip_today() is False def test_corrupt_pool(self, tmp_path: Path) -> None: """Unparsable JSON fails closed: not a free day, the chain continues.""" pool = tmp_path / "freedays" pool.mkdir(exist_ok=True) (pool / "free_days.json").write_text("{bad}") assert is_scheduled_skip_today() is False def test_explicit_day_override(self, tmp_path: Path) -> None: """The `today` argument overrides the real date.""" freedays.mark( date(2026, 12, 24), paths=freedays.Paths.under(tmp_path / "freedays"), ) assert is_scheduled_skip_today(date(2026, 12, 24)) is True class TestHasLoggedToday: """Mirrors test_init_and_log.py's HMAC branches, for the standalone function.""" def test_missing_file(self, tmp_path: Path) -> None: """A missing log means nothing was logged today.""" assert has_logged_today(tmp_path / "log.json") is False def test_corrupt_json(self, tmp_path: Path) -> None: """Unparsable JSON fails closed: nothing logged.""" log_file = tmp_path / "log.json" log_file.write_text("{bad}") assert has_logged_today(log_file) is False def test_no_entry_for_today(self, tmp_path: Path) -> None: """An entry for another day does not count as today's.""" log_file = tmp_path / "log.json" log_file.write_text(json.dumps({"1999-01-01": {}})) assert has_logged_today(log_file) is False def test_valid_hmac(self, tmp_path: Path) -> None: """A correctly signed entry for today counts.""" log_file = tmp_path / "log.json" log_file.write_text(json.dumps({_today(): {"hmac": "sig"}})) with patch( "screen_locker._compliance_predicates.verify_entry_hmac", return_value=True ): assert has_logged_today(log_file) is True def test_unsigned_accepted_when_key_unavailable(self, tmp_path: Path) -> None: """With no HMAC key on the machine, an unsigned entry is accepted.""" log_file = tmp_path / "log.json" log_file.write_text(json.dumps({_today(): {}})) with ( patch( "screen_locker._compliance_predicates.verify_entry_hmac", return_value=False, ), patch( "screen_locker._compliance_predicates.compute_entry_hmac", return_value=None, ), ): assert has_logged_today(log_file) is True def test_rejected_when_key_available(self, tmp_path: Path) -> None: """With a key available, an unsigned entry is rejected as tampering.""" log_file = tmp_path / "log.json" log_file.write_text(json.dumps({_today(): {}})) with ( patch( "screen_locker._compliance_predicates.verify_entry_hmac", return_value=False, ), patch( "screen_locker._compliance_predicates.compute_entry_hmac", return_value="sig", ), ): assert has_logged_today(log_file) is False def test_rejected_when_signed_but_invalid(self, tmp_path: Path) -> None: """A signature that does not verify is rejected.""" log_file = tmp_path / "log.json" log_file.write_text(json.dumps({_today(): {"hmac": "tampered"}})) with ( patch( "screen_locker._compliance_predicates.verify_entry_hmac", return_value=False, ), patch( "screen_locker._compliance_predicates.compute_entry_hmac", return_value=None, ), ): assert has_logged_today(log_file) is False class TestIsEarlyBirdPending: """Mirrors test_early_bird.py's HMAC branches, for the standalone function.""" def test_missing_file(self, tmp_path: Path) -> None: """A missing pending file means no early bird is pending.""" assert is_early_bird_pending(tmp_path / "pending.json") is False def test_corrupt_json(self, tmp_path: Path) -> None: """Unparsable JSON fails closed: nothing pending.""" pending_file = tmp_path / "pending.json" pending_file.write_text("{bad}") assert is_early_bird_pending(pending_file) is False def test_not_a_dict(self, tmp_path: Path) -> None: """A JSON list where a dict is expected fails closed.""" pending_file = tmp_path / "pending.json" pending_file.write_text(json.dumps(["not", "a", "dict"])) assert is_early_bird_pending(pending_file) is False def test_stale_date(self, tmp_path: Path) -> None: """A pending record from an earlier day no longer applies.""" pending_file = tmp_path / "pending.json" pending_file.write_text(json.dumps({"date": "2000-01-01", "hmac": "sig"})) assert is_early_bird_pending(pending_file) is False def test_valid_hmac(self, tmp_path: Path) -> None: """A correctly signed pending record for today counts.""" pending_file = tmp_path / "pending.json" pending_file.write_text(json.dumps({"date": _today(), "hmac": "sig"})) with patch( "screen_locker._compliance_predicates.verify_entry_hmac", return_value=True ): assert is_early_bird_pending(pending_file) is True def test_unsigned_accepted_when_key_unavailable(self, tmp_path: Path) -> None: """With no HMAC key, an unsigned pending record is accepted.""" pending_file = tmp_path / "pending.json" pending_file.write_text(json.dumps({"date": _today()})) with ( patch( "screen_locker._compliance_predicates.verify_entry_hmac", return_value=False, ), patch( "screen_locker._compliance_predicates.compute_entry_hmac", return_value=None, ), ): assert is_early_bird_pending(pending_file) is True def test_rejected_when_key_available(self, tmp_path: Path) -> None: """With a key available, an unsigned pending record is rejected.""" pending_file = tmp_path / "pending.json" pending_file.write_text(json.dumps({"date": _today()})) with ( patch( "screen_locker._compliance_predicates.verify_entry_hmac", return_value=False, ), patch( "screen_locker._compliance_predicates.compute_entry_hmac", return_value="sig", ), ): assert is_early_bird_pending(pending_file) is False def test_rejected_when_signed_but_invalid(self, tmp_path: Path) -> None: """A pending record whose signature fails to verify is rejected.""" pending_file = tmp_path / "pending.json" pending_file.write_text(json.dumps({"date": _today(), "hmac": "bad"})) with ( patch( "screen_locker._compliance_predicates.verify_entry_hmac", return_value=False, ), patch( "screen_locker._compliance_predicates.compute_entry_hmac", return_value=None, ), ): assert is_early_bird_pending(pending_file) is False class TestIsSickDayToday: """Today's sick-day status, from the shared sickdays pool.""" def test_true_when_in_pool(self) -> None: """Today in the pool is a sick day.""" mark_sick() assert is_sick_day_today() is True def test_false_when_not_in_pool(self) -> None: """Another day in the pool does not make today sick.""" mark_sick("1999-01-01") assert is_sick_day_today() is False def test_explicit_day(self) -> None: """The day argument picks which date is asked.""" mark_sick("2020-05-05") assert is_sick_day_today(date(2020, 5, 5)) is True