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