Files
2026-08-09 17:26:13 -04:00

504 lines
23 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Behavior and boundary tests for the bundled Sent utility scripts."""
from __future__ import annotations
import copy
import importlib.util
import json
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
from types import ModuleType
ROOT = Path(__file__).resolve().parents[1]
SKILLS = ROOT / "packages" / "sent" / "skills"
MDR_ROOT = SKILLS / "messaging-performance-analyzer" / "scripts"
TEN_DLC_ROOT = SKILLS / "sms-10dlc-registration" / "scripts"
TEMPLATE_ROOT = SKILLS / "waba-template-author" / "scripts"
def load_module(name: str, path: Path) -> ModuleType:
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise RuntimeError(f"cannot import {path}")
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
return module
MDR = load_module("sent_mdr_analyzer", MDR_ROOT / "analyze_mdr_funnel.py")
EVIDENCE = load_module("sent_10dlc_evidence", TEN_DLC_ROOT / "validate_10dlc_packet.py")
CAMPAIGN = load_module("sent_campaign_validator", TEN_DLC_ROOT / "validate_campaign_payload.py")
TEMPLATE = load_module("sent_template_linter", TEMPLATE_ROOT / "lint_waba_template.py")
def read_fixture(root: Path, name: str) -> object:
return json.loads((root / "fixtures" / name).read_text(encoding="utf-8"))
def campaign_base(use_case: str = "ACCOUNT_NOTIFICATION", samples: int = 1) -> dict:
return {
"campaign": {
"name": "Synthetic campaign",
"description": "Synthetic account notifications for opted-in recipients.",
"type": "App",
"useCases": [
{
"messagingUseCaseUs": use_case,
"sampleMessages": [f"Acme Example: Sample notification {index}." for index in range(samples)],
}
],
"volume": "2000",
},
"sandbox": True,
}
def template_base() -> dict:
return {
"category": "UTILITY",
"language": "en_US",
"definition": {
"header": None,
"body": {
"multiChannel": {
"type": "body",
"template": "Hi {{0:variable}}.",
"variables": [
{"id": 0, "name": "name", "type": "variable", "props": {"sample": "Avery"}}
],
}
},
"footer": None,
"buttons": [],
"definitionVersion": "1.0",
"authenticationConfig": None,
},
"creation_source": "from-api",
"submit_for_review": False,
"sandbox": True,
}
def button(button_type: str, index: int = 1) -> dict:
props: dict[str, object] = {"text": f"Action {index}"}
if button_type == "QUICK_REPLY":
props["quickReplyType"] = "custom"
elif button_type == "URL":
props.update(urlType="static", url=f"https://example.com/{index}")
elif button_type in {"VOICE_CALL", "PHONE_NUMBER"}:
props.update(countryCode="US", phoneNumber="+12025550100")
elif button_type == "COPY_CODE":
props["offerCode"] = "482193"
return {"id": index, "type": button_type, "props": props}
class DirectMDRTests(unittest.TestCase):
def test_loads_list_and_wrapped_json(self) -> None:
records = [{"message_id": "msg_test_001", "status": "DELIVERED"}]
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "messages.json"
for value in (records, {"messages": records}):
path.write_text(json.dumps(value), encoding="utf-8")
self.assertEqual(MDR._load_messages(path), records)
def test_rejects_malformed_json_and_wrong_root(self) -> None:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "messages.json"
for value in ("{", json.dumps({"unexpected": []})):
path.write_text(value, encoding="utf-8")
with self.subTest(value=value), self.assertRaises(MDR.InputError):
MDR._load_messages(path)
def test_latest_only_record_never_infers_earlier_transitions(self) -> None:
normalized = MDR.normalize_record(
{"message_id": "msg_test_001", "channel": "whatsapp", "status": "DELIVERED"},
1,
)
self.assertEqual(normalized.observed_statuses, ("DELIVERED",))
self.assertFalse(normalized.has_history)
report = MDR.build_report([normalized], threshold=20.0, include_errors=False)
group = report["groups"][0]
self.assertEqual(group["progression"]["observed"], {"QUEUED": 0, "ROUTED": 0, "SENT": 0, "DELIVERED": 1})
self.assertTrue(all(item["dropoff_pct"] is None for item in group["progression"]["transitions"]))
self.assertIsNone(report["healthy"])
def test_groups_by_channel_and_direction_with_unknown_buckets(self) -> None:
records = [
{"channel": "sms", "direction": "outbound", "status": "DELIVERED"},
{"channel": "whatsapp", "direction": "inbound", "status": "RECEIVED"},
{"channel": "satellite", "status": "SENT"},
]
report = MDR.analyze(records, threshold=20.0, include_errors=False)
keys = {(group["channel"], group["direction"]) for group in report["groups"]}
self.assertEqual(keys, {("sms", "outbound"), ("whatsapp", "inbound"), ("unknown", "unknown")})
self.assertEqual(report["totals"]["input"], 3)
self.assertEqual(sum(group["totals"]["input"] for group in report["groups"]), 3)
self.assertEqual(sum(report["totals"][name] for name in MDR.OUTCOMES), 3)
def test_full_history_drives_transition_rates_and_terminal_outcomes(self) -> None:
records = [
{
"channel": "whatsapp",
"direction": "outbound",
"statuses": [{"stage": stage} for stage in ("QUEUED", "ROUTED", "SENT", "DELIVERED", "READ")],
},
{
"channel": "whatsapp",
"direction": "outbound",
"statuses": [{"stage": stage} for stage in ("QUEUED", "ROUTED", "SENT", "FAILED")],
},
{
"channel": "whatsapp",
"direction": "outbound",
"statuses": [{"stage": stage} for stage in ("QUEUED", "ROUTED", "SENT")],
},
]
report = MDR.analyze(records, threshold=20.0, include_errors=False)
group = report["groups"][0]
self.assertEqual(group["totals"]["progression"], 1)
self.assertEqual(group["totals"]["terminal_failure"], 1)
self.assertEqual(group["totals"]["deferred"], 1)
sent_to_delivered = next(
item for item in group["progression"]["transitions"] if item["from"] == "SENT"
)
self.assertEqual(sent_to_delivered["eligible"], 2)
self.assertEqual(sent_to_delivered["completed"], 1)
self.assertEqual(sent_to_delivered["dropoff_pct"], 50.0)
self.assertFalse(report["healthy"])
def test_sms_stops_at_delivery_and_read_is_channel_engagement(self) -> None:
records = [
{
"channel": "sms",
"direction": "outbound",
"statuses": [{"stage": stage} for stage in ("QUEUED", "ROUTED", "SENT", "DELIVERED")],
},
{
"channel": "whatsapp",
"direction": "outbound",
"statuses": [{"stage": stage} for stage in ("QUEUED", "ROUTED", "SENT", "DELIVERED", "READ")],
},
]
report = MDR.analyze(records, threshold=20.0, include_errors=False)
groups = {(group["channel"], group["direction"]): group for group in report["groups"]}
self.assertIsNone(groups[("sms", "outbound")]["engagement"])
self.assertEqual(groups[("sms", "outbound")]["progression"]["observed"]["DELIVERED"], 1)
self.assertEqual(groups[("whatsapp", "outbound")]["engagement"]["read_rate_pct"], 100.0)
def test_zero_denominators_render_as_na_and_json_null(self) -> None:
report = MDR.analyze(
[{"channel": "rcs", "direction": "outbound", "status": "DELIVERED"}],
threshold=20.0,
include_errors=False,
)
rendered = MDR.render_text(report)
self.assertIn("N/A", rendered)
self.assertIn('"dropoff_pct": null', json.dumps(report))
def test_error_summary_only_counts_failed_records(self) -> None:
records = [
{"status": "FAILED", "description": "ERR_ROUTE_DENIED then ERR_ROUTE_DENIED"},
{"statuses": [{"stage": "FAILED"}], "description": "ERR_CONSENT_BLOCKED"},
{"status": "DELIVERED", "description": "ERR_TEMPLATE_PARAMS_INVALID"},
]
self.assertEqual(
MDR.summarise_errors(records),
{"ERR_ROUTE_DENIED": 1, "ERR_CONSENT_BLOCKED": 1},
)
def test_malformed_and_unknown_records_are_retained_in_totals(self) -> None:
report = MDR.analyze(
["not-an-object", {"channel": "sms", "direction": "outbound", "status": "MYSTERY"}],
threshold=20.0,
include_errors=True,
)
self.assertEqual(report["totals"]["malformed"], 1)
self.assertEqual(report["totals"]["unknown"], 1)
self.assertEqual(report["totals"]["usable"], 0)
self.assertIsNone(report["healthy"])
self.assertEqual(len(report["diagnostics"]), 2)
class DirectEvidencePacketTests(unittest.TestCase):
def setUp(self) -> None:
self.packet = read_fixture(TEN_DLC_ROOT, "good.json")
def test_checked_in_fixture_is_valid(self) -> None:
self.assertEqual(EVIDENCE.validate(self.packet), [])
def test_wrong_root_and_invalid_records_are_diagnostic(self) -> None:
self.assertIn("<root>: must be a JSON object", "\n".join(EVIDENCE.validate([])))
packet = copy.deepcopy(self.packet)
packet["use_cases"] = ["not-an-object"]
self.assertIn("use_cases[0]: must be an object", "\n".join(EVIDENCE.validate(packet)))
def test_sample_count_and_length_boundaries(self) -> None:
samples = self.packet["use_cases"][0]["sample_messages"]
samples[:] = ["a" * 1024] * 5
self.assertEqual(EVIDENCE.validate(self.packet), [])
samples.append("sixth")
self.assertIn("must contain 15 samples", "\n".join(EVIDENCE.validate(self.packet)))
samples[:] = ["a" * 1025]
self.assertIn("at most 1,024 characters", "\n".join(EVIDENCE.validate(self.packet)))
class DirectCampaignTests(unittest.TestCase):
def test_all_use_cases_and_policy_minimum(self) -> None:
for use_case in CAMPAIGN.USE_CASES:
samples = 2 if use_case in CAMPAIGN.POLICY_TWO_SAMPLE_CASES else 1
with self.subTest(use_case=use_case):
self.assertEqual(CAMPAIGN.validate(campaign_base(use_case, samples)), [])
def test_wrong_root_and_invalid_records_are_diagnostic(self) -> None:
self.assertIn("<root>: must be a JSON object", "\n".join(CAMPAIGN.validate([])))
payload = campaign_base()
payload["campaign"]["useCases"] = ["not-an-object"]
self.assertIn("useCases[0]: must be an object", "\n".join(CAMPAIGN.validate(payload)))
def test_sample_count_length_and_volume_boundaries(self) -> None:
for count, valid in ((0, False), (1, True), (5, True), (6, False)):
issues = CAMPAIGN.validate(campaign_base("ACCOUNT_NOTIFICATION", count))
self.assertEqual(not issues, valid, (count, issues))
payload = campaign_base()
payload["campaign"]["useCases"][0]["sampleMessages"] = ["a" * 1024]
self.assertEqual(CAMPAIGN.validate(payload), [])
payload["campaign"]["useCases"][0]["sampleMessages"] = ["a" * 1025]
self.assertIn("at most 1,024 characters", "\n".join(CAMPAIGN.validate(payload)))
for volume in ("1999", "2000"):
payload = campaign_base()
payload["campaign"]["volume"] = volume
self.assertEqual(CAMPAIGN.validate(payload), [])
payload["campaign"]["volume"] = 2000
self.assertIn("numeric string", "\n".join(CAMPAIGN.validate(payload)))
def test_marketing_and_mixed_require_two_samples(self) -> None:
for use_case in CAMPAIGN.POLICY_TWO_SAMPLE_CASES:
with self.subTest(use_case=use_case):
self.assertTrue(CAMPAIGN.validate(campaign_base(use_case, 1)))
self.assertEqual(CAMPAIGN.validate(campaign_base(use_case, 2)), [])
def test_exact_camel_case(self) -> None:
payload = campaign_base()
payload["campaign"]["use_cases"] = payload["campaign"].pop("useCases")
self.assertIn("exact camelCase", "\n".join(CAMPAIGN.validate(payload)))
class DirectTemplateTests(unittest.TestCase):
def assert_valid(self, payload: dict) -> None:
result = TEMPLATE.lint_template(payload)
self.assertFalse(result.errors, result.errors)
def assert_invalid(self, payload: object, fragment: str) -> None:
result = TEMPLATE.lint_template(payload)
rendered = "\n".join(f"{field}: {message}" for field, message in result.errors)
self.assertIn(fragment, rendered)
def test_wrong_root_and_cloud_api_shape(self) -> None:
self.assert_invalid([], "must be a JSON object")
self.assert_invalid({"category": "UTILITY", "components": []}, "Meta Cloud API")
def test_button_boundaries_and_all_kinds(self) -> None:
payload = template_base()
payload["definition"]["buttons"] = [
button(kind, index) for index, kind in enumerate(sorted(TEMPLATE.VALID_BUTTON_TYPES), 1)
]
self.assert_valid(payload)
payload = template_base()
payload["definition"]["buttons"] = [button("QUICK_REPLY", index) for index in range(1, 11)]
self.assert_valid(payload)
payload["definition"]["buttons"].append(button("QUICK_REPLY", 11))
self.assert_invalid(payload, "at most 10 buttons")
payload = template_base()
payload["definition"]["buttons"] = [button("URL", index) for index in range(1, 4)]
self.assert_invalid(payload, "URL allows at most 2")
def test_body_and_authentication_boundaries(self) -> None:
payload = template_base()
content = payload["definition"]["body"]["multiChannel"]
content["template"] = "a" * 1024
content["variables"] = []
self.assert_valid(payload)
content["template"] += "a"
self.assert_invalid(payload, "1,024-character")
payload = template_base()
payload["category"] = "AUTHENTICATION"
payload["definition"]["buttons"] = [button("COPY_CODE")]
payload["definition"]["authenticationConfig"] = {
"addSecurityRecommendation": True,
"codeExpirationMinutes": 90,
}
self.assert_valid(payload)
payload["definition"]["authenticationConfig"]["codeExpirationMinutes"] = 91
self.assert_invalid(payload, "1 to 90")
class PublicCliBehaviorTests(unittest.TestCase):
def run_cli(self, script: Path, *arguments: object) -> subprocess.CompletedProcess[str]:
return subprocess.run(
[sys.executable, str(script), *(str(argument) for argument in arguments)],
cwd=script.parent,
text=True,
capture_output=True,
check=False,
)
def assert_exit(
self,
script: Path,
fixture: Path,
expected: int,
fragment: str,
stream: str,
) -> None:
result = self.run_cli(script, fixture)
self.assertEqual(result.returncode, expected, (result.stdout, result.stderr))
self.assertIn(fragment, getattr(result, stream))
def test_checked_in_success_and_policy_failure_contracts(self) -> None:
cases = (
(MDR_ROOT / "analyze_mdr_funnel.py", MDR_ROOT / "fixtures/good.json", 0, "OK:", "stdout"),
(MDR_ROOT / "analyze_mdr_funnel.py", MDR_ROOT / "fixtures/bad.json", 3, "FAIL:", "stderr"),
(TEN_DLC_ROOT / "validate_10dlc_packet.py", TEN_DLC_ROOT / "fixtures/good.json", 0, "OK", "stdout"),
(TEN_DLC_ROOT / "validate_10dlc_packet.py", TEN_DLC_ROOT / "fixtures/bad.json", 1, "schema_version", "stderr"),
(TEN_DLC_ROOT / "validate_campaign_payload.py", TEN_DLC_ROOT / "fixtures/campaign_good.json", 0, "OK", "stdout"),
(TEN_DLC_ROOT / "validate_campaign_payload.py", TEN_DLC_ROOT / "fixtures/campaign_bad.json", 1, "exact camelCase", "stderr"),
(TEMPLATE_ROOT / "lint_waba_template.py", TEMPLATE_ROOT / "fixtures/utility_good.json", 0, "OK", "stdout"),
(TEMPLATE_ROOT / "lint_waba_template.py", TEMPLATE_ROOT / "fixtures/utility_bad.json", 1, "Meta Cloud API", "stderr"),
)
for script, fixture, expected, fragment, stream in cases:
with self.subTest(script=script.name, fixture=fixture.name):
self.assert_exit(script, fixture, expected, fragment, stream)
def test_malformed_json_has_deterministic_input_diagnostic(self) -> None:
cases = (
(MDR_ROOT / "analyze_mdr_funnel.py", 2),
(TEN_DLC_ROOT / "validate_10dlc_packet.py", 1),
(TEN_DLC_ROOT / "validate_campaign_payload.py", 1),
(TEMPLATE_ROOT / "lint_waba_template.py", 1),
)
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "malformed.json"
path.write_text("{", encoding="utf-8")
for script, expected in cases:
with self.subTest(script=script.name):
result = self.run_cli(script, path)
self.assertEqual(result.returncode, expected, (result.stdout, result.stderr))
self.assertIn("invalid JSON", result.stderr)
self.assertNotIn("Traceback", result.stderr)
def test_missing_files_have_documented_exit_codes(self) -> None:
cases = (
(MDR_ROOT / "analyze_mdr_funnel.py", 2),
(TEN_DLC_ROOT / "validate_10dlc_packet.py", 1),
(TEN_DLC_ROOT / "validate_campaign_payload.py", 1),
(TEMPLATE_ROOT / "lint_waba_template.py", 1),
)
missing = ROOT / "synthetic-missing-input.json"
for script, expected in cases:
with self.subTest(script=script.name):
result = self.run_cli(script, missing)
self.assertEqual(result.returncode, expected, (result.stdout, result.stderr))
self.assertTrue(result.stderr.strip())
self.assertNotIn("Traceback", result.stderr)
def test_mdr_rejects_unsupported_extension_and_invalid_records(self) -> None:
with tempfile.TemporaryDirectory() as directory:
unsupported = Path(directory) / "messages.txt"
unsupported.write_text("[]", encoding="utf-8")
result = self.run_cli(MDR_ROOT / "analyze_mdr_funnel.py", unsupported)
self.assertEqual(result.returncode, 2)
self.assertIn("unsupported file extension", result.stderr)
invalid = Path(directory) / "messages.json"
invalid.write_text('["not-an-object"]', encoding="utf-8")
result = self.run_cli(MDR_ROOT / "analyze_mdr_funnel.py", invalid)
self.assertEqual(result.returncode, 2)
self.assertIn("no usable analysis cohort", result.stderr)
self.assertNotIn("Traceback", result.stderr)
def test_mdr_exact_threshold_is_healthy(self) -> None:
records = [
*(
{
"message_id": f"msg_test_{index}",
"channel": "whatsapp",
"direction": "outbound",
"statuses": [
{"stage": "QUEUED"},
{"stage": "ROUTED"},
{"stage": "SENT"},
{"stage": "DELIVERED"},
{"stage": "READ"},
],
}
for index in range(4)
),
{
"message_id": "msg_test_5",
"channel": "whatsapp",
"direction": "outbound",
"statuses": [
{"stage": "QUEUED"},
{"stage": "ROUTED"},
{"stage": "SENT"},
{"stage": "DELIVERED"},
],
},
]
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "boundary.json"
path.write_text(json.dumps(records), encoding="utf-8")
result = self.run_cli(MDR_ROOT / "analyze_mdr_funnel.py", path, "--threshold", 20)
self.assertEqual(result.returncode, 0, (result.stdout, result.stderr))
self.assertIn("DELIVERED -> READ: 20.0%", result.stdout)
def test_mdr_json_output_is_machine_readable_and_all_unknown_is_not_healthy(self) -> None:
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "unknown.json"
path.write_text(json.dumps([{"channel": "unknown", "status": "MYSTERY"}]), encoding="utf-8")
result = self.run_cli(MDR_ROOT / "analyze_mdr_funnel.py", path, "--format", "json")
self.assertEqual(result.returncode, 2, (result.stdout, result.stderr))
report = json.loads(result.stdout)
self.assertIsNone(report["healthy"])
self.assertEqual(report["totals"]["usable"], 0)
self.assertEqual(result.stderr.strip(), "")
def test_mdr_json_show_errors_has_stable_error_counts(self) -> None:
records = [
{
"channel": "sms",
"direction": "outbound",
"statuses": [{"stage": "QUEUED"}, {"stage": "FAILED"}],
"description": "ERR_ROUTE_DENIED",
}
]
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / "failed.json"
path.write_text(json.dumps(records), encoding="utf-8")
result = self.run_cli(
MDR_ROOT / "analyze_mdr_funnel.py", path, "--format", "json", "--show-errors"
)
self.assertEqual(result.returncode, 3, (result.stdout, result.stderr))
report = json.loads(result.stdout)
self.assertEqual(report["error_codes"], {"ERR_ROUTE_DENIED": 1})
class FixturePrivacyTests(unittest.TestCase):
def test_checked_in_fixtures_are_synthetic_and_privacy_safe(self) -> None:
fixture_paths = sorted(SKILLS.glob("*/scripts/fixtures/*"))
self.assertTrue(fixture_paths)
corpus = "\n".join(path.read_text(encoding="utf-8") for path in fixture_paths)
self.assertIn("example.com", corpus)
for forbidden in ("api_key", "access_token", "@gmail.com", "@sent.dm"):
self.assertNotIn(forbidden, corpus.lower())
if __name__ == "__main__":
unittest.main(verbosity=2)