Files
twitter-cli-cookiefile/tests/test_output.py
jackwener e496d8f870 refactor: unify exception handling, add ISO 8601 time, dedup commands, expand tests
- Replace _error_code_for_message() string matching with error_code attribute on exception classes
- Add error_code to all TwitterError subclasses (AuthenticationError, RateLimitError, etc.)
- Add InvalidInputError exception class
- TwitterAPIError derives error_code from HTTP status code automatically
- auth.py: use AuthenticationError instead of RuntimeError
- cli.py: catch (TwitterError, RuntimeError) for backward compat
- Extract _fetch_and_display_users() to deduplicate followers/following commands
- Add format_iso8601() to timeutil.py
- Add createdAtISO field to tweet and user profile serialization
- New test files: test_output.py, test_cache.py, test_timeutil.py
- Expand test_filter.py (topN, score mode, custom weights, empty input)
- Tests: 152 → 194 unit tests, all passing
2026-03-16 18:24:35 +08:00

148 lines
5.0 KiB
Python

"""Tests for twitter_cli.output module."""
from __future__ import annotations
import json
import click
import pytest
import yaml
from twitter_cli.output import (
default_structured_format,
emit_structured,
ensure_utf8_streams,
error_payload,
success_payload,
use_rich_output,
)
# ── ensure_utf8_streams ─────────────────────────────────────────────────
def test_ensure_utf8_streams_no_error() -> None:
"""ensure_utf8_streams should not raise on any platform."""
ensure_utf8_streams()
# ── success_payload / error_payload ──────────────────────────────────────
def test_success_payload_structure() -> None:
payload = success_payload({"key": "value"})
assert payload["ok"] is True
assert payload["schema_version"] == "1"
assert payload["data"] == {"key": "value"}
def test_success_payload_with_list() -> None:
payload = success_payload([1, 2, 3])
assert payload["ok"] is True
assert payload["data"] == [1, 2, 3]
def test_error_payload_structure() -> None:
payload = error_payload("not_found", "User not found")
assert payload["ok"] is False
assert payload["schema_version"] == "1"
assert payload["error"]["code"] == "not_found"
assert payload["error"]["message"] == "User not found"
assert "details" not in payload["error"]
def test_error_payload_with_details() -> None:
payload = error_payload("api_error", "oops", details={"id": "123"})
assert payload["error"]["details"] == {"id": "123"}
# ── default_structured_format ────────────────────────────────────────────
def test_format_json_flag() -> None:
assert default_structured_format(as_json=True, as_yaml=False) == "json"
def test_format_yaml_flag() -> None:
assert default_structured_format(as_json=False, as_yaml=True) == "yaml"
def test_format_both_flags_raises() -> None:
with pytest.raises(click.UsageError):
default_structured_format(as_json=True, as_yaml=True)
def test_format_env_yaml(monkeypatch) -> None:
monkeypatch.setenv("OUTPUT", "yaml")
assert default_structured_format(as_json=False, as_yaml=False) == "yaml"
def test_format_env_json(monkeypatch) -> None:
monkeypatch.setenv("OUTPUT", "json")
assert default_structured_format(as_json=False, as_yaml=False) == "json"
def test_format_env_rich(monkeypatch) -> None:
monkeypatch.setenv("OUTPUT", "rich")
assert default_structured_format(as_json=False, as_yaml=False) is None
def test_format_auto_non_tty(monkeypatch) -> None:
monkeypatch.setenv("OUTPUT", "auto")
monkeypatch.setattr("sys.stdout", type("FakeStdout", (), {"isatty": lambda self: False})())
assert default_structured_format(as_json=False, as_yaml=False) == "yaml"
# ── use_rich_output ──────────────────────────────────────────────────────
def test_use_rich_when_no_structured(monkeypatch) -> None:
monkeypatch.setenv("OUTPUT", "rich")
assert use_rich_output(as_json=False, as_yaml=False) is True
def test_no_rich_when_json() -> None:
assert use_rich_output(as_json=True, as_yaml=False) is False
def test_no_rich_when_compact(monkeypatch) -> None:
monkeypatch.setenv("OUTPUT", "rich")
assert use_rich_output(as_json=False, as_yaml=False, compact=True) is False
# ── emit_structured ──────────────────────────────────────────────────────
def test_emit_structured_json(capsys) -> None:
result = emit_structured({"key": "val"}, as_json=True, as_yaml=False)
assert result is True
captured = capsys.readouterr()
parsed = json.loads(captured.out)
assert parsed["ok"] is True
assert parsed["data"]["key"] == "val"
def test_emit_structured_yaml(capsys) -> None:
result = emit_structured({"key": "val"}, as_json=False, as_yaml=True)
assert result is True
captured = capsys.readouterr()
parsed = yaml.safe_load(captured.out)
assert parsed["ok"] is True
assert parsed["data"]["key"] == "val"
def test_emit_structured_returns_false_when_rich(monkeypatch) -> None:
monkeypatch.setenv("OUTPUT", "rich")
result = emit_structured({"key": "val"}, as_json=False, as_yaml=False)
assert result is False
def test_emit_structured_wraps_already_wrapped_payload(capsys) -> None:
"""If data is already in the agent schema, it should not be double-wrapped."""
already_wrapped = {"ok": True, "schema_version": "1", "data": "hello"}
emit_structured(already_wrapped, as_json=True, as_yaml=False)
captured = capsys.readouterr()
parsed = json.loads(captured.out)
assert parsed["data"] == "hello"
assert "data" not in str(parsed.get("data", {}) if isinstance(parsed.get("data"), dict) else "")