import pytest from fastapi.testclient import TestClient from app.main import app from app.game import game_engine @pytest.fixture(autouse=True) def reset_board_state(): import asyncio asyncio.run(game_engine.reset()) yield asyncio.run(game_engine.reset()) def test_health(): client = TestClient(app) response = client.get("/api/health") assert response.status_code == 200 data = response.json() assert data["success"] is True def test_register_player(): client = TestClient(app) res = client.post("/api/players", json={"name": "CyberBot", "color": "#00FFAA"}) assert res.status_code == 201 player = res.json() assert player["name"] == "CyberBot" assert 0 <= player["x"] <= 64 assert 0 <= player["y"] <= 64 def test_turn_order_and_movement(): client = TestClient(app) p1 = client.post("/api/players", json={"name": "Bot1", "color": "#FF0000"}).json() p2 = client.post("/api/players", json={"name": "Bot2", "color": "#0000FF"}).json() # Board state should show Bot1 as active turn board = client.get("/api/board").json() assert board["turn"]["current_player_id"] == p1["id"] assert board["turn"]["round_number"] == 1 # Bot2 cannot move yet (not their turn) res_fail = client.post(f"/api/players/{p2['id']}/move", json={"direction": "UP"}) assert res_fail.status_code == 403 # Manually place Bot1 at (10, 10) for deterministic movement test import asyncio async def place_p1(): player = await game_engine.get_player(p1["id"]) player.x = 10 player.y = 10 asyncio.run(place_p1()) # Check available moves for Bot1 moves_res = client.get(f"/api/players/{p1['id']}/available-moves") assert moves_res.status_code == 200 moves_data = moves_res.json() assert moves_data["is_turn"] is True assert moves_data["moves"]["UP"]["available"] is True assert moves_data["moves"]["UP"]["target_x"] == 10 assert moves_data["moves"]["UP"]["target_y"] == 9 assert moves_data["moves"]["UP_RIGHT"]["target_x"] == 11 assert moves_data["moves"]["UP_RIGHT"]["target_y"] == 9 # Bot1 moves UP move_res = client.post(f"/api/players/{p1['id']}/move", json={"direction": "UP"}) assert move_res.status_code == 200 res_json = move_res.json() assert res_json["new_position"]["x"] == 10 assert res_json["new_position"]["y"] == 9 # Turn should advance to Bot2 assert res_json["turn"]["current_player_id"] == p2["id"] def test_diagonal_movement_and_turn_cycle(): client = TestClient(app) p1 = client.post("/api/players", json={"name": "DiagBot", "color": "#123456"}).json() import asyncio async def place(): player = await game_engine.get_player(p1["id"]) player.x = 20 player.y = 20 asyncio.run(place()) # Move diagonal UP_LEFT res = client.post(f"/api/players/{p1['id']}/move", json={"direction": "UP_LEFT"}) assert res.status_code == 200 assert res.json()["new_position"] == {"x": 19, "y": 19} # Move diagonal DOWN_RIGHT (single player cycle wrapped around) res2 = client.post(f"/api/players/{p1['id']}/move", json={"direction": "SE"}) assert res2.status_code == 200 assert res2.json()["new_position"] == {"x": 20, "y": 20} def test_wall_collision(): client = TestClient(app) p1 = client.post("/api/players", json={"name": "CornerBot", "color": "#123456"}).json() import asyncio async def place_corner(): player = await game_engine.get_player(p1["id"]) player.x = 0 player.y = 0 asyncio.run(place_corner()) # Query single move check check_up = client.get(f"/api/players/{p1['id']}/check-move?direction=UP") assert check_up.status_code == 200 assert check_up.json()["available"] is False assert "Wall collision" in check_up.json()["reason"] # Attempt to move UP through boundary wall (y < 0) res = client.post(f"/api/players/{p1['id']}/move", json={"direction": "UP"}) assert res.status_code == 400 assert "Illegal move" in res.json()["detail"] def test_player_collision(): client = TestClient(app) p1 = client.post("/api/players", json={"name": "Attacker", "color": "#123456"}).json() p2 = client.post("/api/players", json={"name": "Defender", "color": "#654321"}).json() import asyncio async def place_neighbors(): p_att = await game_engine.get_player(p1["id"]) p_att.x = 10 p_att.y = 10 p_def = await game_engine.get_player(p2["id"]) p_def.x = 11 p_def.y = 10 asyncio.run(place_neighbors()) # Attacker tries to move RIGHT into Defender's space check_right = client.get(f"/api/players/{p1['id']}/check-move?direction=RIGHT") assert check_right.status_code == 200 assert check_right.json()["available"] is False assert "occupied" in check_right.json()["reason"].lower() move_fail = client.post(f"/api/players/{p1['id']}/move", json={"direction": "RIGHT"}) assert move_fail.status_code == 400 assert "Illegal move" in move_fail.json()["detail"] def test_pass_turn(): client = TestClient(app) p1 = client.post("/api/players", json={"name": "Passer1", "color": "#111111"}).json() p2 = client.post("/api/players", json={"name": "Passer2", "color": "#222222"}).json() res = client.post(f"/api/players/{p1['id']}/pass") assert res.status_code == 200 assert res.json()["current_player_id"] == p2["id"]