intermittent commit
This commit is contained in:
151
tests/test_partial_validation.py
Normal file
151
tests/test_partial_validation.py
Normal file
@@ -0,0 +1,151 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from govoplan_campaign.backend import router
|
||||
from govoplan_campaign.backend.campaign.models import CampaignConfig
|
||||
from govoplan_campaign.backend.campaign.validation import validate_campaign_config
|
||||
from govoplan_campaign.backend.persistence.versions import validate_campaign_partial
|
||||
|
||||
|
||||
class CampaignPartialValidationTests(unittest.TestCase):
|
||||
def test_partial_validation_reports_counts_across_sections(self) -> None:
|
||||
result = validate_campaign_partial({
|
||||
"campaign": {},
|
||||
"recipients": {"from": {}},
|
||||
"entries": {"source": {}, "mapping": {}},
|
||||
"template": {"body_mode": "both"},
|
||||
"attachments": {},
|
||||
"delivery": {"rate_limit": {"messages_per_minute": 0}},
|
||||
})
|
||||
|
||||
codes = {item["code"] for item in result["issues"]}
|
||||
self.assertFalse(result["ok"])
|
||||
self.assertEqual(3, result["error_count"])
|
||||
self.assertIn("missing_campaign_id", codes)
|
||||
self.assertIn("missing_email_mapping", codes)
|
||||
self.assertIn("missing_template_text_body", codes)
|
||||
self.assertIn("missing_attachment_base_path", codes)
|
||||
self.assertIn("invalid_rate_limit", codes)
|
||||
|
||||
def test_partial_validation_filters_to_requested_section(self) -> None:
|
||||
result = validate_campaign_partial(
|
||||
{
|
||||
"campaign": {},
|
||||
"template": {"body_mode": "text"},
|
||||
"delivery": {"rate_limit": {"messages_per_minute": "fast"}},
|
||||
},
|
||||
section="template",
|
||||
)
|
||||
|
||||
self.assertTrue(result["ok"])
|
||||
self.assertEqual("template", result["section"])
|
||||
self.assertEqual(0, result["error_count"])
|
||||
self.assertEqual({"missing_subject", "missing_template_text_body"}, {item["code"] for item in result["issues"]})
|
||||
|
||||
def test_address_lookup_returns_unavailable_when_addresses_capability_absent(self) -> None:
|
||||
with patch.object(router, "_get_campaign_for_principal", return_value=None), patch.object(router, "_registry_capability", return_value=None):
|
||||
response = router.lookup_campaign_addresses("campaign-1", query="", session=object(), principal=object())
|
||||
|
||||
self.assertFalse(response.available)
|
||||
self.assertEqual(response.candidates, [])
|
||||
|
||||
def test_address_lookup_accepts_empty_query_for_suggestion_preload(self) -> None:
|
||||
class LookupCapability:
|
||||
def lookup(self, _session, _principal, *, query: str, limit: int):
|
||||
self.query = query
|
||||
self.limit = limit
|
||||
return [{
|
||||
"contact_id": "contact-1",
|
||||
"address_book_id": "book-1",
|
||||
"display_name": "Ada Lovelace",
|
||||
"email": "ada@example.local",
|
||||
"email_label": "work",
|
||||
"tags": [],
|
||||
"source_kind": "local",
|
||||
"provenance": {"module": "addresses"},
|
||||
}]
|
||||
|
||||
capability = LookupCapability()
|
||||
with patch.object(router, "_get_campaign_for_principal", return_value=None), patch.object(router, "_registry_capability", return_value=capability):
|
||||
response = router.lookup_campaign_addresses("campaign-1", query="", limit=100, session=object(), principal=object())
|
||||
|
||||
self.assertEqual(capability.query, "")
|
||||
self.assertEqual(capability.limit, 100)
|
||||
self.assertTrue(response.available)
|
||||
self.assertEqual(response.candidates[0].email, "ada@example.local")
|
||||
|
||||
|
||||
class CampaignSemanticValidationTests(unittest.TestCase):
|
||||
def test_semantic_validation_reports_zip_delivery_and_external_mapping_issues(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
(root / "recipients.csv").write_text("email\nada@example.local\n", encoding="utf-8")
|
||||
campaign_file = root / "campaign.json"
|
||||
config = CampaignConfig.model_validate({
|
||||
"version": "1.0",
|
||||
"campaign": {"id": "campaign-1", "name": "Campaign", "mode": "send"},
|
||||
"fields": [
|
||||
{"name": "pin", "type": "string", "can_override": False},
|
||||
{"name": "secret", "type": "password"},
|
||||
],
|
||||
"global_values": {"unknown": "value"},
|
||||
"recipients": {
|
||||
"from": [{"email": "sender@example.local"}],
|
||||
"to": [{"email": "recipient@example.local"}],
|
||||
},
|
||||
"template": {"subject": "Subject", "text": "Body", "body_mode": "text"},
|
||||
"attachments": {
|
||||
"base_path": "attachments",
|
||||
"zip": {
|
||||
"enabled": True,
|
||||
"archives": [
|
||||
{
|
||||
"id": "main",
|
||||
"name": "bundle.zip",
|
||||
"standard": True,
|
||||
"password_enabled": True,
|
||||
"password_field": "secret",
|
||||
"password_scope": "global",
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
"entries": {
|
||||
"source": {"type": "csv", "path": "recipients.csv", "has_header": True},
|
||||
"mapping": {"fields.pin": "missing_pin", "fields.unknown": "missing_unknown"},
|
||||
"defaults": {
|
||||
"attachments": [
|
||||
{
|
||||
"base_dir": "",
|
||||
"file_filter": "*.pdf",
|
||||
"zip": {"archive_id": "missing-archive"},
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
"delivery": {"imap_append_sent": {"enabled": True}},
|
||||
})
|
||||
|
||||
report = validate_campaign_config(config, campaign_file=campaign_file, check_files=True)
|
||||
|
||||
codes = {issue.code for issue in report.issues}
|
||||
self.assertFalse(report.ok)
|
||||
self.assertEqual("external:csv", report.entries_mode)
|
||||
self.assertIsNone(report.entries_count)
|
||||
self.assertIn("unknown_global_value", codes)
|
||||
self.assertIn("zip_global_password_value_missing", codes)
|
||||
self.assertIn("zip_archive_unknown", codes)
|
||||
self.assertIn("delivery_imap_enabled_without_server_imap", codes)
|
||||
self.assertIn("missing_smtp_config", codes)
|
||||
self.assertIn("unknown_mapping_target", codes)
|
||||
self.assertIn("mapping_target_not_overridable", codes)
|
||||
self.assertIn("mapping_columns_missing", codes)
|
||||
self.assertIn("attachments_base_path_not_found", codes)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user