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
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
"""Tests for twitter_cli.filter module."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from twitter_cli.filter import filter_tweets
|
||||
from twitter_cli.filter import filter_tweets, score_tweet
|
||||
|
||||
|
||||
def test_filter_tweets_does_not_mutate_input(tweet_factory) -> None:
|
||||
@@ -29,3 +31,58 @@ def test_filter_tweets_applies_language_and_retweet_filters(tweet_factory) -> No
|
||||
)
|
||||
|
||||
assert [tweet.id for tweet in output] == ["1"]
|
||||
|
||||
|
||||
def test_filter_topn_mode(tweet_factory) -> None:
|
||||
tweets = [tweet_factory(str(i)) for i in range(10)]
|
||||
output = filter_tweets(tweets, {"mode": "topN", "topN": 3})
|
||||
assert len(output) == 3
|
||||
|
||||
|
||||
def test_filter_topn_default(tweet_factory) -> None:
|
||||
"""Default topN is 20, so 5 tweets should all be returned."""
|
||||
tweets = [tweet_factory(str(i)) for i in range(5)]
|
||||
output = filter_tweets(tweets, {"mode": "topN"})
|
||||
assert len(output) == 5
|
||||
|
||||
|
||||
def test_filter_score_mode(tweet_factory) -> None:
|
||||
from twitter_cli.models import Metrics
|
||||
|
||||
tweets = [
|
||||
tweet_factory("high", metrics=Metrics(likes=1000, retweets=500, replies=200, views=100000, bookmarks=50)),
|
||||
tweet_factory("low", metrics=Metrics(likes=0, retweets=0, replies=0, views=1, bookmarks=0)),
|
||||
]
|
||||
output = filter_tweets(tweets, {"mode": "score", "minScore": 100.0})
|
||||
assert len(output) == 1
|
||||
assert output[0].id == "high"
|
||||
|
||||
|
||||
def test_filter_empty_input() -> None:
|
||||
output = filter_tweets([], {"mode": "all"})
|
||||
assert output == []
|
||||
|
||||
|
||||
def test_score_tweet_basic(tweet_factory) -> None:
|
||||
tweet = tweet_factory("1")
|
||||
score = score_tweet(tweet)
|
||||
assert isinstance(score, float)
|
||||
assert score > 0
|
||||
|
||||
|
||||
def test_score_tweet_custom_weights(tweet_factory) -> None:
|
||||
tweet = tweet_factory("1")
|
||||
weights = {"likes": 0, "retweets": 0, "replies": 0, "bookmarks": 0, "views_log": 0}
|
||||
assert score_tweet(tweet, weights) == 0.0
|
||||
|
||||
|
||||
def test_filter_all_mode_sorts_by_score(tweet_factory) -> None:
|
||||
from twitter_cli.models import Metrics
|
||||
|
||||
tweets = [
|
||||
tweet_factory("low", metrics=Metrics(likes=1)),
|
||||
tweet_factory("high", metrics=Metrics(likes=100)),
|
||||
]
|
||||
output = filter_tweets(tweets, {"mode": "all"})
|
||||
assert output[0].id == "high"
|
||||
assert output[1].id == "low"
|
||||
|
||||
Reference in New Issue
Block a user