fix(twitter): support both twitter-cli and bird CLI, never uninstall user tools

CRITICAL FIX: update.md was telling agents to uninstall bird CLI,
which broke users who had working bird installations. Now:

- twitter.py: prefers twitter-cli, falls back to bird/birdx if installed
- update.md: removed "clean up deprecated tools" step entirely
- Added explicit rule: "Never uninstall any existing tools the user already has"
- Tests cover twitter-cli primary + bird fallback + preference order

78 tests passing.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Pnant
2026-03-31 23:01:09 +08:00
parent 7acf3c1367
commit e178470320
3 changed files with 94 additions and 35 deletions
+57 -21
View File
@@ -13,22 +13,25 @@ def _cp(stdout="", stderr="", returncode=0):
return m
# --- twitter-cli tests ---
def test_check_twitter_cli_found_and_auth_ok():
"""twitter-cli found + twitter status ok → ok."""
channel = TwitterChannel()
with patch("shutil.which", return_value="/usr/local/bin/twitter"), patch(
with patch("shutil.which", side_effect=lambda name: "/usr/local/bin/twitter" if name == "twitter" else None), patch(
"subprocess.run",
return_value=_cp(stdout="ok: true\nusername: testuser\n", returncode=0),
):
status, message = channel.check()
assert status == "ok"
assert "twitter-cli" in message
assert "完整可用" in message
def test_check_twitter_cli_found_auth_missing():
"""twitter-cli found + not_authenticated → warn about auth."""
channel = TwitterChannel()
with patch("shutil.which", return_value="/usr/local/bin/twitter"), patch(
with patch("shutil.which", side_effect=lambda name: "/usr/local/bin/twitter" if name == "twitter" else None), patch(
"subprocess.run",
return_value=_cp(
stderr="ok: false\nerror:\n code: not_authenticated\n",
@@ -40,8 +43,44 @@ def test_check_twitter_cli_found_auth_missing():
assert "未认证" in message
def test_check_twitter_cli_not_found():
"""twitter-cli not found → warn with install hint."""
# --- bird CLI fallback tests ---
def test_check_bird_fallback_auth_ok():
"""No twitter-cli, but bird found + bird check ok → ok."""
channel = TwitterChannel()
def which_side_effect(name):
if name == "bird":
return "/usr/local/bin/bird"
return None
with patch("shutil.which", side_effect=which_side_effect), patch(
"subprocess.run",
return_value=_cp(stdout="Authenticated as @user\n", returncode=0),
):
status, message = channel.check()
assert status == "ok"
assert "bird" in message
def test_check_bird_fallback_auth_missing():
"""No twitter-cli, bird found but Missing credentials → warn."""
channel = TwitterChannel()
def which_side_effect(name):
if name == "bird":
return "/usr/local/bin/bird"
return None
with patch("shutil.which", side_effect=which_side_effect), patch(
"subprocess.run",
return_value=_cp(stderr="Missing credentials\n", returncode=1),
):
status, message = channel.check()
assert status == "warn"
assert "未配置认证" in message
# --- neither installed ---
def test_check_nothing_installed():
"""Neither twitter-cli nor bird → warn with install hint."""
channel = TwitterChannel()
with patch("shutil.which", return_value=None):
status, message = channel.check()
@@ -49,24 +88,21 @@ def test_check_twitter_cli_not_found():
assert "twitter-cli" in message
def test_check_twitter_cli_generic_failure():
"""twitter status returns 1 without not_authenticated → generic warn."""
# --- twitter-cli preferred over bird ---
def test_twitter_cli_preferred_over_bird():
"""When both are installed, twitter-cli is used."""
channel = TwitterChannel()
with patch("shutil.which", return_value="/usr/local/bin/twitter"), patch(
def which_side_effect(name):
if name == "twitter":
return "/usr/local/bin/twitter"
if name == "bird":
return "/usr/local/bin/bird"
return None
with patch("shutil.which", side_effect=which_side_effect), patch(
"subprocess.run",
return_value=_cp(stderr="some error\n", returncode=1),
return_value=_cp(stdout="ok: true\n", returncode=0),
):
status, message = channel.check()
assert status == "warn"
assert "认证检查失败" in message
def test_check_twitter_cli_exception():
"""twitter status throws exception → warn."""
channel = TwitterChannel()
with patch("shutil.which", return_value="/usr/local/bin/twitter"), patch(
"subprocess.run", side_effect=Exception("timeout"),
):
status, message = channel.check()
assert status == "warn"
assert "连接失败" in message
assert status == "ok"
assert "twitter-cli" in message