feat(twitter): migrate from xreach to bird CLI
- Replace xreach CLI with bird (@steipete/bird) as Twitter/X backend - bird uses AUTH_TOKEN/CT0 env vars (simpler than xreach's session.json) - Accept both 'bird' and 'birdx' binary names - Remove version detection logic (bird v0.8.0 is the baseline) - Write credentials.env to ~/.config/bird/ for easy sourcing - Keep xfetch session.json sync for backward compatibility - Update SKILL.md commands: bird search/read/user-tweets/thread - Update install/uninstall to use npm @steipete/bird - All 52 tests pass
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
from unittest.mock import patch, Mock
|
||||
|
||||
from agent_reach.channels.twitter import _detect_xreach_version, TwitterChannel
|
||||
from agent_reach.channels.twitter import TwitterChannel
|
||||
|
||||
|
||||
def _cp(stdout="", stderr="", returncode=0):
|
||||
@@ -13,27 +13,58 @@ def _cp(stdout="", stderr="", returncode=0):
|
||||
return m
|
||||
|
||||
|
||||
def test_detect_xreach_version_prefers_npm_when_cli_version_is_stale():
|
||||
with patch("shutil.which", return_value="/opt/homebrew/bin/npm"), patch(
|
||||
"subprocess.run",
|
||||
side_effect=[
|
||||
_cp(stdout="0.3.0\n"),
|
||||
_cp(stdout='{"dependencies":{"xreach-cli":{"version":"0.3.2"}}}'),
|
||||
],
|
||||
):
|
||||
assert _detect_xreach_version("/opt/homebrew/bin/xreach") == "0.3.2"
|
||||
|
||||
|
||||
def test_twitter_channel_does_not_false_warn_when_npm_has_newer_xreach():
|
||||
def test_check_bird_found_and_auth_ok():
|
||||
"""bird found + bird check returns 0 → ok."""
|
||||
channel = TwitterChannel()
|
||||
with patch("shutil.which", side_effect=lambda name: "/opt/homebrew/bin/xreach" if name == "xreach" else "/opt/homebrew/bin/npm"), patch(
|
||||
with patch("shutil.which", side_effect=lambda name: "/usr/local/bin/bird" if name == "bird" else None), patch(
|
||||
"subprocess.run",
|
||||
side_effect=[
|
||||
_cp(stdout="0.3.0\n"),
|
||||
_cp(stdout='{"dependencies":{"xreach-cli":{"version":"0.3.2"}}}'),
|
||||
_cp(stdout="authenticated\n", returncode=0),
|
||||
],
|
||||
return_value=_cp(stdout="Authenticated as @user\n", returncode=0),
|
||||
):
|
||||
status, message = channel.check()
|
||||
assert status == "ok"
|
||||
assert "完整可用" in message
|
||||
|
||||
|
||||
def test_check_bird_found_auth_missing():
|
||||
"""bird found + bird check returns 1 with 'Missing credentials' → warn about auth."""
|
||||
channel = TwitterChannel()
|
||||
with patch("shutil.which", side_effect=lambda name: "/usr/local/bin/bird" if name == "bird" else None), patch(
|
||||
"subprocess.run",
|
||||
return_value=_cp(stderr="Missing credentials: AUTH_TOKEN and CT0 required\n", returncode=1),
|
||||
):
|
||||
status, message = channel.check()
|
||||
assert status == "warn"
|
||||
assert "未配置认证" in message
|
||||
|
||||
|
||||
def test_check_bird_not_found():
|
||||
"""bird not found → warn with install hint for @steipete/bird."""
|
||||
channel = TwitterChannel()
|
||||
with patch("shutil.which", return_value=None):
|
||||
status, message = channel.check()
|
||||
assert status == "warn"
|
||||
assert "@steipete/bird" in message
|
||||
|
||||
|
||||
def test_check_birdx_binary_accepted():
|
||||
"""birdx symlink is accepted as an alternative binary name."""
|
||||
channel = TwitterChannel()
|
||||
with patch("shutil.which", side_effect=lambda name: "/usr/local/bin/birdx" if name == "birdx" else None), patch(
|
||||
"subprocess.run",
|
||||
return_value=_cp(stdout="Authenticated as @user\n", returncode=0),
|
||||
):
|
||||
status, message = channel.check()
|
||||
assert status == "ok"
|
||||
assert "完整可用" in message
|
||||
|
||||
|
||||
def test_check_bird_auth_failure_generic():
|
||||
"""bird check returns 1 without 'Missing credentials' → generic auth failure warn."""
|
||||
channel = TwitterChannel()
|
||||
with patch("shutil.which", side_effect=lambda name: "/usr/local/bin/bird" if name == "bird" else None), patch(
|
||||
"subprocess.run",
|
||||
return_value=_cp(stderr="Error: token expired\n", returncode=1),
|
||||
):
|
||||
status, message = channel.check()
|
||||
assert status == "warn"
|
||||
assert "认证检查失败" in message
|
||||
|
||||
Reference in New Issue
Block a user