feat: add integration smoke tests

CLI-level smoke tests using --yaml output against real Twitter API.
Default skipped via @pytest.mark.smoke marker + pyproject.toml addopts.
Run locally with: uv run pytest -m smoke -v
This commit is contained in:
jackwener
2026-03-10 22:26:46 +08:00
parent fa6255f2ee
commit 9cf74abd56
13 changed files with 562 additions and 193 deletions

View File

@@ -68,7 +68,7 @@ def test_cli_user_error_yaml(monkeypatch) -> None:
assert result.exit_code == 1
payload = yaml.safe_load(result.output)
assert payload["ok"] is False
assert payload["error"]["code"] == "api_error"
assert payload["error"]["code"] == "not_found"
def test_cli_tweet_accepts_shared_url_with_query(monkeypatch) -> None:
@@ -198,6 +198,62 @@ def test_cli_quote_command(monkeypatch) -> None:
assert calls[0]["text"] == "Interesting!"
def test_cli_post_json_output(monkeypatch) -> None:
class FakeClient:
def create_tweet(self, text: str, reply_to_id=None) -> str:
assert text == "hello"
assert reply_to_id is None
return "999"
monkeypatch.setattr("twitter_cli.cli._get_client", lambda config=None: FakeClient())
runner = CliRunner()
result = runner.invoke(cli, ["post", "hello", "--json"])
assert result.exit_code == 0
payload = yaml.safe_load(result.output)
assert payload["ok"] is True
assert payload["data"]["action"] == "post"
assert payload["data"]["id"] == "999"
def test_cli_like_yaml_output(monkeypatch) -> None:
class FakeClient:
def like_tweet(self, tweet_id: str) -> bool:
assert tweet_id == "123"
return True
monkeypatch.setattr("twitter_cli.cli._get_client", lambda config=None: FakeClient())
runner = CliRunner()
result = runner.invoke(cli, ["like", "123", "--yaml"])
assert result.exit_code == 0
payload = yaml.safe_load(result.output)
assert payload["ok"] is True
assert payload["data"]["action"] == "liking_tweet"
assert payload["data"]["id"] == "123"
def test_cli_follow_json_output(monkeypatch) -> None:
class FakeClient:
def resolve_user_id(self, identifier: str) -> str:
assert identifier == "alice"
return "42"
def follow_user(self, user_id: str) -> bool:
assert user_id == "42"
return True
monkeypatch.setattr("twitter_cli.cli._get_client", lambda config=None: FakeClient())
runner = CliRunner()
result = runner.invoke(cli, ["follow", "alice", "--json"])
assert result.exit_code == 0
payload = yaml.safe_load(result.output)
assert payload["ok"] is True
assert payload["data"]["action"] == "follow"
assert payload["data"]["userId"] == "42"
def test_cli_follow_command(monkeypatch) -> None:
actions = []

85
tests/test_smoke.py Normal file
View File

@@ -0,0 +1,85 @@
"""Integration smoke tests for twitter-cli.
These tests invoke the real CLI commands with ``--yaml`` against the live
Twitter/X API using your local browser cookies. They are **skipped by
default** and only run when explicitly requested::
uv run pytest -m smoke -v
Only read-only operations are tested — no writes.
"""
from __future__ import annotations
import yaml
import pytest
from click.testing import CliRunner
from twitter_cli.cli import cli
smoke = pytest.mark.smoke
runner = CliRunner()
def _invoke(*args: str):
"""Run a CLI command with --yaml and return parsed payload."""
result = runner.invoke(cli, [*args, "--yaml"])
if result.output:
payload = yaml.safe_load(result.output)
else:
payload = None
return result, payload
# ── Auth ────────────────────────────────────────────────────────────────
@smoke
class TestAuth:
"""Verify authentication is working end-to-end."""
def test_status(self):
result, payload = _invoke("status")
assert result.exit_code == 0, f"status failed: {result.output}"
assert payload["ok"] is True
assert payload["data"]["authenticated"] is True
assert payload["data"]["user"]["username"]
def test_whoami(self):
result, payload = _invoke("whoami")
assert result.exit_code == 0, f"whoami failed: {result.output}"
assert payload["ok"] is True
assert payload["data"]["user"]["username"]
assert payload["data"]["user"]["id"]
# ── Read-only queries ───────────────────────────────────────────────────
@smoke
class TestReadOnly:
"""Read-only CLI smoke tests."""
def test_user(self):
result, payload = _invoke("user", "elonmusk")
assert result.exit_code == 0, f"user failed: {result.output}"
assert payload["ok"] is True
def test_search(self):
result, payload = _invoke("search", "python", "--max", "3")
assert result.exit_code == 0, f"search failed: {result.output}"
assert payload["ok"] is True
items = payload["data"]
assert isinstance(items, list)
assert len(items) >= 1
def test_user_posts(self):
result, payload = _invoke("user-posts", "elonmusk", "--max", "3")
assert result.exit_code == 0, f"user-posts failed: {result.output}"
assert payload["ok"] is True
def test_feed(self):
result, payload = _invoke("feed", "--max", "3")
assert result.exit_code == 0, f"feed failed: {result.output}"
assert payload["ok"] is True