fix: tighten pagination and platform-specific auth

This commit is contained in:
jackwener
2026-03-11 20:32:51 +08:00
parent 74f06638ee
commit 88a9f4ce97
7 changed files with 139 additions and 13 deletions

View File

@@ -243,6 +243,36 @@ def test_iter_chrome_cookie_files_env_override(monkeypatch, tmp_path) -> None:
assert "Profile 5" in paths[0]
def test_iter_chrome_cookie_files_edge_linux_uses_microsoft_edge_path(monkeypatch, tmp_path) -> None:
monkeypatch.setattr(auth.sys, "platform", "linux")
edge_dir = tmp_path / ".config" / "microsoft-edge"
(edge_dir / "Default").mkdir(parents=True)
(edge_dir / "Default" / "Cookies").touch()
monkeypatch.setenv("HOME", str(tmp_path))
monkeypatch.delenv("TWITTER_CHROME_PROFILE", raising=False)
paths = auth._iter_chrome_cookie_files("edge")
assert len(paths) == 1
assert paths[0].endswith(".config/microsoft-edge/Default/Cookies")
def test_iter_chrome_cookie_files_edge_windows_uses_user_data(monkeypatch, tmp_path) -> None:
monkeypatch.setattr(auth.sys, "platform", "win32")
monkeypatch.setenv("LOCALAPPDATA", str(tmp_path))
monkeypatch.delenv("TWITTER_CHROME_PROFILE", raising=False)
edge_dir = tmp_path / "Microsoft" / "Edge" / "User Data" / "Default"
edge_dir.mkdir(parents=True)
(edge_dir / "Cookies").touch()
paths = auth._iter_chrome_cookie_files("edge")
assert len(paths) == 1
assert "Microsoft/Edge/User Data/Default/Cookies".replace("/", os.sep) in paths[0]
def test_extract_in_process_tries_multiple_profiles(monkeypatch, tmp_path) -> None:
"""When Default has no Twitter cookies but Profile 1 does, it should find them."""
@@ -366,4 +396,3 @@ def test_extract_in_process_returns_diagnostics_on_failure(monkeypatch) -> None:
assert cookies is None
assert any("cookie decryption" in d for d in diagnostics)

View File

@@ -20,6 +20,18 @@ def test_cli_user_command_works_with_client_factory(monkeypatch) -> None:
assert result.exit_code == 0
def test_get_client_for_output_does_not_swallow_real_type_error(monkeypatch) -> None:
def _broken_get_client(config=None, quiet=False):
raise TypeError("real bug")
monkeypatch.setattr("twitter_cli.cli._get_client", _broken_get_client)
with pytest.raises(TypeError, match="real bug"):
from twitter_cli.cli import _get_client_for_output
_get_client_for_output({})
def test_cli_feed_json_input_path(tmp_path, tweet_factory) -> None:
json_path = tmp_path / "tweets.json"
json_path.write_text(tweets_to_json([tweet_factory("1")]), encoding="utf-8")

View File

@@ -276,6 +276,8 @@ class TestBuildHeaders:
assert "sec-ch-ua" in headers
@patch("twitter_cli.client.get_sec_ch_ua_platform", return_value='"Linux"')
@patch("twitter_cli.client.get_sec_ch_ua_platform_version", return_value='""')
@patch("twitter_cli.client.get_sec_ch_ua_arch", return_value='"x86"')
@patch("twitter_cli.client.get_accept_language", return_value="zh-CN,zh;q=0.9,en;q=0.8")
@patch("twitter_cli.client.get_twitter_client_language", return_value="zh")
@patch("twitter_cli.client._get_cffi_session")
@@ -286,6 +288,8 @@ class TestBuildHeaders:
mock_session,
mock_client_language,
mock_accept_language,
mock_arch,
mock_platform_version,
mock_platform,
):
mock_session.return_value = MagicMock()
@@ -307,6 +311,8 @@ class TestBuildHeaders:
assert headers["X-Twitter-Client-Language"] == "zh"
assert headers["Accept-Language"] == "zh-CN,zh;q=0.9,en;q=0.8"
assert headers["sec-ch-ua-platform"] == '"Linux"'
assert headers["sec-ch-ua-arch"] == '"x86"'
assert headers["sec-ch-ua-platform-version"] == '""'
class TestPaginationBehavior:
@@ -356,6 +362,49 @@ class TestPaginationBehavior:
assert tweets == []
assert calls == [None, "cursor-same"]
def test_user_list_continues_when_cursor_advances_without_new_users(self):
client = TwitterClient.__new__(TwitterClient)
client._request_delay = 0.0
client._max_count = 200
responses = iter(
[
{"page": 1},
{"page": 2},
]
)
def _graphql_get(operation_name, variables, features):
return next(responses)
def _parse_user_result(data):
return MagicMock(id=data["id"], screen_name=data["screen_name"])
def _get_instructions(data):
if data["page"] == 1:
return [
{"entries": [{"content": {"entryType": "TimelineTimelineCursor", "cursorType": "Bottom", "value": "cursor-2"}}]}
]
return [
{
"entries": [
{
"content": {
"entryType": "TimelineTimelineItem",
"itemContent": {"user_results": {"result": {"id": "user-1", "screen_name": "alice"}}},
}
}
]
}
]
client._graphql_get = _graphql_get
client._parse_user_result = _parse_user_result
users = client._fetch_user_list("Followers", "1", 1, _get_instructions)
assert [user.screen_name for user in users] == ["alice"]
# ── TwitterClient._parse_tweet_result ─────────────────────────────────────