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

436 lines
16 KiB
Python

"""
Tests for SQLCipher missing scenario.
Verifies that users get helpful error messages when SQLCipher is not installed
and that the LDR_BOOTSTRAP_ALLOW_UNENCRYPTED workaround works.
"""
import os
import pytest
from unittest.mock import patch
from tests.test_utils import add_src_to_path
add_src_to_path()
def _clear_allow_unencrypted_env():
"""Clear both canonical and deprecated env vars."""
os.environ.pop("LDR_BOOTSTRAP_ALLOW_UNENCRYPTED", None)
os.environ.pop("LDR_ALLOW_UNENCRYPTED", None)
class TestSQLCipherMissing:
"""Test behavior when SQLCipher is not available."""
def test_error_message_mentions_sqlcipher(self):
"""Error message should mention SQLCipher so users know what's missing."""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
old_canonical = os.environ.pop("LDR_BOOTSTRAP_ALLOW_UNENCRYPTED", None)
old_deprecated = os.environ.pop("LDR_ALLOW_UNENCRYPTED", None)
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
with pytest.raises(RuntimeError) as exc_info:
DatabaseManager()
error_msg = str(exc_info.value)
assert "SQLCipher" in error_msg, (
f"Error should mention SQLCipher. Got: {error_msg}"
)
finally:
if old_canonical is not None:
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = old_canonical
if old_deprecated is not None:
os.environ["LDR_ALLOW_UNENCRYPTED"] = old_deprecated
def test_error_message_mentions_workaround(self):
"""Error message should mention LDR_BOOTSTRAP_ALLOW_UNENCRYPTED workaround."""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
old_canonical = os.environ.pop("LDR_BOOTSTRAP_ALLOW_UNENCRYPTED", None)
old_deprecated = os.environ.pop("LDR_ALLOW_UNENCRYPTED", None)
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
with pytest.raises(RuntimeError) as exc_info:
DatabaseManager()
error_msg = str(exc_info.value)
assert "LDR_BOOTSTRAP_ALLOW_UNENCRYPTED" in error_msg, (
f"Error should mention workaround. Got: {error_msg}"
)
finally:
if old_canonical is not None:
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = old_canonical
if old_deprecated is not None:
os.environ["LDR_ALLOW_UNENCRYPTED"] = old_deprecated
def test_canonical_workaround_allows_startup_without_encryption(self):
"""LDR_BOOTSTRAP_ALLOW_UNENCRYPTED=true should allow startup without SQLCipher."""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
old_canonical = os.environ.get("LDR_BOOTSTRAP_ALLOW_UNENCRYPTED")
old_deprecated = os.environ.get("LDR_ALLOW_UNENCRYPTED")
_clear_allow_unencrypted_env()
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = "true"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should NOT raise
manager = DatabaseManager()
assert manager.has_encryption is False, (
"With workaround and no SQLCipher, has_encryption should be False"
)
finally:
_clear_allow_unencrypted_env()
if old_canonical is not None:
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = old_canonical
if old_deprecated is not None:
os.environ["LDR_ALLOW_UNENCRYPTED"] = old_deprecated
def test_deprecated_workaround_still_works(self):
"""Deprecated LDR_ALLOW_UNENCRYPTED=true should still allow startup (backward compat)."""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
old_canonical = os.environ.get("LDR_BOOTSTRAP_ALLOW_UNENCRYPTED")
old_deprecated = os.environ.get("LDR_ALLOW_UNENCRYPTED")
_clear_allow_unencrypted_env()
os.environ["LDR_ALLOW_UNENCRYPTED"] = "true"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should NOT raise (backward compatibility)
manager = DatabaseManager()
assert manager.has_encryption is False, (
"With deprecated workaround and no SQLCipher, has_encryption should be False"
)
finally:
_clear_allow_unencrypted_env()
if old_canonical is not None:
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = old_canonical
if old_deprecated is not None:
os.environ["LDR_ALLOW_UNENCRYPTED"] = old_deprecated
def test_db_manager_has_encryption_is_boolean(self):
"""db_manager.has_encryption should be a boolean."""
from local_deep_research.database.encrypted_db import db_manager
assert isinstance(db_manager.has_encryption, bool), (
f"has_encryption should be bool, got {type(db_manager.has_encryption)}"
)
class TestAllowUnencryptedEdgeCases:
"""Edge cases for LDR_BOOTSTRAP_ALLOW_UNENCRYPTED fallback.
The allow_unencrypted setting uses the BooleanSetting from the env
settings registry, which accepts "true", "1", "yes", "on", "enabled"
(case-insensitive) as truthy values and handles deprecated alias
fallback automatically.
"""
def test_canonical_empty_string_does_not_fall_through(self):
"""Empty string canonical should NOT fall through to deprecated.
When LDR_BOOTSTRAP_ALLOW_UNENCRYPTED="" is set, it takes precedence
and the check (empty_string or "").lower() == "true" is False.
"""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = ""
os.environ["LDR_ALLOW_UNENCRYPTED"] = "true"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should raise because empty string is not "true"
with pytest.raises(RuntimeError):
DatabaseManager()
finally:
_clear_allow_unencrypted_env()
def test_uppercase_true_not_accepted(self):
"""'TRUE' (uppercase) is not accepted as true.
The code uses .lower() == "true", so "TRUE".lower() == "true" is True.
"""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = "TRUE"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should NOT raise - "TRUE".lower() == "true" is True
manager = DatabaseManager()
assert manager.has_encryption is False
finally:
_clear_allow_unencrypted_env()
def test_mixed_case_true_accepted(self):
"""'True' (mixed case) is accepted as true due to .lower()."""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = "True"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should NOT raise - "True".lower() == "true" is True
manager = DatabaseManager()
assert manager.has_encryption is False
finally:
_clear_allow_unencrypted_env()
def test_whitespace_padded_true_not_accepted(self):
"""' true ' (whitespace padded) is not accepted as true.
The code doesn't strip whitespace, so " true " != "true".
"""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = " true "
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should raise - " true " != "true"
with pytest.raises(RuntimeError):
DatabaseManager()
finally:
_clear_allow_unencrypted_env()
def test_yes_accepted_as_true(self):
"""'yes' is accepted as true by BooleanSetting."""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = "yes"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should NOT raise - BooleanSetting accepts "yes"
manager = DatabaseManager()
assert manager.has_encryption is False
finally:
_clear_allow_unencrypted_env()
def test_one_accepted_as_true(self):
"""'1' is accepted as true by BooleanSetting."""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = "1"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should NOT raise - BooleanSetting accepts "1"
manager = DatabaseManager()
assert manager.has_encryption is False
finally:
_clear_allow_unencrypted_env()
def test_canonical_false_does_not_check_deprecated(self):
"""canonical='false' should NOT fall through to deprecated='true'.
Once canonical is set (to any value), deprecated is not checked.
"""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = "false"
os.environ["LDR_ALLOW_UNENCRYPTED"] = "true"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should raise - canonical "false" takes precedence
with pytest.raises(RuntimeError):
DatabaseManager()
finally:
_clear_allow_unencrypted_env()
def test_empty_canonical_with_deprecated_true_raises(self):
"""Empty canonical with deprecated='true' - empty takes precedence.
When canonical is "", the deprecated is not checked because
canonical is "set" (to empty string).
"""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
os.environ["LDR_BOOTSTRAP_ALLOW_UNENCRYPTED"] = ""
os.environ["LDR_ALLOW_UNENCRYPTED"] = "true"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should raise - empty string canonical takes precedence
with pytest.raises(RuntimeError):
DatabaseManager()
finally:
_clear_allow_unencrypted_env()
def test_only_deprecated_set_to_true_works(self):
"""When only deprecated is set to 'true', it should work."""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
os.environ["LDR_ALLOW_UNENCRYPTED"] = "true"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should NOT raise - deprecated "true" works
manager = DatabaseManager()
assert manager.has_encryption is False
finally:
_clear_allow_unencrypted_env()
def test_deprecated_uppercase_true_accepted(self):
"""Deprecated 'TRUE' (uppercase) works due to .lower()."""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
os.environ["LDR_ALLOW_UNENCRYPTED"] = "TRUE"
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
# Should NOT raise - "TRUE".lower() == "true"
manager = DatabaseManager()
assert manager.has_encryption is False
finally:
_clear_allow_unencrypted_env()
def test_neither_set_raises_runtime_error(self):
"""When neither canonical nor deprecated is set, RuntimeError is raised."""
from local_deep_research.database.encrypted_db import (
DatabaseManager,
)
_clear_allow_unencrypted_env()
try:
with patch(
"local_deep_research.database.encrypted_db.get_sqlcipher_module"
) as mock_get:
mock_get.side_effect = ImportError(
"No module named 'sqlcipher3'"
)
with pytest.raises(RuntimeError):
DatabaseManager()
finally:
_clear_allow_unencrypted_env()