2026-09-05 21:10:18 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2026-09-05 21:20:39 +00:00
|
|
|
def test_turn_order_and_movement():
|
2026-09-05 21:10:18 +00:00
|
|
|
client = TestClient(app)
|
2026-09-05 21:20:39 +00:00
|
|
|
p1 = client.post("/api/players", json={"name": "Bot1", "color": "#FF0000"}).json()
|
|
|
|
|
p2 = client.post("/api/players", json={"name": "Bot2", "color": "#0000FF"}).json()
|
2026-09-05 21:10:18 +00:00
|
|
|
|
2026-09-05 21:20:39 +00:00
|
|
|
# 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
|
2026-09-05 21:10:18 +00:00
|
|
|
|
2026-09-05 21:20:39 +00:00
|
|
|
# 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():
|
2026-09-05 21:10:18 +00:00
|
|
|
client = TestClient(app)
|
2026-09-05 21:20:39 +00:00
|
|
|
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"})
|
2026-09-05 21:10:18 +00:00
|
|
|
assert res.status_code == 200
|
2026-09-05 21:20:39 +00:00
|
|
|
assert res.json()["new_position"] == {"x": 19, "y": 19}
|
2026-09-05 21:10:18 +00:00
|
|
|
|
2026-09-05 21:20:39 +00:00
|
|
|
# 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}
|
2026-09-05 21:10:18 +00:00
|
|
|
|
2026-09-05 21:20:39 +00:00
|
|
|
|
|
|
|
|
def test_wall_collision():
|
2026-09-05 21:10:18 +00:00
|
|
|
client = TestClient(app)
|
2026-09-05 21:20:39 +00:00
|
|
|
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())
|
2026-09-05 21:10:18 +00:00
|
|
|
|
2026-09-05 21:20:39 +00:00
|
|
|
# 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"]
|
2026-09-05 21:10:18 +00:00
|
|
|
|
2026-09-05 21:20:39 +00:00
|
|
|
# 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"]
|
2026-09-05 21:10:18 +00:00
|
|
|
|
|
|
|
|
|
2026-09-05 21:20:39 +00:00
|
|
|
def test_player_collision():
|
2026-09-05 21:10:18 +00:00
|
|
|
client = TestClient(app)
|
2026-09-05 21:20:39 +00:00
|
|
|
p1 = client.post("/api/players", json={"name": "Attacker", "color": "#123456"}).json()
|
|
|
|
|
p2 = client.post("/api/players", json={"name": "Defender", "color": "#654321"}).json()
|
2026-09-05 21:10:18 +00:00
|
|
|
|
2026-09-05 21:20:39 +00:00
|
|
|
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"]
|