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
260 lines
8.8 KiB
Python
260 lines
8.8 KiB
Python
"""
|
|
Deep behavioral tests for InteractionType enum and StorageManager interaction logic.
|
|
Tests enum values, interaction recording patterns, and user stats calculation.
|
|
"""
|
|
|
|
from unittest.mock import Mock
|
|
|
|
import pytest
|
|
|
|
from local_deep_research.news.core.storage_manager import InteractionType
|
|
|
|
|
|
# --- InteractionType enum ---
|
|
|
|
|
|
class TestInteractionType:
|
|
"""Tests for InteractionType enum values."""
|
|
|
|
def test_view_value(self):
|
|
assert InteractionType.VIEW.value == "view"
|
|
|
|
def test_vote_up_value(self):
|
|
assert InteractionType.VOTE_UP.value == "vote_up"
|
|
|
|
def test_vote_down_value(self):
|
|
assert InteractionType.VOTE_DOWN.value == "vote_down"
|
|
|
|
def test_research_value(self):
|
|
assert InteractionType.RESEARCH.value == "research"
|
|
|
|
def test_share_value(self):
|
|
assert InteractionType.SHARE.value == "share"
|
|
|
|
def test_total_count(self):
|
|
assert len(InteractionType) == 5
|
|
|
|
def test_from_string_view(self):
|
|
assert InteractionType("view") == InteractionType.VIEW
|
|
|
|
def test_from_string_vote_up(self):
|
|
assert InteractionType("vote_up") == InteractionType.VOTE_UP
|
|
|
|
def test_invalid_value_raises(self):
|
|
with pytest.raises(ValueError):
|
|
InteractionType("invalid")
|
|
|
|
def test_enum_is_iterable(self):
|
|
values = [t.value for t in InteractionType]
|
|
assert "view" in values
|
|
assert "vote_up" in values
|
|
assert "vote_down" in values
|
|
assert "research" in values
|
|
assert "share" in values
|
|
|
|
def test_enum_name_matches(self):
|
|
assert InteractionType.VIEW.name == "VIEW"
|
|
assert InteractionType.VOTE_UP.name == "VOTE_UP"
|
|
|
|
def test_enum_comparison(self):
|
|
assert InteractionType.VIEW == InteractionType.VIEW
|
|
assert InteractionType.VIEW != InteractionType.VOTE_UP
|
|
|
|
def test_enum_identity(self):
|
|
assert InteractionType.VIEW is InteractionType.VIEW
|
|
|
|
def test_enum_hashing(self):
|
|
"""Enums should be hashable for use as dict keys."""
|
|
d = {InteractionType.VIEW: "viewed", InteractionType.VOTE_UP: "upvoted"}
|
|
assert d[InteractionType.VIEW] == "viewed"
|
|
|
|
|
|
# --- StorageManager interaction patterns ---
|
|
|
|
|
|
class TestStorageManagerInteractionPatterns:
|
|
"""Tests for how StorageManager handles different interaction types.
|
|
|
|
These test the interaction recording logic by verifying the card
|
|
mutation patterns for each interaction type.
|
|
"""
|
|
|
|
def _make_card_with_interaction(self):
|
|
"""Create a mock card with an interaction dict."""
|
|
card = Mock()
|
|
card.interaction = {
|
|
"views": 0,
|
|
"votes_up": 0,
|
|
"votes_down": 0,
|
|
}
|
|
return card
|
|
|
|
def test_view_increments_view_count(self):
|
|
card = self._make_card_with_interaction()
|
|
# Simulate VIEW interaction logic from StorageManager.record_interaction
|
|
card.interaction["viewed"] = True
|
|
card.interaction["views"] = card.interaction.get("views", 0) + 1
|
|
assert card.interaction["views"] == 1
|
|
assert card.interaction["viewed"] is True
|
|
|
|
def test_view_multiple_times(self):
|
|
card = self._make_card_with_interaction()
|
|
for _ in range(5):
|
|
card.interaction["views"] = card.interaction.get("views", 0) + 1
|
|
assert card.interaction["views"] == 5
|
|
|
|
def test_vote_up_sets_voted_up(self):
|
|
card = self._make_card_with_interaction()
|
|
card.interaction["voted"] = "up"
|
|
card.interaction["votes_up"] = card.interaction.get("votes_up", 0) + 1
|
|
assert card.interaction["voted"] == "up"
|
|
assert card.interaction["votes_up"] == 1
|
|
|
|
def test_vote_down_sets_voted_down(self):
|
|
card = self._make_card_with_interaction()
|
|
card.interaction["voted"] = "down"
|
|
card.interaction["votes_down"] = (
|
|
card.interaction.get("votes_down", 0) + 1
|
|
)
|
|
assert card.interaction["voted"] == "down"
|
|
assert card.interaction["votes_down"] == 1
|
|
|
|
def test_research_sets_researched_flag(self):
|
|
card = self._make_card_with_interaction()
|
|
card.interaction["researched"] = True
|
|
card.interaction["research_count"] = (
|
|
card.interaction.get("research_count", 0) + 1
|
|
)
|
|
assert card.interaction["researched"] is True
|
|
assert card.interaction["research_count"] == 1
|
|
|
|
def test_research_multiple_times(self):
|
|
card = self._make_card_with_interaction()
|
|
for _ in range(3):
|
|
card.interaction["researched"] = True
|
|
card.interaction["research_count"] = (
|
|
card.interaction.get("research_count", 0) + 1
|
|
)
|
|
assert card.interaction["research_count"] == 3
|
|
|
|
def test_metadata_stored_with_interaction(self):
|
|
card = self._make_card_with_interaction()
|
|
metadata = {"source": "button", "position": 3}
|
|
interaction_type = InteractionType.VIEW
|
|
card.interaction[f"{interaction_type}_metadata"] = metadata
|
|
assert card.interaction[f"{interaction_type}_metadata"] == metadata
|
|
|
|
|
|
# --- User stats calculation patterns ---
|
|
|
|
|
|
class TestUserStatsCalculation:
|
|
"""Tests for the stats calculation logic used in get_user_stats."""
|
|
|
|
def test_count_votes_up(self):
|
|
ratings = [
|
|
{"value": 1},
|
|
{"value": 1},
|
|
{"value": -1},
|
|
{"value": 1},
|
|
]
|
|
votes_up = sum(1 for r in ratings if r.get("value", 0) > 0)
|
|
assert votes_up == 3
|
|
|
|
def test_count_votes_down(self):
|
|
ratings = [
|
|
{"value": 1},
|
|
{"value": -1},
|
|
{"value": -1},
|
|
]
|
|
votes_down = sum(1 for r in ratings if r.get("value", 0) < 0)
|
|
assert votes_down == 2
|
|
|
|
def test_count_total_views(self):
|
|
user_cards = [
|
|
{"interaction": {"views": 10}},
|
|
{"interaction": {"views": 5}},
|
|
{"interaction": {}},
|
|
]
|
|
total_views = sum(
|
|
c.get("interaction", {}).get("views", 0) for c in user_cards
|
|
)
|
|
assert total_views == 15
|
|
|
|
def test_empty_ratings(self):
|
|
ratings = []
|
|
votes_up = sum(1 for r in ratings if r.get("value", 0) > 0)
|
|
votes_down = sum(1 for r in ratings if r.get("value", 0) < 0)
|
|
assert votes_up == 0
|
|
assert votes_down == 0
|
|
|
|
def test_missing_value_in_rating(self):
|
|
ratings = [{"other": "data"}]
|
|
votes_up = sum(1 for r in ratings if r.get("value", 0) > 0)
|
|
assert votes_up == 0
|
|
|
|
def test_zero_value_counts_neither(self):
|
|
ratings = [{"value": 0}]
|
|
votes_up = sum(1 for r in ratings if r.get("value", 0) > 0)
|
|
votes_down = sum(1 for r in ratings if r.get("value", 0) < 0)
|
|
assert votes_up == 0
|
|
assert votes_down == 0
|
|
|
|
|
|
# --- Card interaction conversion patterns ---
|
|
|
|
|
|
class TestCardInteractionConversion:
|
|
"""Tests for converting ratings to interaction format (get_card_interactions logic)."""
|
|
|
|
def test_positive_rating_is_upvote(self):
|
|
rating = {"user_id": "u1", "value": 1, "created_at": "2025-01-01"}
|
|
interaction = {
|
|
"user_id": rating.get("user_id"),
|
|
"interaction_type": "vote",
|
|
"interaction_data": {
|
|
"vote": "up" if rating.get("value", 0) > 0 else "down"
|
|
},
|
|
"timestamp": rating.get("created_at"),
|
|
}
|
|
assert interaction["interaction_data"]["vote"] == "up"
|
|
|
|
def test_negative_rating_is_downvote(self):
|
|
rating = {"user_id": "u1", "value": -1, "created_at": "2025-01-01"}
|
|
interaction = {
|
|
"user_id": rating.get("user_id"),
|
|
"interaction_type": "vote",
|
|
"interaction_data": {
|
|
"vote": "up" if rating.get("value", 0) > 0 else "down"
|
|
},
|
|
"timestamp": rating.get("created_at"),
|
|
}
|
|
assert interaction["interaction_data"]["vote"] == "down"
|
|
|
|
def test_zero_rating_is_downvote(self):
|
|
"""Zero value is not > 0, so it goes to 'down' branch."""
|
|
rating = {"value": 0}
|
|
vote = "up" if rating.get("value", 0) > 0 else "down"
|
|
assert vote == "down"
|
|
|
|
def test_multiple_ratings_to_interactions(self):
|
|
ratings = [
|
|
{"user_id": "u1", "value": 1, "created_at": "2025-01-01"},
|
|
{"user_id": "u2", "value": -1, "created_at": "2025-01-02"},
|
|
]
|
|
interactions = []
|
|
for rating in ratings:
|
|
interactions.append(
|
|
{
|
|
"user_id": rating.get("user_id"),
|
|
"interaction_type": "vote",
|
|
"interaction_data": {
|
|
"vote": "up" if rating.get("value", 0) > 0 else "down"
|
|
},
|
|
"timestamp": rating.get("created_at"),
|
|
}
|
|
)
|
|
assert len(interactions) == 2
|
|
assert interactions[0]["interaction_data"]["vote"] == "up"
|
|
assert interactions[1]["interaction_data"]["vote"] == "down"
|