mail-address-semantics/tests/test_destination.py
tegwick c3e2f05ee5 MAS-WP-0005-T09: generator destination transforms with round-trip refusal; example profiles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 252470@bnt-lap001
Assistant-Session: ad628c8f-4be2-4979-94c9-76c7ed6f8241
2026-10-18 22:26:26 +02:00

78 lines
2.9 KiB
Python

"""v0.2 destination transforms (spec v0.2 section 12.1)."""
import pytest
from mas_conformance import statute_document
from mas_core.generator import DESTINATION_PROFILES, CannotExpress, for_destination
from mas_core.statute import build_statute
def statute(resource=True, interaction=True, id_case="insensitive", version="0.2"):
doc = statute_document("valid-great-service.json")
doc["mas_version"] = version
if version == "0.2":
doc["alternate_syntax"] = {"resource": resource, "interaction": interaction}
doc["equivalence"]["resource_id_case"] = id_case
return build_statute(doc, "great-service.example")
V2 = statute()
IONOS = DESTINATION_PROFILES["ionos-forward"]
def test_canonical_when_nothing_is_needed():
r = for_destination("ticket=GS-1+comments", V2, ())
assert (r.local_part, r.applied) == ("ticket=GS-1+comments", ())
def test_ionos_forward_profile():
# the MAS-WP-0003 case: IONOS refuses '=' and forces lower case
r = for_destination("ticket=GS-85QJ8WC09P+comments", V2, IONOS)
assert r.local_part == "ticket--gs-85qj8wc09p+comments"
assert r.applied == ("resource-alternate", "lowercase")
def test_transforms_apply_only_where_they_change_something():
assert for_destination("support", V2, IONOS).applied == ()
assert for_destination("ticket=gs-1", V2, IONOS).applied == ("resource-alternate",)
def test_interaction_alternate():
assert for_destination("ticket!callback", V2, ("interaction-alternate",)).local_part == "ticket---callback"
@pytest.mark.parametrize("st, code", [
(statute(resource=False), "transform-not-permitted"), # no '--' switch
(statute(version="0.1"), "transform-not-permitted"), # v0.1 statute
(statute(id_case="exact"), "would-be-misread"), # lower-casing changes the id
])
def test_refuses_what_the_namespace_would_misread(st, code):
with pytest.raises(CannotExpress) as e:
for_destination("ticket=GS-1", st, IONOS)
assert e.value.code == code
def test_refuses_id_containing_equals():
with pytest.raises(CannotExpress) as e:
for_destination("ticket=RE=2026-4711", V2, ("resource-alternate",))
assert e.value.code == "cannot-express"
def test_refuses_case_change_in_facet():
with pytest.raises(CannotExpress) as e:
for_destination("ticket=gs-1+Comments", V2, ("lowercase",))
assert e.value.code == "would-be-misread"
def test_length_is_checked_after_transforms():
lp = "ticket=" + "a" * 57 # 64 octets canonical, 65 with '--'
assert len(lp) == 64
with pytest.raises(CannotExpress) as e:
for_destination(lp, V2, ("resource-alternate",))
assert e.value.code == "too-long"
def test_lossy_transforms_need_explicit_permission():
with pytest.raises(CannotExpress):
for_destination("ticket=gs-1+comments", V2, ("drop-facet",))
r = for_destination("ticket!callback", V2, ("drop-interaction",), allow_lossy=True)
assert r.local_part == "ticket" and r.warnings