返回 last30days-skill
test_truthsocial.py
根目录 / tests / test_truthsocial.py
1 """Tests for Truth Social source module."""
2 import unittest
3 from unittest.mock import patch, MagicMock
4
5 from lib import truthsocial
6
7
8 class TestStripHtml(unittest.TestCase):
9 """Test HTML tag stripping."""
10
11 def test_basic_paragraph(self):
12 self.assertEqual(truthsocial._strip_html("<p>Hello world</p>"), "Hello world")
13
14 def test_br_tags(self):
15 self.assertEqual(truthsocial._strip_html("Line 1<br>Line 2"), "Line 1\nLine 2")
16 self.assertEqual(truthsocial._strip_html("Line 1<br/>Line 2"), "Line 1\nLine 2")
17 self.assertEqual(truthsocial._strip_html("Line 1<br />Line 2"), "Line 1\nLine 2")
18
19 def test_nested_tags(self):
20 self.assertEqual(truthsocial._strip_html("<p>Hello <a href='#'>world</a></p>"), "Hello world")
21
22 def test_empty_string(self):
23 self.assertEqual(truthsocial._strip_html(""), "")
24
25 def test_no_tags(self):
26 self.assertEqual(truthsocial._strip_html("plain text"), "plain text")
27
28 def test_entities_preserved(self):
29 self.assertEqual(truthsocial._strip_html("<p>&amp; test</p>"), "&amp; test")
30
31
32 class TestExtractCoreSubject(unittest.TestCase):
33 """Test query preprocessing."""
34
35 def test_strips_question_prefix(self):
36 self.assertEqual(truthsocial._extract_core_subject("what are people saying about tariffs"), "tariffs")
37
38 def test_strips_noise_words(self):
39 self.assertEqual(truthsocial._extract_core_subject("latest trending crypto news"), "crypto")
40
41 def test_preserves_core_topic(self):
42 self.assertEqual(truthsocial._extract_core_subject("tariffs"), "tariffs")
43
44 def test_strips_trailing_punctuation(self):
45 self.assertEqual(truthsocial._extract_core_subject("what is bitcoin?"), "bitcoin")
46
47
48 class TestParseDate(unittest.TestCase):
49 """Test date parsing from Mastodon status."""
50
51 def test_iso_date(self):
52 self.assertEqual(truthsocial._parse_date({"created_at": "2026-03-09T12:00:00.000Z"}), "2026-03-09")
53
54 def test_missing_date(self):
55 self.assertIsNone(truthsocial._parse_date({}))
56
57 def test_short_date(self):
58 self.assertIsNone(truthsocial._parse_date({"created_at": "short"}))
59
60 def test_none_value(self):
61 self.assertIsNone(truthsocial._parse_date({"created_at": None}))
62
63
64 class TestDepthConfig(unittest.TestCase):
65 """Test depth configuration."""
66
67 def test_all_depths_exist(self):
68 self.assertIn("quick", truthsocial.DEPTH_CONFIG)
69 self.assertIn("default", truthsocial.DEPTH_CONFIG)
70 self.assertIn("deep", truthsocial.DEPTH_CONFIG)
71
72 def test_depth_ordering(self):
73 self.assertLess(truthsocial.DEPTH_CONFIG["quick"], truthsocial.DEPTH_CONFIG["default"])
74 self.assertLess(truthsocial.DEPTH_CONFIG["default"], truthsocial.DEPTH_CONFIG["deep"])
75
76
77 class TestSearchTruthSocial(unittest.TestCase):
78 """Test search function auth handling."""
79
80 def test_no_config_returns_error(self):
81 result = truthsocial.search_truthsocial("test", "2026-02-09", "2026-03-09")
82 self.assertEqual(result["statuses"], [])
83 self.assertIn("not configured", result["error"])
84
85 def test_empty_token_returns_error(self):
86 result = truthsocial.search_truthsocial(
87 "test", "2026-02-09", "2026-03-09",
88 config={"TRUTHSOCIAL_TOKEN": ""},
89 )
90 self.assertEqual(result["statuses"], [])
91 self.assertIn("not configured", result["error"])
92
93 @patch("lib.truthsocial.http.request")
94 def test_search_sends_browser_like_headers(self, mock_request):
95 """Regression for #909: Cloudflare 403s the skill's default
96 User-Agent regardless of token validity; the request must include
97 browser-like headers alongside Authorization."""
98 from lib import http as http_module
99 mock_request.return_value = {"statuses": []}
100 truthsocial.search_truthsocial(
101 "health policy", "2026-02-09", "2026-03-09",
102 config={"TRUTHSOCIAL_TOKEN": "valid_token"},
103 )
104 _, kwargs = mock_request.call_args
105 headers = kwargs["headers"]
106 self.assertEqual(headers["Authorization"], "Bearer valid_token")
107 self.assertEqual(headers["User-Agent"], http_module.BROWSER_USER_AGENT)
108 self.assertIn("Accept", headers)
109 self.assertIn("Accept-Language", headers)
110 self.assertEqual(headers["Referer"], "https://truthsocial.com/")
111
112 @patch("lib.truthsocial.http.request")
113 def test_401_returns_token_expired(self, mock_request):
114 from lib.http import HTTPError
115 mock_request.side_effect = HTTPError("Unauthorized", status_code=401)
116 result = truthsocial.search_truthsocial(
117 "test", "2026-02-09", "2026-03-09",
118 config={"TRUTHSOCIAL_TOKEN": "expired_token"},
119 )
120 self.assertEqual(result["statuses"], [])
121 self.assertIn("expired", result["error"])
122
123 @patch("lib.truthsocial.http.request")
124 def test_403_returns_access_denied(self, mock_request):
125 from lib.http import HTTPError
126 mock_request.side_effect = HTTPError("Forbidden", status_code=403)
127 result = truthsocial.search_truthsocial(
128 "test", "2026-02-09", "2026-03-09",
129 config={"TRUTHSOCIAL_TOKEN": "blocked_token"},
130 )
131 self.assertEqual(result["statuses"], [])
132 self.assertIn("Cloudflare", result["error"])
133
134 @patch("lib.truthsocial.http.request")
135 def test_429_returns_rate_limited(self, mock_request):
136 from lib.http import HTTPError
137 mock_request.side_effect = HTTPError("Too Many Requests", status_code=429)
138 result = truthsocial.search_truthsocial(
139 "test", "2026-02-09", "2026-03-09",
140 config={"TRUTHSOCIAL_TOKEN": "rate_limited_token"},
141 )
142 self.assertEqual(result["statuses"], [])
143 self.assertIn("rate limited", result["error"])
144
145 @patch("lib.truthsocial.http.request")
146 def test_successful_search(self, mock_request):
147 mock_request.return_value = {
148 "statuses": [
149 {
150 "content": "<p>Test post about tariffs</p>",
151 "created_at": "2026-03-09T12:00:00.000Z",
152 "url": "https://truthsocial.com/@user/123",
153 "account": {"acct": "user", "display_name": "Test User"},
154 "favourites_count": 10,
155 "reblogs_count": 5,
156 "replies_count": 3,
157 }
158 ]
159 }
160 result = truthsocial.search_truthsocial(
161 "tariffs", "2026-02-09", "2026-03-09",
162 config={"TRUTHSOCIAL_TOKEN": "valid_token"},
163 )
164 self.assertEqual(len(result["statuses"]), 1)
165 # Verify bearer token was passed
166 call_args = mock_request.call_args
167 self.assertEqual(call_args[1]["headers"]["Authorization"], "Bearer valid_token")
168
169
170 class TestParseTruthSocialResponse(unittest.TestCase):
171 """Test response parsing."""
172
173 def test_basic_post(self):
174 response = {
175 "statuses": [
176 {
177 "content": "<p>Hello from Truth Social</p>",
178 "created_at": "2026-03-09T12:00:00.000Z",
179 "url": "https://truthsocial.com/@testuser/456",
180 "account": {"acct": "testuser", "display_name": "Test User"},
181 "favourites_count": 100,
182 "reblogs_count": 50,
183 "replies_count": 25,
184 }
185 ]
186 }
187 items = truthsocial.parse_truthsocial_response(response)
188 self.assertEqual(len(items), 1)
189 item = items[0]
190 self.assertEqual(item["handle"], "testuser")
191 self.assertEqual(item["display_name"], "Test User")
192 self.assertEqual(item["text"], "Hello from Truth Social")
193 self.assertEqual(item["url"], "https://truthsocial.com/@testuser/456")
194 self.assertEqual(item["date"], "2026-03-09")
195 self.assertEqual(item["engagement"]["likes"], 100)
196 self.assertEqual(item["engagement"]["reposts"], 50)
197 self.assertEqual(item["engagement"]["replies"], 25)
198 self.assertGreater(item["relevance"], 0)
199
200 def test_empty_response(self):
201 items = truthsocial.parse_truthsocial_response({"statuses": []})
202 self.assertEqual(items, [])
203
204 def test_missing_fields(self):
205 response = {
206 "statuses": [
207 {
208 "content": "",
209 "account": {},
210 }
211 ]
212 }
213 items = truthsocial.parse_truthsocial_response(response)
214 self.assertEqual(len(items), 1)
215 self.assertEqual(items[0]["handle"], "")
216 self.assertEqual(items[0]["text"], "")
217 self.assertEqual(items[0]["engagement"]["likes"], 0)
218
219 def test_relevance_ordering(self):
220 response = {
221 "statuses": [
222 {"content": "<p>First</p>", "account": {"acct": "a"}, "favourites_count": 10, "reblogs_count": 0, "replies_count": 0},
223 {"content": "<p>Second</p>", "account": {"acct": "b"}, "favourites_count": 5, "reblogs_count": 0, "replies_count": 0},
224 {"content": "<p>Third</p>", "account": {"acct": "c"}, "favourites_count": 1, "reblogs_count": 0, "replies_count": 0},
225 ]
226 }
227 items = truthsocial.parse_truthsocial_response(response)
228 self.assertGreaterEqual(items[0]["relevance"], items[1]["relevance"])
229 self.assertGreaterEqual(items[1]["relevance"], items[2]["relevance"])
230
231 def test_html_stripping_in_parse(self):
232 response = {
233 "statuses": [
234 {
235 "content": "<p>Hello <a href='https://example.com'>@user</a> check this out<br/>New line</p>",
236 "account": {"acct": "poster"},
237 "favourites_count": 0,
238 "reblogs_count": 0,
239 "replies_count": 0,
240 }
241 ]
242 }
243 items = truthsocial.parse_truthsocial_response(response)
244 self.assertNotIn("<", items[0]["text"])
245 self.assertNotIn(">", items[0]["text"])
246
247 if __name__ == "__main__":
248 unittest.main()
249
249 lines PYTHON