"""Tests for attract/replay serialization. No GPU, no network, no Modal. Run: python -m pytest tests/ -q """ import sys from pathlib import Path import pandas as pd sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from ttw.dummy import make_random_policy from ttw.game import Game from ttw.replay import FRAME_KEYS, frame_to_views, load_attract, record_frame, save_frames def _game_after_a_turn() -> Game: g = Game(make_random_policy(seed=7), deck_seed=7) g.tempt_fate() g.step() return g def test_record_frame_has_all_view_keys(): frame = record_frame(_game_after_a_turn()) assert sorted(frame.keys()) == sorted(FRAME_KEYS) def test_frame_to_views_arity_and_chart_types(): frame = record_frame(_game_after_a_turn()) views = frame_to_views(frame) assert len(views) == 15 # matches app._views (game_state added by the handler) assert isinstance(views[1], pd.DataFrame) # prices assert isinstance(views[2], pd.DataFrame) # gini assert list(views[1].columns) == ["turn", "good", "price"] assert list(views[2].columns) == ["turn", "gini"] def test_roundtrip_save_load(tmp_path): g = _game_after_a_turn() frames = [record_frame(g)] path = tmp_path / "attract.json" save_frames(frames, path) loaded = load_attract(path) assert len(loaded) == 1 assert loaded[0]["town"] == frames[0]["town"] def test_load_attract_missing_file_is_empty(tmp_path): assert load_attract(tmp_path / "nope.json") == [] def test_frame_to_views_empty_frame_yields_valid_empty_plots(): # A defensive frame with no chart records must still build valid DataFrames. views = frame_to_views({}) assert isinstance(views[1], pd.DataFrame) and views[1].empty assert isinstance(views[2], pd.DataFrame) and views[2].empty