fix: P0 Windows Edge path, add time localization, show --output, cleanup tech debt
- 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
This commit is contained in:
@@ -13,16 +13,22 @@ import pytest
|
||||
|
||||
|
||||
from twitter_cli.client import (
|
||||
FEATURES,
|
||||
_best_chrome_target,
|
||||
TwitterClient,
|
||||
)
|
||||
from twitter_cli.exceptions import TwitterAPIError
|
||||
from twitter_cli.graphql import (
|
||||
FEATURES,
|
||||
_build_graphql_url,
|
||||
_update_features_from_html,
|
||||
)
|
||||
from twitter_cli.parser import (
|
||||
_deep_get,
|
||||
_extract_cursor,
|
||||
_extract_media,
|
||||
_parse_int,
|
||||
_update_features_from_html,
|
||||
TwitterAPIError,
|
||||
TwitterClient,
|
||||
parse_tweet_result,
|
||||
parse_user_result,
|
||||
)
|
||||
|
||||
|
||||
@@ -339,9 +345,9 @@ class TestPaginationBehavior:
|
||||
return [MagicMock(id="tweet-1")], None
|
||||
|
||||
client._graphql_get = _graphql_get
|
||||
client._parse_timeline_response = _parse_timeline_response
|
||||
|
||||
tweets = client._fetch_timeline("HomeTimeline", 1, lambda data: data)
|
||||
with patch('twitter_cli.client.parse_timeline_response', side_effect=_parse_timeline_response):
|
||||
tweets = client._fetch_timeline("HomeTimeline", 1, lambda data: data)
|
||||
|
||||
assert [tweet.id for tweet in tweets] == ["tweet-1"]
|
||||
|
||||
@@ -357,9 +363,9 @@ class TestPaginationBehavior:
|
||||
return {"page": len(calls)}
|
||||
|
||||
client._graphql_get = _graphql_get
|
||||
client._parse_timeline_response = lambda data, get_instructions: ([], "cursor-same")
|
||||
|
||||
tweets = client._fetch_timeline("HomeTimeline", 1, lambda data: data)
|
||||
with patch('twitter_cli.client.parse_timeline_response', return_value=([], "cursor-same")):
|
||||
tweets = client._fetch_timeline("HomeTimeline", 1, lambda data: data)
|
||||
|
||||
assert tweets == []
|
||||
assert calls == [None, "cursor-same"]
|
||||
@@ -401,9 +407,9 @@ class TestPaginationBehavior:
|
||||
]
|
||||
|
||||
client._graphql_get = _graphql_get
|
||||
client._parse_user_result = _parse_user_result
|
||||
|
||||
users = client._fetch_user_list("Followers", "1", 1, _get_instructions)
|
||||
with patch('twitter_cli.client.parse_user_result', side_effect=_parse_user_result):
|
||||
users = client._fetch_user_list("Followers", "1", 1, _get_instructions)
|
||||
|
||||
assert [user.screen_name for user in users] == ["alice"]
|
||||
|
||||
@@ -453,7 +459,7 @@ class TestParseTweetResult:
|
||||
client._ct_init_attempted = True
|
||||
client._client_transaction = None
|
||||
|
||||
tweet = client._parse_tweet_result(copy.deepcopy(self.SAMPLE_TWEET_RESULT))
|
||||
tweet = parse_tweet_result(copy.deepcopy(self.SAMPLE_TWEET_RESULT))
|
||||
assert tweet is not None
|
||||
assert tweet.id == "1234567890"
|
||||
assert tweet.text == "Hello world! This is a test tweet."
|
||||
@@ -475,7 +481,7 @@ class TestParseTweetResult:
|
||||
client._client_transaction = None
|
||||
|
||||
result = {"__typename": "TweetTombstone"}
|
||||
assert client._parse_tweet_result(result) is None
|
||||
assert parse_tweet_result(result) is None
|
||||
|
||||
@patch("twitter_cli.client._get_cffi_session")
|
||||
@patch("twitter_cli.client._gen_ct_headers", return_value={})
|
||||
@@ -491,7 +497,7 @@ class TestParseTweetResult:
|
||||
"__typename": "TweetWithVisibilityResults",
|
||||
"tweet": copy.deepcopy(self.SAMPLE_TWEET_RESULT),
|
||||
}
|
||||
tweet = client._parse_tweet_result(wrapped)
|
||||
tweet = parse_tweet_result(wrapped)
|
||||
assert tweet is not None
|
||||
assert tweet.id == "1234567890"
|
||||
|
||||
@@ -505,7 +511,7 @@ class TestParseTweetResult:
|
||||
client._ct_init_attempted = True
|
||||
client._client_transaction = None
|
||||
|
||||
assert client._parse_tweet_result(self.SAMPLE_TWEET_RESULT, depth=3) is None
|
||||
assert parse_tweet_result(self.SAMPLE_TWEET_RESULT, depth=3) is None
|
||||
|
||||
|
||||
# ── TwitterAPIError ──────────────────────────────────────────────────────
|
||||
@@ -523,7 +529,7 @@ class TestTwitterAPIError:
|
||||
|
||||
class TestParseUserResult:
|
||||
def test_coerces_count_fields_to_int(self):
|
||||
user = TwitterClient._parse_user_result(
|
||||
user = parse_user_result(
|
||||
{
|
||||
"rest_id": "user-1",
|
||||
"legacy": {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from twitter_cli.client import TwitterClient, _deep_get
|
||||
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:
|
||||
@@ -15,10 +18,9 @@ def _make_client() -> TwitterClient:
|
||||
|
||||
|
||||
def test_parse_home_timeline_fixture(fixture_loader) -> None:
|
||||
client = _make_client()
|
||||
payload = fixture_loader("home_timeline.json")
|
||||
|
||||
tweets, cursor = client._parse_timeline_response(
|
||||
tweets, cursor = parse_timeline_response(
|
||||
payload,
|
||||
lambda data: _deep_get(data, "data", "home", "home_timeline_urt", "instructions"),
|
||||
)
|
||||
@@ -34,10 +36,9 @@ def test_parse_home_timeline_fixture(fixture_loader) -> None:
|
||||
|
||||
|
||||
def test_parse_tweet_detail_fixture_with_nested_items(fixture_loader) -> None:
|
||||
client = _make_client()
|
||||
payload = fixture_loader("tweet_detail.json")
|
||||
|
||||
tweets, cursor = client._parse_timeline_response(
|
||||
tweets, cursor = parse_timeline_response(
|
||||
payload,
|
||||
lambda data: _deep_get(data, "data", "threaded_conversation_with_injections_v2", "instructions"),
|
||||
)
|
||||
@@ -47,10 +48,9 @@ def test_parse_tweet_detail_fixture_with_nested_items(fixture_loader) -> None:
|
||||
|
||||
|
||||
def test_parse_search_timeline_fixture_with_module_items(fixture_loader) -> None:
|
||||
client = _make_client()
|
||||
payload = fixture_loader("search_timeline.json")
|
||||
|
||||
tweets, cursor = client._parse_timeline_response(
|
||||
tweets, cursor = parse_timeline_response(
|
||||
payload,
|
||||
lambda data: _deep_get(data, "data", "search_by_raw_query", "search_timeline", "timeline", "instructions"),
|
||||
)
|
||||
@@ -62,10 +62,9 @@ def test_parse_search_timeline_fixture_with_module_items(fixture_loader) -> None
|
||||
|
||||
|
||||
def test_parse_list_timeline_fixture_with_visibility_wrapper(fixture_loader) -> None:
|
||||
client = _make_client()
|
||||
payload = fixture_loader("list_timeline.json")
|
||||
|
||||
tweets, cursor = client._parse_timeline_response(
|
||||
tweets, cursor = parse_timeline_response(
|
||||
payload,
|
||||
lambda data: _deep_get(data, "data", "list", "tweets_timeline", "timeline", "instructions"),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user