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
450 lines
14 KiB
Python
450 lines
14 KiB
Python
"""
|
|
Tests for news/core/storage.py
|
|
|
|
Tests cover:
|
|
- BaseStorage abstract interface
|
|
- CardStorage abstract interface
|
|
- RatingStorage abstract interface
|
|
- PreferenceStorage abstract interface
|
|
- generate_id() utility method
|
|
"""
|
|
|
|
import pytest
|
|
from abc import ABC
|
|
|
|
|
|
class TestBaseStorage:
|
|
"""Tests for BaseStorage abstract interface."""
|
|
|
|
def test_cannot_instantiate_directly(self):
|
|
"""Test that BaseStorage cannot be instantiated directly."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
with pytest.raises(TypeError):
|
|
BaseStorage()
|
|
|
|
def test_is_abstract_base_class(self):
|
|
"""Test that BaseStorage is an ABC."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
assert issubclass(BaseStorage, ABC)
|
|
|
|
def test_defines_create_abstract_method(self):
|
|
"""Test that create is an abstract method."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
assert hasattr(BaseStorage, "create")
|
|
assert (
|
|
getattr(BaseStorage.create, "__isabstractmethod__", False) is True
|
|
)
|
|
|
|
def test_defines_get_abstract_method(self):
|
|
"""Test that get is an abstract method."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
assert hasattr(BaseStorage, "get")
|
|
assert getattr(BaseStorage.get, "__isabstractmethod__", False) is True
|
|
|
|
def test_defines_update_abstract_method(self):
|
|
"""Test that update is an abstract method."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
assert hasattr(BaseStorage, "update")
|
|
assert (
|
|
getattr(BaseStorage.update, "__isabstractmethod__", False) is True
|
|
)
|
|
|
|
def test_defines_delete_abstract_method(self):
|
|
"""Test that delete is an abstract method."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
assert hasattr(BaseStorage, "delete")
|
|
assert (
|
|
getattr(BaseStorage.delete, "__isabstractmethod__", False) is True
|
|
)
|
|
|
|
def test_defines_list_abstract_method(self):
|
|
"""Test that list is an abstract method."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
assert hasattr(BaseStorage, "list")
|
|
assert getattr(BaseStorage.list, "__isabstractmethod__", False) is True
|
|
|
|
def test_generate_id_is_concrete(self):
|
|
"""Test that generate_id is a concrete method."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
assert hasattr(BaseStorage, "generate_id")
|
|
assert (
|
|
getattr(BaseStorage.generate_id, "__isabstractmethod__", False)
|
|
is False
|
|
)
|
|
|
|
def test_generate_id_returns_uuid_string(self):
|
|
"""Test that generate_id returns a valid UUID string."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
# Create a concrete implementation to test generate_id
|
|
class ConcreteStorage(BaseStorage):
|
|
def create(self, data):
|
|
return "id"
|
|
|
|
def get(self, id):
|
|
return None
|
|
|
|
def update(self, id, data):
|
|
return True
|
|
|
|
def delete(self, id):
|
|
return True
|
|
|
|
def list(self, filters=None, limit=100, offset=0):
|
|
return []
|
|
|
|
storage = ConcreteStorage()
|
|
result = storage.generate_id()
|
|
|
|
assert isinstance(result, str)
|
|
assert len(result) == 36 # UUID format: 8-4-4-4-12 with dashes
|
|
assert result.count("-") == 4
|
|
|
|
def test_generate_id_returns_unique_values(self):
|
|
"""Test that generate_id returns unique values."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
class ConcreteStorage(BaseStorage):
|
|
def create(self, data):
|
|
return "id"
|
|
|
|
def get(self, id):
|
|
return None
|
|
|
|
def update(self, id, data):
|
|
return True
|
|
|
|
def delete(self, id):
|
|
return True
|
|
|
|
def list(self, filters=None, limit=100, offset=0):
|
|
return []
|
|
|
|
storage = ConcreteStorage()
|
|
ids = [storage.generate_id() for _ in range(100)]
|
|
|
|
# All IDs should be unique
|
|
assert len(set(ids)) == 100
|
|
|
|
|
|
class TestCardStorage:
|
|
"""Tests for CardStorage abstract interface."""
|
|
|
|
def test_cannot_instantiate_directly(self):
|
|
"""Test that CardStorage cannot be instantiated directly."""
|
|
from local_deep_research.news.core.storage import CardStorage
|
|
|
|
with pytest.raises(TypeError):
|
|
CardStorage()
|
|
|
|
def test_inherits_from_base_storage(self):
|
|
"""Test that CardStorage inherits from BaseStorage."""
|
|
from local_deep_research.news.core.storage import (
|
|
CardStorage,
|
|
BaseStorage,
|
|
)
|
|
|
|
assert issubclass(CardStorage, BaseStorage)
|
|
|
|
def test_defines_get_by_user_abstract_method(self):
|
|
"""Test that get_by_user is an abstract method."""
|
|
from local_deep_research.news.core.storage import CardStorage
|
|
|
|
assert hasattr(CardStorage, "get_by_user")
|
|
assert (
|
|
getattr(CardStorage.get_by_user, "__isabstractmethod__", False)
|
|
is True
|
|
)
|
|
|
|
def test_defines_get_latest_version_abstract_method(self):
|
|
"""Test that get_latest_version is an abstract method."""
|
|
from local_deep_research.news.core.storage import CardStorage
|
|
|
|
assert hasattr(CardStorage, "get_latest_version")
|
|
assert (
|
|
getattr(
|
|
CardStorage.get_latest_version, "__isabstractmethod__", False
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_defines_add_version_abstract_method(self):
|
|
"""Test that add_version is an abstract method."""
|
|
from local_deep_research.news.core.storage import CardStorage
|
|
|
|
assert hasattr(CardStorage, "add_version")
|
|
assert (
|
|
getattr(CardStorage.add_version, "__isabstractmethod__", False)
|
|
is True
|
|
)
|
|
|
|
def test_defines_update_latest_info_abstract_method(self):
|
|
"""Test that update_latest_info is an abstract method."""
|
|
from local_deep_research.news.core.storage import CardStorage
|
|
|
|
assert hasattr(CardStorage, "update_latest_info")
|
|
assert (
|
|
getattr(
|
|
CardStorage.update_latest_info, "__isabstractmethod__", False
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_defines_archive_card_abstract_method(self):
|
|
"""Test that archive_card is an abstract method."""
|
|
from local_deep_research.news.core.storage import CardStorage
|
|
|
|
assert hasattr(CardStorage, "archive_card")
|
|
assert (
|
|
getattr(CardStorage.archive_card, "__isabstractmethod__", False)
|
|
is True
|
|
)
|
|
|
|
def test_defines_pin_card_abstract_method(self):
|
|
"""Test that pin_card is an abstract method."""
|
|
from local_deep_research.news.core.storage import CardStorage
|
|
|
|
assert hasattr(CardStorage, "pin_card")
|
|
assert (
|
|
getattr(CardStorage.pin_card, "__isabstractmethod__", False) is True
|
|
)
|
|
|
|
|
|
class TestRatingStorage:
|
|
"""Tests for RatingStorage abstract interface."""
|
|
|
|
def test_cannot_instantiate_directly(self):
|
|
"""Test that RatingStorage cannot be instantiated directly."""
|
|
from local_deep_research.news.core.storage import RatingStorage
|
|
|
|
with pytest.raises(TypeError):
|
|
RatingStorage()
|
|
|
|
def test_inherits_from_base_storage(self):
|
|
"""Test that RatingStorage inherits from BaseStorage."""
|
|
from local_deep_research.news.core.storage import (
|
|
RatingStorage,
|
|
BaseStorage,
|
|
)
|
|
|
|
assert issubclass(RatingStorage, BaseStorage)
|
|
|
|
def test_defines_get_user_rating_abstract_method(self):
|
|
"""Test that get_user_rating is an abstract method."""
|
|
from local_deep_research.news.core.storage import RatingStorage
|
|
|
|
assert hasattr(RatingStorage, "get_user_rating")
|
|
assert (
|
|
getattr(
|
|
RatingStorage.get_user_rating, "__isabstractmethod__", False
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_defines_upsert_rating_abstract_method(self):
|
|
"""Test that upsert_rating is an abstract method."""
|
|
from local_deep_research.news.core.storage import RatingStorage
|
|
|
|
assert hasattr(RatingStorage, "upsert_rating")
|
|
assert (
|
|
getattr(RatingStorage.upsert_rating, "__isabstractmethod__", False)
|
|
is True
|
|
)
|
|
|
|
def test_defines_get_ratings_summary_abstract_method(self):
|
|
"""Test that get_ratings_summary is an abstract method."""
|
|
from local_deep_research.news.core.storage import RatingStorage
|
|
|
|
assert hasattr(RatingStorage, "get_ratings_summary")
|
|
assert (
|
|
getattr(
|
|
RatingStorage.get_ratings_summary, "__isabstractmethod__", False
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_defines_get_user_ratings_abstract_method(self):
|
|
"""Test that get_user_ratings is an abstract method."""
|
|
from local_deep_research.news.core.storage import RatingStorage
|
|
|
|
assert hasattr(RatingStorage, "get_user_ratings")
|
|
assert (
|
|
getattr(
|
|
RatingStorage.get_user_ratings, "__isabstractmethod__", False
|
|
)
|
|
is True
|
|
)
|
|
|
|
|
|
class TestPreferenceStorage:
|
|
"""Tests for PreferenceStorage abstract interface."""
|
|
|
|
def test_cannot_instantiate_directly(self):
|
|
"""Test that PreferenceStorage cannot be instantiated directly."""
|
|
from local_deep_research.news.core.storage import PreferenceStorage
|
|
|
|
with pytest.raises(TypeError):
|
|
PreferenceStorage()
|
|
|
|
def test_inherits_from_base_storage(self):
|
|
"""Test that PreferenceStorage inherits from BaseStorage."""
|
|
from local_deep_research.news.core.storage import (
|
|
PreferenceStorage,
|
|
BaseStorage,
|
|
)
|
|
|
|
assert issubclass(PreferenceStorage, BaseStorage)
|
|
|
|
def test_defines_get_user_preferences_abstract_method(self):
|
|
"""Test that get_user_preferences is an abstract method."""
|
|
from local_deep_research.news.core.storage import PreferenceStorage
|
|
|
|
assert hasattr(PreferenceStorage, "get_user_preferences")
|
|
assert (
|
|
getattr(
|
|
PreferenceStorage.get_user_preferences,
|
|
"__isabstractmethod__",
|
|
False,
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_defines_upsert_preferences_abstract_method(self):
|
|
"""Test that upsert_preferences is an abstract method."""
|
|
from local_deep_research.news.core.storage import PreferenceStorage
|
|
|
|
assert hasattr(PreferenceStorage, "upsert_preferences")
|
|
assert (
|
|
getattr(
|
|
PreferenceStorage.upsert_preferences,
|
|
"__isabstractmethod__",
|
|
False,
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_defines_add_liked_item_abstract_method(self):
|
|
"""Test that add_liked_item is an abstract method."""
|
|
from local_deep_research.news.core.storage import PreferenceStorage
|
|
|
|
assert hasattr(PreferenceStorage, "add_liked_item")
|
|
assert (
|
|
getattr(
|
|
PreferenceStorage.add_liked_item, "__isabstractmethod__", False
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_defines_add_disliked_item_abstract_method(self):
|
|
"""Test that add_disliked_item is an abstract method."""
|
|
from local_deep_research.news.core.storage import PreferenceStorage
|
|
|
|
assert hasattr(PreferenceStorage, "add_disliked_item")
|
|
assert (
|
|
getattr(
|
|
PreferenceStorage.add_disliked_item,
|
|
"__isabstractmethod__",
|
|
False,
|
|
)
|
|
is True
|
|
)
|
|
|
|
def test_defines_update_preference_embedding_abstract_method(self):
|
|
"""Test that update_preference_embedding is an abstract method."""
|
|
from local_deep_research.news.core.storage import PreferenceStorage
|
|
|
|
assert hasattr(PreferenceStorage, "update_preference_embedding")
|
|
assert (
|
|
getattr(
|
|
PreferenceStorage.update_preference_embedding,
|
|
"__isabstractmethod__",
|
|
False,
|
|
)
|
|
is True
|
|
)
|
|
|
|
|
|
class TestConcreteImplementation:
|
|
"""Tests for concrete implementation of BaseStorage."""
|
|
|
|
def test_can_create_concrete_subclass(self):
|
|
"""Test that we can create a concrete subclass of BaseStorage."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
class ConcreteStorage(BaseStorage):
|
|
def create(self, data):
|
|
return self.generate_id()
|
|
|
|
def get(self, id):
|
|
return {"id": id}
|
|
|
|
def update(self, id, data):
|
|
return True
|
|
|
|
def delete(self, id):
|
|
return True
|
|
|
|
def list(self, filters=None, limit=100, offset=0):
|
|
return []
|
|
|
|
storage = ConcreteStorage()
|
|
|
|
# Should be able to call methods
|
|
created_id = storage.create({})
|
|
assert isinstance(created_id, str)
|
|
|
|
retrieved = storage.get("test-id")
|
|
assert retrieved == {"id": "test-id"}
|
|
|
|
updated = storage.update("test-id", {"name": "Test"})
|
|
assert updated is True
|
|
|
|
deleted = storage.delete("test-id")
|
|
assert deleted is True
|
|
|
|
listed = storage.list()
|
|
assert listed == []
|
|
|
|
def test_concrete_subclass_must_implement_all_methods(self):
|
|
"""Test that concrete subclass must implement all abstract methods."""
|
|
from local_deep_research.news.core.storage import BaseStorage
|
|
|
|
# Missing some methods
|
|
class IncompleteStorage(BaseStorage):
|
|
def create(self, data):
|
|
return "id"
|
|
|
|
def get(self, id):
|
|
return None
|
|
|
|
# Missing update, delete, list
|
|
|
|
with pytest.raises(TypeError):
|
|
IncompleteStorage()
|
|
|
|
|
|
class TestModuleImports:
|
|
"""Tests for module imports."""
|
|
|
|
def test_all_classes_importable(self):
|
|
"""Test that all storage classes can be imported."""
|
|
from local_deep_research.news.core.storage import (
|
|
BaseStorage,
|
|
CardStorage,
|
|
RatingStorage,
|
|
PreferenceStorage,
|
|
)
|
|
|
|
assert BaseStorage is not None
|
|
assert CardStorage is not None
|
|
assert RatingStorage is not None
|
|
assert PreferenceStorage is not None
|