testspace / tests /test_api.py
nilshoehing's picture
Add explicit first-50 puzzle chooser
4d7b4ed verified
Raw
History Blame Contribute Delete
4.59 kB
from __future__ import annotations
import os
import tempfile
import unittest
from pathlib import Path
from fastapi.testclient import TestClient
os.environ["TOPOBENCH_SKIP_AUTOAPP"] = "1"
from space_app.config import Settings
from space_app.dataset import DatasetStore
from space_app.db import SessionStore
from space_app.main import create_app
from space_app.models import DatasetRow
class FakeVerifier:
def verify(self, *, puzzle_type: str, problem_ascii: str, board_ascii: str, args: str):
return {
"board_exists": True,
"board_valid": True,
"board_modified": False,
"correct": board_ascii == problem_ascii,
"puzzle_type": puzzle_type,
"args": args,
}
class ApiTests(unittest.TestCase):
def setUp(self) -> None:
self.tempdir = tempfile.TemporaryDirectory()
data_dir = Path(self.tempdir.name)
self.settings = Settings(
data_dir=data_dir,
database_path=data_dir / "test.sqlite",
hf_cache_dir=data_dir / "hf-cache",
admin_api_token="secret-token",
frontend_dist_dir=data_dir / "frontend-dist",
host="127.0.0.1",
port=7860,
)
self.settings.prepare()
rows = [
DatasetRow(
filename="bridges_1.txt",
puzzlename="bridges",
args="5x5de",
problem="1.1\n...\n1.1",
solution="1-1\n|.|\n1-1",
difficulty="easy",
)
]
self.app = create_app(
settings=self.settings,
dataset_store=DatasetStore(rows),
session_store=SessionStore(self.settings.database_path),
verifier=FakeVerifier(),
)
self.client = TestClient(self.app)
def tearDown(self) -> None:
self.tempdir.cleanup()
def test_session_lifecycle(self) -> None:
list_response = self.client.get(
"/api/puzzles",
params={"puzzle_type": "bridges", "difficulty": "easy"},
)
self.assertEqual(list_response.status_code, 200)
options = list_response.json()
self.assertEqual(len(options), 1)
self.assertEqual(options[0]["puzzle_id"], "bridges_1.txt")
create_response = self.client.post(
"/api/sessions",
json={
"player_name": "Ada Lovelace",
"puzzle_type": "bridges",
"difficulty": "easy",
"puzzle_id": "bridges_1.txt",
},
)
self.assertEqual(create_response.status_code, 200)
session = create_response.json()
self.assertEqual(session["puzzle_type"], "bridges")
self.assertEqual(session["payload"]["problem_ascii"], "1.1\n...\n1.1")
def test_session_requires_matching_selected_puzzle(self) -> None:
create_response = self.client.post(
"/api/sessions",
json={
"player_name": "Ada Lovelace",
"puzzle_type": "flow_free",
"difficulty": "easy",
"puzzle_id": "bridges_1.txt",
},
)
self.assertEqual(create_response.status_code, 400)
def test_ready_and_submit_lifecycle(self) -> None:
create_response = self.client.post(
"/api/sessions",
json={
"player_name": "Ada Lovelace",
"puzzle_type": "bridges",
"difficulty": "easy",
"puzzle_id": "bridges_1.txt",
},
)
session = create_response.json()
ready_response = self.client.post(f"/api/sessions/{session['session_id']}/ready")
self.assertEqual(ready_response.status_code, 200)
self.assertIsNotNone(ready_response.json()["started_at"])
submit_response = self.client.post(
f"/api/sessions/{session['session_id']}/submit",
json={"board_ascii": "1.1\n...\n1.1"},
)
self.assertEqual(submit_response.status_code, 200)
payload = submit_response.json()
self.assertTrue(payload["solved"])
self.assertEqual(payload["status"], "solved")
def test_admin_endpoints_require_token(self) -> None:
unauthorized = self.client.get("/api/admin/solves")
self.assertEqual(unauthorized.status_code, 401)
authorized = self.client.get(
"/api/admin/aggregates",
headers={"Authorization": "Bearer secret-token"},
)
self.assertEqual(authorized.status_code, 200)