fix: harden search validation and release v0.7.1
This commit is contained in:
@@ -44,6 +44,26 @@ def test_cli_feed_json_input_path(tmp_path, tweet_factory) -> None:
|
||||
assert '"id": "1"' in result.output
|
||||
|
||||
|
||||
def test_cli_feed_input_accepts_structured_json_envelope(tmp_path, tweet_factory) -> None:
|
||||
json_path = tmp_path / "tweets.json"
|
||||
json_path.write_text(
|
||||
(
|
||||
"{\n"
|
||||
' "ok": true,\n'
|
||||
' "schema_version": "1",\n'
|
||||
' "data": %s\n'
|
||||
"}\n"
|
||||
)
|
||||
% tweets_to_json([tweet_factory("1")]),
|
||||
encoding="utf-8",
|
||||
)
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(cli, ["feed", "--input", str(json_path), "--json"])
|
||||
assert result.exit_code == 0
|
||||
assert '"id": "1"' in result.output
|
||||
|
||||
|
||||
def test_print_tweet_table_truncates_text_by_default(tweet_factory) -> None:
|
||||
long_text = "A" * 140
|
||||
console = Console(record=True, width=400)
|
||||
@@ -489,6 +509,24 @@ def test_cli_search_empty_query_no_options() -> None:
|
||||
assert "Provide a QUERY" in result.output
|
||||
|
||||
|
||||
def test_cli_search_invalid_date_rejected(monkeypatch) -> None:
|
||||
monkeypatch.setattr("twitter_cli.cli._get_client", lambda config=None: None)
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(cli, ["search", "python", "--since", "not-a-date"])
|
||||
assert result.exit_code != 0
|
||||
assert "--since must be in YYYY-MM-DD format" in result.output
|
||||
|
||||
|
||||
def test_cli_search_rejects_reversed_date_range(monkeypatch) -> None:
|
||||
monkeypatch.setattr("twitter_cli.cli._get_client", lambda config=None: None)
|
||||
runner = CliRunner()
|
||||
|
||||
result = runner.invoke(cli, ["search", "python", "--since", "2026-03-02", "--until", "2026-03-01"])
|
||||
assert result.exit_code != 0
|
||||
assert "--since must be on or before --until" in result.output
|
||||
|
||||
|
||||
def test_cli_compact_mode(tmp_path, tweet_factory) -> None:
|
||||
json_path = tmp_path / "tweets.json"
|
||||
json_path.write_text(tweets_to_json([tweet_factory("1")]), encoding="utf-8")
|
||||
|
||||
@@ -24,6 +24,9 @@ class TestBuildSearchQuery:
|
||||
def test_lang(self) -> None:
|
||||
assert build_search_query("news", lang="fr") == "news lang:fr"
|
||||
|
||||
def test_lang_is_trimmed_and_lowercased(self) -> None:
|
||||
assert build_search_query("news", lang=" EN ") == "news lang:en"
|
||||
|
||||
def test_since(self) -> None:
|
||||
assert build_search_query("python", since="2026-01-01") == "python since:2026-01-01"
|
||||
|
||||
@@ -76,6 +79,30 @@ class TestBuildSearchQuery:
|
||||
result = build_search_query("", from_user="elonmusk", since="2026-03-01")
|
||||
assert result == "from:elonmusk since:2026-03-01"
|
||||
|
||||
def test_date_range_rejects_reversed_order(self) -> None:
|
||||
try:
|
||||
build_search_query("python", since="2026-03-02", until="2026-03-01")
|
||||
except ValueError as exc:
|
||||
assert "--since must be on or before --until" in str(exc)
|
||||
else:
|
||||
raise AssertionError("Expected ValueError for reversed date range")
|
||||
|
||||
def test_invalid_since_rejected(self) -> None:
|
||||
try:
|
||||
build_search_query("python", since="not-a-date")
|
||||
except ValueError as exc:
|
||||
assert "--since must be in YYYY-MM-DD format" in str(exc)
|
||||
else:
|
||||
raise AssertionError("Expected ValueError for invalid since date")
|
||||
|
||||
def test_negative_min_likes_rejected(self) -> None:
|
||||
try:
|
||||
build_search_query("python", min_likes=-1)
|
||||
except ValueError as exc:
|
||||
assert "--min-likes must be greater than or equal to 0" in str(exc)
|
||||
else:
|
||||
raise AssertionError("Expected ValueError for negative min_likes")
|
||||
|
||||
def test_whitespace_query_trimmed(self) -> None:
|
||||
assert build_search_query(" python ", lang="en") == "python lang:en"
|
||||
|
||||
|
||||
@@ -22,6 +22,21 @@ def test_tweets_json_roundtrip(tweet_factory) -> None:
|
||||
assert restored[1].lang == "zh"
|
||||
|
||||
|
||||
def test_tweets_from_json_accepts_structured_success_envelope(tweet_factory) -> None:
|
||||
tweets = [tweet_factory("1")]
|
||||
raw = (
|
||||
"{\n"
|
||||
' "ok": true,\n'
|
||||
' "schema_version": "1",\n'
|
||||
' "data": %s\n'
|
||||
"}\n"
|
||||
) % tweets_to_json(tweets)
|
||||
|
||||
restored = tweets_from_json(raw)
|
||||
|
||||
assert [tweet.id for tweet in restored] == ["1"]
|
||||
|
||||
|
||||
def test_compact_serialization(tweet_factory) -> None:
|
||||
from twitter_cli.serialization import tweet_to_compact_dict, tweets_to_compact_json
|
||||
import json
|
||||
|
||||
Reference in New Issue
Block a user