Files
wehub-resource-sync 7a0da7932b
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
chore: import upstream snapshot with attribution
2026-07-13 13:08:55 +08:00

433 lines
18 KiB
Python

"""
Tests for database/library_init.py.
Tests the library initialization module which handles:
- Seeding source_types table with predefined types
- Creating the default "Library" collection
- Full library initialization orchestration
"""
from unittest.mock import MagicMock, patch
import pytest
from sqlalchemy.exc import IntegrityError
from tests.test_utils import add_src_to_path
add_src_to_path()
from local_deep_research.database.library_init import ( # noqa: E402
seed_source_types,
ensure_default_library_collection,
ensure_research_history_collection,
initialize_library_for_user,
get_default_library_id,
get_source_type_id,
)
class TestSeedSourceTypes:
"""Tests for seed_source_types function."""
@patch("local_deep_research.database.library_init.get_user_db_session")
@patch("local_deep_research.database.library_init.uuid.uuid4")
def test_creates_all_predefined_types_when_none_exist(
self, mock_uuid, mock_get_session
):
"""Should create all predefined source types."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
# Simulate no existing types
mock_session.query.return_value.filter_by.return_value.first.return_value = None
mock_uuid.return_value = "test-uuid-123"
seed_source_types("testuser", "testpass")
# Should have queried for each type and added 6 new ones
# (research_download, user_upload, manual_entry, research_report,
# research_source, zotero)
assert mock_session.add.call_count == 6
mock_session.commit.assert_called_once()
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_skips_existing_types(self, mock_get_session):
"""Should not duplicate existing source types."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
# Simulate all types already exist
mock_existing = MagicMock()
mock_session.query.return_value.filter_by.return_value.first.return_value = mock_existing
seed_source_types("testuser", "testpass")
# Should not add any new types
mock_session.add.assert_not_called()
mock_session.commit.assert_called_once()
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_handles_integrity_error_gracefully(self, mock_get_session):
"""Should handle IntegrityError gracefully without raising."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.return_value.filter_by.return_value.first.return_value = None
mock_session.commit.side_effect = IntegrityError(
"statement", "params", "orig"
)
# Should not raise
seed_source_types("testuser", "testpass")
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_raises_on_unexpected_error(self, mock_get_session):
"""Should re-raise unexpected exceptions."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.side_effect = RuntimeError(
"Database connection failed"
)
with pytest.raises(RuntimeError, match="Database connection failed"):
seed_source_types("testuser", "testpass")
@patch("local_deep_research.database.library_init.logger")
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_logs_creation_messages(self, mock_get_session, mock_logger):
"""Should log when creating source types."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.return_value.filter_by.return_value.first.return_value = None
seed_source_types("testuser", "testpass")
# Should log info for each created type and final success
assert mock_logger.info.call_count >= 3
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_creates_types_with_correct_attributes(self, mock_get_session):
"""Should create source types with correct name, display_name, description, icon."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.return_value.filter_by.return_value.first.return_value = None
seed_source_types("testuser")
# Check the types created
added_types = [call.args[0] for call in mock_session.add.call_args_list]
type_names = [t.name for t in added_types]
assert "research_download" in type_names
assert "user_upload" in type_names
assert "manual_entry" in type_names
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_password_is_optional(self, mock_get_session):
"""Should work when password is not provided."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.return_value.filter_by.return_value.first.return_value = MagicMock()
# Should not raise when password is None
seed_source_types("testuser")
mock_get_session.assert_called_once_with("testuser", None)
class TestEnsureDefaultLibraryCollection:
"""Tests for ensure_default_library_collection function."""
@patch("local_deep_research.database.library_init.get_user_db_session")
@patch("local_deep_research.database.library_init.uuid.uuid4")
def test_creates_collection_when_none_exists(
self, mock_uuid, mock_get_session
):
"""Should create default Library collection when none exists."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.return_value.filter_by.return_value.first.return_value = None
mock_uuid.return_value = "new-library-uuid"
result = ensure_default_library_collection("testuser", "testpass")
assert result == "new-library-uuid"
mock_session.add.assert_called_once()
mock_session.commit.assert_called_once()
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_returns_existing_collection_id(self, mock_get_session):
"""Should return ID of existing default collection."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_existing = MagicMock()
mock_existing.id = "existing-library-uuid"
mock_session.query.return_value.filter_by.return_value.first.return_value = mock_existing
result = ensure_default_library_collection("testuser", "testpass")
assert result == "existing-library-uuid"
mock_session.add.assert_not_called()
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_creates_with_is_default_true(self, mock_get_session):
"""Should set is_default=True on new collection."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.return_value.filter_by.return_value.first.return_value = None
ensure_default_library_collection("testuser")
# Check that the collection was created with is_default=True
added_collection = mock_session.add.call_args.args[0]
assert added_collection.is_default is True
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_creates_with_correct_name_and_type(self, mock_get_session):
"""Should create collection with name='Library' and type='default_library'."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.return_value.filter_by.return_value.first.return_value = None
ensure_default_library_collection("testuser")
added_collection = mock_session.add.call_args.args[0]
assert added_collection.name == "Library"
assert added_collection.collection_type == "default_library"
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_raises_on_database_error(self, mock_get_session):
"""Should re-raise exceptions from database operations."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.side_effect = RuntimeError("Database error")
with pytest.raises(RuntimeError, match="Database error"):
ensure_default_library_collection("testuser")
class TestEnsureResearchHistoryCollection:
"""Tests for ensure_research_history_collection function."""
@patch("local_deep_research.database.library_init.get_user_db_session")
@patch("local_deep_research.database.library_init.uuid.uuid4")
def test_creates_collection_when_none_exists(
self, mock_uuid, mock_get_session
):
"""Should create Research History collection when none exists."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.return_value.filter_by.return_value.first.return_value = None
mock_uuid.return_value = "new-history-uuid"
result = ensure_research_history_collection("testuser", "testpass")
assert result == "new-history-uuid"
mock_session.add.assert_called_once()
mock_session.commit.assert_called_once()
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_returns_existing_collection_id(self, mock_get_session):
"""Should return ID of existing Research History collection."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_existing = MagicMock()
mock_existing.id = "existing-history-uuid"
mock_session.query.return_value.filter_by.return_value.first.return_value = mock_existing
result = ensure_research_history_collection("testuser", "testpass")
assert result == "existing-history-uuid"
mock_session.add.assert_not_called()
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_reraises_exception(self, mock_get_session):
"""Should re-raise exceptions from database operations."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.side_effect = RuntimeError("Database error")
with pytest.raises(RuntimeError, match="Database error"):
ensure_research_history_collection("testuser")
class TestInitializeLibraryForUser:
"""Tests for initialize_library_for_user function."""
@patch(
"local_deep_research.database.library_init.ensure_research_history_collection"
)
@patch(
"local_deep_research.database.library_init.ensure_default_library_collection"
)
@patch("local_deep_research.database.library_init.seed_source_types")
def test_returns_success_result(self, mock_seed, mock_ensure, mock_history):
"""Should return dict with success=True on success."""
mock_ensure.return_value = "library-uuid-123"
mock_history.return_value = "history-uuid-456"
result = initialize_library_for_user("testuser", "testpass")
assert result["success"] is True
assert result["source_types_seeded"] is True
assert result["library_collection_id"] == "library-uuid-123"
assert result["research_history_collection_id"] == "history-uuid-456"
assert "error" not in result
@patch(
"local_deep_research.database.library_init.ensure_research_history_collection"
)
@patch(
"local_deep_research.database.library_init.ensure_default_library_collection"
)
@patch("local_deep_research.database.library_init.seed_source_types")
def test_returns_error_on_seed_failure(
self, mock_seed, mock_ensure, mock_history
):
"""Should include error message when seed_source_types fails."""
mock_seed.side_effect = RuntimeError("Seeding failed")
result = initialize_library_for_user("testuser")
assert result["success"] is False
assert result["source_types_seeded"] is False
assert "error" in result
assert "Seeding failed" in result["error"]
@patch(
"local_deep_research.database.library_init.ensure_research_history_collection"
)
@patch(
"local_deep_research.database.library_init.ensure_default_library_collection"
)
@patch("local_deep_research.database.library_init.seed_source_types")
def test_returns_error_on_ensure_failure(
self, mock_seed, mock_ensure, mock_history
):
"""Should include error message when ensure_default_library_collection fails."""
mock_ensure.side_effect = RuntimeError("Collection creation failed")
result = initialize_library_for_user("testuser")
assert result["success"] is False
assert result["source_types_seeded"] is True # Seeding succeeded
assert result["library_collection_id"] is None
assert "error" in result
assert "Collection creation failed" in result["error"]
@patch(
"local_deep_research.database.library_init.ensure_research_history_collection"
)
@patch(
"local_deep_research.database.library_init.ensure_default_library_collection"
)
@patch("local_deep_research.database.library_init.seed_source_types")
def test_calls_seed_and_ensure_in_order(
self, mock_seed, mock_ensure, mock_history
):
"""Should call both seed_source_types and ensure_default_library_collection."""
mock_ensure.return_value = "library-uuid"
call_order = []
mock_seed.side_effect = lambda *args, **kwargs: call_order.append(
"seed"
)
mock_ensure.side_effect = lambda *args, **kwargs: (
call_order.append("ensure"),
"library-uuid",
)[1]
initialize_library_for_user("testuser", "testpass")
assert call_order == ["seed", "ensure"]
mock_seed.assert_called_once_with("testuser", "testpass")
mock_ensure.assert_called_once_with("testuser", "testpass")
@patch(
"local_deep_research.database.library_init.ensure_default_library_collection"
)
@patch("local_deep_research.database.library_init.seed_source_types")
def test_returns_all_expected_keys(self, mock_seed, mock_ensure):
"""Should return dict with all expected keys."""
mock_ensure.return_value = "lib-id"
result = initialize_library_for_user("testuser")
assert "source_types_seeded" in result
assert "library_collection_id" in result
assert "success" in result
class TestGetDefaultLibraryId:
"""Tests for get_default_library_id function."""
@patch(
"local_deep_research.database.library_init.ensure_default_library_collection"
)
def test_returns_library_id(self, mock_ensure):
"""Should return the library collection ID."""
mock_ensure.return_value = "default-lib-uuid"
result = get_default_library_id("testuser", "testpass")
assert result == "default-lib-uuid"
mock_ensure.assert_called_once_with("testuser", "testpass")
@patch(
"local_deep_research.database.library_init.ensure_default_library_collection"
)
def test_creates_library_if_missing(self, mock_ensure):
"""Should create library if it doesn't exist (via ensure_default_library_collection)."""
mock_ensure.return_value = "new-lib-uuid"
result = get_default_library_id("testuser")
assert result == "new-lib-uuid"
class TestGetSourceTypeId:
"""Tests for get_source_type_id function."""
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_returns_id_for_valid_type(self, mock_get_session):
"""Should return ID for existing source type."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_source_type = MagicMock()
mock_source_type.id = "research-download-uuid"
mock_session.query.return_value.filter_by.return_value.first.return_value = mock_source_type
result = get_source_type_id("testuser", "research_download", "testpass")
assert result == "research-download-uuid"
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_raises_value_error_for_unknown_type(self, mock_get_session):
"""Should raise ValueError for non-existent type."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.return_value.filter_by.return_value.first.return_value = None
with pytest.raises(ValueError, match="Source type not found"):
get_source_type_id("testuser", "nonexistent_type")
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_raises_on_database_error(self, mock_get_session):
"""Should re-raise database errors after logging."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_session.query.side_effect = RuntimeError("Connection lost")
with pytest.raises(RuntimeError, match="Connection lost"):
get_source_type_id("testuser", "user_upload")
@patch("local_deep_research.database.library_init.get_user_db_session")
def test_password_is_optional(self, mock_get_session):
"""Should work when password is not provided."""
mock_session = MagicMock()
mock_get_session.return_value.__enter__.return_value = mock_session
mock_source_type = MagicMock()
mock_source_type.id = "type-uuid"
mock_session.query.return_value.filter_by.return_value.first.return_value = mock_source_type
result = get_source_type_id("testuser", "manual_entry")
assert result == "type-uuid"
mock_get_session.assert_called_once_with("testuser", None)