import unittest from importlib import import_module try: from fastapi.testclient import TestClient server_app = import_module("server.app") except ModuleNotFoundError: # pragma: no cover - dependency-gated TestClient = None server_app = None @unittest.skipIf(TestClient is None, "fastapi test dependencies are not installed in the local test env") class ServerApiTests(unittest.TestCase): def setUp(self) -> None: server_app.env = server_app.IDPIExfilEnv() self.client = TestClient(server_app.app) def test_health_endpoint(self) -> None: response = self.client.get("/health") self.assertEqual(response.status_code, 200) payload = response.json() self.assertEqual(payload["status"], "healthy") self.assertEqual(payload["env"], "idpi-exfil-env") def test_root_landing_page_is_html(self) -> None: response = self.client.get("/") self.assertEqual(response.status_code, 200) self.assertIn("text/html", response.headers.get("content-type", "")) self.assertIn("IDPI Exfil Environment", response.text) self.assertIn("/docs", response.text) def test_web_landing_page_is_html(self) -> None: response = self.client.get("/web") self.assertEqual(response.status_code, 200) self.assertIn("text/html", response.headers.get("content-type", "")) self.assertIn("OpenEnv-compatible", response.text) def test_metadata_endpoint(self) -> None: response = self.client.get("/metadata") self.assertEqual(response.status_code, 200) payload = response.json() self.assertEqual(payload["name"], "idpi-exfil-env") self.assertIn("prompt injection", payload["description"].lower()) self.assertEqual(payload["tasks"], ["easy", "medium", "hard"]) def test_schema_endpoint(self) -> None: response = self.client.get("/schema") self.assertEqual(response.status_code, 200) payload = response.json() self.assertIn("action", payload) self.assertIn("observation", payload) self.assertIn("state", payload) self.assertIn("reward", payload) self.assertIn("result", payload) def test_mcp_endpoint_returns_jsonrpc_contract(self) -> None: response = self.client.post("/mcp", json={"id": "check-1"}) self.assertEqual(response.status_code, 200) payload = response.json() self.assertEqual(payload["jsonrpc"], "2.0") self.assertEqual(payload["id"], "check-1") self.assertIn("error", payload) def test_reset_and_step_contract(self) -> None: reset = self.client.post("/reset", json={"difficulty": "easy", "seed": 11}) self.assertEqual(reset.status_code, 200) observation = reset.json() self.assertEqual(observation["task_difficulty"], "easy") step = self.client.post("/step", json={"action": {"type": "ReadDoc"}}) self.assertEqual(step.status_code, 200) payload = step.json() self.assertIn("observation", payload) self.assertIn("reward", payload) self.assertIn("done", payload) self.assertIn("info", payload) self.assertIn("policy", payload["info"]) self.assertIn("grader_inputs", payload["info"]) def test_reset_without_body_uses_defaults(self) -> None: response = self.client.post("/reset") self.assertEqual(response.status_code, 200) observation = response.json() self.assertEqual(observation["task_difficulty"], "easy") self.assertEqual(observation["step_count"], 0) def test_invalid_reset_payload_returns_422(self) -> None: response = self.client.post("/reset", json={"difficulty": "legendary"}) self.assertEqual(response.status_code, 422) def test_step_before_reset_returns_400(self) -> None: response = self.client.post("/step", json={"action": {"type": "ReadDoc"}}) self.assertEqual(response.status_code, 400) if __name__ == "__main__": unittest.main()