"""tests/test_api.py — Integration tests for /reset /step /state endpoints.""" import sys, os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import pytest from fastapi.testclient import TestClient from app.main import app client = TestClient(app) SIMPLE_SECURE_CODE = """ import hashlib def generate_hash(data: str) -> str: \"\"\"Generate a secure SHA-256 hash of the input.\"\"\" if data is None: data = "" return hashlib.sha256(data.encode()).hexdigest() """ class TestHealth: def test_health_returns_200(self): r = client.get("/health") assert r.status_code == 200 data = r.json() assert data["status"] == "ok" assert data["version"] == "2.0.0" assert data["tasks"] == 9 def test_root_returns_200(self): r = client.get("/") assert r.status_code == 200 data = r.json() assert "endpoints" in data class TestReset: def test_reset_easy(self): r = client.post("/reset", params={"difficulty": "easy"}) assert r.status_code == 200 data = r.json() assert "session_id" in data assert "task_id" in data assert "problem_statement" in data assert "cwe_targets" in data assert "codegraph" in data assert "starter_code" in data assert data["difficulty"] == "easy" def test_reset_medium(self): r = client.post("/reset", params={"difficulty": "medium"}) assert r.status_code == 200 data = r.json() assert data["difficulty"] == "medium" def test_reset_hard(self): r = client.post("/reset", params={"difficulty": "hard"}) assert r.status_code == 200 def test_reset_invalid_difficulty(self): r = client.post("/reset", params={"difficulty": "impossible"}) assert r.status_code == 400 def test_reset_returns_valid_task_id(self): from tasks.task_registry import list_tasks valid_ids = {t["id"] for t in list_tasks()} r = client.post("/reset", params={"difficulty": "easy"}) data = r.json() assert data["task_id"] in valid_ids class TestStep: def _new_session(self, difficulty="easy"): r = client.post("/reset", params={"difficulty": difficulty}) return r.json() def test_step_returns_reward_in_range(self): episode = self._new_session("easy") r = client.post("/step", json={ "session_id": episode["session_id"], "task_id": episode["task_id"], "filename": "solution.py", "code": SIMPLE_SECURE_CODE, }) assert r.status_code == 200 data = r.json() assert 0.0 <= data["total_reward"] <= 1.0 def test_step_returns_all_score_keys(self): episode = self._new_session("easy") r = client.post("/step", json={ "session_id": episode["session_id"], "task_id": episode["task_id"], "filename": "solution.py", "code": SIMPLE_SECURE_CODE, }) data = r.json() expected_keys = { "correctness", "attack_resist", "static_security", "consistency", "performance", "documentation", "code_structure", "supply_chain", } assert expected_keys.issubset(set(data["scores"].keys())) def test_step_missing_session_returns_404(self): r = client.post("/step", json={ "session_id": "nonexistent-uuid-1234", "task_id": "hash_generator", "filename": "solution.py", "code": SIMPLE_SECURE_CODE, }) assert r.status_code == 404 def test_step_empty_code_returns_422(self): episode = self._new_session("easy") r = client.post("/step", json={ "session_id": episode["session_id"], "task_id": episode["task_id"], "filename": "solution.py", "code": " ", }) assert r.status_code == 422 def test_done_after_max_steps(self): episode = self._new_session("easy") sid = episode["session_id"] task_id = episode["task_id"] last_result = None for i in range(5): r = client.post("/step", json={ "session_id": sid, "task_id": task_id, "filename": f"step{i}.py", "code": SIMPLE_SECURE_CODE, }) if r.status_code != 200: break last_result = r.json() assert last_result is not None assert last_result["done"] is True def test_step_updates_codegraph(self): episode = self._new_session("easy") r = client.post("/step", json={ "session_id": episode["session_id"], "task_id": episode["task_id"], "filename": "solution.py", "code": SIMPLE_SECURE_CODE, }) data = r.json() assert "codegraph" in data assert "conventions" in data["codegraph"] class TestState: def test_state_returns_current_episode(self): r = client.post("/reset", params={"difficulty": "medium"}) sid = r.json()["session_id"] r2 = client.get("/state", params={"session_id": sid}) assert r2.status_code == 200 data = r2.json() assert data["step"] == 0 assert data["done"] is False assert "task_id" in data def test_state_missing_session_returns_404(self): r = client.get("/state", params={"session_id": "bad-uuid-xyz"}) assert r.status_code == 404 if __name__ == "__main__": pytest.main([__file__, "-v"])