refactor: match upstream unittest convention

Convert all new tests from bare pytest style to unittest.TestCase
with sys.path.insert, matching the convention used by all existing
tests. Remove pyproject.toml and conftest.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
P
2026-03-09 16:43:04 -04:00
parent 0979f506db
commit 4756c20ec0
6 changed files with 175 additions and 152 deletions
-3
View File
@@ -1,3 +0,0 @@
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["scripts"]
-33
View File
@@ -1,33 +0,0 @@
"""Shared pytest fixtures for last30days tests."""
import json
from pathlib import Path
import pytest
@pytest.fixture
def project_root():
"""Path to the repository root."""
return Path(__file__).parent.parent
@pytest.fixture
def fixtures_dir(project_root):
"""Path to the fixtures directory."""
return project_root / "fixtures"
@pytest.fixture
def tmp_config_dir(tmp_path):
"""Temporary directory for config file tests. Auto-cleaned by pytest."""
return tmp_path
@pytest.fixture
def load_fixture(fixtures_dir):
"""Load a JSON fixture file by name."""
def _load(name):
with open(fixtures_dir / name) as f:
return json.load(f)
return _load
+37 -23
View File
@@ -1,74 +1,88 @@
"""Tests for instagram.py — ScrapeCreators Instagram search module.""" """Tests for instagram.py — ScrapeCreators Instagram search module."""
import sys
import unittest
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from lib import instagram from lib import instagram
class TestTokenize: class TestTokenize(unittest.TestCase):
"""Tests for _tokenize().""" """Tests for _tokenize()."""
def test_strips_stopwords(self): def test_strips_stopwords(self):
tokens = instagram._tokenize("how to use the AI tools") tokens = instagram._tokenize("how to use the AI tools")
assert "how" not in tokens self.assertNotIn("how", tokens)
assert "the" not in tokens self.assertNotIn("the", tokens)
assert "to" not in tokens self.assertNotIn("to", tokens)
def test_expands_synonyms(self): def test_expands_synonyms(self):
tokens = instagram._tokenize("ai tools") tokens = instagram._tokenize("ai tools")
assert "artificial" in tokens or "intelligence" in tokens self.assertTrue("artificial" in tokens or "intelligence" in tokens)
def test_removes_single_char(self): def test_removes_single_char(self):
tokens = instagram._tokenize("a b c python") tokens = instagram._tokenize("a b c python")
assert "a" not in tokens self.assertNotIn("a", tokens)
assert "b" not in tokens self.assertNotIn("b", tokens)
assert "python" in tokens self.assertIn("python", tokens)
def test_lowercases(self): def test_lowercases(self):
tokens = instagram._tokenize("Python REACT") tokens = instagram._tokenize("Python REACT")
assert "python" in tokens self.assertIn("python", tokens)
assert "react" in tokens self.assertIn("react", tokens)
def test_strips_punctuation(self): def test_strips_punctuation(self):
tokens = instagram._tokenize("hello, world!") tokens = instagram._tokenize("hello, world!")
assert "hello" in tokens self.assertIn("hello", tokens)
assert "world" in tokens self.assertIn("world", tokens)
class TestComputeRelevance: class TestComputeRelevance(unittest.TestCase):
"""Tests for _compute_relevance().""" """Tests for _compute_relevance()."""
def test_exact_match_high(self): def test_exact_match_high(self):
rel = instagram._compute_relevance("claude code", "Claude Code tricks and tips") rel = instagram._compute_relevance("claude code", "Claude Code tricks and tips")
assert rel >= 0.8 self.assertGreaterEqual(rel, 0.8)
def test_partial_match_lower(self): def test_partial_match_lower(self):
rel = instagram._compute_relevance("claude code tips", "Best AI tools for coding") rel = instagram._compute_relevance("claude code tips", "Best AI tools for coding")
assert rel < 0.5 self.assertLess(rel, 0.5)
def test_hashtag_boost(self): def test_hashtag_boost(self):
base = instagram._compute_relevance("claude code", "random video about stuff") base = instagram._compute_relevance("claude code", "random video about stuff")
boosted = instagram._compute_relevance("claude code", "random video about stuff", ["claudecode", "ai"]) boosted = instagram._compute_relevance("claude code", "random video about stuff", ["claudecode", "ai"])
assert boosted > base self.assertGreater(boosted, base)
def test_floor_at_01(self): def test_floor_at_01(self):
rel = instagram._compute_relevance("quantum physics", "cat dancing video") rel = instagram._compute_relevance("quantum physics", "cat dancing video")
assert rel >= 0.1 self.assertGreaterEqual(rel, 0.1)
def test_empty_query_returns_default(self): def test_empty_query_returns_default(self):
rel = instagram._compute_relevance("", "Some video title") rel = instagram._compute_relevance("", "Some video title")
assert rel == 0.5 self.assertEqual(rel, 0.5)
class TestInstagramDepthConfig: class TestInstagramDepthConfig(unittest.TestCase):
"""Tests for DEPTH_CONFIG.""" """Tests for DEPTH_CONFIG."""
def test_all_depths_exist(self): def test_all_depths_exist(self):
for depth in ("quick", "default", "deep"): for depth in ("quick", "default", "deep"):
assert depth in instagram.DEPTH_CONFIG self.assertIn(depth, instagram.DEPTH_CONFIG)
def test_required_keys(self): def test_required_keys(self):
for depth, config in instagram.DEPTH_CONFIG.items(): for depth, config in instagram.DEPTH_CONFIG.items():
assert "results_per_page" in config self.assertIn("results_per_page", config)
assert "max_captions" in config self.assertIn("max_captions", config)
def test_deep_has_more_results(self): def test_deep_has_more_results(self):
assert instagram.DEPTH_CONFIG["deep"]["results_per_page"] > instagram.DEPTH_CONFIG["quick"]["results_per_page"] self.assertGreater(
instagram.DEPTH_CONFIG["deep"]["results_per_page"],
instagram.DEPTH_CONFIG["quick"]["results_per_page"],
)
if __name__ == "__main__":
unittest.main()
+49 -30
View File
@@ -1,58 +1,73 @@
"""Tests for reddit_enrich.py — comment enrichment and parsing.""" """Tests for reddit_enrich.py — comment enrichment and parsing."""
import json
import sys
import unittest
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from lib import reddit_enrich from lib import reddit_enrich
FIXTURES_DIR = Path(__file__).parent.parent / "fixtures"
class TestExtractRedditPath:
def _load_fixture(name):
with open(FIXTURES_DIR / name) as f:
return json.load(f)
class TestExtractRedditPath(unittest.TestCase):
"""Tests for extract_reddit_path().""" """Tests for extract_reddit_path()."""
def test_valid_url(self): def test_valid_url(self):
url = "https://www.reddit.com/r/ClaudeAI/comments/abc123/post_title/" url = "https://www.reddit.com/r/ClaudeAI/comments/abc123/post_title/"
path = reddit_enrich.extract_reddit_path(url) path = reddit_enrich.extract_reddit_path(url)
assert path == "/r/ClaudeAI/comments/abc123/post_title/" self.assertEqual(path, "/r/ClaudeAI/comments/abc123/post_title/")
def test_non_reddit_url(self): def test_non_reddit_url(self):
assert reddit_enrich.extract_reddit_path("https://example.com/foo") is None self.assertIsNone(reddit_enrich.extract_reddit_path("https://example.com/foo"))
def test_empty_string(self): def test_empty_string(self):
assert reddit_enrich.extract_reddit_path("") is None self.assertIsNone(reddit_enrich.extract_reddit_path(""))
def test_old_reddit(self): def test_old_reddit(self):
url = "https://old.reddit.com/r/test/comments/xyz/" url = "https://old.reddit.com/r/test/comments/xyz/"
assert reddit_enrich.extract_reddit_path(url) is not None self.assertIsNotNone(reddit_enrich.extract_reddit_path(url))
class TestParseThreadData: class TestParseThreadData(unittest.TestCase):
"""Tests for parse_thread_data() using fixture.""" """Tests for parse_thread_data() using fixture."""
def test_parses_submission(self, load_fixture): def test_parses_submission(self):
data = load_fixture("reddit_thread_sample.json") data = _load_fixture("reddit_thread_sample.json")
result = reddit_enrich.parse_thread_data(data) result = reddit_enrich.parse_thread_data(data)
assert result["submission"] is not None self.assertIsNotNone(result["submission"])
assert result["submission"]["score"] == 847 self.assertEqual(result["submission"]["score"], 847)
assert result["submission"]["num_comments"] == 156 self.assertEqual(result["submission"]["num_comments"], 156)
def test_parses_comments(self, load_fixture): def test_parses_comments(self):
data = load_fixture("reddit_thread_sample.json") data = _load_fixture("reddit_thread_sample.json")
result = reddit_enrich.parse_thread_data(data) result = reddit_enrich.parse_thread_data(data)
assert len(result["comments"]) == 8 self.assertEqual(len(result["comments"]), 8)
assert result["comments"][0]["author"] == "skill_expert" self.assertEqual(result["comments"][0]["author"], "skill_expert")
def test_empty_input(self): def test_empty_input(self):
result = reddit_enrich.parse_thread_data([]) result = reddit_enrich.parse_thread_data([])
assert result["submission"] is None self.assertIsNone(result["submission"])
assert result["comments"] == [] self.assertEqual(result["comments"], [])
def test_malformed_input(self): def test_malformed_input(self):
result = reddit_enrich.parse_thread_data("not a list") result = reddit_enrich.parse_thread_data("not a list")
assert result["submission"] is None self.assertIsNone(result["submission"])
def test_none_input(self): def test_none_input(self):
result = reddit_enrich.parse_thread_data(None) result = reddit_enrich.parse_thread_data(None)
assert result["submission"] is None self.assertIsNone(result["submission"])
class TestGetTopComments: class TestGetTopComments(unittest.TestCase):
"""Tests for get_top_comments().""" """Tests for get_top_comments()."""
def test_sorted_by_score(self): def test_sorted_by_score(self):
@@ -62,8 +77,8 @@ class TestGetTopComments:
{"score": 50, "author": "c"}, {"score": 50, "author": "c"},
] ]
top = reddit_enrich.get_top_comments(comments, limit=3) top = reddit_enrich.get_top_comments(comments, limit=3)
assert top[0]["score"] == 100 self.assertEqual(top[0]["score"], 100)
assert top[1]["score"] == 50 self.assertEqual(top[1]["score"], 50)
def test_filters_deleted(self): def test_filters_deleted(self):
comments = [ comments = [
@@ -72,19 +87,19 @@ class TestGetTopComments:
{"score": 10, "author": "real_user"}, {"score": 10, "author": "real_user"},
] ]
top = reddit_enrich.get_top_comments(comments) top = reddit_enrich.get_top_comments(comments)
assert len(top) == 1 self.assertEqual(len(top), 1)
assert top[0]["author"] == "real_user" self.assertEqual(top[0]["author"], "real_user")
def test_respects_limit(self): def test_respects_limit(self):
comments = [{"score": i, "author": f"u{i}"} for i in range(20)] comments = [{"score": i, "author": f"u{i}"} for i in range(20)]
top = reddit_enrich.get_top_comments(comments, limit=5) top = reddit_enrich.get_top_comments(comments, limit=5)
assert len(top) == 5 self.assertEqual(len(top), 5)
def test_empty_list(self): def test_empty_list(self):
assert reddit_enrich.get_top_comments([]) == [] self.assertEqual(reddit_enrich.get_top_comments([]), [])
class TestExtractCommentInsights: class TestExtractCommentInsights(unittest.TestCase):
"""Tests for extract_comment_insights().""" """Tests for extract_comment_insights()."""
def test_filters_short_comments(self): def test_filters_short_comments(self):
@@ -93,7 +108,7 @@ class TestExtractCommentInsights:
{"body": "A" * 50 + " this is a substantive comment about the topic."}, {"body": "A" * 50 + " this is a substantive comment about the topic."},
] ]
insights = reddit_enrich.extract_comment_insights(comments) insights = reddit_enrich.extract_comment_insights(comments)
assert len(insights) == 1 self.assertEqual(len(insights), 1)
def test_filters_low_value_patterns(self): def test_filters_low_value_patterns(self):
comments = [ comments = [
@@ -102,9 +117,13 @@ class TestExtractCommentInsights:
{"body": "A" * 50 + " Here's a real insight about how to approach this problem."}, {"body": "A" * 50 + " Here's a real insight about how to approach this problem."},
] ]
insights = reddit_enrich.extract_comment_insights(comments) insights = reddit_enrich.extract_comment_insights(comments)
assert len(insights) == 1 self.assertEqual(len(insights), 1)
def test_respects_limit(self): def test_respects_limit(self):
comments = [{"body": f"Comment number {i} " + "x" * 50} for i in range(20)] comments = [{"body": f"Comment number {i} " + "x" * 50} for i in range(20)]
insights = reddit_enrich.extract_comment_insights(comments, limit=3) insights = reddit_enrich.extract_comment_insights(comments, limit=3)
assert len(insights) <= 3 self.assertLessEqual(len(insights), 3)
if __name__ == "__main__":
unittest.main()
+45 -30
View File
@@ -1,59 +1,66 @@
"""Tests for reddit.py — ScrapeCreators Reddit search module.""" """Tests for reddit.py — ScrapeCreators Reddit search module."""
import sys
import unittest
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from lib import reddit from lib import reddit
class TestExtractCoreSubject: class TestExtractCoreSubject(unittest.TestCase):
"""Tests for _extract_core_subject().""" """Tests for _extract_core_subject()."""
def test_strips_what_are_prefix(self): def test_strips_what_are_prefix(self):
assert reddit._extract_core_subject("what are the best AI tools") == "ai tools" self.assertEqual(reddit._extract_core_subject("what are the best AI tools"), "ai tools")
def test_strips_how_to_prefix(self): def test_strips_how_to_prefix(self):
assert reddit._extract_core_subject("how to use cursor IDE") == "cursor ide" self.assertEqual(reddit._extract_core_subject("how to use cursor IDE"), "cursor ide")
def test_strips_noise_words(self): def test_strips_noise_words(self):
result = reddit._extract_core_subject("latest trending updates") result = reddit._extract_core_subject("latest trending updates")
assert result == "latest trending updates" self.assertEqual(result, "latest trending updates")
def test_preserves_product_name(self): def test_preserves_product_name(self):
assert reddit._extract_core_subject("cursor IDE") == "cursor ide" self.assertEqual(reddit._extract_core_subject("cursor IDE"), "cursor ide")
def test_strips_trailing_punctuation(self): def test_strips_trailing_punctuation(self):
result = reddit._extract_core_subject("what is Claude?") result = reddit._extract_core_subject("what is Claude?")
assert not result.endswith("?") self.assertFalse(result.endswith("?"))
def test_empty_string(self): def test_empty_string(self):
result = reddit._extract_core_subject("") result = reddit._extract_core_subject("")
assert result == "" self.assertEqual(result, "")
def test_strips_what_do_people_think(self): def test_strips_what_do_people_think(self):
result = reddit._extract_core_subject("what do people think about React Server Components") result = reddit._extract_core_subject("what do people think about React Server Components")
assert result == "react server components" self.assertEqual(result, "react server components")
class TestExpandRedditQueries: class TestExpandRedditQueries(unittest.TestCase):
"""Tests for expand_reddit_queries().""" """Tests for expand_reddit_queries()."""
def test_quick_returns_one_query(self): def test_quick_returns_one_query(self):
queries = reddit.expand_reddit_queries("cursor IDE", "quick") queries = reddit.expand_reddit_queries("cursor IDE", "quick")
assert len(queries) >= 1 self.assertGreaterEqual(len(queries), 1)
def test_default_includes_review_variant(self): def test_default_includes_review_variant(self):
queries = reddit.expand_reddit_queries("cursor IDE", "default") queries = reddit.expand_reddit_queries("cursor IDE", "default")
assert any("worth it" in q or "review" in q for q in queries) self.assertTrue(any("worth it" in q or "review" in q for q in queries))
def test_deep_includes_issues_variant(self): def test_deep_includes_issues_variant(self):
queries = reddit.expand_reddit_queries("cursor IDE", "deep") queries = reddit.expand_reddit_queries("cursor IDE", "deep")
assert any("issues" in q or "problems" in q for q in queries) self.assertTrue(any("issues" in q or "problems" in q for q in queries))
def test_deep_has_more_queries_than_quick(self): def test_deep_has_more_queries_than_quick(self):
quick = reddit.expand_reddit_queries("cursor IDE", "quick") quick = reddit.expand_reddit_queries("cursor IDE", "quick")
deep = reddit.expand_reddit_queries("cursor IDE", "deep") deep = reddit.expand_reddit_queries("cursor IDE", "deep")
assert len(deep) > len(quick) self.assertGreater(len(deep), len(quick))
class TestDiscoverSubreddits: class TestDiscoverSubreddits(unittest.TestCase):
"""Tests for discover_subreddits().""" """Tests for discover_subreddits()."""
def test_ranks_by_frequency(self): def test_ranks_by_frequency(self):
@@ -63,7 +70,7 @@ class TestDiscoverSubreddits:
{"subreddit": "python", "score": 5}, {"subreddit": "python", "score": 5},
] ]
subs = reddit.discover_subreddits(results, max_subs=5) subs = reddit.discover_subreddits(results, max_subs=5)
assert subs[0] == "programming" self.assertEqual(subs[0], "programming")
def test_utility_sub_penalty(self): def test_utility_sub_penalty(self):
results = [ results = [
@@ -72,7 +79,7 @@ class TestDiscoverSubreddits:
{"subreddit": "python", "score": 10}, {"subreddit": "python", "score": 10},
] ]
subs = reddit.discover_subreddits(results, topic="python", max_subs=5) subs = reddit.discover_subreddits(results, topic="python", max_subs=5)
assert subs[0] == "python" self.assertEqual(subs[0], "python")
def test_topic_name_bonus(self): def test_topic_name_bonus(self):
results = [ results = [
@@ -80,7 +87,7 @@ class TestDiscoverSubreddits:
{"subreddit": "webdev", "score": 10}, {"subreddit": "webdev", "score": 10},
] ]
subs = reddit.discover_subreddits(results, topic="react hooks", max_subs=5) subs = reddit.discover_subreddits(results, topic="react hooks", max_subs=5)
assert subs[0] == "reactjs" self.assertEqual(subs[0], "reactjs")
def test_engagement_bonus(self): def test_engagement_bonus(self):
results = [ results = [
@@ -88,48 +95,56 @@ class TestDiscoverSubreddits:
{"subreddit": "OtherSub", "ups": 5}, {"subreddit": "OtherSub", "ups": 5},
] ]
subs = reddit.discover_subreddits(results, max_subs=5) subs = reddit.discover_subreddits(results, max_subs=5)
assert subs[0] == "AIsub" self.assertEqual(subs[0], "AIsub")
def test_max_subs_limit(self): def test_max_subs_limit(self):
results = [{"subreddit": f"sub{i}"} for i in range(20)] results = [{"subreddit": f"sub{i}"} for i in range(20)]
subs = reddit.discover_subreddits(results, max_subs=3) subs = reddit.discover_subreddits(results, max_subs=3)
assert len(subs) <= 3 self.assertLessEqual(len(subs), 3)
def test_empty_results(self): def test_empty_results(self):
assert reddit.discover_subreddits([]) == [] self.assertEqual(reddit.discover_subreddits([]), [])
def test_missing_subreddit_field(self): def test_missing_subreddit_field(self):
results = [{"title": "no sub field"}] results = [{"title": "no sub field"}]
assert reddit.discover_subreddits(results) == [] self.assertEqual(reddit.discover_subreddits(results), [])
class TestParseDate: class TestParseDate(unittest.TestCase):
"""Tests for _parse_date().""" """Tests for _parse_date()."""
def test_valid_timestamp(self): def test_valid_timestamp(self):
assert reddit._parse_date(1705363200) == "2024-01-16" self.assertEqual(reddit._parse_date(1705363200), "2024-01-16")
def test_string_timestamp(self): def test_string_timestamp(self):
assert reddit._parse_date("1705363200") == "2024-01-16" self.assertEqual(reddit._parse_date("1705363200"), "2024-01-16")
def test_none_returns_none(self): def test_none_returns_none(self):
assert reddit._parse_date(None) is None self.assertIsNone(reddit._parse_date(None))
def test_zero_returns_none(self): def test_zero_returns_none(self):
assert reddit._parse_date(0) is None self.assertIsNone(reddit._parse_date(0))
class TestDepthConfig: class TestDepthConfig(unittest.TestCase):
"""Tests for DEPTH_CONFIG structure.""" """Tests for DEPTH_CONFIG structure."""
def test_all_depths_exist(self): def test_all_depths_exist(self):
for depth in ("quick", "default", "deep"): for depth in ("quick", "default", "deep"):
assert depth in reddit.DEPTH_CONFIG self.assertIn(depth, reddit.DEPTH_CONFIG)
def test_required_keys(self): def test_required_keys(self):
required = {"global_searches", "subreddit_searches", "comment_enrichments", "timeframe"} required = {"global_searches", "subreddit_searches", "comment_enrichments", "timeframe"}
for depth, config in reddit.DEPTH_CONFIG.items(): for depth, config in reddit.DEPTH_CONFIG.items():
assert required.issubset(config.keys()), f"Missing keys in {depth}: {required - config.keys()}" self.assertTrue(required.issubset(config.keys()),
f"Missing keys in {depth}: {required - config.keys()}")
def test_deep_has_more_searches(self): def test_deep_has_more_searches(self):
assert reddit.DEPTH_CONFIG["deep"]["global_searches"] > reddit.DEPTH_CONFIG["quick"]["global_searches"] self.assertGreater(
reddit.DEPTH_CONFIG["deep"]["global_searches"],
reddit.DEPTH_CONFIG["quick"]["global_searches"],
)
if __name__ == "__main__":
unittest.main()
+44 -33
View File
@@ -1,20 +1,27 @@
"""Tests for schema.py — data class serialization roundtrips.""" """Tests for schema.py — data class serialization roundtrips."""
import sys
import unittest
from pathlib import Path
# Add lib to path
sys.path.insert(0, str(Path(__file__).parent.parent / "scripts"))
from lib import schema from lib import schema
class TestEngagement: class TestEngagement(unittest.TestCase):
"""Tests for Engagement.to_dict().""" """Tests for Engagement.to_dict()."""
def test_sparse_fields(self): def test_sparse_fields(self):
eng = schema.Engagement(score=100, num_comments=50) eng = schema.Engagement(score=100, num_comments=50)
d = eng.to_dict() d = eng.to_dict()
assert d == {"score": 100, "num_comments": 50} self.assertEqual(d, {"score": 100, "num_comments": 50})
assert "likes" not in d self.assertNotIn("likes", d)
def test_all_none_returns_none(self): def test_all_none_returns_none(self):
eng = schema.Engagement() eng = schema.Engagement()
assert eng.to_dict() is None self.assertIsNone(eng.to_dict())
def test_all_fields(self): def test_all_fields(self):
eng = schema.Engagement( eng = schema.Engagement(
@@ -23,19 +30,19 @@ class TestEngagement:
views=7, shares=8, volume=9.0, liquidity=10.0, views=7, shares=8, volume=9.0, liquidity=10.0,
) )
d = eng.to_dict() d = eng.to_dict()
assert len(d) == 11 self.assertEqual(len(d), 11)
class TestComment: class TestComment(unittest.TestCase):
def test_basic(self): def test_basic(self):
c = schema.Comment(score=50, date="2026-03-01", author="user", excerpt="text", url="http://x") c = schema.Comment(score=50, date="2026-03-01", author="user", excerpt="text", url="http://x")
d = c.to_dict() d = c.to_dict()
assert d["score"] == 50 self.assertEqual(d["score"], 50)
assert d["author"] == "user" self.assertEqual(d["author"], "user")
assert len(d) == 5 self.assertEqual(len(d), 5)
class TestRedditItem: class TestRedditItem(unittest.TestCase):
def test_roundtrip(self): def test_roundtrip(self):
item = schema.RedditItem( item = schema.RedditItem(
id="R1", title="Test", url="http://reddit.com/r/test", id="R1", title="Test", url="http://reddit.com/r/test",
@@ -43,10 +50,10 @@ class TestRedditItem:
engagement=schema.Engagement(score=100), engagement=schema.Engagement(score=100),
) )
d = item.to_dict() d = item.to_dict()
assert d["id"] == "R1" self.assertEqual(d["id"], "R1")
assert d["subreddit"] == "test" self.assertEqual(d["subreddit"], "test")
assert d["engagement"] == {"score": 100} self.assertEqual(d["engagement"], {"score": 100})
assert "cross_refs" not in d self.assertNotIn("cross_refs", d)
def test_cross_refs_included_when_present(self): def test_cross_refs_included_when_present(self):
item = schema.RedditItem( item = schema.RedditItem(
@@ -54,78 +61,82 @@ class TestRedditItem:
cross_refs=["X1", "HN2"], cross_refs=["X1", "HN2"],
) )
d = item.to_dict() d = item.to_dict()
assert d["cross_refs"] == ["X1", "HN2"] self.assertEqual(d["cross_refs"], ["X1", "HN2"])
class TestXItem: class TestXItem(unittest.TestCase):
def test_roundtrip(self): def test_roundtrip(self):
item = schema.XItem( item = schema.XItem(
id="X1", text="tweet", url="http://x.com/1", id="X1", text="tweet", url="http://x.com/1",
author_handle="user", date="2026-03-01", author_handle="user", date="2026-03-01",
) )
d = item.to_dict() d = item.to_dict()
assert d["id"] == "X1" self.assertEqual(d["id"], "X1")
assert d["author_handle"] == "user" self.assertEqual(d["author_handle"], "user")
assert "cross_refs" not in d self.assertNotIn("cross_refs", d)
class TestYouTubeItem: class TestYouTubeItem(unittest.TestCase):
def test_roundtrip(self): def test_roundtrip(self):
item = schema.YouTubeItem( item = schema.YouTubeItem(
id="YT1", title="Video", url="http://youtube.com/1", id="YT1", title="Video", url="http://youtube.com/1",
channel_name="chan", channel_name="chan",
) )
d = item.to_dict() d = item.to_dict()
assert d["channel_name"] == "chan" self.assertEqual(d["channel_name"], "chan")
assert d["date_confidence"] == "high" self.assertEqual(d["date_confidence"], "high")
class TestTikTokItem: class TestTikTokItem(unittest.TestCase):
def test_roundtrip(self): def test_roundtrip(self):
item = schema.TikTokItem( item = schema.TikTokItem(
id="TK1", text="caption", url="http://tiktok.com/1", id="TK1", text="caption", url="http://tiktok.com/1",
author_name="creator", hashtags=["ai", "code"], author_name="creator", hashtags=["ai", "code"],
) )
d = item.to_dict() d = item.to_dict()
assert d["hashtags"] == ["ai", "code"] self.assertEqual(d["hashtags"], ["ai", "code"])
assert d["author_name"] == "creator" self.assertEqual(d["author_name"], "creator")
class TestInstagramItem: class TestInstagramItem(unittest.TestCase):
def test_roundtrip(self): def test_roundtrip(self):
item = schema.InstagramItem( item = schema.InstagramItem(
id="IG1", text="caption", url="http://instagram.com/reel/1", id="IG1", text="caption", url="http://instagram.com/reel/1",
author_name="creator", author_name="creator",
) )
d = item.to_dict() d = item.to_dict()
assert d["id"] == "IG1" self.assertEqual(d["id"], "IG1")
class TestWebSearchItem: class TestWebSearchItem(unittest.TestCase):
def test_roundtrip(self): def test_roundtrip(self):
item = schema.WebSearchItem( item = schema.WebSearchItem(
id="W1", title="Article", url="http://example.com", id="W1", title="Article", url="http://example.com",
source_domain="example.com", snippet="text", source_domain="example.com", snippet="text",
) )
d = item.to_dict() d = item.to_dict()
assert d["source_domain"] == "example.com" self.assertEqual(d["source_domain"], "example.com")
class TestHackerNewsItem: class TestHackerNewsItem(unittest.TestCase):
def test_roundtrip(self): def test_roundtrip(self):
item = schema.HackerNewsItem( item = schema.HackerNewsItem(
id="HN1", title="Show HN", url="http://example.com", id="HN1", title="Show HN", url="http://example.com",
hn_url="http://news.ycombinator.com/item?id=1", author="pg", hn_url="http://news.ycombinator.com/item?id=1", author="pg",
) )
d = item.to_dict() d = item.to_dict()
assert d["hn_url"].startswith("http://news.ycombinator.com") self.assertTrue(d["hn_url"].startswith("http://news.ycombinator.com"))
class TestPolymarketItem: class TestPolymarketItem(unittest.TestCase):
def test_roundtrip(self): def test_roundtrip(self):
item = schema.PolymarketItem( item = schema.PolymarketItem(
id="PM1", title="Election", question="Who wins?", id="PM1", title="Election", question="Who wins?",
url="http://polymarket.com/1", url="http://polymarket.com/1",
) )
d = item.to_dict() d = item.to_dict()
assert d["question"] == "Who wins?" self.assertEqual(d["question"], "Who wins?")
if __name__ == "__main__":
unittest.main()