- Fix auth.py subprocess script Windows Edge cookie path inconsistency - Add timeutil.py for UTC→local time and relative time conversion - Integrate time localization into formatter.py and serialization.py - Add --output/-o option to show command for saving tweet detail as JSON - Remove constants.py legacy aliases (USER_AGENT, SEC_CH_UA) - Remove client.py backward-compat delegation methods and re-exports - Update test imports to use parser module directly
93 lines
3.0 KiB
Python
93 lines
3.0 KiB
Python
from __future__ import annotations
|
|
|
|
from unittest.mock import patch
|
|
|
|
from twitter_cli.client import TwitterClient
|
|
from twitter_cli.parser import _deep_get, parse_timeline_response
|
|
|
|
|
|
def _make_client() -> TwitterClient:
|
|
client = TwitterClient.__new__(TwitterClient)
|
|
client._ct_init_attempted = True
|
|
client._client_transaction = None
|
|
client._request_delay = 0.0
|
|
client._max_retries = 0
|
|
client._retry_base_delay = 0.0
|
|
client._max_count = 200
|
|
return client
|
|
|
|
|
|
def test_parse_home_timeline_fixture(fixture_loader) -> None:
|
|
payload = fixture_loader("home_timeline.json")
|
|
|
|
tweets, cursor = parse_timeline_response(
|
|
payload,
|
|
lambda data: _deep_get(data, "data", "home", "home_timeline_urt", "instructions"),
|
|
)
|
|
|
|
assert [tweet.id for tweet in tweets] == ["1", "20"]
|
|
assert cursor == "cursor-bottom-1"
|
|
assert tweets[0].media[0].type == "photo"
|
|
assert tweets[0].urls == ["https://example.com/post"]
|
|
assert tweets[1].is_retweet is True
|
|
assert tweets[1].retweeted_by == "bob"
|
|
assert tweets[1].quoted_tweet is not None
|
|
assert tweets[1].quoted_tweet.id == "30"
|
|
|
|
|
|
def test_parse_tweet_detail_fixture_with_nested_items(fixture_loader) -> None:
|
|
payload = fixture_loader("tweet_detail.json")
|
|
|
|
tweets, cursor = parse_timeline_response(
|
|
payload,
|
|
lambda data: _deep_get(data, "data", "threaded_conversation_with_injections_v2", "instructions"),
|
|
)
|
|
|
|
assert [tweet.id for tweet in tweets] == ["100", "101"]
|
|
assert cursor == "conversation-cursor"
|
|
|
|
|
|
def test_parse_search_timeline_fixture_with_module_items(fixture_loader) -> None:
|
|
payload = fixture_loader("search_timeline.json")
|
|
|
|
tweets, cursor = parse_timeline_response(
|
|
payload,
|
|
lambda data: _deep_get(data, "data", "search_by_raw_query", "search_timeline", "timeline", "instructions"),
|
|
)
|
|
|
|
assert [tweet.id for tweet in tweets] == ["500"]
|
|
assert cursor == "search-cursor"
|
|
assert tweets[0].media[0].type == "video"
|
|
assert tweets[0].media[0].url == "https://video-high.mp4"
|
|
|
|
|
|
def test_parse_list_timeline_fixture_with_visibility_wrapper(fixture_loader) -> None:
|
|
payload = fixture_loader("list_timeline.json")
|
|
|
|
tweets, cursor = parse_timeline_response(
|
|
payload,
|
|
lambda data: _deep_get(data, "data", "list", "tweets_timeline", "timeline", "instructions"),
|
|
)
|
|
|
|
assert [tweet.id for tweet in tweets] == ["700"]
|
|
assert cursor == "list-cursor"
|
|
assert tweets[0].author.verified is True
|
|
assert tweets[0].lang == "zh"
|
|
|
|
|
|
def test_fetch_user_list_with_fixture(monkeypatch, fixture_loader) -> None:
|
|
client = _make_client()
|
|
payload = fixture_loader("followers_page.json")
|
|
monkeypatch.setattr(client, "_graphql_get", lambda operation_name, variables, features: payload)
|
|
|
|
users = client._fetch_user_list(
|
|
"Followers",
|
|
"user-id",
|
|
20,
|
|
lambda data: _deep_get(data, "data", "user", "result", "timeline", "timeline", "instructions"),
|
|
)
|
|
|
|
assert len(users) == 1
|
|
assert users[0].screen_name == "follower1"
|
|
assert users[0].verified is True
|