"""Tests for CommitRecord schema v2. Changes: - ``format_version`` field removed entirely. - ``branch`` renamed to ``created_on_branch``. These tests are written RED-first. They fail against the current implementation and pass once the schema changes are applied to ``muse/core/store.py``. """ from __future__ import annotations import datetime import pytest from muse.core.store import CommitRecord, CommitDict from muse.core._types import MsgpackDict # --------------------------------------------------------------------------- # Helpers # --------------------------------------------------------------------------- _NOW = datetime.datetime(2025, 1, 1, 0, 0, 0, tzinfo=datetime.timezone.utc) _TS = _NOW.isoformat() _CID = "sha256:" + "a" * 64 _SID = "sha256:" + "b" * 64 def _minimal_msgpack(branch_key: str = "created_on_branch") -> MsgpackDict: """Minimal raw dict for CommitRecord.from_msgpack.""" return { "commit_id": _CID, "repo_id": "test-repo", branch_key: "main", "snapshot_id": _SID, "message": "test commit", "committed_at": _TS, } # --------------------------------------------------------------------------- # CommitRecord field existence # --------------------------------------------------------------------------- class TestCommitRecordFields: def test_has_created_on_branch(self) -> None: rec = CommitRecord( commit_id=_CID, repo_id="r", created_on_branch="main", snapshot_id=_SID, message="m", committed_at=_NOW, ) assert rec.created_on_branch == "main" def test_has_no_branch_field(self) -> None: rec = CommitRecord( commit_id=_CID, repo_id="r", created_on_branch="main", snapshot_id=_SID, message="m", committed_at=_NOW, ) assert not hasattr(rec, "branch"), ( "CommitRecord must not have a 'branch' field after v2 schema change" ) def test_has_no_format_version_field(self) -> None: rec = CommitRecord( commit_id=_CID, repo_id="r", created_on_branch="main", snapshot_id=_SID, message="m", committed_at=_NOW, ) assert not hasattr(rec, "format_version"), ( "CommitRecord must not have a 'format_version' field after v2 schema change" ) # --------------------------------------------------------------------------- # to_dict serialisation # --------------------------------------------------------------------------- class TestCommitRecordToDict: def _make(self) -> CommitRecord: return CommitRecord( commit_id=_CID, repo_id="r", created_on_branch="dev", snapshot_id=_SID, message="m", committed_at=_NOW, ) def test_to_dict_has_created_on_branch(self) -> None: d = self._make().to_dict() assert d["created_on_branch"] == "dev" def test_to_dict_has_no_branch_key(self) -> None: d = self._make().to_dict() assert "branch" not in d, ( "to_dict() must not emit 'branch' key in v2 schema" ) def test_to_dict_has_no_format_version_key(self) -> None: d = self._make().to_dict() assert "format_version" not in d, ( "to_dict() must not emit 'format_version' key in v2 schema" ) # --------------------------------------------------------------------------- # from_msgpack deserialisation # --------------------------------------------------------------------------- class TestCommitRecordFromMsgpack: def test_reads_created_on_branch(self) -> None: raw = _minimal_msgpack("created_on_branch") rec = CommitRecord.from_msgpack(raw) assert rec.created_on_branch == "main" def test_falls_back_to_branch_key_for_old_records(self) -> None: """Old records serialised with 'branch' key must still deserialise. This shim supports the migration window: old commit files on disk carry 'branch', new files carry 'created_on_branch'. Once all repos are migrated via 'muse code migrate', this path is dead but harmless. """ raw = _minimal_msgpack("branch") rec = CommitRecord.from_msgpack(raw) assert rec.created_on_branch == "main" def test_raises_when_neither_key_present(self) -> None: raw = _minimal_msgpack("created_on_branch") del raw["created_on_branch"] with pytest.raises(TypeError, match="created_on_branch"): CommitRecord.from_msgpack(raw) def test_from_msgpack_ignores_format_version_key(self) -> None: """Old records with format_version in the dict must still deserialise without storing the field on the returned record.""" raw = _minimal_msgpack("created_on_branch") raw["format_version"] = 8 rec = CommitRecord.from_msgpack(raw) assert not hasattr(rec, "format_version") # --------------------------------------------------------------------------- # CommitDict TypedDict # --------------------------------------------------------------------------- class TestCommitDict: def test_commit_dict_has_created_on_branch_key(self) -> None: """CommitDict must declare 'created_on_branch', not 'branch'.""" annotations = CommitDict.__annotations__ assert "created_on_branch" in annotations, ( "CommitDict must have 'created_on_branch' key" ) assert "branch" not in annotations, ( "CommitDict must not have 'branch' key" ) def test_commit_dict_has_no_format_version_key(self) -> None: annotations = CommitDict.__annotations__ assert "format_version" not in annotations, ( "CommitDict must not have 'format_version' key" )