chore: import upstream snapshot with attribution
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
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
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
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,423 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Test ORM operations with encrypted user databases."""
|
||||
|
||||
import sys
|
||||
import tempfile
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
sys.path.insert(
|
||||
0,
|
||||
str(Path(__file__).parent.parent.parent.resolve()),
|
||||
)
|
||||
|
||||
from local_deep_research.database.encrypted_db import DatabaseManager
|
||||
from local_deep_research.database.models import (
|
||||
APIKey,
|
||||
Report,
|
||||
ResearchHistory,
|
||||
ResearchLog,
|
||||
ResearchResource,
|
||||
ResearchTask,
|
||||
SearchQuery,
|
||||
SearchResult,
|
||||
UserSettings,
|
||||
)
|
||||
|
||||
|
||||
class TestEncryptedDatabaseORM:
|
||||
"""Test ORM operations in encrypted user databases."""
|
||||
|
||||
@pytest.fixture
|
||||
def temp_data_dir(self):
|
||||
"""Create a temporary directory for test databases."""
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
yield Path(tmpdir)
|
||||
|
||||
@pytest.fixture
|
||||
def db_manager(self, temp_data_dir, monkeypatch):
|
||||
"""Create a database manager with temporary directory."""
|
||||
monkeypatch.setattr(
|
||||
"local_deep_research.database.encrypted_db.get_data_directory",
|
||||
lambda: temp_data_dir,
|
||||
)
|
||||
manager = DatabaseManager()
|
||||
yield manager
|
||||
# Cleanup
|
||||
for username in list(manager.connections.keys()):
|
||||
manager.close_user_database(username)
|
||||
|
||||
@pytest.fixture
|
||||
def test_user_session(self, db_manager):
|
||||
"""Create a test user with encrypted database and return session."""
|
||||
username = "testuser"
|
||||
password = "TestPassword123!"
|
||||
|
||||
# Create user database
|
||||
db_manager.create_user_database(username, password)
|
||||
|
||||
# Get session
|
||||
session = db_manager.get_session(username)
|
||||
yield session
|
||||
|
||||
# Cleanup
|
||||
session.close()
|
||||
db_manager.close_user_database(username)
|
||||
|
||||
def test_user_settings_crud(self, test_user_session):
|
||||
"""Test CRUD operations on UserSettings."""
|
||||
session = test_user_session
|
||||
|
||||
# Create settings
|
||||
setting1 = UserSettings(
|
||||
key="llm.provider",
|
||||
value={"provider": "openai", "model": "gpt-4"},
|
||||
category="llm",
|
||||
)
|
||||
setting2 = UserSettings(
|
||||
key="search.engine",
|
||||
value={"engine": "duckduckgo", "safe_search": True},
|
||||
category="search",
|
||||
)
|
||||
|
||||
session.add_all([setting1, setting2])
|
||||
session.commit()
|
||||
|
||||
# Read settings
|
||||
llm_setting = (
|
||||
session.query(UserSettings).filter_by(key="llm.provider").first()
|
||||
)
|
||||
assert llm_setting is not None
|
||||
assert llm_setting.value["provider"] == "openai"
|
||||
assert llm_setting.category == "llm"
|
||||
|
||||
# Update setting
|
||||
llm_setting.value = {"provider": "anthropic", "model": "claude-3"}
|
||||
session.commit()
|
||||
|
||||
# Verify update
|
||||
updated = (
|
||||
session.query(UserSettings).filter_by(key="llm.provider").first()
|
||||
)
|
||||
assert updated.value["provider"] == "anthropic"
|
||||
|
||||
# Delete setting
|
||||
session.delete(setting2)
|
||||
session.commit()
|
||||
|
||||
# Verify deletion
|
||||
remaining = session.query(UserSettings).count()
|
||||
assert remaining == 1
|
||||
|
||||
def test_api_keys_encryption(self, test_user_session):
|
||||
"""Test API key storage and retrieval."""
|
||||
session = test_user_session
|
||||
|
||||
# Store API keys
|
||||
openai_key = APIKey(
|
||||
provider="openai",
|
||||
key="sk-test123456789", # This will be encrypted in the database
|
||||
is_active=True,
|
||||
)
|
||||
anthropic_key = APIKey(
|
||||
provider="anthropic", key="sk-ant-test987654321", is_active=False
|
||||
)
|
||||
|
||||
session.add_all([openai_key, anthropic_key])
|
||||
session.commit()
|
||||
|
||||
# Retrieve and verify
|
||||
stored_key = session.query(APIKey).filter_by(provider="openai").first()
|
||||
assert stored_key is not None
|
||||
assert stored_key.key == "sk-test123456789" # Should decrypt properly
|
||||
assert stored_key.is_active is True
|
||||
|
||||
# Query by active status
|
||||
active_keys = session.query(APIKey).filter_by(is_active=True).all()
|
||||
assert len(active_keys) == 1
|
||||
assert active_keys[0].provider == "openai"
|
||||
|
||||
def test_research_history_with_resources(self, test_user_session):
|
||||
"""Test ResearchHistory with related ResearchResource objects."""
|
||||
session = test_user_session
|
||||
|
||||
# Create research history
|
||||
research_id = str(uuid.uuid4())
|
||||
research = ResearchHistory(
|
||||
id=research_id,
|
||||
query="quantum computing applications",
|
||||
mode="detailed",
|
||||
status="completed",
|
||||
created_at=datetime.now(timezone.utc).isoformat(),
|
||||
completed_at=datetime.now(timezone.utc).isoformat(),
|
||||
progress=100,
|
||||
research_meta={
|
||||
"model": "gpt-4",
|
||||
"temperature": 0.7,
|
||||
"iterations": 3,
|
||||
},
|
||||
)
|
||||
session.add(research)
|
||||
session.commit()
|
||||
|
||||
# Add resources
|
||||
resources = [
|
||||
ResearchResource(
|
||||
research_id=research_id,
|
||||
title="Quantum Computing in Drug Discovery",
|
||||
url="https://example.com/quantum-drug",
|
||||
content_preview="Recent advances in quantum computing...",
|
||||
source_type="article",
|
||||
resource_metadata={"author": "Dr. Smith", "year": 2024},
|
||||
created_at=datetime.now(timezone.utc).isoformat(),
|
||||
),
|
||||
ResearchResource(
|
||||
research_id=research_id,
|
||||
title="IBM Quantum Network",
|
||||
url="https://example.com/ibm-quantum",
|
||||
content_preview="IBM's quantum computing initiative...",
|
||||
source_type="web",
|
||||
resource_metadata={"company": "IBM", "relevance": 0.95},
|
||||
created_at=datetime.now(timezone.utc).isoformat(),
|
||||
),
|
||||
]
|
||||
|
||||
session.add_all(resources)
|
||||
session.commit()
|
||||
|
||||
# Query with relationship
|
||||
stored_research = (
|
||||
session.query(ResearchHistory).filter_by(id=research_id).first()
|
||||
)
|
||||
assert stored_research is not None
|
||||
assert len(stored_research.resources) == 2
|
||||
|
||||
# Query resources directly
|
||||
quantum_resources = (
|
||||
session.query(ResearchResource)
|
||||
.filter(ResearchResource.title.contains("Quantum"))
|
||||
.all()
|
||||
)
|
||||
assert len(quantum_resources) == 2
|
||||
|
||||
def test_research_task_workflow(self, test_user_session):
|
||||
"""Test complete research task workflow with queries and results."""
|
||||
session = test_user_session
|
||||
|
||||
# Create research task
|
||||
task = ResearchTask(
|
||||
title="AI Safety Research",
|
||||
description="Comprehensive research on AI alignment and safety",
|
||||
status="in_progress",
|
||||
)
|
||||
session.add(task)
|
||||
session.commit()
|
||||
|
||||
# Add search queries
|
||||
query1 = SearchQuery(
|
||||
research_task_id=task.id,
|
||||
query="AI alignment problem solutions",
|
||||
search_engine="google",
|
||||
search_type="academic",
|
||||
status="completed",
|
||||
)
|
||||
query2 = SearchQuery(
|
||||
research_task_id=task.id,
|
||||
query="AI safety research organizations",
|
||||
search_engine="duckduckgo",
|
||||
search_type="web",
|
||||
status="completed",
|
||||
)
|
||||
|
||||
session.add_all([query1, query2])
|
||||
session.commit()
|
||||
|
||||
# Add search results
|
||||
results = [
|
||||
SearchResult(
|
||||
research_task_id=task.id,
|
||||
search_query_id=query1.id,
|
||||
title="Concrete Problems in AI Safety",
|
||||
url="https://example.com/ai-safety-paper",
|
||||
snippet="A comprehensive survey of AI safety challenges...",
|
||||
relevance_score=0.98,
|
||||
position=1,
|
||||
),
|
||||
SearchResult(
|
||||
research_task_id=task.id,
|
||||
search_query_id=query2.id,
|
||||
title="Center for AI Safety",
|
||||
url="https://example.com/cais",
|
||||
snippet="Leading research organization focused on AI safety...",
|
||||
relevance_score=0.95,
|
||||
position=1,
|
||||
),
|
||||
]
|
||||
|
||||
session.add_all(results)
|
||||
session.commit()
|
||||
|
||||
# Add report
|
||||
report = Report(
|
||||
research_task_id=task.id,
|
||||
title="AI Safety Research Report",
|
||||
content="# AI Safety Research\n\n## Executive Summary\n...",
|
||||
format="markdown",
|
||||
is_draft=False,
|
||||
word_count=1500,
|
||||
section_count=5,
|
||||
)
|
||||
|
||||
session.add(report)
|
||||
session.commit()
|
||||
|
||||
# Update task status
|
||||
task.status = "completed"
|
||||
task.completed_at = datetime.now(timezone.utc)
|
||||
session.commit()
|
||||
|
||||
# Verify relationships
|
||||
completed_task = (
|
||||
session.query(ResearchTask).filter_by(id=task.id).first()
|
||||
)
|
||||
assert completed_task.status == "completed"
|
||||
assert len(completed_task.searches) == 2
|
||||
assert len(completed_task.results) == 2
|
||||
assert len(completed_task.reports) == 1
|
||||
|
||||
# Query high relevance results
|
||||
high_relevance = (
|
||||
session.query(SearchResult)
|
||||
.filter(SearchResult.relevance_score > 0.9)
|
||||
.all()
|
||||
)
|
||||
assert len(high_relevance) == 2
|
||||
|
||||
def test_research_logs(self, test_user_session):
|
||||
"""Test research logging functionality."""
|
||||
session = test_user_session
|
||||
|
||||
# ResearchLog.research_id is a String(36) FK to
|
||||
# research_history.id (UUID), not research.id (Integer).
|
||||
# Production writes UUIDs via log_utils, so the test must too.
|
||||
research = ResearchHistory(
|
||||
id=str(uuid.uuid4()),
|
||||
query="Test research for logging",
|
||||
mode="detailed",
|
||||
status="in_progress",
|
||||
created_at=datetime.now(timezone.utc).isoformat(),
|
||||
)
|
||||
session.add(research)
|
||||
session.commit()
|
||||
|
||||
# Add various log entries
|
||||
logs = [
|
||||
ResearchLog(
|
||||
research_id=research.id,
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
level="INFO",
|
||||
message="Starting research process",
|
||||
module="research_service",
|
||||
function="start_research",
|
||||
line_no=100,
|
||||
),
|
||||
ResearchLog(
|
||||
research_id=research.id,
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
level="DEBUG",
|
||||
message="Executing search query",
|
||||
module="search_engine",
|
||||
function="search",
|
||||
line_no=250,
|
||||
),
|
||||
ResearchLog(
|
||||
research_id=research.id,
|
||||
timestamp=datetime.now(timezone.utc),
|
||||
level="ERROR",
|
||||
message="API rate limit exceeded",
|
||||
module="api_client",
|
||||
function="make_request",
|
||||
line_no=75,
|
||||
),
|
||||
]
|
||||
|
||||
session.add_all(logs)
|
||||
session.commit()
|
||||
|
||||
# Query logs by level
|
||||
error_logs = (
|
||||
session.query(ResearchLog)
|
||||
.filter_by(research_id=research.id, level="ERROR")
|
||||
.all()
|
||||
)
|
||||
assert len(error_logs) == 1
|
||||
assert "rate limit" in error_logs[0].message
|
||||
|
||||
# Query all logs for research
|
||||
all_logs = (
|
||||
session.query(ResearchLog)
|
||||
.filter_by(research_id=research.id)
|
||||
.order_by(ResearchLog.timestamp)
|
||||
.all()
|
||||
)
|
||||
assert len(all_logs) == 3
|
||||
|
||||
def test_database_integrity(self, db_manager):
|
||||
"""Test database integrity checks."""
|
||||
username = "integrity_test_user"
|
||||
password = "IntegrityTest123!"
|
||||
|
||||
# Create and open database
|
||||
db_manager.create_user_database(username, password)
|
||||
|
||||
# Check integrity
|
||||
integrity_ok = db_manager.check_database_integrity(username)
|
||||
assert integrity_ok is True
|
||||
|
||||
# Cleanup
|
||||
db_manager.close_user_database(username)
|
||||
|
||||
def test_concurrent_users(self, db_manager):
|
||||
"""Test multiple users with separate encrypted databases."""
|
||||
users = [
|
||||
("alice", "AlicePass123!"),
|
||||
("bob", "BobPass456!"),
|
||||
("charlie", "CharliePass789!"),
|
||||
]
|
||||
|
||||
# Create databases for each user
|
||||
for username, password in users:
|
||||
engine = db_manager.create_user_database(username, password)
|
||||
assert engine is not None
|
||||
|
||||
# Add a setting specific to each user
|
||||
session = db_manager.get_session(username)
|
||||
setting = UserSettings(
|
||||
key="user.theme",
|
||||
value={"theme": f"{username}_theme"},
|
||||
category="ui",
|
||||
)
|
||||
session.add(setting)
|
||||
session.commit()
|
||||
session.close()
|
||||
|
||||
# Verify each user has their own data
|
||||
for username, password in users:
|
||||
session = db_manager.get_session(username)
|
||||
setting = (
|
||||
session.query(UserSettings).filter_by(key="user.theme").first()
|
||||
)
|
||||
assert setting is not None
|
||||
assert setting.value["theme"] == f"{username}_theme"
|
||||
session.close()
|
||||
|
||||
# Cleanup
|
||||
for username, _ in users:
|
||||
db_manager.close_user_database(username)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pytest.main([__file__, "-v"])
|
||||
Reference in New Issue
Block a user