""" Deep behavioral tests for rating system enums, base classes, and rating distribution. Tests RelevanceRating, QualityRating, BaseRatingSystem, QualityRatingSystem, RelevanceRatingSystem, and _get_rating_distribution. """ from unittest.mock import Mock import pytest from local_deep_research.news.rating_system.base_rater import ( BaseRatingSystem, QualityRating, QualityRatingSystem, RelevanceRating, RelevanceRatingSystem, ) # --- RelevanceRating enum --- class TestRelevanceRating: """Tests for RelevanceRating enum.""" def test_up_value(self): assert RelevanceRating.UP.value == "up" def test_down_value(self): assert RelevanceRating.DOWN.value == "down" def test_total_count(self): assert len(RelevanceRating) == 2 def test_from_string(self): assert RelevanceRating("up") == RelevanceRating.UP assert RelevanceRating("down") == RelevanceRating.DOWN def test_invalid_value_raises(self): with pytest.raises(ValueError): RelevanceRating("sideways") # --- QualityRating enum --- class TestQualityRating: """Tests for QualityRating enum.""" def test_one_star(self): assert QualityRating.ONE_STAR.value == 1 def test_two_stars(self): assert QualityRating.TWO_STARS.value == 2 def test_three_stars(self): assert QualityRating.THREE_STARS.value == 3 def test_four_stars(self): assert QualityRating.FOUR_STARS.value == 4 def test_five_stars(self): assert QualityRating.FIVE_STARS.value == 5 def test_total_count(self): assert len(QualityRating) == 5 def test_from_int(self): assert QualityRating(3) == QualityRating.THREE_STARS def test_invalid_value_raises(self): with pytest.raises(ValueError): QualityRating(6) def test_values_are_sequential(self): values = [q.value for q in QualityRating] assert values == [1, 2, 3, 4, 5] # --- BaseRatingSystem --- class ConcreteRatingSystem(BaseRatingSystem): """Concrete implementation for testing abstract base class.""" def rate(self, user_id, card_id, rating_value, metadata=None): return {"success": True} def get_rating(self, user_id, card_id): return None def get_rating_type(self): return "test" class TestBaseRatingSystem: """Tests for BaseRatingSystem base class.""" def test_cannot_instantiate_directly(self): with pytest.raises(TypeError): BaseRatingSystem() def test_stores_storage_backend(self): backend = Mock() r = ConcreteRatingSystem(storage_backend=backend) assert r.storage_backend is backend def test_storage_backend_none_by_default(self): r = ConcreteRatingSystem() assert r.storage_backend is None def test_rating_type_is_class_name(self): r = ConcreteRatingSystem() assert r.rating_type == "ConcreteRatingSystem" def test_get_recent_ratings_returns_empty(self): r = ConcreteRatingSystem() result = r.get_recent_ratings("user1") assert result == [] def test_get_card_ratings_returns_default(self): r = ConcreteRatingSystem() result = r.get_card_ratings("card1") assert result == {"total": 0, "average": None} def test_remove_rating_returns_false(self): r = ConcreteRatingSystem() assert r.remove_rating("user1", "card1") is False # --- _create_rating_record --- class TestCreateRatingRecord: """Tests for the rating record creation helper.""" def test_includes_user_id(self): r = ConcreteRatingSystem() record = r._create_rating_record("user1", "card1", "value") assert record["user_id"] == "user1" def test_includes_card_id(self): r = ConcreteRatingSystem() record = r._create_rating_record("user1", "card1", "value") assert record["card_id"] == "card1" def test_includes_rating_type(self): r = ConcreteRatingSystem() record = r._create_rating_record("user1", "card1", "value") assert record["rating_type"] == "test" def test_includes_rating_value(self): r = ConcreteRatingSystem() record = r._create_rating_record("user1", "card1", RelevanceRating.UP) assert record["rating_value"] == RelevanceRating.UP def test_includes_rated_at(self): r = ConcreteRatingSystem() record = r._create_rating_record("user1", "card1", "value") assert "rated_at" in record def test_empty_metadata_by_default(self): r = ConcreteRatingSystem() record = r._create_rating_record("user1", "card1", "value") assert record["metadata"] == {} def test_includes_provided_metadata(self): r = ConcreteRatingSystem() record = r._create_rating_record( "user1", "card1", "value", metadata={"context": "test"} ) assert record["metadata"] == {"context": "test"} # --- _validate_rating_value --- class TestValidateRatingValue: """Tests for rating value validation.""" def test_none_raises_value_error(self): r = ConcreteRatingSystem() with pytest.raises(ValueError, match="cannot be None"): r._validate_rating_value(None) def test_valid_value_no_error(self): r = ConcreteRatingSystem() r._validate_rating_value("anything") # Should not raise def test_zero_is_valid(self): r = ConcreteRatingSystem() r._validate_rating_value(0) # Should not raise def test_empty_string_is_valid(self): r = ConcreteRatingSystem() r._validate_rating_value("") # Should not raise # --- QualityRatingSystem --- class TestQualityRatingSystem: """Tests for QualityRatingSystem.""" def test_rating_type(self): qs = QualityRatingSystem() assert qs.get_rating_type() == "quality" def test_rate_valid_quality(self): qs = QualityRatingSystem() result = qs.rate("user1", "card1", QualityRating.FOUR_STARS) assert result["success"] is True assert "rating" in result assert "4 stars" in result["message"] def test_rate_one_star(self): qs = QualityRatingSystem() result = qs.rate("user1", "card1", QualityRating.ONE_STAR) assert result["success"] is True assert "1 stars" in result["message"] def test_rate_five_stars(self): qs = QualityRatingSystem() result = qs.rate("user1", "card1", QualityRating.FIVE_STARS) assert result["success"] is True def test_rate_with_metadata(self): qs = QualityRatingSystem() result = qs.rate( "user1", "card1", QualityRating.THREE_STARS, metadata={"note": "good"}, ) assert result["rating"]["metadata"] == {"note": "good"} def test_validate_rejects_non_quality_rating(self): qs = QualityRatingSystem() with pytest.raises(ValueError, match="QualityRating"): qs.rate("user1", "card1", "not_an_enum") def test_validate_rejects_relevance_rating(self): qs = QualityRatingSystem() with pytest.raises(ValueError, match="QualityRating"): qs.rate("user1", "card1", RelevanceRating.UP) def test_validate_rejects_none(self): qs = QualityRatingSystem() with pytest.raises(ValueError): qs.rate("user1", "card1", None) def test_validate_rejects_integer(self): qs = QualityRatingSystem() with pytest.raises(ValueError, match="QualityRating"): qs.rate("user1", "card1", 3) def test_get_rating_returns_none_without_backend(self): qs = QualityRatingSystem() assert qs.get_rating("user1", "card1") is None def test_rating_record_has_quality_type(self): qs = QualityRatingSystem() result = qs.rate("user1", "card1", QualityRating.THREE_STARS) assert result["rating"]["rating_type"] == "quality" # --- RelevanceRatingSystem --- class TestRelevanceRatingSystem: """Tests for RelevanceRatingSystem.""" def test_rating_type(self): rs = RelevanceRatingSystem() assert rs.get_rating_type() == "relevance" def test_rate_up(self): rs = RelevanceRatingSystem() result = rs.rate("user1", "card1", RelevanceRating.UP) assert result["success"] is True assert "thumbs up" in result["message"] def test_rate_down(self): rs = RelevanceRatingSystem() result = rs.rate("user1", "card1", RelevanceRating.DOWN) assert result["success"] is True assert "thumbs down" in result["message"] def test_rate_with_metadata(self): rs = RelevanceRatingSystem() result = rs.rate( "user1", "card1", RelevanceRating.UP, metadata={"reason": "helpful"} ) assert result["rating"]["metadata"] == {"reason": "helpful"} def test_validate_rejects_non_relevance_rating(self): rs = RelevanceRatingSystem() with pytest.raises(ValueError, match="RelevanceRating"): rs.rate("user1", "card1", "up") def test_validate_rejects_quality_rating(self): rs = RelevanceRatingSystem() with pytest.raises(ValueError, match="RelevanceRating"): rs.rate("user1", "card1", QualityRating.FIVE_STARS) def test_validate_rejects_none(self): rs = RelevanceRatingSystem() with pytest.raises(ValueError): rs.rate("user1", "card1", None) def test_get_rating_returns_none_without_backend(self): rs = RelevanceRatingSystem() assert rs.get_rating("user1", "card1") is None def test_rating_record_has_relevance_type(self): rs = RelevanceRatingSystem() result = rs.rate("user1", "card1", RelevanceRating.UP) assert result["rating"]["rating_type"] == "relevance" # --- _get_rating_distribution (from storage.py, testing the logic) --- class TestRatingDistribution: """Tests for rating distribution calculation logic.""" def _get_distribution(self, ratings): """Reproduce the distribution logic from SQLRatingStorage.""" distribution = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0} for rating in ratings: if 1 <= rating <= 5: distribution[rating] += 1 return distribution def test_empty_ratings(self): result = self._get_distribution([]) assert result == {1: 0, 2: 0, 3: 0, 4: 0, 5: 0} def test_single_rating(self): result = self._get_distribution([3]) assert result == {1: 0, 2: 0, 3: 1, 4: 0, 5: 0} def test_multiple_same_rating(self): result = self._get_distribution([5, 5, 5]) assert result == {1: 0, 2: 0, 3: 0, 4: 0, 5: 3} def test_full_distribution(self): result = self._get_distribution([1, 2, 3, 4, 5]) assert result == {1: 1, 2: 1, 3: 1, 4: 1, 5: 1} def test_out_of_range_ignored(self): result = self._get_distribution([0, 6, -1, 100]) assert result == {1: 0, 2: 0, 3: 0, 4: 0, 5: 0} def test_mixed_valid_and_invalid(self): result = self._get_distribution([1, 0, 3, 6, 5]) assert result == {1: 1, 2: 0, 3: 1, 4: 0, 5: 1} def test_boundary_values(self): result = self._get_distribution([1, 5]) assert result[1] == 1 assert result[5] == 1 def test_heavy_on_one_value(self): result = self._get_distribution([4] * 100) assert result[4] == 100 # --- Ratings summary calculation patterns --- class TestRatingsSummaryCalculation: """Tests for the ratings summary aggregation logic.""" def test_quality_average_calculation(self): quality_values = [3, 4, 5, 4, 3] avg = sum(quality_values) / len(quality_values) assert avg == 3.8 def test_quality_average_empty(self): quality_values = [] avg = sum(quality_values) / len(quality_values) if quality_values else 0 assert avg == 0 def test_relevance_net_score(self): up_votes = 15 down_votes = 5 net = up_votes - down_votes assert net == 10 def test_relevance_negative_net_score(self): up_votes = 3 down_votes = 8 net = up_votes - down_votes assert net == -5 def test_quality_isdigit_filter(self): """Rating values that aren't digits should be filtered out.""" raw_values = ["3", "4", "five", "2", ""] quality_values = [int(v) for v in raw_values if v.isdigit()] assert quality_values == [3, 4, 2] def test_relevance_vote_counting(self): rating_values = ["up", "down", "up", "up", "down"] up = sum(1 for v in rating_values if v == "up") down = sum(1 for v in rating_values if v == "down") assert up == 3 assert down == 2