返回 last30days-skill
test_instagram.py
根目录 / tests / test_instagram.py
1 import unittest
2
3 from lib.instagram import _parse_items
4
5
6 class TestInstagramOwnerTypeSafety(unittest.TestCase):
7 def _make_raw(self, **overrides):
8 base = {
9 "id": "1",
10 "code": "ABC123",
11 "caption": "test caption",
12 "owner": {"username": "testuser"},
13 }
14 base.update(overrides)
15 return base
16
17 def test_owner_as_dict(self):
18 items = _parse_items([self._make_raw()], "test")
19 self.assertEqual("testuser", items[0]["author_name"])
20
21 def test_owner_as_string(self):
22 items = _parse_items([self._make_raw(owner="stringuser")], "test")
23 self.assertEqual("stringuser", items[0]["author_name"])
24
25 def test_owner_missing(self):
26 raw = self._make_raw()
27 del raw["owner"]
28 items = _parse_items([raw], "test")
29 self.assertEqual("", items[0]["author_name"])
30
31 def test_owner_none(self):
32 items = _parse_items([self._make_raw(owner=None)], "test")
33 self.assertEqual("", items[0]["author_name"])
34
35 def test_user_field_fallback(self):
36 raw = self._make_raw()
37 del raw["owner"]
38 raw["user"] = {"username": "fallbackuser"}
39 items = _parse_items([raw], "test")
40 self.assertEqual("fallbackuser", items[0]["author_name"])
41
42
43 class TestInstagramUserReelsEnvelope(unittest.TestCase):
44 def test_wrapped_user_reel_is_parsed_with_its_fields(self):
45 from unittest.mock import patch
46 from lib import instagram
47
48 reel = {
49 "id": "reel-1017",
50 "code": "ABC1017",
51 "taken_at": "2026-08-01T12:34:56.000Z",
52 "caption": {"text": "Wrapped reel caption"},
53 "video_play_count": 123,
54 "like_count": 45,
55 "comment_count": 6,
56 }
57 response = {"items": [{"media": reel}]}
58
59 with patch.object(instagram.http, "get", return_value=response):
60 raw_items = instagram._user_reels("creator", token="test-token")
61
62 items = _parse_items(raw_items, "wrapped reel")
63
64 self.assertEqual(1, len(items))
65 self.assertEqual("reel-1017", items[0]["video_id"])
66 self.assertEqual("2026-08-01", items[0]["date"])
67 self.assertEqual("Wrapped reel caption", items[0]["text"])
68 self.assertEqual(
69 {"views": 123, "likes": 45, "comments": 6},
70 items[0]["engagement"],
71 )
72
73
74 class TestInstagramComments(unittest.TestCase):
75 """U1: Instagram comment fetching via ScrapeCreators."""
76
77 def test_fetch_post_comments_parses_and_sorts_by_likes(self):
78 from unittest.mock import patch
79 from lib import instagram
80
81 fake = {
82 "success": True,
83 "comments": [
84 {"text": "mid", "comment_like_count": 3,
85 "created_at": "2026-07-04T14:27:58.000Z", "user": {"username": "bob"}},
86 {"text": "gold", "comment_like_count": 500,
87 "created_at": "2026-07-03T10:00:00.000Z", "user": {"username": "alice"}},
88 {"text": "", "comment_like_count": 999,
89 "created_at": "2026-07-02T10:00:00.000Z", "user": {"username": "skip"}},
90 ],
91 "cursor": None,
92 }
93 with patch.object(instagram.http, "get", return_value=fake):
94 out = instagram._fetch_post_comments(
95 "https://www.instagram.com/reel/ABC/", token="k", max_comments=5,
96 )
97 # Empty-text dropped; sorted desc by comment_like_count.
98 self.assertEqual([c["text"] for c in out], ["gold", "mid"])
99 self.assertEqual(out[0]["comment_like_count"], 500)
100 self.assertEqual(out[0]["author"], "alice")
101 self.assertEqual(out[0]["date"], "2026-07-03")
102
103 def test_fetch_post_comments_error_returns_empty(self):
104 from unittest.mock import patch
105 from lib import instagram
106
107 def _boom(*a, **k):
108 raise RuntimeError("network")
109
110 with patch.object(instagram.http, "get", side_effect=_boom):
111 out = instagram._fetch_post_comments("https://x/", token="k")
112 self.assertEqual(out, [])
113
114 def test_enrich_with_comments_no_token_or_items_noop(self):
115 from lib import instagram
116 self.assertEqual([], instagram.enrich_with_comments([], token="k"))
117 items = [{"url": "u", "engagement": {"likes": 5}}]
118 self.assertEqual(items, instagram.enrich_with_comments(items, token=""))
119 self.assertNotIn("top_comments", items[0])
120
121 def test_is_instagram_comments_available_gate(self):
122 from lib import env
123 self.assertFalse(env.is_instagram_comments_available({}))
124 self.assertFalse(env.is_instagram_comments_available(
125 {"SCRAPECREATORS_API_KEY": "k"})) # key but no INCLUDE_SOURCES
126 self.assertFalse(env.is_instagram_comments_available(
127 {"INCLUDE_SOURCES": "instagram_comments"})) # opt-in but no key
128 self.assertTrue(env.is_instagram_comments_available(
129 {"SCRAPECREATORS_API_KEY": "k", "INCLUDE_SOURCES": "tiktok,instagram_comments"}))
130
131
132 class TestExpandInstagramQueries(unittest.TestCase):
133 """Tests for expand_instagram_queries() multi-query generation."""
134
135 def test_default_depth_returns_two_plus_queries(self):
136 from lib.instagram import expand_instagram_queries
137 queries = expand_instagram_queries("Kanye West", "default")
138 self.assertGreaterEqual(len(queries), 2)
139 # Breaking_news intent should include reaction/edit variant
140 variant_found = any(
141 "reaction" in q.lower() or "edit" in q.lower()
142 for q in queries
143 )
144 self.assertTrue(variant_found, f"Expected reaction/edit variant: {queries}")
145
146 def test_quick_depth_returns_one_query(self):
147 from lib.instagram import expand_instagram_queries
148 queries = expand_instagram_queries("Kanye West", "quick")
149 self.assertEqual(len(queries), 1)
150
151 if __name__ == "__main__":
152 unittest.main()
153
153 lines PYTHON