Files
learningcircuit--local-deep…/tests/web/services/test_socket_service_extra_coverage.py
wehub-resource-sync 7a0da7932b
Backwards Compatibility / Verify Encryption Constants (push) Waiting to run
Backwards Compatibility / PyPI Version Compatibility (push) Waiting to run
Backwards Compatibility / Database Migration Tests (push) Waiting to run
CodeQL Advanced / Analyze (javascript-typescript) (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Waiting to run
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Blocked by required conditions
Docker Tests (Consolidated) / detect-changes (push) Waiting to run
Docker Tests (Consolidated) / Build Test Image (push) Waiting to run
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Blocked by required conditions
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Blocked by required conditions
Docker Tests (Consolidated) / Accessibility Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Unit Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / LLM Example Tests (push) Blocked by required conditions
Docker Tests (Consolidated) / Production Image Smoke Test (push) Blocked by required conditions
Docker Tests (Consolidated) / Infrastructure Tests (push) Blocked by required conditions
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Waiting to run
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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

264 lines
9.1 KiB
Python

"""Extra coverage tests for socket_service.py — private event handlers and logging."""
from unittest.mock import MagicMock, patch
MODULE = "local_deep_research.web.services.socket_service"
def _get_service_with_patched_init():
"""Get a SocketIOService instance bypassing singleton + Flask requirement."""
from local_deep_research.web.services.socket_service import SocketIOService
with patch.object(
SocketIOService, "__new__", lambda cls, *a, **kw: object.__new__(cls)
):
svc = object.__new__(SocketIOService)
svc._SocketIOService__socket_subscriptions = {}
svc._SocketIOService__lock = __import__("threading").Lock()
svc._SocketIOService__socketio = MagicMock()
svc._SocketIOService__logging_enabled = True
return svc
# ===========================================================================
# __handle_connect
# ===========================================================================
class TestHandleConnect:
def test_rejects_unauthenticated(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
mock_request.sid = "client-123"
with patch(f"{MODULE}.session", {}):
assert svc._SocketIOService__handle_connect(mock_request) is False
def test_rejects_when_no_db_session(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
mock_request.sid = "client-123"
with (
patch(f"{MODULE}.session", {"username": "alice"}),
patch(f"{MODULE}.db_manager") as mock_db,
):
mock_db.is_user_connected.return_value = False
assert svc._SocketIOService__handle_connect(mock_request) is False
def test_accepts_authenticated(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
mock_request.sid = "client-123"
with (
patch(f"{MODULE}.session", {"username": "alice"}),
patch(f"{MODULE}.db_manager") as mock_db,
patch(f"{MODULE}.join_room"),
):
mock_db.is_user_connected.return_value = True
assert svc._SocketIOService__handle_connect(mock_request) is True
# ===========================================================================
# __handle_disconnect
# ===========================================================================
class TestHandleDisconnect:
def test_removes_subscription(self):
svc = _get_service_with_patched_init()
svc._SocketIOService__socket_subscriptions = {
"res-1": {"client-1", "client-2"},
"res-2": {"client-1"},
}
mock_request = MagicMock()
mock_request.sid = "client-1"
with patch(f"{MODULE}.cleanup_current_thread", create=True):
svc._SocketIOService__handle_disconnect(
mock_request, "transport close"
)
assert "client-1" not in svc._SocketIOService__socket_subscriptions.get(
"res-1", set()
)
# res-2 should be cleaned up entirely since no clients left
assert "res-2" not in svc._SocketIOService__socket_subscriptions
def test_empty_subscriptions_handled(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
mock_request.sid = "unknown-client"
with patch(f"{MODULE}.cleanup_current_thread", create=True):
svc._SocketIOService__handle_disconnect(mock_request, "timeout")
def test_cleanup_import_error_swallowed(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
mock_request.sid = "client-1"
with patch.dict(
"sys.modules",
{"local_deep_research.database.thread_local_session": None},
):
svc._SocketIOService__handle_disconnect(mock_request, "close")
def test_cleanup_exception_swallowed(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
mock_request.sid = "client-1"
mock_mod = MagicMock()
mock_mod.cleanup_current_thread.side_effect = RuntimeError(
"cleanup fail"
)
with patch.dict(
"sys.modules",
{"local_deep_research.database.thread_local_session": mock_mod},
):
svc._SocketIOService__handle_disconnect(mock_request, "close")
def test_outer_exception_swallowed(self):
svc = _get_service_with_patched_init()
# Make the lock acquisition fail
svc._SocketIOService__lock = MagicMock()
svc._SocketIOService__lock.__enter__ = MagicMock(
side_effect=RuntimeError("lock fail")
)
svc._SocketIOService__lock.__exit__ = MagicMock(return_value=False)
mock_request = MagicMock()
mock_request.sid = "client-1"
# Should not raise
svc._SocketIOService__handle_disconnect(mock_request, "error")
# ===========================================================================
# __handle_subscribe
# ===========================================================================
class TestHandleSubscribe:
def test_adds_subscriber(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
mock_request.sid = "client-1"
with patch(f"{MODULE}.get_active_research_snapshot", return_value=None):
svc._SocketIOService__handle_subscribe(
{"research_id": "res-1"}, mock_request
)
assert "client-1" in svc._SocketIOService__socket_subscriptions["res-1"]
def test_sends_current_status_when_available(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
mock_request.sid = "client-1"
snapshot = {
"progress": 50,
"log": [{"message": "Searching...", "phase": "search"}],
}
with patch(
f"{MODULE}.get_active_research_snapshot", return_value=snapshot
):
svc._SocketIOService__handle_subscribe(
{"research_id": "res-1"}, mock_request
)
svc._SocketIOService__socketio.emit.assert_called()
def test_no_status_when_snapshot_none(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
mock_request.sid = "client-1"
with patch(f"{MODULE}.get_active_research_snapshot", return_value=None):
svc._SocketIOService__handle_subscribe(
{"research_id": "res-1"}, mock_request
)
svc._SocketIOService__socketio.emit.assert_not_called()
def test_empty_log_no_emit(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
mock_request.sid = "client-1"
snapshot = {"progress": 0, "log": []}
with patch(
f"{MODULE}.get_active_research_snapshot", return_value=snapshot
):
svc._SocketIOService__handle_subscribe(
{"research_id": "res-1"}, mock_request
)
svc._SocketIOService__socketio.emit.assert_not_called()
def test_no_research_id_ignored(self):
svc = _get_service_with_patched_init()
mock_request = MagicMock()
svc._SocketIOService__handle_subscribe({}, mock_request)
assert len(svc._SocketIOService__socket_subscriptions) == 0
# ===========================================================================
# __handle_socket_error / __handle_default_error
# ===========================================================================
class TestErrorHandlers:
def test_socket_error_returns_false(self):
svc = _get_service_with_patched_init()
result = svc._SocketIOService__handle_socket_error(RuntimeError("test"))
assert result is False
def test_default_error_returns_false(self):
svc = _get_service_with_patched_init()
result = svc._SocketIOService__handle_default_error(ValueError("test"))
assert result is False
# ===========================================================================
# __log_info / __log_error / __log_exception — logging control
# ===========================================================================
class TestLoggingControl:
def test_log_info_when_enabled(self):
svc = _get_service_with_patched_init()
svc._SocketIOService__logging_enabled = True
svc._SocketIOService__log_info("test message")
def test_log_info_when_disabled(self):
svc = _get_service_with_patched_init()
svc._SocketIOService__logging_enabled = False
svc._SocketIOService__log_info("should not log")
def test_log_error_when_enabled(self):
svc = _get_service_with_patched_init()
svc._SocketIOService__logging_enabled = True
svc._SocketIOService__log_error("error message")
def test_log_exception_when_enabled(self):
svc = _get_service_with_patched_init()
svc._SocketIOService__logging_enabled = True
svc._SocketIOService__log_exception("exception message")
def test_log_exception_when_disabled(self):
svc = _get_service_with_patched_init()
svc._SocketIOService__logging_enabled = False
svc._SocketIOService__log_exception("should not log")