Some checks failed
CI/CD Pipeline / Code Quality & Linting (push) Has been cancelled
CI/CD Pipeline / Policy Validation (push) Has been cancelled
CI/CD Pipeline / Test Suite (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-firm-connectors) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-forms) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-hmrc) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ingestion) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-normalize-map) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-ocr) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-indexer) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-reason) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (svc-rpa) (push) Has been cancelled
CI/CD Pipeline / Build Docker Images (ui-review) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-coverage) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-extract) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-kg) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (svc-rag-retriever) (push) Has been cancelled
CI/CD Pipeline / Security Scanning (ui-review) (push) Has been cancelled
CI/CD Pipeline / Generate SBOM (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / Notifications (push) Has been cancelled
271 lines
9.9 KiB
Python
271 lines
9.9 KiB
Python
"""Unit tests for predicate compilation and DSL parsing."""
|
|
|
|
# FILE: tests/unit/coverage/test_predicate_compilation.py
|
|
|
|
import pytest
|
|
|
|
from libs.policy import PolicyLoader
|
|
|
|
# pylint: disable=wrong-import-position,import-error,too-few-public-methods,global-statement
|
|
# pylint: disable=raise-missing-from,unused-argument,too-many-arguments,too-many-positional-arguments
|
|
# pylint: disable=too-many-locals,import-outside-toplevel
|
|
# mypy: disable-error-code=union-attr
|
|
# mypy: disable-error-code=no-untyped-def
|
|
|
|
|
|
class TestPredicateCompilation:
|
|
"""Test predicate compilation and DSL parsing"""
|
|
|
|
@pytest.fixture
|
|
def policy_loader(self):
|
|
"""Create policy loader for testing"""
|
|
return PolicyLoader()
|
|
|
|
def test_compile_exists_condition(self, policy_loader):
|
|
"""Test compilation of exists() conditions"""
|
|
condition = "exists(IncomeItem[type='Employment'])"
|
|
predicate = policy_loader._compile_condition(condition)
|
|
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_compile_exists_condition_with_filters(self, policy_loader):
|
|
"""Test exists() with complex filters"""
|
|
condition = "exists(IncomeItem[type='SelfEmployment' AND turnover_lt_vat_threshold=true])"
|
|
predicate = policy_loader._compile_condition(condition)
|
|
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_compile_property_conditions(self, policy_loader):
|
|
"""Test compilation of property conditions"""
|
|
conditions = [
|
|
"property_joint_ownership",
|
|
"candidate_FHL",
|
|
"claims_FTCR",
|
|
"claims_remittance_basis",
|
|
"received_estate_income",
|
|
]
|
|
|
|
for condition in conditions:
|
|
predicate = policy_loader._compile_condition(condition)
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_compile_computed_conditions(self, policy_loader):
|
|
"""Test compilation of computed conditions"""
|
|
conditions = [
|
|
"turnover_lt_vat_threshold",
|
|
"turnover_ge_vat_threshold",
|
|
]
|
|
|
|
for condition in conditions:
|
|
predicate = policy_loader._compile_condition(condition)
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_compile_taxpayer_flag_conditions(self, policy_loader):
|
|
"""Test compilation of taxpayer flag conditions"""
|
|
condition = "taxpayer_flag:has_employment"
|
|
predicate = policy_loader._compile_condition(condition)
|
|
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_compile_filing_mode_conditions(self, policy_loader):
|
|
"""Test compilation of filing mode conditions"""
|
|
condition = "filing_mode:paper"
|
|
predicate = policy_loader._compile_condition(condition)
|
|
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_compile_unknown_condition(self, policy_loader):
|
|
"""Test compilation of unknown condition defaults to False"""
|
|
condition = "unknown_condition_type"
|
|
predicate = policy_loader._compile_condition(condition)
|
|
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert result is False # Unknown conditions default to False
|
|
|
|
def test_exists_predicate_creation(self, policy_loader):
|
|
"""Test exists predicate creation with different entity types"""
|
|
entity_types = [
|
|
"IncomeItem",
|
|
"ExpenseItem",
|
|
"PropertyAsset",
|
|
"TrustDistribution",
|
|
]
|
|
|
|
for entity_type in entity_types:
|
|
predicate = policy_loader._create_exists_predicate(
|
|
entity_type, "type='test'"
|
|
)
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_property_predicate_creation(self, policy_loader):
|
|
"""Test property predicate creation"""
|
|
properties = [
|
|
"property_joint_ownership",
|
|
"candidate_FHL",
|
|
"claims_FTCR",
|
|
]
|
|
|
|
for prop in properties:
|
|
predicate = policy_loader._create_property_predicate(prop)
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_computed_predicate_creation(self, policy_loader):
|
|
"""Test computed predicate creation"""
|
|
computations = [
|
|
"turnover_lt_vat_threshold",
|
|
"turnover_ge_vat_threshold",
|
|
]
|
|
|
|
for comp in computations:
|
|
predicate = policy_loader._create_computed_predicate(comp)
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_flag_predicate_creation(self, policy_loader):
|
|
"""Test flag predicate creation"""
|
|
flags = [
|
|
"has_employment",
|
|
"is_self_employed_short",
|
|
"has_property_income",
|
|
"has_foreign_income",
|
|
]
|
|
|
|
for flag in flags:
|
|
predicate = policy_loader._create_flag_predicate(flag)
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_filing_mode_predicate_creation(self, policy_loader):
|
|
"""Test filing mode predicate creation"""
|
|
modes = ["paper", "online", "agent"]
|
|
|
|
for mode in modes:
|
|
predicate = policy_loader._create_filing_mode_predicate(mode)
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_exists_condition_regex_parsing(self, policy_loader):
|
|
"""Test regex parsing of exists conditions"""
|
|
test_cases = [
|
|
(
|
|
"exists(IncomeItem[type='Employment'])",
|
|
"IncomeItem",
|
|
"type='Employment'",
|
|
),
|
|
(
|
|
"exists(ExpenseItem[category='FinanceCosts'])",
|
|
"ExpenseItem",
|
|
"category='FinanceCosts'",
|
|
),
|
|
(
|
|
"exists(PropertyAsset[joint_ownership=true])",
|
|
"PropertyAsset",
|
|
"joint_ownership=true",
|
|
),
|
|
]
|
|
|
|
for condition, expected_entity, expected_filters in test_cases:
|
|
# Test that the regex matches correctly
|
|
import re
|
|
|
|
exists_match = re.match(r"exists\((\w+)\[([^\]]+)\]\)", condition)
|
|
assert exists_match is not None
|
|
assert exists_match.group(1) == expected_entity
|
|
assert exists_match.group(2) == expected_filters
|
|
|
|
def test_condition_whitespace_handling(self, policy_loader):
|
|
"""Test that conditions handle whitespace correctly"""
|
|
conditions_with_whitespace = [
|
|
" exists(IncomeItem[type='Employment']) ",
|
|
"\tproperty_joint_ownership\t",
|
|
"\n taxpayer_flag:has_employment \n",
|
|
]
|
|
|
|
for condition in conditions_with_whitespace:
|
|
predicate = policy_loader._compile_condition(condition)
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_complex_exists_filters(self, policy_loader):
|
|
"""Test exists conditions with complex filter expressions"""
|
|
complex_conditions = [
|
|
"exists(IncomeItem[type='SelfEmployment' AND turnover_lt_vat_threshold=true])",
|
|
"exists(ExpenseItem[category='CapitalAllowances'])",
|
|
"exists(IncomeItem[type IN ['ForeignInterest','ForeignDividends']])",
|
|
]
|
|
|
|
for condition in complex_conditions:
|
|
predicate = policy_loader._compile_condition(condition)
|
|
assert callable(predicate)
|
|
result = predicate("T-001", "2024-25")
|
|
assert isinstance(result, bool)
|
|
|
|
def test_predicate_consistency(self, policy_loader):
|
|
"""Test that predicates return consistent results for same inputs"""
|
|
condition = "exists(IncomeItem[type='Employment'])"
|
|
predicate = policy_loader._compile_condition(condition)
|
|
|
|
# Call multiple times with same inputs
|
|
result1 = predicate("T-001", "2024-25")
|
|
result2 = predicate("T-001", "2024-25")
|
|
result3 = predicate("T-001", "2024-25")
|
|
|
|
# Should be consistent
|
|
assert result1 == result2 == result3
|
|
|
|
def test_predicate_different_inputs(self, policy_loader):
|
|
"""Test predicates with different input combinations"""
|
|
condition = "exists(IncomeItem[type='Employment'])"
|
|
predicate = policy_loader._compile_condition(condition)
|
|
|
|
# Test with different taxpayer IDs and tax years
|
|
test_inputs = [
|
|
("T-001", "2024-25"),
|
|
("T-002", "2024-25"),
|
|
("T-001", "2023-24"),
|
|
("T-999", "2025-26"),
|
|
]
|
|
|
|
for taxpayer_id, tax_year in test_inputs:
|
|
result = predicate(taxpayer_id, tax_year)
|
|
assert isinstance(result, bool)
|
|
|
|
def test_edge_case_conditions(self, policy_loader):
|
|
"""Test edge cases in condition parsing"""
|
|
edge_cases = [
|
|
"", # Empty string
|
|
" ", # Whitespace only
|
|
"exists()", # Empty exists
|
|
"exists(Entity[])", # Empty filter
|
|
"taxpayer_flag:", # Empty flag
|
|
"filing_mode:", # Empty mode
|
|
]
|
|
|
|
for condition in edge_cases:
|
|
predicate = policy_loader._compile_condition(condition)
|
|
assert callable(predicate)
|
|
# Should default to False for malformed conditions
|
|
result = predicate("T-001", "2024-25")
|
|
assert result is False
|