Files
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

404 lines
14 KiB
Python

"""Extended tests for database/session_context.py - targeting untested paths.
Covers:
- ensure_db_session() decorator (lines 160-212, entirely untested)
- get_user_db_session() reusing g.db_session
- get_user_db_session() password from g.user_password
- get_user_db_session() password from session_password_store
- get_user_db_session() password from thread context
- get_user_db_session() get_metrics_session returns None
"""
from unittest.mock import Mock, patch
import pytest
from flask import Flask, g, session as flask_session
@pytest.fixture
def app():
"""Create test Flask application."""
app = Flask(__name__)
app.config["TESTING"] = True
app.secret_key = "test-secret-key"
return app
# ── ensure_db_session decorator ──────────────────────────────────
class TestEnsureDbSession:
"""Tests for ensure_db_session decorator (lines 160-212, entirely untested)."""
def test_no_username_passes_through(self, app):
"""When no username in session, view runs without db_session setup."""
from local_deep_research.database.session_context import (
ensure_db_session,
)
view_called = []
@ensure_db_session
def my_view():
view_called.append(True)
return "ok"
with app.test_request_context():
result = my_view()
assert result == "ok"
assert view_called == [True]
def test_connected_user_gets_session(self, app):
"""Connected user gets g.db_session set from db_manager."""
from local_deep_research.database.session_context import (
ensure_db_session,
)
mock_session = Mock()
@ensure_db_session
def my_view():
return g.db_session
with app.test_request_context():
flask_session["username"] = "testuser"
with patch(
"local_deep_research.database.session_context.db_manager"
) as mock_db:
mock_db.is_user_connected.return_value = True
mock_db.get_session.return_value = mock_session
result = my_view()
assert result is mock_session
mock_db.get_session.assert_called_once_with("testuser")
def test_encrypted_db_not_connected_redirects_to_login(self, app):
"""Encrypted DB + disconnected user → clear session + redirect."""
from local_deep_research.database.session_context import (
ensure_db_session,
)
# Register auth blueprint with login route
from flask import Blueprint
auth_bp = Blueprint("auth", __name__)
@auth_bp.route("/login")
def login():
return "login page"
app.register_blueprint(auth_bp)
@ensure_db_session
def my_view():
return "should not reach"
with app.test_request_context():
flask_session["username"] = "testuser"
flask_session["some_data"] = "preserve_check"
with patch(
"local_deep_research.database.session_context.db_manager"
) as mock_db:
mock_db.is_user_connected.return_value = False
mock_db.has_encryption = True
result = my_view()
# Should be a redirect response
assert result.status_code == 302
assert "/login" in result.location
def test_unencrypted_db_not_connected_reopens(self, app):
"""Unencrypted DB + disconnected user → reopen database."""
from local_deep_research.database.session_context import (
ensure_db_session,
UNENCRYPTED_DB_PLACEHOLDER,
)
mock_session = Mock()
@ensure_db_session
def my_view():
return g.db_session
with app.test_request_context():
flask_session["username"] = "testuser"
with patch(
"local_deep_research.database.session_context.db_manager"
) as mock_db:
mock_db.is_user_connected.return_value = False
mock_db.has_encryption = False
mock_engine = Mock()
mock_db.open_user_database.return_value = mock_engine
mock_db.get_session.return_value = mock_session
result = my_view()
mock_db.open_user_database.assert_called_once_with(
"testuser", UNENCRYPTED_DB_PLACEHOLDER
)
assert result is mock_session
def test_unencrypted_reopen_fails_gracefully(self, app):
"""Failed unencrypted reopen doesn't crash — view still runs."""
from local_deep_research.database.session_context import (
ensure_db_session,
)
@ensure_db_session
def my_view():
return "view ran"
with app.test_request_context():
flask_session["username"] = "testuser"
with patch(
"local_deep_research.database.session_context.db_manager"
) as mock_db:
mock_db.is_user_connected.return_value = False
mock_db.has_encryption = False
mock_db.open_user_database.return_value = None # Engine failed
result = my_view()
assert result == "view ran"
def test_exception_during_session_setup_returns_500(self, app):
"""Exception in session setup returns 500 error response."""
from local_deep_research.database.session_context import (
ensure_db_session,
)
@ensure_db_session
def my_view():
return "recovered"
with app.test_request_context():
flask_session["username"] = "testuser"
with patch(
"local_deep_research.database.session_context.db_manager"
) as mock_db:
mock_db.is_user_connected.side_effect = RuntimeError("DB crash")
result = my_view()
assert result[1] == 500
def test_preserves_function_name(self):
"""Decorator preserves wrapped function name via functools.wraps."""
from local_deep_research.database.session_context import (
ensure_db_session,
)
@ensure_db_session
def my_special_view():
pass
assert my_special_view.__name__ == "my_special_view"
def test_passes_args_to_view(self, app):
"""Decorator passes positional and keyword args to wrapped view."""
from local_deep_research.database.session_context import (
ensure_db_session,
)
@ensure_db_session
def my_view(item_id, mode="default"):
return f"{item_id}:{mode}"
with app.test_request_context():
# No username → passes through without DB setup
result = my_view(42, mode="edit")
assert result == "42:edit"
# ── get_user_db_session: g.db_session reuse ──────────────────────
class TestGetUserDbSessionReuse:
"""Tests for g.db_session reuse path (lines 62-65)."""
def test_reuses_existing_g_db_session(self, app):
"""When g.db_session exists, it is reused without creating new session."""
from local_deep_research.database.session_context import (
get_user_db_session,
)
existing_session = Mock()
with app.test_request_context():
flask_session["username"] = "testuser"
g.db_session = existing_session
with get_user_db_session() as session:
assert session is existing_session
# ── get_user_db_session: password resolution paths ───────────────
class TestGetUserDbSessionPasswordPaths:
"""Tests for password resolution fallback chain."""
def test_password_from_g_user_password(self, app):
"""Password is retrieved from g.user_password."""
from local_deep_research.database.session_context import (
get_user_db_session,
)
mock_session = Mock()
with app.test_request_context():
flask_session["username"] = "testuser"
g.user_password = "secret123"
with patch(
"local_deep_research.database.session_context.db_manager"
) as mock_db:
mock_db.has_encryption = True
with patch(
"local_deep_research.database.thread_local_session.get_metrics_session"
) as mock_get:
mock_get.return_value = mock_session
with get_user_db_session() as session:
assert session is mock_session
mock_get.assert_called_once_with("testuser", "secret123")
def test_password_from_session_password_store(self, app):
"""Password is retrieved from session_password_store."""
from local_deep_research.database.session_context import (
get_user_db_session,
)
mock_session = Mock()
with app.test_request_context():
flask_session["username"] = "testuser"
flask_session["session_id"] = "sess-abc"
with (
patch(
"local_deep_research.database.session_context.db_manager"
) as mock_db,
patch(
"local_deep_research.database.session_context.get_search_context"
) as mock_ctx,
):
mock_db.has_encryption = True
mock_ctx.return_value = None # No thread context
# session_password_store is imported locally inside get_user_db_session
with patch(
"local_deep_research.database.session_passwords.session_password_store"
) as mock_store:
mock_store.get_session_password.return_value = "store-pass"
with patch(
"local_deep_research.database.thread_local_session.get_metrics_session"
) as mock_get:
mock_get.return_value = mock_session
with get_user_db_session() as session:
assert session is mock_session
mock_get.assert_called_once_with(
"testuser", "store-pass"
)
mock_store.get_session_password.assert_called_once_with(
"testuser", "sess-abc"
)
def test_password_from_thread_context(self, app):
"""Password is retrieved from thread context (background threads)."""
from local_deep_research.database.session_context import (
get_user_db_session,
)
mock_session = Mock()
with app.test_request_context():
flask_session["username"] = "testuser"
with (
patch(
"local_deep_research.database.session_context.db_manager"
) as mock_db,
patch(
"local_deep_research.database.session_context.get_search_context"
) as mock_ctx,
):
mock_db.has_encryption = True
mock_ctx.return_value = {"user_password": "thread-pass"}
with patch(
"local_deep_research.database.thread_local_session.get_metrics_session"
) as mock_get:
mock_get.return_value = mock_session
with get_user_db_session() as session:
assert session is mock_session
mock_get.assert_called_once_with("testuser", "thread-pass")
def test_get_metrics_session_returns_none_raises(self, app):
"""When get_metrics_session returns None, DatabaseSessionError is raised."""
from local_deep_research.database.session_context import (
get_user_db_session,
DatabaseSessionError,
)
with app.test_request_context():
flask_session["username"] = "testuser"
with patch(
"local_deep_research.database.session_context.db_manager"
) as mock_db:
mock_db.has_encryption = False
with patch(
"local_deep_research.database.thread_local_session.get_metrics_session"
) as mock_get:
mock_get.return_value = None
with pytest.raises(
DatabaseSessionError,
match="Could not establish session",
):
with get_user_db_session():
pass
def test_password_stored_in_g_after_success(self, app):
"""After successful session creation, password is stored in g."""
from local_deep_research.database.session_context import (
get_user_db_session,
)
mock_session = Mock()
with app.test_request_context():
flask_session["username"] = "testuser"
with patch(
"local_deep_research.database.session_context.db_manager"
) as mock_db:
mock_db.has_encryption = False
with patch(
"local_deep_research.database.thread_local_session.get_metrics_session"
) as mock_get:
mock_get.return_value = mock_session
with get_user_db_session() as _session:
# After yield, g.user_password should be set
assert hasattr(g, "user_password")