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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user