Add tests for last30days scoring, dedup, normalization, and dates

115 tests covering the four pure-logic modules:
- test_dates: date parsing, ranges, confidence, recency scoring
- test_score: engagement computation, normalization, scoring for
  Reddit/X/YouTube/WebSearch, sort order and tiebreaking
- test_dedupe: text normalization, n-grams, Jaccard similarity,
  duplicate detection, deduplication with score preservation
- test_normalize: date range filtering, normalization of raw API
  data to schema objects for all four source types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Dan Guido
2026-02-15 17:56:07 -05:00
parent 85b0952260
commit 824423f19f
6 changed files with 893 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
[project]
name = "last30days"
version = "0.0.0"
requires-python = ">=3.11"
[tool.pytest.ini_options]
testpaths = ["tests"]
[tool.ruff]
line-length = 100
@@ -0,0 +1,134 @@
"""Tests for lib.dates module."""
from datetime import UTC, datetime, timedelta
from lib import dates
class TestGetDateRange:
def test_default_30_days(self):
from_date, to_date = dates.get_date_range()
today = datetime.now(UTC).date()
assert to_date == today.isoformat()
expected_from = (today - timedelta(days=30)).isoformat()
assert from_date == expected_from
def test_custom_days(self):
from_date, to_date = dates.get_date_range(7)
today = datetime.now(UTC).date()
expected_from = (today - timedelta(days=7)).isoformat()
assert from_date == expected_from
assert to_date == today.isoformat()
def test_one_day(self):
from_date, to_date = dates.get_date_range(1)
today = datetime.now(UTC).date()
yesterday = (today - timedelta(days=1)).isoformat()
assert from_date == yesterday
assert to_date == today.isoformat()
class TestParseDate:
def test_yyyy_mm_dd(self):
result = dates.parse_date("2026-01-15")
assert result.year == 2026
assert result.month == 1
assert result.day == 15
def test_iso_with_time(self):
result = dates.parse_date("2026-01-15T10:30:00Z")
assert result.year == 2026
assert result.hour == 10
def test_unix_timestamp(self):
result = dates.parse_date("1700000000")
assert result is not None
assert result.year == 2023
def test_none_input(self):
assert dates.parse_date(None) is None
def test_empty_string(self):
assert dates.parse_date("") is None
def test_garbage_input(self):
assert dates.parse_date("not-a-date") is None
class TestTimestampToDate:
def test_valid_timestamp(self):
# 2023-11-14 in UTC
result = dates.timestamp_to_date(1700000000)
assert result == "2023-11-14"
def test_none(self):
assert dates.timestamp_to_date(None) is None
def test_zero(self):
result = dates.timestamp_to_date(0)
assert result == "1970-01-01"
class TestGetDateConfidence:
def test_in_range_is_high(self):
assert dates.get_date_confidence("2026-01-15", "2026-01-01", "2026-01-31") == "high"
def test_on_boundary_start_is_high(self):
assert dates.get_date_confidence("2026-01-01", "2026-01-01", "2026-01-31") == "high"
def test_on_boundary_end_is_high(self):
assert dates.get_date_confidence("2026-01-31", "2026-01-01", "2026-01-31") == "high"
def test_before_range_is_low(self):
assert dates.get_date_confidence("2025-12-31", "2026-01-01", "2026-01-31") == "low"
def test_after_range_is_low(self):
assert dates.get_date_confidence("2026-02-01", "2026-01-01", "2026-01-31") == "low"
def test_none_is_low(self):
assert dates.get_date_confidence(None, "2026-01-01", "2026-01-31") == "low"
def test_invalid_format_is_low(self):
assert dates.get_date_confidence("garbage", "2026-01-01", "2026-01-31") == "low"
class TestDaysAgo:
def test_today_is_zero(self):
today = datetime.now(UTC).date().isoformat()
assert dates.days_ago(today) == 0
def test_yesterday(self):
yesterday = (datetime.now(UTC).date() - timedelta(days=1)).isoformat()
assert dates.days_ago(yesterday) == 1
def test_none(self):
assert dates.days_ago(None) is None
def test_invalid(self):
assert dates.days_ago("not-a-date") is None
class TestRecencyScore:
def test_today_is_100(self):
today = datetime.now(UTC).date().isoformat()
assert dates.recency_score(today) == 100
def test_max_days_ago_is_zero(self):
old = (datetime.now(UTC).date() - timedelta(days=30)).isoformat()
assert dates.recency_score(old) == 0
def test_half_max_is_about_50(self):
half = (datetime.now(UTC).date() - timedelta(days=15)).isoformat()
assert dates.recency_score(half) == 50
def test_none_is_zero(self):
assert dates.recency_score(None) == 0
def test_future_date_is_100(self):
future = (datetime.now(UTC).date() + timedelta(days=5)).isoformat()
assert dates.recency_score(future) == 100
def test_custom_max_days(self):
one_week_ago = (datetime.now(UTC).date() - timedelta(days=7)).isoformat()
assert dates.recency_score(one_week_ago, max_days=7) == 0
assert dates.recency_score(one_week_ago, max_days=14) == 50
@@ -0,0 +1,200 @@
"""Tests for lib.dedupe module."""
from lib import dedupe, schema
class TestNormalizeText:
def test_lowercase(self):
assert dedupe.normalize_text("Hello World") == "hello world"
def test_remove_punctuation(self):
assert dedupe.normalize_text("hello, world!") == "hello world"
def test_collapse_whitespace(self):
assert dedupe.normalize_text("hello world") == "hello world"
def test_combined(self):
assert dedupe.normalize_text(" Hello, World! ") == "hello world"
def test_empty(self):
assert dedupe.normalize_text("") == ""
class TestGetNgrams:
def test_basic(self):
ngrams = dedupe.get_ngrams("hello")
# "hello" normalized -> "hello", 3-grams: hel, ell, llo
assert "hel" in ngrams
assert "ell" in ngrams
assert "llo" in ngrams
assert len(ngrams) == 3
def test_short_text(self):
ngrams = dedupe.get_ngrams("hi")
assert ngrams == {"hi"}
def test_single_char(self):
ngrams = dedupe.get_ngrams("a")
assert ngrams == {"a"}
class TestJaccardSimilarity:
def test_identical_sets(self):
s = {"a", "b", "c"}
assert dedupe.jaccard_similarity(s, s) == 1.0
def test_disjoint_sets(self):
assert dedupe.jaccard_similarity({"a", "b"}, {"c", "d"}) == 0.0
def test_partial_overlap(self):
result = dedupe.jaccard_similarity({"a", "b", "c"}, {"b", "c", "d"})
assert result == 2 / 4 # intersection=2, union=4
def test_empty_sets(self):
assert dedupe.jaccard_similarity(set(), set()) == 0.0
def test_one_empty(self):
assert dedupe.jaccard_similarity({"a"}, set()) == 0.0
class TestFindDuplicates:
def _reddit(self, title, score=50):
return schema.RedditItem(
id="r1",
title=title,
url="",
subreddit="test",
score=score,
)
def test_exact_duplicates(self):
items = [self._reddit("Best AI tools 2026"), self._reddit("Best AI tools 2026")]
pairs = dedupe.find_duplicates(items)
assert len(pairs) == 1
assert pairs[0] == (0, 1)
def test_near_duplicates(self):
items = [
self._reddit("Best AI tools for coding in 2026"),
self._reddit("Best AI tools for coding 2026"),
]
pairs = dedupe.find_duplicates(items, threshold=0.7)
assert len(pairs) == 1
def test_different_items(self):
items = [
self._reddit("Best AI tools for coding"),
self._reddit("How to make sourdough bread"),
]
pairs = dedupe.find_duplicates(items)
assert len(pairs) == 0
def test_empty_list(self):
assert dedupe.find_duplicates([]) == []
def test_single_item(self):
assert dedupe.find_duplicates([self._reddit("test")]) == []
class TestDedupeItems:
def _reddit(self, title, score=50):
return schema.RedditItem(
id="r1",
title=title,
url="",
subreddit="test",
score=score,
)
def test_keeps_higher_scored(self):
items = [
self._reddit("Best AI tools 2026", score=90),
self._reddit("Best AI tools 2026", score=50),
]
result = dedupe.dedupe_items(items)
assert len(result) == 1
assert result[0].score == 90
def test_no_duplicates_unchanged(self):
items = [
self._reddit("Best AI tools", score=90),
self._reddit("Sourdough bread recipe", score=80),
]
result = dedupe.dedupe_items(items)
assert len(result) == 2
def test_empty_list(self):
assert dedupe.dedupe_items([]) == []
def test_single_item(self):
items = [self._reddit("test")]
assert dedupe.dedupe_items(items) == items
def test_three_duplicates_keeps_one(self):
items = [
self._reddit("Best AI tools 2026", score=90),
self._reddit("Best AI tools 2026", score=70),
self._reddit("Best AI tools 2026", score=50),
]
result = dedupe.dedupe_items(items)
assert len(result) == 1
assert result[0].score == 90
def test_custom_threshold(self):
items = [
self._reddit("Best AI tools for coding in 2026"),
self._reddit("Best AI tools for coding 2026"),
]
# High threshold: not considered duplicates
result_strict = dedupe.dedupe_items(items, threshold=0.99)
assert len(result_strict) == 2
# Low threshold: considered duplicates
result_loose = dedupe.dedupe_items(items, threshold=0.5)
assert len(result_loose) == 1
class TestDedupeX:
def test_dedupes_x_items(self):
items = [
schema.XItem(
id="1",
text="Claude Code is amazing",
url="",
author_handle="a",
score=90,
),
schema.XItem(
id="2",
text="Claude Code is amazing!",
url="",
author_handle="b",
score=50,
),
]
result = dedupe.dedupe_x(items)
assert len(result) == 1
assert result[0].score == 90
class TestDedupeYoutube:
def test_dedupes_youtube_items(self):
items = [
schema.YouTubeItem(
id="v1",
title="Claude Code Tutorial",
url="",
channel_name="TechChannel",
score=90,
),
schema.YouTubeItem(
id="v2",
title="Claude Code Tutorial",
url="",
channel_name="TechChannel",
score=50,
),
]
result = dedupe.dedupe_youtube(items)
assert len(result) == 1
assert result[0].score == 90
@@ -0,0 +1,183 @@
"""Tests for lib.normalize module."""
from lib import normalize, schema
class TestFilterByDateRange:
def _reddit(self, date=None):
return schema.RedditItem(
id="r1",
title="Test",
url="",
subreddit="test",
date=date,
)
def test_in_range_kept(self):
items = [self._reddit("2026-01-15")]
result = normalize.filter_by_date_range(items, "2026-01-01", "2026-01-31")
assert len(result) == 1
def test_before_range_excluded(self):
items = [self._reddit("2025-12-15")]
result = normalize.filter_by_date_range(items, "2026-01-01", "2026-01-31")
assert len(result) == 0
def test_after_range_excluded(self):
items = [self._reddit("2026-02-15")]
result = normalize.filter_by_date_range(items, "2026-01-01", "2026-01-31")
assert len(result) == 0
def test_on_boundary_start_kept(self):
items = [self._reddit("2026-01-01")]
result = normalize.filter_by_date_range(items, "2026-01-01", "2026-01-31")
assert len(result) == 1
def test_on_boundary_end_kept(self):
items = [self._reddit("2026-01-31")]
result = normalize.filter_by_date_range(items, "2026-01-01", "2026-01-31")
assert len(result) == 1
def test_none_date_kept_by_default(self):
items = [self._reddit(None)]
result = normalize.filter_by_date_range(items, "2026-01-01", "2026-01-31")
assert len(result) == 1
def test_none_date_excluded_when_required(self):
items = [self._reddit(None)]
result = normalize.filter_by_date_range(
items,
"2026-01-01",
"2026-01-31",
require_date=True,
)
assert len(result) == 0
def test_mixed_items(self):
items = [
self._reddit("2026-01-15"), # in range
self._reddit("2025-06-01"), # too old
self._reddit(None), # no date
self._reddit("2026-01-20"), # in range
]
result = normalize.filter_by_date_range(items, "2026-01-01", "2026-01-31")
assert len(result) == 3 # 2 in range + 1 no date
def test_empty_list(self):
assert normalize.filter_by_date_range([], "2026-01-01", "2026-01-31") == []
class TestNormalizeRedditItems:
def test_basic_normalization(self):
raw = [
{
"id": "abc123",
"title": "Test Post",
"url": "https://reddit.com/r/test/abc123",
"subreddit": "test",
"date": "2026-01-15",
"relevance": 0.9,
"why_relevant": "Very relevant",
"engagement": {"score": 100, "num_comments": 50, "upvote_ratio": 0.95},
"top_comments": [
{
"score": 42,
"date": "2026-01-15",
"author": "commenter",
"excerpt": "Great post",
"url": "https://reddit.com/r/test/abc123/comment",
},
],
},
]
result = normalize.normalize_reddit_items(raw, "2026-01-01", "2026-01-31")
assert len(result) == 1
item = result[0]
assert isinstance(item, schema.RedditItem)
assert item.id == "abc123"
assert item.title == "Test Post"
assert item.relevance == 0.9
assert item.engagement.score == 100
assert len(item.top_comments) == 1
assert item.date_confidence == "high"
def test_missing_fields_use_defaults(self):
raw = [{"id": "x"}]
result = normalize.normalize_reddit_items(raw, "2026-01-01", "2026-01-31")
assert result[0].title == ""
assert result[0].relevance == 0.5
assert result[0].engagement is None
def test_empty_list(self):
assert normalize.normalize_reddit_items([], "2026-01-01", "2026-01-31") == []
class TestNormalizeXItems:
def test_basic_normalization(self):
raw = [
{
"id": "tweet1",
"text": "Test tweet about AI",
"url": "https://x.com/user/tweet1",
"author_handle": "testuser",
"date": "2026-01-15",
"relevance": 0.85,
"why_relevant": "On topic",
"engagement": {"likes": 500, "reposts": 50, "replies": 20, "quotes": 5},
},
]
result = normalize.normalize_x_items(raw, "2026-01-01", "2026-01-31")
assert len(result) == 1
item = result[0]
assert isinstance(item, schema.XItem)
assert item.text == "Test tweet about AI"
assert item.engagement.likes == 500
assert item.date_confidence == "high"
def test_missing_engagement(self):
raw = [{"id": "t1", "text": "No engagement"}]
result = normalize.normalize_x_items(raw, "2026-01-01", "2026-01-31")
assert result[0].engagement is None
class TestNormalizeYoutubeItems:
def test_basic_normalization(self):
raw = [
{
"video_id": "dQw4w9WgXcQ",
"title": "AI Tutorial",
"url": "https://youtube.com/watch?v=dQw4w9WgXcQ",
"channel_name": "TechChannel",
"date": "2026-01-15",
"relevance": 0.9,
"engagement": {"views": 50000, "likes": 1000, "comments": 100},
"transcript_snippet": "Today we'll learn about...",
},
]
result = normalize.normalize_youtube_items(raw, "2026-01-01", "2026-01-31")
assert len(result) == 1
item = result[0]
assert isinstance(item, schema.YouTubeItem)
assert item.id == "dQw4w9WgXcQ"
assert item.channel_name == "TechChannel"
assert item.engagement.views == 50000
assert item.date_confidence == "high" # YouTube always high
class TestItemsToDicts:
def test_reddit_roundtrip(self):
items = [
schema.RedditItem(
id="r1",
title="Test",
url="https://reddit.com/r/test/r1",
subreddit="test",
date="2026-01-15",
relevance=0.8,
),
]
dicts = normalize.items_to_dicts(items)
assert len(dicts) == 1
assert dicts[0]["id"] == "r1"
assert dicts[0]["title"] == "Test"
assert dicts[0]["relevance"] == 0.8
@@ -0,0 +1,366 @@
"""Tests for lib.score module."""
import math
from datetime import UTC, datetime
from lib import schema, score
class TestLog1pSafe:
def test_positive_value(self):
assert score.log1p_safe(100) == math.log1p(100)
def test_zero(self):
assert score.log1p_safe(0) == 0.0
def test_none(self):
assert score.log1p_safe(None) == 0.0
def test_negative(self):
assert score.log1p_safe(-5) == 0.0
class TestNormalizeTo100:
def test_two_values(self):
result = score.normalize_to_100([0, 10])
assert result == [0.0, 100.0]
def test_three_values(self):
result = score.normalize_to_100([0, 5, 10])
assert result == [0.0, 50.0, 100.0]
def test_all_same(self):
result = score.normalize_to_100([5, 5, 5])
assert result == [50, 50, 50]
def test_with_none(self):
result = score.normalize_to_100([0, None, 10])
assert result[0] == 0.0
assert result[1] is None
assert result[2] == 100.0
def test_all_none(self):
result = score.normalize_to_100([None, None])
assert result == [50, 50]
def test_empty(self):
assert score.normalize_to_100([]) == []
def test_single_value(self):
result = score.normalize_to_100([42])
assert result == [50] # Single value -> midpoint
class TestComputeRedditEngagementRaw:
def test_typical_post(self):
eng = schema.Engagement(score=100, num_comments=50, upvote_ratio=0.95)
result = score.compute_reddit_engagement_raw(eng)
assert result is not None
assert result > 0
def test_none_engagement(self):
assert score.compute_reddit_engagement_raw(None) is None
def test_no_metrics(self):
eng = schema.Engagement()
assert score.compute_reddit_engagement_raw(eng) is None
def test_only_score(self):
eng = schema.Engagement(score=100)
result = score.compute_reddit_engagement_raw(eng)
assert result is not None
def test_higher_engagement_scores_higher(self):
low = schema.Engagement(score=10, num_comments=5, upvote_ratio=0.8)
high = schema.Engagement(score=1000, num_comments=500, upvote_ratio=0.95)
assert score.compute_reddit_engagement_raw(high) > score.compute_reddit_engagement_raw(low)
class TestComputeXEngagementRaw:
def test_typical_post(self):
eng = schema.Engagement(likes=500, reposts=50, replies=20, quotes=5)
result = score.compute_x_engagement_raw(eng)
assert result is not None
assert result > 0
def test_none_engagement(self):
assert score.compute_x_engagement_raw(None) is None
def test_no_metrics(self):
eng = schema.Engagement()
assert score.compute_x_engagement_raw(eng) is None
def test_higher_engagement_scores_higher(self):
low = schema.Engagement(likes=10, reposts=1)
high = schema.Engagement(likes=10000, reposts=1000)
assert score.compute_x_engagement_raw(high) > score.compute_x_engagement_raw(low)
class TestComputeYoutubeEngagementRaw:
def test_typical_video(self):
eng = schema.Engagement(views=50000, likes=1000, num_comments=100)
result = score.compute_youtube_engagement_raw(eng)
assert result is not None
assert result > 0
def test_none_engagement(self):
assert score.compute_youtube_engagement_raw(None) is None
def test_views_dominate(self):
high_views = schema.Engagement(views=1000000, likes=10)
high_likes = schema.Engagement(views=100, likes=10000)
# Views have 0.50 weight vs likes at 0.35, and log dampens large values,
# but million views should still outweigh
assert score.compute_youtube_engagement_raw(
high_views
) > score.compute_youtube_engagement_raw(high_likes)
class TestScoreRedditItems:
def _make_item(self, **kwargs):
today = datetime.now(UTC).date().isoformat()
defaults = {
"id": "abc",
"title": "Test post",
"url": "https://reddit.com/r/test/abc",
"subreddit": "test",
"date": today,
"date_confidence": "high",
"relevance": 0.8,
"engagement": schema.Engagement(score=100, num_comments=50, upvote_ratio=0.95),
}
defaults.update(kwargs)
return schema.RedditItem(**defaults)
def test_empty_list(self):
assert score.score_reddit_items([]) == []
def test_single_item_gets_score(self):
item = self._make_item()
result = score.score_reddit_items([item])
assert result[0].score > 0
assert result[0].score <= 100
def test_score_is_int(self):
item = self._make_item()
result = score.score_reddit_items([item])
assert isinstance(result[0].score, int)
def test_subscores_populated(self):
item = self._make_item()
result = score.score_reddit_items([item])
assert result[0].subs.relevance > 0
assert result[0].subs.recency > 0
def test_high_relevance_beats_low(self):
high = self._make_item(relevance=0.95)
low = self._make_item(relevance=0.1)
scored = score.score_reddit_items([high, low])
assert scored[0].score > scored[1].score
def test_low_date_confidence_penalized(self):
high_conf = self._make_item(date_confidence="high")
low_conf = self._make_item(date_confidence="low")
score.score_reddit_items([high_conf])
score.score_reddit_items([low_conf])
assert high_conf.score > low_conf.score
def test_no_engagement_gets_normalized_default(self):
"""Single item with None engagement gets normalize_to_100 default (50)."""
item = self._make_item(engagement=None)
result = score.score_reddit_items([item])
# normalize_to_100([None]) returns [50] as default
assert result[0].subs.engagement == 50
class TestScoreXItems:
def _make_item(self, **kwargs):
today = datetime.now(UTC).date().isoformat()
defaults = {
"id": "123",
"text": "Test tweet",
"url": "https://x.com/user/123",
"author_handle": "user",
"date": today,
"date_confidence": "high",
"relevance": 0.8,
"engagement": schema.Engagement(likes=500, reposts=50, replies=20),
}
defaults.update(kwargs)
return schema.XItem(**defaults)
def test_empty_list(self):
assert score.score_x_items([]) == []
def test_single_item_gets_score(self):
item = self._make_item()
result = score.score_x_items([item])
assert 0 < result[0].score <= 100
def test_high_engagement_beats_low(self):
high = self._make_item(
engagement=schema.Engagement(likes=10000, reposts=1000),
)
low = self._make_item(
engagement=schema.Engagement(likes=5, reposts=0),
)
scored = score.score_x_items([high, low])
assert scored[0].score > scored[1].score
class TestScoreWebSearchItems:
def _make_item(self, **kwargs):
today = datetime.now(UTC).date().isoformat()
defaults = {
"id": "w1",
"title": "Test page",
"url": "https://example.com/test",
"source_domain": "example.com",
"snippet": "A test page",
"date": today,
"date_confidence": "high",
"relevance": 0.8,
}
defaults.update(kwargs)
return schema.WebSearchItem(**defaults)
def test_empty_list(self):
assert score.score_websearch_items([]) == []
def test_single_item_gets_score(self):
item = self._make_item()
result = score.score_websearch_items([item])
assert result[0].score > 0
def test_engagement_subscore_is_zero(self):
item = self._make_item()
score.score_websearch_items([item])
assert item.subs.engagement == 0
def test_high_confidence_bonus(self):
high = self._make_item(date_confidence="high")
low = self._make_item(date_confidence="low")
score.score_websearch_items([high])
score.score_websearch_items([low])
assert high.score > low.score
def test_source_penalty_applied(self):
"""WebSearch items get a 15-point source penalty."""
item = self._make_item(relevance=0.8, date_confidence="high")
score.score_websearch_items([item])
# Without penalty: 0.55*80 + 0.45*100 + 10 (high bonus) = 44+45+10 = 99
# With penalty: 99 - 15 = 84
assert item.score == 84
def test_web_lower_than_reddit_with_spread(self):
"""With engagement spread, Reddit high-engagement beats web."""
today = datetime.now(UTC).date().isoformat()
web = self._make_item(relevance=0.8, date=today, date_confidence="high")
# Multiple Reddit items so engagement normalization creates a range
reddit_high = schema.RedditItem(
id="r1",
title="High",
url="",
subreddit="test",
date=today,
date_confidence="high",
relevance=0.8,
engagement=schema.Engagement(score=1000, num_comments=500, upvote_ratio=0.95),
)
reddit_low = schema.RedditItem(
id="r2",
title="Low",
url="",
subreddit="test",
date=today,
date_confidence="high",
relevance=0.8,
engagement=schema.Engagement(score=1, num_comments=0, upvote_ratio=0.5),
)
score.score_websearch_items([web])
score.score_reddit_items([reddit_high, reddit_low])
assert reddit_high.score > web.score
class TestSortItems:
def test_sorts_by_score_descending(self):
a = schema.RedditItem(
id="a",
title="A",
url="",
subreddit="",
score=90,
)
b = schema.RedditItem(
id="b",
title="B",
url="",
subreddit="",
score=50,
)
result = score.sort_items([b, a])
assert result[0].id == "a"
assert result[1].id == "b"
def test_tiebreak_by_date(self):
a = schema.RedditItem(
id="a",
title="A",
url="",
subreddit="",
score=80,
date="2026-02-15",
)
b = schema.RedditItem(
id="b",
title="B",
url="",
subreddit="",
score=80,
date="2026-02-10",
)
result = score.sort_items([b, a])
assert result[0].id == "a" # More recent first
def test_tiebreak_by_source_priority(self):
reddit = schema.RedditItem(
id="r",
title="Same",
url="",
subreddit="",
score=80,
date="2026-02-15",
)
x = schema.XItem(
id="x",
text="Same",
url="",
author_handle="",
score=80,
date="2026-02-15",
)
result = score.sort_items([x, reddit])
assert isinstance(result[0], schema.RedditItem) # Reddit > X
def test_empty_list(self):
assert score.sort_items([]) == []
def test_none_date_handled(self):
a = schema.RedditItem(
id="a",
title="A",
url="",
subreddit="",
score=80,
date=None,
)
b = schema.RedditItem(
id="b",
title="B",
url="",
subreddit="",
score=80,
date="2026-02-15",
)
result = score.sort_items([a, b])
assert result[0].id == "b" # Dated item first