返回 last30days-skill
test_categories.py
根目录 / tests / test_categories.py
1 """Unit tests for scripts/lib/categories.py — the Step 0.55 category-peer map.
2
3 Guards the 2026-04-22 `Prompting GPT Image 2` failure mode: the original bug
4 was that Step 0.55 resolved only brand-adjacent subs (r/OpenAI, r/ChatGPT)
5 and missed the category peers (r/StableDiffusion, r/midjourney, r/dalle2)
6 where prompting techniques actually live.
7 """
8
9 import re
10 import unittest
11
12 from lib import categories
13 from lib.categories import CATEGORY_PEERS, detect_category, peer_subs_for
14
15
16 class DetectCategoryHappyPath(unittest.TestCase):
17 def test_prompting_gpt_image_2_matches_image_generation(self):
18 self.assertEqual(
19 detect_category("Prompting GPT Image 2"),
20 "ai_image_generation",
21 )
22
23 def test_claude_code_matches_coding_agent(self):
24 self.assertEqual(
25 detect_category("Claude Code skills"),
26 "ai_coding_agent",
27 )
28
29 def test_suno_matches_music_generation(self):
30 self.assertEqual(detect_category("Suno v4 review"), "ai_music_generation")
31
32 def test_polymarket_matches_prediction_markets(self):
33 self.assertEqual(
34 detect_category("Polymarket election odds"),
35 "prediction_markets",
36 )
37
38 def test_sora_matches_video_generation(self):
39 self.assertEqual(detect_category("Sora 2 prompts"), "ai_video_generation")
40
41
42 class PeerSubsForHappyPath(unittest.TestCase):
43 def test_image_generation_peer_subs_priority_order(self):
44 subs = peer_subs_for("ai_image_generation")
45 self.assertIn("StableDiffusion", subs)
46 self.assertIn("midjourney", subs)
47 self.assertIn("dalle2", subs)
48 self.assertLess(subs.index("StableDiffusion"), subs.index("midjourney"))
49 self.assertLess(subs.index("midjourney"), subs.index("dalle2"))
50
51 def test_unknown_category_returns_empty_list(self):
52 self.assertEqual(peer_subs_for("unknown_category"), [])
53
54 def test_none_category_returns_empty_list(self):
55 self.assertEqual(peer_subs_for(None), [])
56
57 def test_returned_list_is_fresh_copy(self):
58 first = peer_subs_for("ai_image_generation")
59 first.append("MutatedSub")
60 second = peer_subs_for("ai_image_generation")
61 self.assertNotIn("MutatedSub", second)
62
63
64 class DetectCategoryEdgeCases(unittest.TestCase):
65 def test_case_insensitive_match(self):
66 self.assertEqual(
67 detect_category("STABLE DIFFUSION walkthrough"),
68 "ai_image_generation",
69 )
70
71 def test_non_category_topic_returns_none(self):
72 self.assertIsNone(detect_category("Kanye West"))
73
74 def test_bare_image_word_does_not_trigger_image_generation(self):
75 # Compound-term guard: "image" alone is not a pattern; only
76 # multi-word compounds or domain-specific brand names match.
77 self.assertIsNone(detect_category("image editing on my phone"))
78
79 def test_bare_ai_word_does_not_trigger_any_category(self):
80 self.assertIsNone(detect_category("ai news today"))
81
82 def test_empty_topic_returns_none(self):
83 self.assertIsNone(detect_category(""))
84
85 def test_none_topic_returns_none(self):
86 self.assertIsNone(detect_category(None))
87
88 def test_first_match_wins_image_gen_before_chat_model(self):
89 # "gpt image 2" contains "gpt image" (ai_image_generation) and the
90 # substring "gpt" could resemble gpt-N chat-model patterns. The
91 # narrower category wins because it is declared earlier.
92 self.assertEqual(
93 detect_category("gpt image 2 review"),
94 "ai_image_generation",
95 )
96
97
98 class CategoryMapInvariants(unittest.TestCase):
99 """Regression guards on the map itself — catch accidental bare-word patterns."""
100
101 # Common nouns that would produce false positives if used as bare patterns.
102 FORBIDDEN_BARE_PATTERNS = frozenset({
103 "image", "video", "music", "ai", "model", "agent", "chat",
104 "code", "cli", "app", "tool", "defi",
105 })
106
107 def test_no_category_has_a_bare_common_noun_pattern(self):
108 offenders = []
109 for category_id, entry in CATEGORY_PEERS.items():
110 for pattern in entry["patterns"]:
111 if pattern.strip() in self.FORBIDDEN_BARE_PATTERNS:
112 offenders.append((category_id, pattern))
113 self.assertEqual(
114 offenders,
115 [],
116 msg=(
117 "Bare common-noun patterns cause false positives. "
118 f"Offenders: {offenders}. Patterns must be compound "
119 "(e.g. 'image generation') or domain-specific "
120 "(e.g. 'midjourney')."
121 ),
122 )
123
124 def test_every_category_has_at_least_one_compound_or_brand_pattern(self):
125 multi_word_or_brand = re.compile(r"(\s|-|\.)|^[a-z][a-z0-9]{3,}$")
126 for category_id, entry in CATEGORY_PEERS.items():
127 patterns = entry["patterns"]
128 self.assertTrue(patterns, f"{category_id} has no patterns")
129 has_strong = any(multi_word_or_brand.search(p) for p in patterns)
130 self.assertTrue(
131 has_strong,
132 f"{category_id} needs at least one multi-word or brand pattern",
133 )
134
135 def test_every_category_has_at_least_two_peer_subs(self):
136 for category_id, entry in CATEGORY_PEERS.items():
137 self.assertGreaterEqual(
138 len(entry["peer_subs"]),
139 2,
140 f"{category_id} should list at least 2 peer subs",
141 )
142
143 def test_category_count_is_in_expected_range(self):
144 # Sanity check: the map is intentionally small and curated.
145 self.assertGreaterEqual(len(CATEGORY_PEERS), 8)
146 self.assertLessEqual(len(CATEGORY_PEERS), 20)
147
148 if __name__ == "__main__":
149 unittest.main()
150
150 lines PYTHON