conformance/statute-aware.json (vendored as mas_conformance/statute_aware.json) covers the 6.3 stand-in table, resource_id_case equivalence and the 12.1 destination results and refusals; mas test runs it. Misread refusals now name the field that would change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5787@bnt-lap001 Assistant-Session: 5db26b82-138b-40cc-9856-4d5eb0f752fd
153 lines
5.4 KiB
Python
153 lines
5.4 KiB
Python
import json
|
|
import socket
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from mas_conformance import address_fixtures, property_fixtures
|
|
from mas_conformance.runner import check_address_fixture, run_parser_corpus
|
|
from mas_core.generator import generate_local_part, resource_local_part
|
|
from mas_core.model import RequiredProfile
|
|
from mas_core.parser import (
|
|
MasSyntaxError,
|
|
parse_address,
|
|
parse_local_part,
|
|
parse_received_address,
|
|
validate_profile,
|
|
)
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
FIXTURES = address_fixtures() + property_fixtures()
|
|
|
|
|
|
def test_vendored_corpus_matches_spec_bundle():
|
|
spec = json.loads((ROOT / "mas-v0.1-spec/conformance/addresses.json").read_text())
|
|
assert spec == address_fixtures()
|
|
|
|
|
|
def test_vendored_schema_matches_spec_bundle():
|
|
a = (ROOT / "mas-v0.1-spec/schema/mas-statute-v0.1.schema.json").read_bytes()
|
|
b = (ROOT / "mas_core/schema/mas-statute-v0.1.schema.json").read_bytes()
|
|
assert a == b
|
|
|
|
|
|
@pytest.mark.parametrize("fixture", FIXTURES, ids=[f["id"] for f in FIXTURES])
|
|
def test_fixture(fixture):
|
|
assert check_address_fixture(fixture) is None
|
|
|
|
|
|
def test_whole_corpus_passes():
|
|
assert run_parser_corpus().ok
|
|
|
|
|
|
@pytest.fixture
|
|
def no_network(monkeypatch):
|
|
def deny(*a, **k):
|
|
raise AssertionError("parser attempted network I/O")
|
|
monkeypatch.setattr(socket, "socket", deny)
|
|
monkeypatch.setattr(socket, "create_connection", deny)
|
|
monkeypatch.setattr(socket, "getaddrinfo", deny)
|
|
|
|
|
|
def test_parser_performs_no_io(no_network, monkeypatch):
|
|
monkeypatch.setattr("builtins.open", lambda *a, **k: (_ for _ in ()).throw(AssertionError("file I/O")))
|
|
for f in FIXTURES:
|
|
try:
|
|
parse_local_part(f["local_part"])
|
|
except MasSyntaxError:
|
|
pass
|
|
parse_address("anna.mueller@acme.coulomb.social")
|
|
|
|
|
|
def test_maximal_parser_reports_required_profile():
|
|
p = parse_local_part("ticket!callback")
|
|
assert p.required_profile is RequiredProfile.P2
|
|
assert not validate_profile(p, "p1")
|
|
assert validate_profile(p, "P2")
|
|
assert parse_local_part("ticket=PR42+comments").required_profile is RequiredProfile.P1
|
|
|
|
|
|
def test_combined_form_model():
|
|
p = parse_local_part("ticket=PR42+comments!callback")
|
|
assert p.target.resource_type == "ticket" and p.target.resource_id == "PR42"
|
|
assert p.facet.value == "comments" and p.interaction.mode == "callback"
|
|
assert p.required_profile is RequiredProfile.P2
|
|
assert not p.generator_allowed
|
|
|
|
|
|
def test_no_kind_inference_from_punctuation():
|
|
for lp in ("bernd.worsch", "research-companion", "invoice_ingest"):
|
|
d = parse_local_part(lp).to_dict()
|
|
assert d["target_kind"] == "opaque"
|
|
assert not {"kind", "person", "actor", "function"} & set(d)
|
|
|
|
|
|
def test_facet_loss_degrades_to_base():
|
|
p = parse_local_part("ticket=GS-41872+comments")
|
|
assert p.base == "ticket=GS-41872"
|
|
stripped = p.without_facet()
|
|
assert stripped.raw == "ticket=GS-41872" and stripped.target == p.target
|
|
|
|
|
|
def test_parse_address_domain():
|
|
a = parse_address("ticket=GS-41872@Great-Service.Example")
|
|
assert a.domain == "great-service.example"
|
|
with pytest.raises(MasSyntaxError):
|
|
parse_address("no-domain")
|
|
|
|
|
|
def test_generator_rules():
|
|
assert generate_local_part("ticket", interaction="callback") == "ticket!callback"
|
|
assert resource_local_part("ticket", "GS-41872") == "ticket=GS-41872"
|
|
with pytest.raises(MasSyntaxError):
|
|
generate_local_part("ticket=PR42", facet="comments", interaction="callback")
|
|
with pytest.raises(MasSyntaxError):
|
|
generate_local_part("ticket", facet="a+b")
|
|
with pytest.raises(MasSyntaxError):
|
|
generate_local_part("ticket", facet="")
|
|
with pytest.raises(MasSyntaxError):
|
|
resource_local_part("ticket", "x" * 60)
|
|
for f in FIXTURES:
|
|
if f["valid"] and f.get("generator_allowed") is False:
|
|
p = parse_local_part(f["local_part"])
|
|
with pytest.raises(MasSyntaxError):
|
|
generate_local_part(str(p.target), facet=p.facet.value, interaction=p.interaction.mode)
|
|
|
|
|
|
@pytest.mark.parametrize("raw, expected", [
|
|
('"ticket=PR42"@example.com', "ticket=PR42"), # SCP-1: unnecessary quotes
|
|
('"ticket!callback"@example.com', "ticket!callback"),
|
|
("ticket=PR42@example.com", "ticket=PR42"),
|
|
])
|
|
def test_received_quoted_local_part_is_equivalent(raw, expected):
|
|
assert parse_received_address(raw).local.raw == expected
|
|
|
|
|
|
@pytest.mark.parametrize("raw", ['"a\\"b"@example.com', '"a b"@example.com', '"x@y"@example.com'])
|
|
def test_received_quoted_non_mas_stays_invalid(raw):
|
|
with pytest.raises(MasSyntaxError):
|
|
parse_received_address(raw)
|
|
|
|
|
|
def test_canonical_parser_still_rejects_quotes():
|
|
with pytest.raises(MasSyntaxError):
|
|
parse_address('"ticket=PR42"@example.com')
|
|
|
|
|
|
def test_vendored_v02_schema_matches_draft_bundle():
|
|
a = (ROOT / "mas-v0.2-spec/schema/mas-statute-v0.2.schema.json").read_bytes()
|
|
b = (ROOT / "mas_core/schema/mas-statute-v0.2.schema.json").read_bytes()
|
|
assert a == b
|
|
|
|
|
|
def test_vendored_v02_statute_aware_corpus_matches_draft_bundle():
|
|
a = (ROOT / "mas-v0.2-spec/conformance/statute-aware.json").read_bytes()
|
|
b = (ROOT / "mas_conformance/statute_aware.json").read_bytes()
|
|
assert a == b
|
|
|
|
|
|
def test_v02_statute_aware_corpus():
|
|
from mas_conformance.runner import run_statute_aware_corpus
|
|
rep = run_statute_aware_corpus()
|
|
assert rep.ok, rep.failures
|
|
assert rep.passed == 29
|