copilot-swe-agent[bot]
Add Maritime monorepo scaffold: backend FastAPI + frontend Next.js + CI/CD workflow
6e1c8c8 | """ | |
| pytest tests for the SUB-SENTINEL FastAPI backend. | |
| Run: | |
| cd backend | |
| pytest tests/ -v | |
| """ | |
| import base64 | |
| import io | |
| import os | |
| import pytest | |
| from fastapi.testclient import TestClient | |
| from PIL import Image | |
| # Ensure we do NOT need a real GROQ_API_KEY for tests | |
| os.environ.setdefault("GROQ_API_KEY", "") | |
| from main import app # noqa: E402 – import after env setup | |
| client = TestClient(app) | |
| # --------------------------------------------------------------------------- | |
| # Helpers | |
| # --------------------------------------------------------------------------- | |
| def _make_png_bytes(width: int = 64, height: int = 64, color=(30, 80, 120)) -> bytes: | |
| """Create a small solid-colour PNG in memory.""" | |
| img = Image.new("RGB", (width, height), color) | |
| buf = io.BytesIO() | |
| img.save(buf, format="PNG") | |
| return buf.getvalue() | |
| # --------------------------------------------------------------------------- | |
| # /health | |
| # --------------------------------------------------------------------------- | |
| def test_health_ok(): | |
| response = client.get("/health") | |
| assert response.status_code == 200 | |
| assert response.json() == {"status": "ok"} | |
| # --------------------------------------------------------------------------- | |
| # /process – happy path | |
| # --------------------------------------------------------------------------- | |
| def test_process_returns_required_keys(): | |
| png = _make_png_bytes() | |
| response = client.post( | |
| "/process", | |
| files={"file": ("test.png", png, "image/png")}, | |
| ) | |
| assert response.status_code == 200 | |
| data = response.json() | |
| assert "enhanced_image_base64" in data | |
| assert "heatmap_base64" in data | |
| assert "detections" in data | |
| assert "sitrep_text" in data | |
| def test_process_enhanced_image_is_valid_base64(): | |
| png = _make_png_bytes() | |
| response = client.post( | |
| "/process", | |
| files={"file": ("test.png", png, "image/png")}, | |
| ) | |
| assert response.status_code == 200 | |
| b64_str = response.json()["enhanced_image_base64"] | |
| # Should be a data-URI | |
| assert b64_str.startswith("data:image/") | |
| # Extract the raw base64 portion and verify it decodes | |
| raw = b64_str.split(",", 1)[1] | |
| decoded = base64.b64decode(raw) | |
| img = Image.open(io.BytesIO(decoded)) | |
| assert img.size[0] > 0 | |
| assert img.size[1] > 0 | |
| def test_process_heatmap_is_valid_base64(): | |
| png = _make_png_bytes() | |
| response = client.post( | |
| "/process", | |
| files={"file": ("test.png", png, "image/png")}, | |
| ) | |
| assert response.status_code == 200 | |
| b64_str = response.json()["heatmap_base64"] | |
| assert b64_str.startswith("data:image/") | |
| raw = b64_str.split(",", 1)[1] | |
| decoded = base64.b64decode(raw) | |
| img = Image.open(io.BytesIO(decoded)) | |
| assert img.size[0] > 0 | |
| def test_process_detections_structure(): | |
| """Each detection must have the required keys with correct types.""" | |
| png = _make_png_bytes() | |
| response = client.post( | |
| "/process", | |
| files={"file": ("test.png", png, "image/png")}, | |
| ) | |
| assert response.status_code == 200 | |
| detections = response.json()["detections"] | |
| assert isinstance(detections, list) | |
| for det in detections: | |
| assert "class" in det | |
| assert "mapped_label" in det | |
| assert "confidence" in det | |
| assert "bbox" in det | |
| assert isinstance(det["confidence"], float) | |
| assert len(det["bbox"]) == 4 | |
| def test_process_sitrep_is_string(): | |
| png = _make_png_bytes() | |
| response = client.post( | |
| "/process", | |
| files={"file": ("test.png", png, "image/png")}, | |
| ) | |
| assert response.status_code == 200 | |
| sitrep = response.json()["sitrep_text"] | |
| assert isinstance(sitrep, str) | |
| assert len(sitrep) > 0 | |
| # --------------------------------------------------------------------------- | |
| # /process – error cases | |
| # --------------------------------------------------------------------------- | |
| def test_process_rejects_non_image(): | |
| response = client.post( | |
| "/process", | |
| files={"file": ("data.txt", b"hello world", "text/plain")}, | |
| ) | |
| assert response.status_code == 400 | |
| def test_process_rejects_empty_file(): | |
| response = client.post( | |
| "/process", | |
| files={"file": ("empty.png", b"", "image/png")}, | |
| ) | |
| assert response.status_code == 400 | |
| def test_process_jpeg_image(): | |
| """Pipeline should work with JPEG input.""" | |
| img = Image.new("RGB", (80, 80), (50, 100, 150)) | |
| buf = io.BytesIO() | |
| img.save(buf, format="JPEG") | |
| response = client.post( | |
| "/process", | |
| files={"file": ("test.jpg", buf.getvalue(), "image/jpeg")}, | |
| ) | |
| assert response.status_code == 200 | |
| assert "enhanced_image_base64" in response.json() | |