返回 last30days-skill
test_relevance.py
根目录 / tests / test_relevance.py
1 """Tests for relevance.py — shared relevance scoring.
2
3 Migrated from test_youtube_relevance.py + new hashtag/synonym tests.
4 """
5
6 import unittest
7
8 from lib.relevance import STOPWORDS, SYNONYMS, token_overlap_relevance, tokenize
9
10
11 class TestTokenize(unittest.TestCase):
12 """Tests for tokenize()."""
13
14 def test_removes_stopwords(self):
15 tokens = tokenize("how to use the AI tools")
16 self.assertNotIn("how", tokens)
17 self.assertNotIn("to", tokens)
18 self.assertNotIn("the", tokens)
19 self.assertIn("ai", tokens)
20 self.assertIn("tools", tokens)
21
22 def test_lowercases(self):
23 tokens = tokenize("Python REACT")
24 self.assertIn("python", tokens)
25 self.assertIn("react", tokens)
26
27 def test_strips_punctuation(self):
28 tokens = tokenize("hello, world!")
29 self.assertIn("hello", tokens)
30 self.assertIn("world", tokens)
31
32 def test_drops_single_char(self):
33 tokens = tokenize("a b c python")
34 self.assertNotIn("a", tokens)
35 self.assertNotIn("b", tokens)
36 self.assertNotIn("c", tokens)
37 self.assertIn("python", tokens)
38
39 def test_expands_synonyms(self):
40 tokens = tokenize("ai tools")
41 self.assertIn("artificial", tokens)
42 self.assertIn("intelligence", tokens)
43
44 def test_expands_js_synonym(self):
45 tokens = tokenize("js framework")
46 self.assertIn("javascript", tokens)
47
48 def test_expands_svelte(self):
49 tokens = tokenize("svelte app")
50 self.assertIn("sveltejs", tokens)
51
52 def test_expands_vue(self):
53 tokens = tokenize("vue components")
54 self.assertIn("vuejs", tokens)
55
56
57 class TestTokenOverlapRelevance(unittest.TestCase):
58 """Tests for token_overlap_relevance()."""
59
60 def test_high_relevance_exact_match(self):
61 rel = token_overlap_relevance("claude code", "Claude Code tricks and tips")
62 self.assertGreater(rel, 0.7)
63
64 def test_low_relevance_no_match(self):
65 rel = token_overlap_relevance("claude code tips", "Best AI tools for coding")
66 self.assertLess(rel, 0.5)
67
68 def test_empty_query_returns_neutral(self):
69 rel = token_overlap_relevance("", "Some video title")
70 self.assertEqual(rel, 0.5)
71
72 def test_floor_at_0_1(self):
73 rel = token_overlap_relevance("quantum physics", "cat dancing video")
74 self.assertEqual(rel, 0.0)
75
76 def test_full_match_returns_1(self):
77 rel = token_overlap_relevance("python tutorial", "Python Tutorial for Beginners")
78 self.assertEqual(rel, 1.0)
79
80 def test_partial_match(self):
81 rel = token_overlap_relevance("react native tutorial", "React Native Guide")
82 self.assertGreater(rel, 0.3)
83 self.assertLess(rel, 1.0)
84
85 def test_synonym_boosts_relevance(self):
86 # "js" should match "javascript" via synonym expansion
87 rel_with_syn = token_overlap_relevance("js framework", "javascript framework comparison")
88 rel_without = token_overlap_relevance("python framework", "javascript framework comparison")
89 self.assertGreater(rel_with_syn, rel_without)
90
91 def test_stopword_only_query(self):
92 rel = token_overlap_relevance("the a is", "some content here")
93 self.assertEqual(rel, 0.5)
94
95 def test_generic_only_overlap_stays_below_filter_threshold(self):
96 rel = token_overlap_relevance("anthropic odds", "Republican house odds update")
97 self.assertLess(rel, 0.3)
98
99 def test_informative_partial_match_stays_above_generic_only(self):
100 generic_only = token_overlap_relevance("anthropic odds", "Republican house odds update")
101 informative = token_overlap_relevance("anthropic odds", "Anthropic valuation market")
102 self.assertGreater(informative, generic_only)
103
104
105 class TestHashtagRelevance(unittest.TestCase):
106 """Tests for hashtag-aware relevance (TikTok/Instagram pattern)."""
107
108 def test_hashtag_boost(self):
109 rel_no_hash = token_overlap_relevance("claude code", "random video about stuff")
110 rel_with_hash = token_overlap_relevance(
111 "claude code", "random video about stuff", ["claudecode", "ai"]
112 )
113 self.assertGreater(rel_with_hash, rel_no_hash)
114
115 def test_concatenated_hashtag_splitting(self):
116 # "claudecode" should match "claude" from query via substring check
117 rel = token_overlap_relevance("claude", "video", ["claudecode"])
118 self.assertGreater(rel, 0.5)
119
120 def test_none_hashtags_same_as_no_hashtags(self):
121 rel1 = token_overlap_relevance("test query", "test content", None)
122 rel2 = token_overlap_relevance("test query", "test content")
123 self.assertEqual(rel1, rel2)
124
125 if __name__ == "__main__":
126 unittest.main()
127
127 lines PYTHON