Session-gated MinIO save sync with AppImage GUI, CLI edit/session flow, and Gitea release helper. Co-authored-by: Cursor <[email protected]>
72 lines
2.1 KiB
Python
72 lines
2.1 KiB
Python
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)
|