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
424 lines
13 KiB
Python
424 lines
13 KiB
Python
#!/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"])
|