Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

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"]