7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
268 lines
10 KiB
Python
268 lines
10 KiB
Python
"""
|
|
Tests for chat route exception handling.
|
|
|
|
These tests verify that all route endpoints handle exceptions gracefully
|
|
and return proper 500 error responses with appropriate error messages.
|
|
"""
|
|
|
|
import json
|
|
from unittest.mock import patch, MagicMock
|
|
from sqlalchemy.exc import SQLAlchemyError
|
|
|
|
from local_deep_research.chat.service import ArchiveBlockedError
|
|
|
|
|
|
class TestCreateSessionExceptionHandling:
|
|
"""Tests for exception handling in create_session endpoint."""
|
|
|
|
def test_create_session_db_failure_returns_500(self, authenticated_client):
|
|
"""Test that database failure in create_session returns 500."""
|
|
with patch(
|
|
"local_deep_research.chat.routes.ChatService"
|
|
) as mock_service_class:
|
|
mock_service = MagicMock()
|
|
mock_service.create_session.side_effect = SQLAlchemyError(
|
|
"Database connection failed"
|
|
)
|
|
mock_service_class.return_value = mock_service
|
|
|
|
response = authenticated_client.post(
|
|
"/api/chat/sessions",
|
|
json={"initial_query": "Test query"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = json.loads(response.data)
|
|
assert data["success"] is False
|
|
assert "error" in data
|
|
|
|
def test_create_session_database_failure_returns_500(
|
|
self, authenticated_client
|
|
):
|
|
"""Test that database failure during session creation returns 500."""
|
|
with patch(
|
|
"local_deep_research.chat.service.get_user_db_session"
|
|
) as mock_get_session:
|
|
mock_get_session.side_effect = RuntimeError(
|
|
"Database connection timeout"
|
|
)
|
|
|
|
response = authenticated_client.post(
|
|
"/api/chat/sessions",
|
|
json={"initial_query": "Test query"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = json.loads(response.data)
|
|
assert data["success"] is False
|
|
|
|
|
|
class TestGetSessionExceptionHandling:
|
|
"""Tests for exception handling in get_session endpoint."""
|
|
|
|
def test_get_session_db_timeout_returns_500(self, authenticated_client):
|
|
"""Test that database timeout in get_session returns 500."""
|
|
with patch(
|
|
"local_deep_research.chat.routes.ChatService"
|
|
) as mock_service_class:
|
|
mock_service = MagicMock()
|
|
mock_service.get_session.side_effect = SQLAlchemyError(
|
|
"Database query timeout"
|
|
)
|
|
mock_service_class.return_value = mock_service
|
|
|
|
response = authenticated_client.get(
|
|
"/api/chat/sessions/some-session-id"
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = json.loads(response.data)
|
|
assert data["success"] is False
|
|
assert "Failed to get chat session" in data["error"]
|
|
|
|
|
|
class TestListSessionsExceptionHandling:
|
|
"""Tests for exception handling in list_sessions endpoint."""
|
|
|
|
def test_list_sessions_db_connection_error_returns_500(
|
|
self, authenticated_client
|
|
):
|
|
"""Test that database connection error in list_sessions returns 500."""
|
|
with patch(
|
|
"local_deep_research.chat.routes.ChatService"
|
|
) as mock_service_class:
|
|
mock_service = MagicMock()
|
|
mock_service.list_sessions.side_effect = SQLAlchemyError(
|
|
"Connection refused"
|
|
)
|
|
mock_service_class.return_value = mock_service
|
|
|
|
response = authenticated_client.get("/api/chat/sessions")
|
|
|
|
assert response.status_code == 500
|
|
data = json.loads(response.data)
|
|
assert data["success"] is False
|
|
assert "Failed to list chat sessions" in data["error"]
|
|
|
|
|
|
class TestUpdateSessionExceptionHandling:
|
|
"""Tests for exception handling in update_session endpoint."""
|
|
|
|
def test_update_session_db_write_failure_returns_500(
|
|
self, authenticated_client
|
|
):
|
|
"""Test that database write failure in update_session returns 500."""
|
|
with patch(
|
|
"local_deep_research.chat.routes.ChatService"
|
|
) as mock_service_class:
|
|
mock_service = MagicMock()
|
|
mock_service.update_session_title.side_effect = SQLAlchemyError(
|
|
"Write failed"
|
|
)
|
|
mock_service_class.return_value = mock_service
|
|
|
|
response = authenticated_client.patch(
|
|
"/api/chat/sessions/some-session-id",
|
|
json={"title": "New Title"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = json.loads(response.data)
|
|
assert data["success"] is False
|
|
assert "Failed to update chat session" in data["error"]
|
|
|
|
|
|
class TestArchiveBlockedReturns409:
|
|
"""HTTP-layer regression test for the archive-while-running guard.
|
|
|
|
The service layer raises ``ArchiveBlockedError`` when a chat
|
|
session has a ResearchHistory row in ``status='in_progress'`` for
|
|
it. The route MUST turn that into a 409 with the exact hard-coded
|
|
message (never echo ``str(exc)`` here — CWE-209). Without
|
|
this test, a regression that drops the ``except ArchiveBlockedError``
|
|
branch, changes the status code, or starts interpolating exception
|
|
text into the response would slip past the suite — the existing
|
|
service-layer test in test_chat_archive_blocked_in_progress.py
|
|
only covers the raise, not the route wiring.
|
|
"""
|
|
|
|
def test_archive_in_progress_returns_409_with_hardcoded_message(
|
|
self, authenticated_client
|
|
):
|
|
with patch(
|
|
"local_deep_research.chat.routes.ChatService"
|
|
) as mock_service_class:
|
|
mock_service = MagicMock()
|
|
# The route calls get_session() first to verify existence,
|
|
# so it must succeed before archive_session() is reached.
|
|
mock_service.get_session.return_value = {
|
|
"id": "sess-1",
|
|
"title": "t",
|
|
"status": "active",
|
|
}
|
|
# And on success it reads the session back for the response.
|
|
mock_service.archive_session.side_effect = ArchiveBlockedError(
|
|
# Deliberately use a message containing something we
|
|
# would NOT want to leak — confirms the route does not
|
|
# echo str(exc) into the response body.
|
|
"research <id=secret-123> in_progress for session sess-1"
|
|
)
|
|
mock_service_class.return_value = mock_service
|
|
|
|
response = authenticated_client.patch(
|
|
"/api/chat/sessions/sess-1",
|
|
json={"status": "archived"},
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 409
|
|
data = json.loads(response.data)
|
|
assert data["success"] is False
|
|
# Exact hard-coded message — must not include the exc text.
|
|
assert (
|
|
data["error"]
|
|
== "Cannot archive: research in_progress. Stop it first."
|
|
)
|
|
assert "secret-123" not in data["error"]
|
|
|
|
|
|
class TestDeleteSessionExceptionHandling:
|
|
"""Tests for exception handling in delete_session endpoint."""
|
|
|
|
def test_delete_session_db_failure_returns_500(self, authenticated_client):
|
|
"""Test that database failure in delete_session returns 500."""
|
|
with patch(
|
|
"local_deep_research.chat.routes.ChatService"
|
|
) as mock_service_class:
|
|
mock_service = MagicMock()
|
|
mock_service.delete_session.side_effect = SQLAlchemyError(
|
|
"Delete operation failed"
|
|
)
|
|
mock_service_class.return_value = mock_service
|
|
|
|
response = authenticated_client.delete(
|
|
"/api/chat/sessions/some-session-id"
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = json.loads(response.data)
|
|
assert data["success"] is False
|
|
assert "Failed to delete chat session" in data["error"]
|
|
|
|
|
|
class TestGetMessagesExceptionHandling:
|
|
"""Tests for exception handling in get_messages endpoint."""
|
|
|
|
def test_get_messages_db_query_failure_returns_500(
|
|
self, authenticated_client
|
|
):
|
|
"""Test that database query failure in get_messages returns 500."""
|
|
with patch(
|
|
"local_deep_research.chat.routes.ChatService"
|
|
) as mock_service_class:
|
|
mock_service = MagicMock()
|
|
mock_service.get_session_messages.side_effect = SQLAlchemyError(
|
|
"Query failed"
|
|
)
|
|
mock_service_class.return_value = mock_service
|
|
|
|
response = authenticated_client.get(
|
|
"/api/chat/sessions/some-session-id/messages"
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = json.loads(response.data)
|
|
assert data["success"] is False
|
|
assert "Failed to get chat messages" in data["error"]
|
|
|
|
|
|
class TestSendMessageExceptionHandling:
|
|
"""Tests for exception handling in send_message endpoint."""
|
|
|
|
def test_send_message_service_failure_returns_500(
|
|
self, authenticated_client
|
|
):
|
|
"""Test that service failure in send_message returns 500."""
|
|
with patch(
|
|
"local_deep_research.chat.routes.ChatService"
|
|
) as mock_service_class:
|
|
mock_service = MagicMock()
|
|
mock_service.get_session.side_effect = RuntimeError(
|
|
"Service unavailable"
|
|
)
|
|
mock_service_class.return_value = mock_service
|
|
|
|
response = authenticated_client.post(
|
|
"/api/chat/sessions/some-session-id/messages",
|
|
json={"content": "Test message", "trigger_research": False},
|
|
content_type="application/json",
|
|
)
|
|
|
|
assert response.status_code == 500
|
|
data = json.loads(response.data)
|
|
assert data["success"] is False
|
|
assert "Failed to send message" in data["error"]
|