diff --git a/backend/main.py b/backend/main.py index 4aae7ed4..1b2b6802 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,4 +1,4 @@ -from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Query, Request +from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Query, Request, Response from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.gzip import GZipMiddleware @@ -293,9 +293,10 @@ async def chat_endpoint(request: ChatRequest): def get_recent_issues(db: Session = Depends(get_db)): cached_data = recent_issues_cache.get() if cached_data: - # Check if cached data is already serialized (list of dicts) - # We return JSONResponse directly to bypass FastAPI's Pydantic validation/serialization - # which is redundant for cached data that was already validated when stored. + # Check if cached data is already serialized (string) + if isinstance(cached_data, str): + return Response(content=cached_data, media_type="application/json") + # Fallback for legacy format (list of dicts) or if cache implementation changes return JSONResponse(content=cached_data) # Fetch last 10 issues @@ -328,11 +329,13 @@ def get_recent_issues(db: Session = Depends(get_db)): latitude=i.latitude, longitude=i.longitude, action_plan=action_plan_val - ).model_dump(mode='json')) # Store as JSON-compatible dict in cache + ).model_dump(mode='json')) # Store as JSON-compatible dict - recent_issues_cache.set(data) + # Serialize to JSON string and cache + json_data = json.dumps(data) + recent_issues_cache.set(json_data) - return data + return Response(content=json_data, media_type="application/json") @app.post("/api/detect-pothole") async def detect_pothole_endpoint(image: UploadFile = File(...)): diff --git a/tests/test_recent_issues.py b/tests/test_recent_issues.py new file mode 100644 index 00000000..fa41d305 --- /dev/null +++ b/tests/test_recent_issues.py @@ -0,0 +1,89 @@ +import sys +import os +import json +from datetime import datetime +import pytest + +# Adjust path to import backend +sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'backend')) + +from fastapi.testclient import TestClient +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.pool import StaticPool + +# Import app and dependencies +from main import app, get_db +from database import Base +from models import Issue +from cache import recent_issues_cache + +# Setup in-memory DB for testing +SQLALCHEMY_DATABASE_URL = "sqlite:///:memory:" + +engine = create_engine( + SQLALCHEMY_DATABASE_URL, + connect_args={"check_same_thread": False}, + poolclass=StaticPool, +) +TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) + +def override_get_db(): + try: + db = TestingSessionLocal() + yield db + finally: + db.close() + +app.dependency_overrides[get_db] = override_get_db + +def test_recent_issues_caching(): + # Clear cache + recent_issues_cache.invalidate() + + # Create tables + Base.metadata.create_all(bind=engine) + + # Add dummy data + db = TestingSessionLocal() + issue1 = Issue( + description="Test Issue 1", + category="Road", + status="open", + created_at=datetime(2023, 1, 1, 12, 0, 0), + action_plan=json.dumps({"step": "1"}) + ) + db.add(issue1) + db.commit() + db.close() + + with TestClient(app) as client: + # First call - Cache Miss + response1 = client.get("/api/issues/recent") + assert response1.status_code == 200 + data1 = response1.json() + assert len(data1) == 1 + assert data1[0]["description"] == "Test Issue 1" + + # Verify cache is populated + assert recent_issues_cache.get() is not None + + # Second call - Cache Hit (String format) + response2 = client.get("/api/issues/recent") + assert response2.status_code == 200 + data2 = response2.json() + assert data2 == data1 + assert response2.headers["content-type"] == "application/json" + + # Verify robustness with legacy format (List of dicts) + # Manually inject list into cache + recent_issues_cache.set([{"id": 999, "description": "Legacy", "category": "Road", "created_at": "2023-01-01T12:00:00", "status": "open", "upvotes": 0}]) + + response3 = client.get("/api/issues/recent") + assert response3.status_code == 200 + data3 = response3.json() + assert len(data3) == 1 + assert data3[0]["description"] == "Legacy" + +if __name__ == "__main__": + test_recent_issues_caching()