import json import os import sys import unittest sys.path.insert(0, os.path.dirname(__file__)) from _helpers import TempDirMixin from app import client_policy class ClientPolicyTests(TempDirMixin, unittest.TestCase): def _payload(self, *, check=True, enforce=False): return { "policy_version": 1, "subscription_check_enabled": check, "subscription_enforcement_enabled": enforce, "updated_at": "2026-07-28T15:19:19+08:00", } def test_parse_supports_off_observe_and_enforce(self): off = client_policy.parse_client_policy( self._payload(check=False, enforce=False) ) observe = client_policy.parse_client_policy( self._payload(check=True, enforce=False) ) enforce = client_policy.parse_client_policy( self._payload(check=True, enforce=True) ) self.assertEqual("off", off.mode) self.assertEqual("observe", observe.mode) self.assertEqual("enforce", enforce.mode) def test_illegal_combination_is_normalized_to_off(self): policy = client_policy.parse_client_policy( self._payload(check=False, enforce=True) ) self.assertEqual("off", policy.mode) self.assertFalse(policy.subscription_enforcement_enabled) self.assertIn("组合非法", policy.warning) def test_parser_rejects_loose_types_or_timestamp_without_timezone(self): invalid_bool = self._payload() invalid_bool["subscription_check_enabled"] = 1 invalid_time = self._payload() invalid_time["updated_at"] = "2026-07-28T15:19:19" with self.assertRaises(client_policy.ClientPolicyError): client_policy.parse_client_policy(invalid_bool) with self.assertRaises(client_policy.ClientPolicyError): client_policy.parse_client_policy(invalid_time) def test_valid_remote_policy_is_cached_without_sensitive_fields(self): with self.make_temp_dir() as temp_dir: path = os.path.join(temp_dir, "config", "client_policy.json") remote = client_policy.parse_client_policy( self._payload(check=True, enforce=True) ) resolved = client_policy.resolve_client_policy(remote, path=path) loaded = client_policy.load_cached_policy(path=path) self.assertEqual("remote", resolved.source) self.assertEqual("cache", loaded.source) self.assertEqual("enforce", loaded.mode) with open(path, "r", encoding="utf-8") as handle: raw = json.load(handle) self.assertIn("cached_at", raw) self.assertNotIn("api_key", json.dumps(raw).lower()) self.assertNotIn("account", json.dumps(raw).lower()) def test_missing_remote_uses_cache_then_observation_fallback(self): with self.make_temp_dir() as temp_dir: path = os.path.join(temp_dir, "client_policy.json") cached = client_policy.parse_client_policy( self._payload(check=False, enforce=False) ) client_policy.save_cached_policy(cached, path=path) from_cache = client_policy.resolve_client_policy(None, path=path) self.assertEqual("cache", from_cache.source) self.assertEqual("off", from_cache.mode) with open(path, "w", encoding="utf-8") as handle: handle.write("{broken") fallback = client_policy.resolve_client_policy(None, path=path) self.assertEqual("fallback", fallback.source) self.assertEqual("observe", fallback.mode) self.assertIn("观察模式", fallback.warning) if __name__ == "__main__": unittest.main()