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
267 lines
9.3 KiB
Python
267 lines
9.3 KiB
Python
"""Deep coverage tests for database/session_context.py.
|
||
|
||
Focuses on uncovered branches:
|
||
- get_user_db_session: g.db_session reuse path
|
||
- get_user_db_session: g.user_password path
|
||
- get_user_db_session: session_password_store path
|
||
- get_user_db_session: thread context password path
|
||
- get_user_db_session: unencrypted DB placeholder
|
||
- get_user_db_session: failed session raises DatabaseSessionError
|
||
- ensure_db_session: user not in session (unauthenticated)
|
||
- ensure_db_session: db not connected + encrypted -> redirect
|
||
- ensure_db_session: db not connected + unencrypted -> reopen
|
||
- UNENCRYPTED_DB_PLACEHOLDER constant
|
||
- DatabaseAccessMixin.execute_with_db
|
||
"""
|
||
|
||
from unittest.mock import MagicMock, patch
|
||
|
||
import pytest
|
||
|
||
MODULE = "local_deep_research.database.session_context"
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Constants
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestConstants:
|
||
def test_unencrypted_placeholder_is_string(self):
|
||
from local_deep_research.database.session_context import (
|
||
UNENCRYPTED_DB_PLACEHOLDER,
|
||
)
|
||
|
||
assert isinstance(UNENCRYPTED_DB_PLACEHOLDER, str)
|
||
assert len(UNENCRYPTED_DB_PLACEHOLDER) > 0
|
||
|
||
def test_database_session_error_is_exception(self):
|
||
from local_deep_research.database.session_context import (
|
||
DatabaseSessionError,
|
||
)
|
||
|
||
assert issubclass(DatabaseSessionError, Exception)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# get_user_db_session – happy paths
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestGetUserDbSessionHappyPaths:
|
||
def test_uses_g_db_session_when_available(self):
|
||
"""When g.db_session exists, it is yielded without opening a new session."""
|
||
from local_deep_research.database.session_context import (
|
||
get_user_db_session,
|
||
)
|
||
|
||
mock_sess = MagicMock()
|
||
mock_g = MagicMock()
|
||
mock_g.db_session = mock_sess
|
||
|
||
with (
|
||
patch(f"{MODULE}.has_request_context", return_value=False),
|
||
patch(f"{MODULE}.has_app_context", return_value=True),
|
||
patch(f"{MODULE}.g", mock_g),
|
||
):
|
||
with get_user_db_session(username="alice") as sess:
|
||
assert sess is mock_sess
|
||
|
||
def test_unencrypted_db_uses_placeholder_password( # DevSkim: ignore DS101155
|
||
self,
|
||
):
|
||
"""When no password and db is unencrypted, placeholder password is used.""" # DevSkim: ignore DS101155
|
||
from local_deep_research.database.session_context import (
|
||
get_user_db_session,
|
||
)
|
||
|
||
mock_sess = MagicMock()
|
||
mock_g = MagicMock(spec=[]) # No db_session attribute
|
||
|
||
with (
|
||
patch(f"{MODULE}.has_request_context", return_value=False),
|
||
patch(f"{MODULE}.has_app_context", return_value=False),
|
||
patch(f"{MODULE}.get_search_context", return_value=None),
|
||
patch(f"{MODULE}.db_manager") as mock_db,
|
||
patch(
|
||
"local_deep_research.database.thread_local_session.thread_session_manager.get_session",
|
||
return_value=mock_sess,
|
||
),
|
||
patch(f"{MODULE}.g", mock_g),
|
||
):
|
||
mock_db.has_encryption = False
|
||
with get_user_db_session(username="alice") as sess:
|
||
assert sess is mock_sess
|
||
|
||
def test_password_from_thread_context(self):
|
||
"""Password is retrieved from thread search context."""
|
||
from local_deep_research.database.session_context import (
|
||
get_user_db_session,
|
||
)
|
||
|
||
mock_sess = MagicMock()
|
||
mock_g = MagicMock(spec=[])
|
||
|
||
with (
|
||
patch(f"{MODULE}.has_request_context", return_value=False),
|
||
patch(f"{MODULE}.has_app_context", return_value=False),
|
||
patch(
|
||
f"{MODULE}.get_search_context",
|
||
return_value={"user_password": "thread_pw"},
|
||
),
|
||
patch(f"{MODULE}.db_manager") as mock_db,
|
||
patch(
|
||
"local_deep_research.database.thread_local_session.thread_session_manager.get_session",
|
||
return_value=mock_sess,
|
||
),
|
||
patch(f"{MODULE}.g", mock_g),
|
||
):
|
||
mock_db.has_encryption = True
|
||
with get_user_db_session(username="alice") as sess:
|
||
assert sess is mock_sess
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# get_user_db_session – error paths
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestGetUserDbSessionErrors:
|
||
def test_no_username_raises(self):
|
||
from local_deep_research.database.session_context import (
|
||
get_user_db_session,
|
||
DatabaseSessionError,
|
||
)
|
||
|
||
with (
|
||
patch(f"{MODULE}.has_request_context", return_value=False),
|
||
patch(f"{MODULE}.has_app_context", return_value=False),
|
||
):
|
||
with pytest.raises(
|
||
DatabaseSessionError, match="No authenticated user"
|
||
):
|
||
with get_user_db_session():
|
||
pass
|
||
|
||
def test_encrypted_db_no_password_raises(self):
|
||
from local_deep_research.database.session_context import (
|
||
get_user_db_session,
|
||
DatabaseSessionError,
|
||
)
|
||
|
||
mock_g = MagicMock(spec=[])
|
||
|
||
with (
|
||
patch(f"{MODULE}.has_request_context", return_value=False),
|
||
patch(f"{MODULE}.has_app_context", return_value=False),
|
||
patch(f"{MODULE}.get_search_context", return_value=None),
|
||
patch(f"{MODULE}.db_manager") as mock_db,
|
||
patch(f"{MODULE}.g", mock_g),
|
||
):
|
||
mock_db.has_encryption = True
|
||
with pytest.raises(DatabaseSessionError, match="requires password"):
|
||
with get_user_db_session(username="alice"):
|
||
pass
|
||
|
||
def test_failed_session_raises(self):
|
||
"""When get_metrics_session returns None, DatabaseSessionError is raised."""
|
||
from local_deep_research.database.session_context import (
|
||
get_user_db_session,
|
||
DatabaseSessionError,
|
||
)
|
||
|
||
mock_g = MagicMock(spec=[])
|
||
|
||
with (
|
||
patch(f"{MODULE}.has_request_context", return_value=False),
|
||
patch(f"{MODULE}.has_app_context", return_value=False),
|
||
patch(f"{MODULE}.get_search_context", return_value=None),
|
||
patch(f"{MODULE}.db_manager") as mock_db,
|
||
patch(
|
||
"local_deep_research.database.thread_local_session.thread_session_manager.get_session",
|
||
return_value=None,
|
||
),
|
||
patch(f"{MODULE}.g", mock_g),
|
||
):
|
||
mock_db.has_encryption = False
|
||
with pytest.raises(
|
||
DatabaseSessionError, match="Could not establish session"
|
||
):
|
||
with get_user_db_session(username="alice"):
|
||
pass
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# with_user_database decorator
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestWithUserDatabase:
|
||
def test_injects_session_as_first_arg(self):
|
||
from local_deep_research.database.session_context import (
|
||
with_user_database,
|
||
)
|
||
|
||
mock_sess = MagicMock()
|
||
|
||
@with_user_database
|
||
def my_func(db_session, x, y=0):
|
||
return (db_session, x + y)
|
||
|
||
with patch(f"{MODULE}.get_user_db_session") as mock_ctx:
|
||
mock_ctx.return_value.__enter__ = MagicMock(return_value=mock_sess)
|
||
mock_ctx.return_value.__exit__ = MagicMock(return_value=False)
|
||
result = my_func(10, y=5)
|
||
|
||
assert result == (mock_sess, 15)
|
||
|
||
def test_passes_username_and_password_from_kwargs(self):
|
||
from local_deep_research.database.session_context import (
|
||
with_user_database,
|
||
)
|
||
|
||
@with_user_database
|
||
def my_func(db_session):
|
||
return db_session
|
||
|
||
mock_sess = MagicMock()
|
||
with patch(f"{MODULE}.get_user_db_session") as mock_ctx:
|
||
mock_ctx.return_value.__enter__ = MagicMock(return_value=mock_sess)
|
||
mock_ctx.return_value.__exit__ = MagicMock(return_value=False)
|
||
result = my_func(_username="alice", _password="pw")
|
||
|
||
mock_ctx.assert_called_once_with("alice", "pw")
|
||
assert result is mock_sess
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# DatabaseAccessMixin
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class TestDatabaseAccessMixin:
|
||
def test_get_db_session_raises_deprecation_warning(self):
|
||
from local_deep_research.database.session_context import (
|
||
DatabaseAccessMixin,
|
||
)
|
||
|
||
class MyService(DatabaseAccessMixin):
|
||
pass
|
||
|
||
svc = MyService()
|
||
with pytest.raises(DeprecationWarning):
|
||
svc.get_db_session()
|
||
|
||
def test_get_db_session_message_content(self):
|
||
from local_deep_research.database.session_context import (
|
||
DatabaseAccessMixin,
|
||
)
|
||
|
||
svc = DatabaseAccessMixin()
|
||
try:
|
||
svc.get_db_session()
|
||
except DeprecationWarning as e:
|
||
assert "deprecated" in str(
|
||
e
|
||
).lower() or "get_user_db_session" in str(e)
|