generated from coulomb/repo-seed
Curate package scaffold + flavor-agnostic SolutionPattern artifact with separate per-flavor rendering hints (OQ4): Resolution/Scope/Provenance sub-records, stable source-key id, semver bump helper, deterministic round-trip serialization. 7 new tests; suite 47/47 green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
81 lines
2.7 KiB
Python
81 lines
2.7 KiB
Python
"""Round-trip + validation tests for the Solution Pattern schema (T01)."""
|
|
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from session_memory.curate.schema import ( # noqa: E402
|
|
Provenance,
|
|
Resolution,
|
|
Scope,
|
|
SolutionPattern,
|
|
)
|
|
|
|
|
|
def _sample() -> SolutionPattern:
|
|
src = "success:clean_pass:outcome"
|
|
return SolutionPattern(
|
|
id=SolutionPattern.make_id(src),
|
|
name="Run tests before declaring success",
|
|
version="1.0.0",
|
|
polarity="success",
|
|
problem="Sessions that run tests and finish with no retries resolve cheaply.",
|
|
resolutions=[Resolution(summary="Always run the suite", steps=["edit", "test", "commit"])],
|
|
scope=Scope(flavors=["claude", "grok"]),
|
|
provenance=Provenance(source_key=src, evidence={"frequency": 18, "cross_flavor": True}),
|
|
rendering_hints={"claude": {"target": "CLAUDE.md"}, "codex": {"target": "AGENTS.md"}},
|
|
status="approved",
|
|
distribution_ready=True,
|
|
)
|
|
|
|
|
|
def test_round_trip_is_lossless():
|
|
p = _sample()
|
|
again = SolutionPattern.from_json(p.to_json())
|
|
assert again.to_dict() == p.to_dict()
|
|
assert again.resolutions[0].steps == ["edit", "test", "commit"]
|
|
assert again.scope.flavors == ["claude", "grok"]
|
|
assert again.provenance.evidence["cross_flavor"] is True
|
|
|
|
|
|
def test_serialization_is_deterministic():
|
|
p = _sample()
|
|
assert p.to_json() == p.to_json()
|
|
assert SolutionPattern.from_json(p.to_json()).to_json() == p.to_json()
|
|
|
|
|
|
def test_make_id_is_stable_and_slugged():
|
|
assert SolutionPattern.make_id("success:clean_pass:outcome") == "sp-success-clean_pass-outcome"
|
|
# same source key -> same id regardless of later wording
|
|
assert SolutionPattern.make_id("problem:abandoned:outcome") == SolutionPattern.make_id(
|
|
"problem:abandoned:outcome"
|
|
)
|
|
|
|
|
|
def test_bump_version():
|
|
assert SolutionPattern.bump_version("1.0.0") == "1.0.1"
|
|
assert SolutionPattern.bump_version("1.2.3", "minor") == "1.3.0"
|
|
assert SolutionPattern.bump_version("1.2.3", "major") == "2.0.0"
|
|
|
|
|
|
def test_rejects_unknown_polarity():
|
|
with pytest.raises(ValueError):
|
|
SolutionPattern(id="x", name="n", version="1.0.0", polarity="meh", problem="p")
|
|
|
|
|
|
def test_rejects_unknown_status():
|
|
with pytest.raises(ValueError):
|
|
SolutionPattern(id="x", name="n", version="1.0.0", polarity="problem",
|
|
problem="p", status="bogus")
|
|
|
|
|
|
def test_rejects_unknown_flavor_in_hints_and_scope():
|
|
with pytest.raises(ValueError):
|
|
SolutionPattern(id="x", name="n", version="1.0.0", polarity="problem",
|
|
problem="p", rendering_hints={"gpt": {}})
|
|
with pytest.raises(ValueError):
|
|
Scope(flavors=["gpt"])
|