Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/routers/detection.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,7 @@ async def detect_abandoned_vehicle_endpoint(image: UploadFile = File(...)):
@router.post("/api/detect-emotion")
async def detect_emotion_endpoint(
image: UploadFile = File(...),
client: httpx.AsyncClient = backend.dependencies.Depends(get_http_client)
client = backend.dependencies.Depends(get_http_client)
):
Comment on lines 460 to 463
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing the httpx.AsyncClient annotation avoids the runtime NameError, but it also drops useful typing. Consider keeping the type safely by importing httpx in a TYPE_CHECKING block and using a forward reference (e.g., client: "httpx.AsyncClient" = Depends(...)), or just importing httpx in this module if that’s acceptable.

Copilot uses AI. Check for mistakes.
"""
Analyze facial emotions in the image using Hugging Face inference.
Expand Down
96 changes: 96 additions & 0 deletions backend/tests/test_field_officer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import sys
from unittest.mock import MagicMock

# Mock heavy optional dependencies before importing backend.main
for _mock_module in [
"google", "google.generativeai",
"magic",
"telegram", "telegram.ext",
"anthropic", "openai",
"cv2",
"numpy",
"sklearn", "sklearn.cluster",
"transformers",
"torch",
"PIL", "PIL.Image",
"speech_recognition",
"googletrans",
"langdetect",
"pydub",
]:
sys.modules.setdefault(_mock_module, MagicMock())
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Module-level sys.modules mocking leaks across the whole test run and can corrupt unrelated tests that import these libraries later.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/tests/test_field_officer.py, line 21:

<comment>Module-level `sys.modules` mocking leaks across the whole test run and can corrupt unrelated tests that import these libraries later.</comment>

<file context>
@@ -0,0 +1,96 @@
+    "langdetect",
+    "pydub",
+]:
+    sys.modules.setdefault(_mock_module, MagicMock())
+
+import os
</file context>
Fix with Cubic

Comment on lines +4 to +21
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The module-level sys.modules mocking is very broad and can leak into the rest of the suite during collection (e.g., mocking PIL/numpy/torch will affect other tests that expect the real packages). Prefer mocking only modules that are truly missing (e.g., guard with importlib.util.find_spec) and/or apply mocks via a fixture/monkeypatch so they’re restored after the test module completes.

Copilot uses AI. Check for mistakes.

import os
os.environ.setdefault("FRONTEND_URL", "http://localhost:5173")

import pytest
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from backend.database import Base, get_db
from backend.main import app
from backend.models import FieldOfficerVisit, Issue

# Use an in-memory SQLite database for tests
TEST_DATABASE_URL = "sqlite://"

engine = create_engine(TEST_DATABASE_URL, connect_args={"check_same_thread": False})
Comment on lines +30 to +38
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using TEST_DATABASE_URL = "sqlite://" with the default connection pool can be flaky for in-memory SQLite: each new connection can see a different empty database. For reliable in-memory tests (and for the TestClient thread), use an in-memory URL like sqlite+pysqlite:///:memory: and configure the engine with poolclass=StaticPool (plus check_same_thread=False) so all sessions share the same in-memory DB.

Suggested change
from backend.database import Base, get_db
from backend.main import app
from backend.models import FieldOfficerVisit, Issue
# Use an in-memory SQLite database for tests
TEST_DATABASE_URL = "sqlite://"
engine = create_engine(TEST_DATABASE_URL, connect_args={"check_same_thread": False})
from sqlalchemy.pool import StaticPool
from backend.database import Base, get_db
from backend.main import app
from backend.models import FieldOfficerVisit, Issue
# Use an in-memory SQLite database for tests
TEST_DATABASE_URL = "sqlite+pysqlite:///:memory:"
engine = create_engine(
TEST_DATABASE_URL,
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)

Copilot uses AI. Check for mistakes.
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)


@pytest.fixture
def db_session():
Base.metadata.create_all(bind=engine)
session = TestingSessionLocal()
try:
yield session
finally:
session.close()
Base.metadata.drop_all(bind=engine)


@pytest.fixture
def client(db_session):
def override_get_db():
try:
yield db_session
finally:
pass

app.dependency_overrides[get_db] = override_get_db
with TestClient(app) as c:
yield c
Comment on lines +54 to +63
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

override_get_db yields the db_session fixture’s Session instance, which is created in the test thread but then used inside the FastAPI request handler running in the TestClient thread. SQLAlchemy Sessions are not thread-safe, so this can cause intermittent failures. Prefer creating a new Session inside override_get_db (per request) and closing it there; with a StaticPool in-memory engine you can still share the same DB state across sessions for the test.

Copilot uses AI. Check for mistakes.
app.dependency_overrides.clear()
Copy link

Copilot AI Mar 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

app.dependency_overrides.clear() removes all overrides on the global app, which can interfere with other tests if they set overrides too. It’s safer to remove only the key you added (e.g., app.dependency_overrides.pop(get_db, None)) in the fixture teardown.

Suggested change
app.dependency_overrides.clear()
app.dependency_overrides.pop(get_db, None)

Copilot uses AI. Check for mistakes.


def test_get_issue_visit_history(db_session, client):
# Setup test data
issue = Issue(
description="Test description",
category="Pothole",
status="open",
latitude=18.5204,
longitude=73.8567,
)
db_session.add(issue)
db_session.commit()
db_session.refresh(issue)

visit = FieldOfficerVisit(
issue_id=issue.id,
officer_email="test@officer.com",
officer_name="Test Officer",
check_in_latitude=18.5204,
check_in_longitude=73.8567,
is_public=True,
)
db_session.add(visit)
db_session.commit()

response = client.get(f"/api/field-officer/issue/{issue.id}/visit-history")
assert response.status_code == 200
data = response.json()
assert data["total_visits"] == 1
assert len(data["visits"]) == 1
assert data["visits"][0]["officer_name"] == "Test Officer"
Loading