""" Tests for Phase 9: validation service. """ import pytest from src.models.normalized_template import ( NormalizedDocument, NormalizedField, NormalizedRole, NormalizedTemplate, ) from src.services.validation_service import ( ValidationResult, compare_field_counts, validate_template, ) from src.reports.report_builder import ( MigrationReport, MigrationStatus, build_blocked_report, build_error_report, build_skipped_report, build_success_report, ) def _make_template(**kwargs) -> NormalizedTemplate: defaults = dict( name="Test Template", roles=[NormalizedRole(name="Signer 1", order=1)], fields=[ NormalizedField( type="signature", label="sig1", page=1, x=100, y=500, width=140, height=28, role_name="Signer 1", ) ], documents=[NormalizedDocument(name="test.pdf", checksum_sha256="abc", source_path="/fake.pdf")], ) defaults.update(kwargs) return NormalizedTemplate(**defaults) class TestValidationService: def test_valid_template_passes(self): t = _make_template() result = validate_template(t) assert result.is_ok() assert result.blockers == [] def test_no_recipients_is_blocker(self): t = _make_template(roles=[]) result = validate_template(t) assert result.has_blockers() assert any("recipient" in b.lower() or "role" in b.lower() for b in result.blockers) def test_no_documents_is_blocker(self): t = _make_template(documents=[]) result = validate_template(t) assert result.has_blockers() assert any("document" in b.lower() for b in result.blockers) def test_no_fields_is_warning(self): t = _make_template(fields=[]) result = validate_template(t) assert result.is_ok() # not a blocker assert any("0 field" in w or "empty" in w.lower() for w in result.warnings) def test_no_signature_field_is_warning(self): t = _make_template(fields=[ NormalizedField(type="text", label="name", page=1, x=0, y=0, width=120, height=24, role_name="Signer 1") ]) result = validate_template(t) assert result.is_ok() assert any("signature" in w.lower() for w in result.warnings) def test_field_with_unknown_role_is_warning(self): t = _make_template(fields=[ NormalizedField( type="signature", label="sig1", page=1, x=0, y=0, width=140, height=28, role_name="NonExistentRole" ) ]) result = validate_template(t) assert result.is_ok() assert any("role" in w.lower() or "assign" in w.lower() for w in result.warnings) def test_unsupported_features_become_warnings(self): t = _make_template(unsupported_features=["Conditional HIDE action", "Webhook associations"]) result = validate_template(t) assert result.is_ok() assert len([w for w in result.warnings if "Unsupported" in w or "manual" in w.lower()]) >= 2 def test_validation_result_all_issues(self): r = ValidationResult(blockers=["blocker1"], warnings=["warn1"]) issues = r.all_issues() assert any("BLOCKER" in i for i in issues) assert any("WARNING" in i for i in issues) class TestCompareFieldCounts: def test_matching_counts_no_warnings(self): t = _make_template(fields=[ NormalizedField(type="signature", label="sig1", page=1, x=0, y=0, width=140, height=28, role_name="Signer 1") ]) ds = { "recipients": { "signers": [{"tabs": {"signHereTabs": [{"tabLabel": "sig1"}]}}] } } result = compare_field_counts(t, ds) assert result.is_ok() def test_mismatched_counts_warns(self): t = _make_template(fields=[ NormalizedField(type="signature", label="s1", page=1, x=0, y=0, width=140, height=28, role_name="Signer 1"), NormalizedField(type="text", label="t1", page=1, x=0, y=50, width=120, height=24, role_name="Signer 1"), ]) ds = {"recipients": {"signers": [{"tabs": {"signHereTabs": [{}]}}]}} result = compare_field_counts(t, ds) assert any("mismatch" in w.lower() or "count" in w.lower() for w in result.warnings) def test_zero_tabs_with_fields_warns(self): t = _make_template() ds = {"recipients": {"signers": []}} result = compare_field_counts(t, ds) assert result.warnings # should warn about 0 tabs class TestReportBuilder: def test_success_report(self): r = build_success_report("My Template", "src_001", "ds_001", warnings=[]) assert r.status == MigrationStatus.SUCCESS assert r.docusign_template_id == "ds_001" def test_success_with_warnings(self): r = build_success_report("My Template", "src_001", "ds_001", warnings=["some warning"]) assert r.status == MigrationStatus.SUCCESS_WITH_WARNINGS def test_blocked_report(self): r = build_blocked_report("T", "id1", blockers=["no docs"], warnings=[]) assert r.status == MigrationStatus.BLOCKED assert r.blockers == ["no docs"] def test_error_report(self): r = build_error_report("T", "id1", error="Connection refused") assert r.status == MigrationStatus.ERROR assert "Connection" in r.error def test_skipped_report(self): r = build_skipped_report("T", "id1", reason="already migrated") assert r.status == MigrationStatus.SKIPPED def test_migration_report_summary(self): report = MigrationReport() report.add(build_success_report("T1", "1", "ds1", [])) report.add(build_success_report("T2", "2", "ds2", ["warn"])) report.add(build_error_report("T3", "3", "fail")) summary = report.summary() assert summary["total"] == 3 assert summary.get("success", 0) == 1 assert summary.get("error", 0) == 1 def test_report_to_dict(self): report = MigrationReport() report.add(build_success_report("T1", "1", "ds1", [])) d = report.to_dict() assert "summary" in d assert "templates" in d assert d["templates"][0]["template_name"] == "T1" def test_report_has_errors(self): report = MigrationReport() report.add(build_error_report("T", "1", "err")) assert report.has_errors() def test_report_no_errors(self): report = MigrationReport() report.add(build_success_report("T", "1", "ds1", [])) assert not report.has_errors()