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
169 lines
5.9 KiB
Python
169 lines
5.9 KiB
Python
"""Test multi-user encrypted database functionality."""
|
|
|
|
import shutil
|
|
import tempfile
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from local_deep_research.database.encrypted_db import DatabaseManager
|
|
from local_deep_research.database.models import (
|
|
ResearchHistory,
|
|
)
|
|
from local_deep_research.database.models.auth import User
|
|
|
|
|
|
class TestMultiUserDatabase:
|
|
"""Test suite for multi-user encrypted database functionality."""
|
|
|
|
@pytest.fixture
|
|
def temp_dir(self):
|
|
"""Create a temporary directory for test databases."""
|
|
temp_dir = tempfile.mkdtemp()
|
|
yield temp_dir
|
|
shutil.rmtree(temp_dir)
|
|
|
|
@pytest.fixture
|
|
def db_manager(self, temp_dir):
|
|
"""Create a database manager with a custom data directory."""
|
|
# Mock the data directory to use our temp directory
|
|
with patch(
|
|
"local_deep_research.database.encrypted_db.get_data_directory"
|
|
) as mock_get_dir:
|
|
mock_get_dir.return_value = Path(temp_dir)
|
|
manager = DatabaseManager()
|
|
manager.data_dir = Path(temp_dir) / "encrypted_databases"
|
|
manager.data_dir.mkdir(parents=True, exist_ok=True)
|
|
yield manager
|
|
|
|
@pytest.fixture
|
|
def mock_auth_db(self, monkeypatch):
|
|
"""Mock the auth database functions."""
|
|
mock_session = MagicMock()
|
|
mock_user = MagicMock(spec=User)
|
|
mock_user.username = "testuser"
|
|
|
|
mock_session.query.return_value.filter_by.return_value.first.return_value = mock_user
|
|
mock_session.close = MagicMock()
|
|
|
|
def mock_get_auth_db_session():
|
|
return mock_session
|
|
|
|
monkeypatch.setattr(
|
|
"local_deep_research.database.auth_db.get_auth_db_session",
|
|
mock_get_auth_db_session,
|
|
)
|
|
|
|
return mock_session
|
|
|
|
def test_database_isolation_without_sqlcipher(
|
|
self, db_manager, mock_auth_db
|
|
):
|
|
"""Test that the system handles missing SQLCipher gracefully."""
|
|
# This test verifies the system's behavior when SQLCipher is not available
|
|
# In a real deployment, SQLCipher would be required
|
|
|
|
# Mock SQLAlchemy to simulate SQLCipher not being available
|
|
with patch(
|
|
"local_deep_research.database.encrypted_db.create_engine"
|
|
) as mock_engine:
|
|
mock_engine.side_effect = ImportError(
|
|
"No module named 'pysqlcipher3'"
|
|
)
|
|
|
|
# Attempt to create a user database
|
|
with pytest.raises(ImportError):
|
|
db_manager.create_user_database("testuser", "password123")
|
|
|
|
def test_user_exists_check(self, db_manager, mock_auth_db):
|
|
"""Test checking if a user exists."""
|
|
# Test existing user
|
|
assert db_manager.user_exists("testuser") is True
|
|
|
|
# Test non-existing user
|
|
mock_auth_db.query.return_value.filter_by.return_value.first.return_value = None
|
|
assert db_manager.user_exists("nonexistent") is False
|
|
|
|
def test_database_path_generation(self, db_manager):
|
|
"""Test that database paths are generated correctly."""
|
|
# Test path generation for different usernames
|
|
path1 = db_manager._get_user_db_path("user1")
|
|
path2 = db_manager._get_user_db_path("user2")
|
|
|
|
# Paths should be different
|
|
assert path1 != path2
|
|
|
|
# Paths should be in the correct directory
|
|
assert path1.parent == db_manager.data_dir
|
|
assert path2.parent == db_manager.data_dir
|
|
|
|
# Paths should use hashed usernames
|
|
assert "user1" not in str(path1)
|
|
assert "user2" not in str(path2)
|
|
|
|
def test_memory_usage_tracking(self, db_manager):
|
|
"""Test memory usage statistics."""
|
|
stats = db_manager.get_memory_usage()
|
|
|
|
assert stats["active_connections"] == 0
|
|
assert stats["active_sessions"] == 0
|
|
assert stats["estimated_memory_mb"] == 0
|
|
|
|
def test_session_management_without_sqlcipher(self, db_manager):
|
|
"""Test session management when SQLCipher is not available."""
|
|
# Without an open database, get_session should return None
|
|
session = db_manager.get_session("testuser")
|
|
assert session is None
|
|
|
|
@pytest.mark.skipif(
|
|
True, # Always skip for now since SQLCipher is not installed
|
|
reason="SQLCipher not available in test environment",
|
|
)
|
|
def test_full_multiuser_flow(self, db_manager, mock_auth_db):
|
|
"""Test complete multi-user flow with SQLCipher (skipped if not available)."""
|
|
# This test would run if SQLCipher were installed
|
|
|
|
# Create databases for two users
|
|
|
|
# Get sessions for each user
|
|
session1 = db_manager.get_session("user1")
|
|
session2 = db_manager.get_session("user2")
|
|
|
|
# Add research to user1's database
|
|
research1 = ResearchHistory(
|
|
query="User 1 research", mode="quick", status="completed"
|
|
)
|
|
session1.add(research1)
|
|
session1.commit()
|
|
|
|
# Add research to user2's database
|
|
research2 = ResearchHistory(
|
|
query="User 2 research", mode="deep", status="completed"
|
|
)
|
|
session2.add(research2)
|
|
session2.commit()
|
|
|
|
# Verify isolation - each user only sees their own data
|
|
user1_research = session1.query(ResearchHistory).all()
|
|
user2_research = session2.query(ResearchHistory).all()
|
|
|
|
assert len(user1_research) == 1
|
|
assert len(user2_research) == 1
|
|
assert user1_research[0].query == "User 1 research"
|
|
assert user2_research[0].query == "User 2 research"
|
|
|
|
# Test password change
|
|
success = db_manager.change_password(
|
|
"user1", "password1", "newpassword1"
|
|
)
|
|
assert success is True
|
|
|
|
# Close databases
|
|
db_manager.close_user_database("user1")
|
|
db_manager.close_user_database("user2")
|
|
|
|
# Verify databases are closed
|
|
assert "user1" not in db_manager.connections
|
|
assert "user2" not in db_manager.connections
|