Initial commit
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
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
This commit is contained in:
340
tests/conftest.py
Normal file
340
tests/conftest.py
Normal file
@@ -0,0 +1,340 @@
|
||||
"""Pytest configuration and shared fixtures for coverage tests."""
|
||||
|
||||
# FILE: tests/conftest.py
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from libs.schemas import (
|
||||
CompiledCoveragePolicy,
|
||||
ConflictRules,
|
||||
CoveragePolicy,
|
||||
Defaults,
|
||||
EvidenceItem,
|
||||
Privacy,
|
||||
QuestionTemplates,
|
||||
Role,
|
||||
SchedulePolicy,
|
||||
StatusClassifier,
|
||||
StatusClassifierConfig,
|
||||
TaxYearBoundary,
|
||||
Trigger,
|
||||
)
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop():
|
||||
"""Create an instance of the default event loop for the test session."""
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def temp_config_dir():
|
||||
"""Create temporary config directory with test policy files"""
|
||||
with tempfile.TemporaryDirectory() as temp_dir:
|
||||
config_dir = Path(temp_dir)
|
||||
|
||||
# Create baseline policy
|
||||
baseline_policy = {
|
||||
"version": "1.0",
|
||||
"jurisdiction": "UK",
|
||||
"tax_year": "2024-25",
|
||||
"tax_year_boundary": {"start": "2024-04-06", "end": "2025-04-05"},
|
||||
"defaults": {
|
||||
"confidence_thresholds": {"ocr": 0.82, "extract": 0.85},
|
||||
"date_tolerance_days": 30,
|
||||
},
|
||||
"document_kinds": ["P60", "P11D", "P45"],
|
||||
"triggers": {
|
||||
"SA102": {"any_of": ["exists(IncomeItem[type='Employment'])"]},
|
||||
"SA105": {"any_of": ["exists(IncomeItem[type='UKPropertyRent'])"]},
|
||||
},
|
||||
"schedules": {
|
||||
"SA102": {
|
||||
"evidence": [
|
||||
{
|
||||
"id": "P60",
|
||||
"role": "REQUIRED",
|
||||
"boxes": ["SA102_b1", "SA102_b2"],
|
||||
"acceptable_alternatives": ["P45", "FinalPayslipYTD"],
|
||||
},
|
||||
{
|
||||
"id": "P11D",
|
||||
"role": "CONDITIONALLY_REQUIRED",
|
||||
"condition": "exists(BenefitInKind=true)",
|
||||
"boxes": ["SA102_b9"],
|
||||
},
|
||||
]
|
||||
},
|
||||
"SA105": {
|
||||
"evidence": [
|
||||
{
|
||||
"id": "LettingAgentStatements",
|
||||
"role": "REQUIRED",
|
||||
"boxes": ["SA105_b5"],
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
"status_classifier": {
|
||||
"present_verified": {
|
||||
"min_ocr": 0.82,
|
||||
"min_extract": 0.85,
|
||||
"date_in_year": True,
|
||||
},
|
||||
"present_unverified": {
|
||||
"min_ocr": 0.60,
|
||||
"min_extract": 0.70,
|
||||
"date_in_year_or_tolerance": True,
|
||||
},
|
||||
"conflicting": {"conflict_rules": ["Same doc kind, different totals"]},
|
||||
"missing": {"default": True},
|
||||
},
|
||||
"conflict_resolution": {"precedence": ["P60", "P11D"]},
|
||||
"question_templates": {
|
||||
"default": {
|
||||
"text": "To complete the {schedule} for {tax_year}, we need {evidence}.",
|
||||
"why": "{why}. See guidance: {guidance_doc}.",
|
||||
}
|
||||
},
|
||||
"privacy": {"vector_pii_free": True, "redact_patterns": []},
|
||||
}
|
||||
|
||||
with open(config_dir / "coverage.yaml", "w") as f:
|
||||
yaml.dump(baseline_policy, f)
|
||||
|
||||
yield config_dir
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_policy():
|
||||
"""Create sample compiled policy for testing"""
|
||||
policy = CoveragePolicy(
|
||||
version="1.0",
|
||||
jurisdiction="UK",
|
||||
tax_year="2024-25",
|
||||
tax_year_boundary=TaxYearBoundary(start="2024-04-06", end="2025-04-05"),
|
||||
defaults=Defaults(
|
||||
confidence_thresholds={"ocr": 0.82, "extract": 0.85},
|
||||
date_tolerance_days=30,
|
||||
),
|
||||
document_kinds=["P60", "P11D"],
|
||||
triggers={"SA102": Trigger(any_of=["exists(IncomeItem[type='Employment'])"])},
|
||||
schedules={
|
||||
"SA102": SchedulePolicy(
|
||||
evidence=[
|
||||
EvidenceItem(
|
||||
id="P60",
|
||||
role=Role.REQUIRED,
|
||||
boxes=["SA102_b1", "SA102_b2"],
|
||||
acceptable_alternatives=["P45", "FinalPayslipYTD"],
|
||||
)
|
||||
]
|
||||
)
|
||||
},
|
||||
status_classifier=StatusClassifierConfig(
|
||||
present_verified=StatusClassifier(min_ocr=0.82, min_extract=0.85),
|
||||
present_unverified=StatusClassifier(min_ocr=0.60, min_extract=0.70),
|
||||
conflicting=StatusClassifier(conflict_rules=[]),
|
||||
missing=StatusClassifier(conflict_rules=[]),
|
||||
),
|
||||
conflict_resolution=ConflictRules(precedence=["P60"]),
|
||||
question_templates=QuestionTemplates(default={"text": "test", "why": "test"}),
|
||||
privacy=Privacy(vector_pii_free=True, redact_patterns=[]),
|
||||
)
|
||||
|
||||
# Create compiled policy with mock predicates
|
||||
compiled = CompiledCoveragePolicy(
|
||||
policy=policy,
|
||||
compiled_predicates={
|
||||
"exists(IncomeItem[type='Employment'])": lambda tid, ty: True
|
||||
},
|
||||
compiled_at=datetime.utcnow(),
|
||||
hash="test-hash",
|
||||
source_files=["test.yaml"],
|
||||
)
|
||||
|
||||
return compiled
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_kg_client():
|
||||
"""Create mock KG client for testing"""
|
||||
client = AsyncMock()
|
||||
|
||||
# Default successful evidence finding
|
||||
client.run_query = AsyncMock(
|
||||
return_value=[
|
||||
{
|
||||
"doc_id": "DOC-P60-001",
|
||||
"kind": "P60",
|
||||
"page": 1,
|
||||
"bbox": {"x": 100, "y": 200, "width": 300, "height": 50},
|
||||
"ocr_confidence": 0.95,
|
||||
"extract_confidence": 0.92,
|
||||
"date": "2024-05-15",
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_rag_client():
|
||||
"""Create mock RAG client for testing"""
|
||||
client = AsyncMock()
|
||||
|
||||
# Default citation search results
|
||||
client.search = AsyncMock(
|
||||
return_value=[
|
||||
{
|
||||
"doc_id": "SA102-Notes-2025",
|
||||
"locator": "p.3 §1.1",
|
||||
"url": "https://docs.local/SA102-Notes-2025#p3s1.1",
|
||||
}
|
||||
]
|
||||
)
|
||||
|
||||
return client
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_session():
|
||||
"""Create mock database session for testing"""
|
||||
session = AsyncMock()
|
||||
|
||||
# Mock database operations
|
||||
session.add = MagicMock()
|
||||
session.commit = AsyncMock()
|
||||
session.rollback = AsyncMock()
|
||||
session.close = AsyncMock()
|
||||
|
||||
return session
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_policy_loader():
|
||||
"""Create mock policy loader for testing"""
|
||||
loader = MagicMock()
|
||||
|
||||
# Mock policy loading
|
||||
loader.load_policy = MagicMock()
|
||||
loader.compile_predicates = MagicMock()
|
||||
loader.validate_policy = MagicMock()
|
||||
|
||||
return loader
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup_test_environment():
|
||||
"""Set up test environment variables"""
|
||||
original_env = os.environ.copy()
|
||||
|
||||
# Set test environment variables
|
||||
os.environ.update(
|
||||
{
|
||||
"ENVIRONMENT": "test",
|
||||
"CONFIG_DIR": "/tmp/test-config",
|
||||
"NEO4J_URI": "bolt://localhost:7687",
|
||||
"NEO4J_USER": "neo4j",
|
||||
"NEO4J_PASSWORD": "testpass",
|
||||
"POSTGRES_URL": "postgresql://postgres:postgres@localhost:5432/test_db",
|
||||
"QDRANT_URL": "http://localhost:6333",
|
||||
"VAULT_URL": "http://localhost:8200",
|
||||
"VAULT_TOKEN": "test-token",
|
||||
}
|
||||
)
|
||||
|
||||
yield
|
||||
|
||||
# Restore original environment
|
||||
os.environ.clear()
|
||||
os.environ.update(original_env)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_evidence_data():
|
||||
"""Sample evidence data for testing"""
|
||||
return [
|
||||
{
|
||||
"doc_id": "DOC-P60-001",
|
||||
"kind": "P60",
|
||||
"page": 1,
|
||||
"bbox": {"x": 100, "y": 200, "width": 300, "height": 50},
|
||||
"ocr_confidence": 0.95,
|
||||
"extract_confidence": 0.92,
|
||||
"date": "2024-05-15T10:00:00Z",
|
||||
},
|
||||
{
|
||||
"doc_id": "DOC-P11D-001",
|
||||
"kind": "P11D",
|
||||
"page": 1,
|
||||
"bbox": {"x": 50, "y": 100, "width": 400, "height": 60},
|
||||
"ocr_confidence": 0.88,
|
||||
"extract_confidence": 0.90,
|
||||
"date": "2024-07-06T14:30:00Z",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_citation_data():
|
||||
"""Sample citation data for testing"""
|
||||
return [
|
||||
{
|
||||
"rule_id": "UK.SA102.P60.Required",
|
||||
"doc_id": "SA102-Notes-2025",
|
||||
"locator": "p.3 §1.1",
|
||||
"url": "https://docs.local/SA102-Notes-2025#p3s1.1",
|
||||
},
|
||||
{
|
||||
"rule_id": "UK.SA102.P11D.Conditional",
|
||||
"doc_id": "SA102-Notes-2025",
|
||||
"locator": "p.5 §2.3",
|
||||
"url": "https://docs.local/SA102-Notes-2025#p5s2.3",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# Pytest markers for test categorization
|
||||
pytest_plugins: list[str] = []
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
"""Configure pytest markers"""
|
||||
config.addinivalue_line("markers", "unit: mark test as a unit test")
|
||||
config.addinivalue_line("markers", "integration: mark test as an integration test")
|
||||
config.addinivalue_line("markers", "e2e: mark test as an end-to-end test")
|
||||
config.addinivalue_line("markers", "slow: mark test as slow running")
|
||||
|
||||
|
||||
def pytest_collection_modifyitems(config, items):
|
||||
"""Automatically mark tests based on their location"""
|
||||
for item in items:
|
||||
# Mark tests based on directory structure
|
||||
if "unit" in str(item.fspath):
|
||||
item.add_marker(pytest.mark.unit)
|
||||
elif "integration" in str(item.fspath):
|
||||
item.add_marker(pytest.mark.integration)
|
||||
elif "e2e" in str(item.fspath):
|
||||
item.add_marker(pytest.mark.e2e)
|
||||
|
||||
# Mark async tests
|
||||
if asyncio.iscoroutinefunction(item.function):
|
||||
item.add_marker(pytest.mark.asyncio)
|
||||
Reference in New Issue
Block a user