from __future__ import annotations from pathlib import Path import pytest from syncgames.config import AgentConfig, GameConfig from syncgames.errors import StaleWipError from syncgames.session import end_session, start_session from syncgames.store import FilesystemStore @pytest.fixture def env(tmp_path: Path): cfg_root = tmp_path / "cfg" games = cfg_root / "games" games.mkdir(parents=True) native = tmp_path / "native" / "saves" native.mkdir(parents=True) (native / "slot1.sav").write_bytes(b"v1") cfg = AgentConfig( device_id="pc-test", endpoint_url="", store="filesystem", ssot_root=tmp_path / "ssot", config_root=cfg_root, state_root=tmp_path / "state", ) game = GameConfig( id="demo", name="Demo", platform="other", paths=[str(native)], versions_to_keep=3, ) # seed remote empty then first end will push after start with empty live store = FilesystemStore(cfg.ssot_root) return cfg, store, game, native def test_start_end_roundtrip(env): cfg, store, game, native = env start_session(cfg, store, game) (native / "slot1.sav").write_bytes(b"v2-progress") meta = end_session(cfg, store, game) assert meta.live_hash assert store.list_live("demo") def test_hash_gate_blocks_stale(env): cfg, store, game, native = env start_session(cfg, store, game) (native / "slot1.sav").write_bytes(b"A") end_session(cfg, store, game) # Simulate stale session with old parent after remote advanced elsewhere start_session(cfg, store, game) # Mutate remote live out from under us lives = list((Path(cfg.ssot_root) / "games" / "demo" / "live").rglob("slot1.sav")) assert lives lives[0].write_bytes(b"REMOTE-NEWER") # Update meta live_hash to mismatch parent meta = store.get_meta("demo") assert meta meta.raw["live_hash"] = "sha256:deadbeef" store.put_meta("demo", meta) (native / "slot1.sav").write_bytes(b"local-stale-wip") with pytest.raises(StaleWipError): end_session(cfg, store, game)