|
9 | 9 |
|
10 | 10 | import pytest |
11 | 11 |
|
12 | | -from xclient import XTimelineClient |
| 12 | +from xclient import XTimelineClient, expand_tco_urls |
13 | 13 |
|
14 | 14 | # --------------------------------------------------------------------------- |
15 | 15 | # Helpers |
@@ -131,6 +131,22 @@ def _wrap_user(name: str, screen_name: str, image_url: str = "") -> dict: |
131 | 131 | }, |
132 | 132 | } |
133 | 133 |
|
| 134 | +TWEET_WITH_METRICS: dict = { |
| 135 | + "__typename": "Tweet", |
| 136 | + "rest_id": "2039000000000000003", |
| 137 | + "core": _wrap_user("Someone", "someone"), |
| 138 | + "views": {"count": "24248", "state": "Enabled"}, |
| 139 | + "legacy": { |
| 140 | + "id_str": "2039000000000000003", |
| 141 | + "full_text": "Some tweet", |
| 142 | + "created_at": "Wed Apr 01 19:15:49 +0000 2026", |
| 143 | + "favorite_count": 120, |
| 144 | + "retweet_count": 45, |
| 145 | + "reply_count": 10, |
| 146 | + "entities": {"hashtags": [], "symbols": []}, |
| 147 | + }, |
| 148 | +} |
| 149 | + |
134 | 150 | TWEET_WITH_TICKERS_AND_HASHTAGS: dict = { |
135 | 151 | "__typename": "Tweet", |
136 | 152 | "rest_id": "2039000000000000002", |
@@ -297,6 +313,100 @@ def test_media_types_mirror_media(self, client): |
297 | 313 | # Tickers and hashtags |
298 | 314 | # --------------------------------------------------------------------------- |
299 | 315 |
|
| 316 | +class TestMetrics: |
| 317 | + def test_created_at_parsed_to_iso(self, client): |
| 318 | + t = _parse(client, TWEET_WITH_METRICS) |
| 319 | + assert t.created_at == "2026-04-01T19:15:49Z" |
| 320 | + |
| 321 | + def test_likes(self, client): |
| 322 | + t = _parse(client, TWEET_WITH_METRICS) |
| 323 | + assert t.likes == 120 |
| 324 | + |
| 325 | + def test_retweets(self, client): |
| 326 | + t = _parse(client, TWEET_WITH_METRICS) |
| 327 | + assert t.retweets == 45 |
| 328 | + |
| 329 | + def test_replies(self, client): |
| 330 | + t = _parse(client, TWEET_WITH_METRICS) |
| 331 | + assert t.replies == 10 |
| 332 | + |
| 333 | + def test_views(self, client): |
| 334 | + t = _parse(client, TWEET_WITH_METRICS) |
| 335 | + assert t.views == 24248 |
| 336 | + |
| 337 | + def test_missing_metrics_default_to_zero(self, client): |
| 338 | + t = _parse(client, PLAIN_TWEET) |
| 339 | + assert t.likes == 0 |
| 340 | + assert t.retweets == 0 |
| 341 | + assert t.views == 0 |
| 342 | + |
| 343 | + def test_missing_created_at_defaults_to_empty(self, client): |
| 344 | + t = _parse(client, PLAIN_TWEET) |
| 345 | + assert t.created_at == "" |
| 346 | + |
| 347 | + |
| 348 | +TWEET_WITH_URL: dict = { |
| 349 | + "__typename": "Tweet", |
| 350 | + "rest_id": "2039000000000000004", |
| 351 | + "core": _wrap_user("FlappyBert", "flappybert"), |
| 352 | + "legacy": { |
| 353 | + "id_str": "2039000000000000004", |
| 354 | + "full_text": "Play now 👉 https://t.co/tOjx6u4o0o", |
| 355 | + "entities": { |
| 356 | + "hashtags": [], |
| 357 | + "symbols": [], |
| 358 | + "urls": [ |
| 359 | + { |
| 360 | + "url": "https://t.co/tOjx6u4o0o", |
| 361 | + "expanded_url": "http://t.me/FlappyBertBot", |
| 362 | + "display_url": "t.me/FlappyBertBot", |
| 363 | + } |
| 364 | + ], |
| 365 | + }, |
| 366 | + }, |
| 367 | +} |
| 368 | + |
| 369 | +TWEET_WITH_MEDIA_URL: dict = { |
| 370 | + "__typename": "Tweet", |
| 371 | + "rest_id": "2039000000000000005", |
| 372 | + "core": _wrap_user("Someone", "someone"), |
| 373 | + "legacy": { |
| 374 | + "id_str": "2039000000000000005", |
| 375 | + "full_text": "Check this out https://t.co/medialink", |
| 376 | + # media t.co links are NOT in entities.urls — only in entities.media |
| 377 | + "entities": {"hashtags": [], "symbols": [], "urls": []}, |
| 378 | + "extended_entities": { |
| 379 | + "media": [ |
| 380 | + {"media_url_https": "https://pbs.twimg.com/media/photo.jpg", "type": "photo"} |
| 381 | + ] |
| 382 | + }, |
| 383 | + }, |
| 384 | +} |
| 385 | + |
| 386 | + |
| 387 | +class TestUrlExpansion: |
| 388 | + def test_tco_replaced_with_expanded(self, client): |
| 389 | + t = _parse(client, TWEET_WITH_URL) |
| 390 | + assert "http://t.me/FlappyBertBot" in t.text |
| 391 | + assert "t.co" not in t.text |
| 392 | + |
| 393 | + def test_media_tco_still_stripped(self, client): |
| 394 | + # media t.co has no entities.urls entry, so strip_trailing_tco removes it |
| 395 | + t = _parse(client, TWEET_WITH_MEDIA_URL) |
| 396 | + assert "t.co" not in t.text |
| 397 | + |
| 398 | + def test_expand_tco_urls_replaces_all_occurrences(self): |
| 399 | + entities = [{"url": "https://t.co/abc", "expanded_url": "https://example.com"}] |
| 400 | + result = expand_tco_urls("see https://t.co/abc and https://t.co/abc", entities) |
| 401 | + assert result == "see https://example.com and https://example.com" |
| 402 | + |
| 403 | + def test_expand_tco_urls_ignores_missing_fields(self): |
| 404 | + entities = [{"url": "", "expanded_url": "https://example.com"}, {"url": "https://t.co/abc"}] |
| 405 | + # should not raise, text unchanged for invalid entries |
| 406 | + result = expand_tco_urls("https://t.co/abc", entities) |
| 407 | + assert result == "https://t.co/abc" |
| 408 | + |
| 409 | + |
300 | 410 | class TestEntities: |
301 | 411 | def test_ticker_uppercased(self, client): |
302 | 412 | t = _parse(client, TWEET_WITH_TICKERS_AND_HASHTAGS) |
|
0 commit comments