import json import pytest from models.category import Category, CategoryWord def _seed(db_session): cat = Category(name="animals") db_session.add(cat) db_session.flush() for w in ["TIGER", "LION", "BEAR", "WOLF", "EAGLE", "HAWK", "DEER", "FROG"]: db_session.add(CategoryWord(category_id=cat.id, word=w)) db_session.commit() return cat @pytest.fixture def seeded_db(db_session): return _seed(db_session) async def test_get_categories(http_server_client, seeded_db): resp = await http_server_client.fetch("/api/categories") assert resp.code == 200 data = json.loads(resp.body) assert len(data) >= 1 assert data[0]["name"] == "animals" async def test_create_new_game(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id, "board_size": 10}) resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) assert resp.code == 200 data = json.loads(resp.body) assert data["board_size"] == 10 assert data["status"] == "in_progress" assert len(data["grid"]) == 10 assert len(data["words"]) >= 1 # Words should NOT include position info for w in data["words"]: assert "start_row" not in w assert "direction" not in w async def test_get_game_state(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id}) create_resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) game_id = json.loads(create_resp.body)["id"] resp = await http_server_client.fetch(f"/api/game/{game_id}") assert resp.code == 200 data = json.loads(resp.body) assert data["id"] == game_id async def test_text_guess_correct(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id}) create_resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) game_data = json.loads(create_resp.body) game_id = game_data["id"] # Pick a word that was placed target_word = game_data["words"][0]["text"] guess_body = json.dumps({"word": target_word}) resp = await http_server_client.fetch( f"/api/game/{game_id}/guess", method="POST", body=guess_body, headers={"Content-Type": "application/json"}, ) assert resp.code == 200 data = json.loads(resp.body) assert data["correct"] is True assert data["word"] == target_word assert "cells" in data async def test_text_guess_incorrect(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id}) create_resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) game_id = json.loads(create_resp.body)["id"] guess_body = json.dumps({"word": "ZZZZNOTAWORD"}) resp = await http_server_client.fetch( f"/api/game/{game_id}/guess", method="POST", body=guess_body, headers={"Content-Type": "application/json"}, ) assert resp.code == 200 data = json.loads(resp.body) assert data["correct"] is False async def test_new_game_invalid_json(http_server_client, seeded_db): resp = await http_server_client.fetch( "/api/game/new", method="POST", body="not json", headers={"Content-Type": "application/json"}, raise_error=False, ) assert resp.code == 400 data = json.loads(resp.body) assert "error" in data async def test_new_game_missing_category_id(http_server_client, seeded_db): body = json.dumps({"board_size": 10}) resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, raise_error=False, ) assert resp.code == 400 data = json.loads(resp.body) assert "error" in data async def test_new_game_string_board_size(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id, "board_size": "big"}) resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, raise_error=False, ) assert resp.code == 400 data = json.loads(resp.body) assert "error" in data async def test_new_game_custom_word_count(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id, "board_size": 10, "word_count": 4}) resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) assert resp.code == 200 data = json.loads(resp.body) assert len(data["words"]) <= 4 async def test_new_game_string_word_count(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id, "word_count": "many"}) resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, raise_error=False, ) assert resp.code == 400 data = json.loads(resp.body) assert "error" in data async def test_new_game_nonexistent_category(http_server_client, seeded_db): body = json.dumps({"category_id": 99999}) resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, raise_error=False, ) assert resp.code == 404 data = json.loads(resp.body) assert "error" in data async def test_guess_invalid_json(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id}) create_resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) game_id = json.loads(create_resp.body)["id"] resp = await http_server_client.fetch( f"/api/game/{game_id}/guess", method="POST", body="not json", headers={"Content-Type": "application/json"}, raise_error=False, ) assert resp.code == 400 data = json.loads(resp.body) assert "error" in data async def test_guess_non_string_word(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id}) create_resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) game_id = json.loads(create_resp.body)["id"] guess_body = json.dumps({"word": 12345}) resp = await http_server_client.fetch( f"/api/game/{game_id}/guess", method="POST", body=guess_body, headers={"Content-Type": "application/json"}, ) assert resp.code == 200 data = json.loads(resp.body) assert data["correct"] is False async def test_guess_cells_with_null_element(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id}) create_resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) game_id = json.loads(create_resp.body)["id"] guess_body = json.dumps({"cells": [None, {"row": 0, "col": 0}]}) resp = await http_server_client.fetch( f"/api/game/{game_id}/guess", method="POST", body=guess_body, headers={"Content-Type": "application/json"}, ) assert resp.code == 200 data = json.loads(resp.body) assert data["correct"] is False async def test_new_game_with_player_name(http_server_client, seeded_db): body = json.dumps( {"category_id": seeded_db.id, "board_size": 10, "player_name": "Alice"} ) resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) assert resp.code == 200 data = json.loads(resp.body) assert data["player_name"] == "Alice" async def test_new_game_default_player_name(http_server_client, seeded_db): body = json.dumps({"category_id": seeded_db.id, "board_size": 10}) resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) assert resp.code == 200 data = json.loads(resp.body) assert data["player_name"] == "Anonymous" async def test_new_game_invalid_player_name_chars(http_server_client, seeded_db): body = json.dumps( {"category_id": seeded_db.id, "player_name": ""} ) resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, raise_error=False, ) assert resp.code == 400 data = json.loads(resp.body) assert "error" in data async def test_scoreboard_empty(http_server_client, seeded_db): resp = await http_server_client.fetch("/api/scoreboard") assert resp.code == 200 data = json.loads(resp.body) assert data["leaderboards"] == {} assert isinstance(data["popular_categories"], list) async def test_scoreboard_with_completed_game(http_server_client, seeded_db): # Create a game with few words so we can complete it body = json.dumps( { "category_id": seeded_db.id, "board_size": 10, "word_count": 3, "player_name": "TestPlayer", } ) create_resp = await http_server_client.fetch( "/api/game/new", method="POST", body=body, headers={"Content-Type": "application/json"}, ) game_data = json.loads(create_resp.body) game_id = game_data["id"] # Find all words by text guess for w in game_data["words"]: guess_body = json.dumps({"word": w["text"]}) resp = await http_server_client.fetch( f"/api/game/{game_id}/guess", method="POST", body=guess_body, headers={"Content-Type": "application/json"}, ) assert resp.code == 200 # Verify game is completed state_resp = await http_server_client.fetch(f"/api/game/{game_id}") state_data = json.loads(state_resp.body) assert state_data["status"] == "completed" # Now check scoreboard sb_resp = await http_server_client.fetch("/api/scoreboard") assert sb_resp.code == 200 sb_data = json.loads(sb_resp.body) assert "animals" in sb_data["leaderboards"] entry = sb_data["leaderboards"]["animals"][0] assert entry["player_name"] == "TestPlayer" assert entry["time_seconds"] >= 0 assert entry["board_size"] == 10