import unittest from datetime import datetime, timezone from server.database.compact.codec import ( decode_body, decode_latency, decode_move, decode_points, encode_body, encode_latency, encode_move, encode_points, from_epoch_us, to_epoch_us, ) from server.database.compact.records import PointsState, SnakeState, TurnRecord, compact_turn from server.database.compact.replay_blob import pack_replay, unpack_replay from server.database.compact.thinking import derived_values, restore_thinking, slim_thinking def _body(*points): return [{"x": x, "y": y} for x, y in points] class TestCodec(unittest.TestCase): def test_body_roundtrip_including_stacked_tail(self): body = _body((5, 5), (5, 6), (4, 6), (4, 6)) encoded = encode_body(body) self.assertEqual(decode_body(encoded), body) # 1 format byte + head pair + 3 step codes self.assertEqual(len(encoded), 6) def test_body_roundtrip_with_wrapped_jump(self): body = _body((0, 5), (10, 5), (9, 5)) self.assertEqual(decode_body(encode_body(body)), body) def test_body_out_of_byte_range_falls_back_to_json(self): body = _body((-1, 3), (300, 3)) self.assertEqual(decode_body(encode_body(body)), body) def test_empty_body(self): self.assertEqual(decode_body(encode_body([])), []) def test_points_keep_none_as_unchanged(self): self.assertIsNone(encode_points(None)) self.assertIsNone(decode_points(None)) self.assertEqual(decode_points(encode_points([])), []) food = _body((1, 2), (10, 0)) self.assertEqual(decode_points(encode_points(food)), food) def test_moves_and_latency(self): for move in ("up", "down", "left", "right", None): self.assertEqual(decode_move(encode_move(move)), move) self.assertEqual(decode_latency(encode_latency("77")), "77") self.assertIsNone(encode_latency("")) self.assertIsNone(encode_latency("timeout")) def test_epoch_microseconds_roundtrip(self): value = datetime(2026, 11, 4, 21, 41, 48, 123456, tzinfo=timezone.utc) self.assertEqual(from_epoch_us(to_epoch_us(value)), value) self.assertEqual(from_epoch_us(to_epoch_us(value.isoformat())), value) class TestThinking(unittest.TestCase): def setUp(self): self.derived = derived_values( turn=12, move="up", you={"head": {"x": 1, "y": 2}, "health": 90, "length": 4}, food=[{"x": 0, "y": 0}], width=11, height=11, snake_count=2, ) def test_strips_and_restores_derived_fields(self): thinking = { "turn": 12, "move": "up", "head": {"x": 1, "y": 2}, "health": 90, "length": 4, "food_count": 1, "board_size": "11x11", "snakes": 2, "scores": {"up": 1.5}, "mode": "duel", } slim = slim_thinking(thinking, self.derived) self.assertEqual(set(slim), {"scores", "mode", "_derived"}) self.assertEqual(restore_thinking(slim, self.derived), thinking) def test_keeps_values_that_differ_from_derived(self): thinking = {"turn": 11, "health": 89, "snakes": True} slim = slim_thinking(thinking, self.derived) self.assertEqual(slim, thinking) self.assertEqual(restore_thinking(slim, self.derived), thinking) def test_payload_using_marker_key_is_stored_verbatim(self): thinking = {"_derived": "mine", "turn": 12} self.assertEqual(restore_thinking(slim_thinking(thinking, self.derived), self.derived), thinking) def test_non_dict_payload_passes_through(self): self.assertIsNone(slim_thinking(None, self.derived)) self.assertEqual(slim_thinking(["a"], self.derived), ["a"]) class TestCompactTurn(unittest.TestCase): def _compact(self, turn, food, previous): states = [SnakeState("me", turn, 90, 5, _body((1, 1)))] return compact_turn(turn, None, "up", None, food, [], states, "me", 11, 11, previous) def test_unchanged_food_is_stored_as_none_only_after_consecutive_turn(self): food = _body((3, 3)) first, points = self._compact(1, food, None) second, points = self._compact(2, list(food), points) after_gap, _ = self._compact(4, list(food), points) self.assertEqual(first.food, food) self.assertIsNone(second.food) self.assertEqual(second.hazards, None) self.assertEqual(after_gap.food, food) def test_previous_state_from_other_turn_is_not_used(self): record, _ = self._compact(5, [], PointsState(5, [], [])) self.assertEqual(record.food, []) class TestReplayBlob(unittest.TestCase): def test_pack_roundtrip(self): observed = datetime(2026, 11, 4, 12, 0, 0, 5, tzinfo=timezone.utc) turns = [ TurnRecord(0, observed, None, None, _body((1, 1)), []), TurnRecord(1, observed, "left", {"scores": {"left": 1.0}, "_derived": 3}, None, None), ] states = [ SnakeState("me", 0, 100, None, _body((5, 5), (5, 5), (5, 5))), SnakeState("me", 1, 99, 42, _body((4, 5), (5, 5), (5, 5))), SnakeState("enemy", 1, 99, 7, _body((-1, 2))), ] unpacked_turns, unpacked_states = unpack_replay(pack_replay(turns, states)) self.assertEqual(unpacked_turns, turns) self.assertEqual(sorted(unpacked_states, key=lambda s: (s.snake_id, s.turn)), sorted(states, key=lambda s: (s.snake_id, s.turn))) def test_unknown_format_is_rejected(self): with self.assertRaises(ValueError): unpack_replay(b"", 99) if __name__ == "__main__": unittest.main()