106 lines
4.7 KiB
Python
106 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
import unittest
|
|
|
|
from govoplan_policy.backend.retention import (
|
|
PrivacyRetentionPolicy,
|
|
PrivacyRetentionPolicyPatch,
|
|
PrivacyPolicyError,
|
|
_parent_privacy_policy_scope_id,
|
|
_privacy_policy_patch_payload,
|
|
_required_privacy_policy_scope_id,
|
|
)
|
|
from govoplan_policy.backend.hierarchy import (
|
|
PolicyRestrictionRule,
|
|
simulate_hierarchical_policy_change,
|
|
validate_hierarchical_policy_patch,
|
|
)
|
|
|
|
|
|
class PolicyHierarchyTests(unittest.TestCase):
|
|
def test_privacy_policy_parent_scope_ids_follow_scope_precedence(self) -> None:
|
|
self.assertEqual(
|
|
"tenant-1",
|
|
_parent_privacy_policy_scope_id(tenant_id="tenant-1", scope_type="tenant", scope_id=None),
|
|
)
|
|
self.assertIsNone(_parent_privacy_policy_scope_id(tenant_id="tenant-1", scope_type="user", scope_id=None))
|
|
self.assertIsNone(_parent_privacy_policy_scope_id(tenant_id="tenant-1", scope_type="group", scope_id=None))
|
|
self.assertEqual(
|
|
"campaign-1",
|
|
_parent_privacy_policy_scope_id(tenant_id="tenant-1", scope_type="campaign", scope_id="campaign-1"),
|
|
)
|
|
|
|
def test_privacy_policy_patch_payload_validates_and_removes_unset_fields(self) -> None:
|
|
payload = _privacy_policy_patch_payload({"audit_detail_level": "minimal", "generated_eml_retention_days": None})
|
|
|
|
self.assertEqual({"audit_detail_level": "minimal"}, payload)
|
|
|
|
def test_privacy_policy_models_extend_shared_schema_contract(self) -> None:
|
|
policy = PrivacyRetentionPolicy.model_validate({"generated_eml_retention_days": ""})
|
|
patch = PrivacyRetentionPolicyPatch.model_validate({"allow_lower_level_limits": {"audit_detail_level": False}})
|
|
|
|
self.assertIsNone(policy.generated_eml_retention_days)
|
|
self.assertEqual({"audit_detail_level": False}, patch.allow_lower_level_limits)
|
|
|
|
def test_required_privacy_policy_scope_id_reports_missing_scope(self) -> None:
|
|
with self.assertRaises(PrivacyPolicyError) as captured:
|
|
_required_privacy_policy_scope_id("group", None)
|
|
|
|
self.assertEqual("Group privacy policy requires scope_id", str(captured.exception))
|
|
|
|
def test_parent_locks_block_lower_level_field_changes_and_limit_reenable(self) -> None:
|
|
issues = validate_hierarchical_policy_patch(
|
|
parent_policy={"retention_days": 30},
|
|
patch={"retention_days": 20, "allow_lower_level_limits": {"retention_days": True}},
|
|
field_keys=("retention_days",),
|
|
parent_allow_lower_level_limits={"retention_days": False},
|
|
)
|
|
|
|
self.assertEqual(["field_locked_by_parent", "lower_level_limit_locked_by_parent"], [issue.code for issue in issues])
|
|
self.assertEqual(["retention_days", "retention_days"], [issue.field for issue in issues])
|
|
|
|
def test_restriction_rules_block_less_restrictive_patch(self) -> None:
|
|
issues = validate_hierarchical_policy_patch(
|
|
parent_policy={"retention_days": 30},
|
|
patch={"retention_days": 45},
|
|
field_keys=("retention_days",),
|
|
parent_allow_lower_level_limits={"retention_days": True},
|
|
restriction_rules=(
|
|
PolicyRestrictionRule(
|
|
field="retention_days",
|
|
is_more_restrictive_or_equal=lambda parent, requested: int(requested) <= int(parent),
|
|
less_restrictive_message="retention_days cannot be widened",
|
|
),
|
|
),
|
|
)
|
|
|
|
self.assertEqual(1, len(issues))
|
|
self.assertEqual("less_restrictive_than_parent", issues[0].code)
|
|
self.assertEqual("retention_days cannot be widened", issues[0].message)
|
|
|
|
def test_policy_simulation_returns_decision_payload(self) -> None:
|
|
simulation = simulate_hierarchical_policy_change(
|
|
parent_policy={"retention_days": 30},
|
|
current_policy={"retention_days": 30},
|
|
patch={"retention_days": 45},
|
|
field_keys=("retention_days",),
|
|
parent_allow_lower_level_limits={"retention_days": True},
|
|
restriction_rules=(
|
|
PolicyRestrictionRule(
|
|
field="retention_days",
|
|
is_more_restrictive_or_equal=lambda parent, requested: int(requested) <= int(parent),
|
|
less_restrictive_message="retention_days cannot be widened",
|
|
),
|
|
),
|
|
)
|
|
|
|
self.assertFalse(simulation.allowed)
|
|
self.assertEqual(("retention_days",), simulation.changed_fields)
|
|
self.assertIsNotNone(simulation.decision)
|
|
self.assertEqual(("retention_days",), simulation.decision.requirements)
|
|
self.assertEqual("Requested policy change is blocked by parent policy constraints.", simulation.decision.reason)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|