import io import unittest from unittest.mock import AsyncMock, patch from fastapi.testclient import TestClient from backend.main import app class ApiContractTests(unittest.TestCase): def setUp(self): self.client = TestClient(app) def test_submit_rejects_non_pdf(self): response = self.client.post( "/api/submit", files={"file": ("note.txt", b"hello", "text/plain")}, data={"email": "user@example.com"}, ) self.assertEqual(response.status_code, 400) self.assertIn("Only PDF files", response.json()["detail"]) def test_review_lookup_404_for_unknown_key(self): with patch("backend.main.job_store.get_job", new=AsyncMock(return_value=None)): response = self.client.get("/api/review/missing-key") self.assertEqual(response.status_code, 404) self.assertIn("Review not found", response.json()["detail"]) def test_review_lookup_returns_existing_job_shape(self): job = { "access_key": "known-key", "email": "user@example.com", "status": "completed", "created_at": "2026-01-01T00:00:00+00:00", "completed_at": "2026-01-01T00:05:00+00:00", "paper_filename": "paper.pdf", "review": {"overall_assessment": 3.5, "paper_summary": "Solid."}, "has_parsed_markdown": True, "error": None, } with patch("backend.main.job_store.get_job", new=AsyncMock(return_value=job)): response = self.client.get("/api/review/known-key") self.assertEqual(response.status_code, 200) body = response.json() self.assertEqual(body["access_key"], "known-key") self.assertEqual(body["review"]["overall_assessment"], 3.5) # Retrieval links are attached for the original PDF and parsed markdown. self.assertEqual(body["pdf_url"], "/api/review/known-key/pdf") self.assertEqual(body["markdown_url"], "/api/review/known-key/markdown") def test_submit_returns_access_key_with_valid_pdf(self): fake_job = { "access_key": "new-secret", "status": "pending", } with patch("backend.main.job_store.create_job", new=AsyncMock(return_value=fake_job)), \ patch("backend.main.send_access_key_email"), \ patch("backend.main._run_pipeline", new=AsyncMock()): response = self.client.post( "/api/submit", files={"file": ("paper.pdf", io.BytesIO(b"%PDF-1.4 sample"), "application/pdf")}, data={"email": "user@example.com"}, ) self.assertEqual(response.status_code, 200) body = response.json() self.assertEqual(body["access_key"], "new-secret") self.assertNotIn("submission_key", body) self.assertIn("submitted", body["message"].lower()) def test_feedback_is_saved_in_feedback_table_contract(self): job = {"access_key": "known-key", "status": "completed"} save_feedback = AsyncMock(return_value={"id": 1}) with patch("backend.main.job_store.get_job", new=AsyncMock(return_value=job)), \ patch("backend.main.job_store.save_feedback", new=save_feedback): response = self.client.post( "/api/feedback/known-key", json={ "helpfulness": "high", "has_critical_error": False, "has_suggestions": True, "comments": "Good signal", }, ) self.assertEqual(response.status_code, 200) save_feedback.assert_awaited_once()