diff --git a/tests/test_blacklist.py b/tests/test_blacklist.py index 62d1af5..35ae1e2 100644 --- a/tests/test_blacklist.py +++ b/tests/test_blacklist.py @@ -2,7 +2,9 @@ from __future__ import annotations import tempfile from pathlib import Path +from types import SimpleNamespace from typing import TYPE_CHECKING +from typing import cast from reader import Entry from reader import Feed @@ -12,6 +14,7 @@ from reader import make_reader from discord_rss_bot.filter.blacklist import entry_should_be_skipped from discord_rss_bot.filter.blacklist import feed_has_blacklist_tags from discord_rss_bot.filter.evaluator import evaluate_entry_filters +from discord_rss_bot.filter.evaluator import get_entry_fields from discord_rss_bot.filter.evaluator import get_filter_values_from_reader if TYPE_CHECKING: @@ -67,6 +70,23 @@ def check_if_has_tag(reader: Reader, feed: Feed, blacklist_name: str) -> None: assert feed_has_blacklist_tags(reader=reader, feed=feed) is False, asset_msg +def test_get_entry_fields_uses_authors_str() -> None: + entry = cast( + "Entry", + SimpleNamespace( + title="Title", + summary="Summary", + content=[], + author="Legacy Author", + authors_str="Author One, Author Two", + ), + ) + + fields: dict[str, str] = get_entry_fields(entry) + + assert fields["author"] == "Author One, Author Two" + + def test_should_be_skipped() -> None: reader: Reader = get_reader() diff --git a/tests/test_custom_message.py b/tests/test_custom_message.py index 7346efa..38cf649 100644 --- a/tests/test_custom_message.py +++ b/tests/test_custom_message.py @@ -188,6 +188,41 @@ def test_replace_tags_in_text_message_skips_non_string_replacement_values( assert rendered == "{{entry_id}}" +@patch("discord_rss_bot.custom_message.get_custom_message") +def test_replace_tags_in_text_message_uses_authors_str(mock_get_custom_message: MagicMock) -> None: + mock_get_custom_message.return_value = "{{feed_author}} | {{entry_author}}" + entry_ns: SimpleNamespace = make_entry("
Summary
") + entry_ns.feed.author = "Legacy Feed Author" + entry_ns.feed.authors_str = "Feed Author One, Feed Author Two" + entry_ns.author = "Legacy Entry Author" + entry_ns.authors_str = "Entry Author One, Entry Author Two" + + rendered: str = replace_tags_in_text_message( + typing.cast("Entry", entry_ns), + reader=MagicMock(), + ) + + assert rendered == "Feed Author One, Feed Author Two | Entry Author One, Entry Author Two" + + +@patch("discord_rss_bot.custom_message.get_embed") +def test_replace_tags_in_embed_uses_authors_str(mock_get_embed: MagicMock) -> None: + mock_get_embed.return_value = CustomEmbed(description="{{feed_author}} | {{entry_author}}") + entry_ns: SimpleNamespace = make_entry("Summary
") + entry_ns.feed.author = "Legacy Feed Author" + entry_ns.feed.authors_str = "Feed Author One, Feed Author Two" + entry_ns.author = "Legacy Entry Author" + entry_ns.authors_str = "Entry Author One, Entry Author Two" + + embed: CustomEmbed = replace_tags_in_embed( + entry_ns.feed, + typing.cast("Entry", entry_ns), + reader=MagicMock(), + ) + + assert embed.description == "Feed Author One, Feed Author Two | Entry Author One, Entry Author Two" + + def test_get_first_image_prefers_content_image_over_summary_image() -> None: summary = '

Massive MMO news update
", - author="Blizzard", - authors_str="Blizzard", + author="Legacy Blizzard Author", + authors_str="Blizzard Author One, Blizzard Author Two", link="https://example.com/wow-1", published=datetime(2024, 1, 1, tzinfo=UTC), content=[DummyContent("The expansion launches soon.
")], @@ -464,10 +465,90 @@ def test_blacklist_preview_shows_labeled_field_values_for_substring_match() -> N assert 'orld' in response.text assert "Massive MMO news update" in response.text assert "The expansion launches soon." in response.text + assert "By Blizzard Author One, Blizzard Author Two |" in response.text + assert "Legacy Blizzard Author" not in response.text finally: app.dependency_overrides = {} +def test_author_templates_render_authors_str() -> None: + request = SimpleNamespace(url="https://example.com/page", base_url="https://example.com/") + feed = SimpleNamespace( + title="Example Feed", + url="https://example.com/feed.xml", + author="Legacy Feed Author", + authors_str="Feed Author One, Feed Author Two", + added=None, + last_exception=None, + last_updated=None, + link="https://example.com/feed", + subtitle="", + updated=None, + updates_enabled=True, + user_title="", + version="atom10", + ) + entry = SimpleNamespace( + id="entry-1", + title="Entry Title", + link="https://example.com/entry-1", + author="Legacy Entry Author", + authors_str="Entry Author One, Entry Author Two", + added=None, + content=[], + important=False, + published=None, + read=False, + read_modified=None, + summary="Summary", + updated=None, + ) + filter_row = SimpleNamespace( + entry=entry, + published_label="Never", + status_class="success", + status_label="Sent", + decision=SimpleNamespace(reason="Sent", blacklist_match=None, whitelist_match=None), + field_rows=[], + first_image="", + ) + preview_summary = SimpleNamespace( + total=1, + sent=1, + skipped=0, + blacklist_matches=0, + whitelist_matches=0, + ) + + custom_html: str = main_module.templates.get_template("custom.html").render( + request=request, + feed=feed, + entry=entry, + custom_message="", + ) + embed_html: str = main_module.templates.get_template("embed.html").render( + request=request, + feed=feed, + entry=entry, + ) + filter_preview_html: str = main_module.templates.get_template("_filter_preview.html").render( + feed=feed, + preview_limit=50, + preview_summary=preview_summary, + preview_helper_text="", + preview_rows=[filter_row], + ) + + for html in (custom_html, embed_html): + assert "Feed Author One, Feed Author Two" in html + assert "Entry Author One, Entry Author Two" in html + assert "Legacy Feed Author" not in html + assert "Legacy Entry Author" not in html + + assert "By Entry Author One, Entry Author Two |" in filter_preview_html + assert "Legacy Entry Author" not in filter_preview_html + + def test_settings_page_shows_screenshot_layout_setting() -> None: response: Response = client.get(url="/settings") assert response.status_code == 200, f"/settings failed: {response.text}" @@ -1464,8 +1545,8 @@ def test_create_html_marks_entries_from_another_feed(monkeypatch: pytest.MonkeyP original_feed_url: str | None = None link: str = "https://example.com/post" title: str = "Example title" - author: str = "Author" - authors_str: str = "Author" + author: str = "Legacy Author" + authors_str: str = "Author One, Author Two" summary: str = "Summary" content: list[DummyContent] = field(default_factory=lambda: [DummyContent("Content")]) published: None = None @@ -1504,6 +1585,41 @@ def test_create_html_marks_entries_from_another_feed(monkeypatch: pytest.MonkeyP assert "From another feed: https://example.com/feed-b.xml" in html assert "From another feed: https://example.com/feed-a.xml" not in html + assert "By Author One, Author Two @" in html + assert "By Legacy Author @" not in html + + +@patch("discord_rss_bot.main.httpx2.get") +def test_get_data_from_hook_url_fetches_metadata_with_httpx2(mock_get: MagicMock) -> None: + hook_url = "https://discord.com/api/webhooks/123/token" + response = MagicMock(is_success=True) + response.text = ( + '{"type": 1, "id": "123", "name": "Discord Hook", "avatar": "avatar", ' + '"channel_id": "456", "guild_id": "789", "token": "token"}' + ) + mock_get.return_value = response + main_module.get_data_from_hook_url.cache_clear() + + hook_info = main_module.get_data_from_hook_url("Saved Hook", f" {hook_url} ") + + mock_get.assert_called_once_with(hook_url, timeout=10.0) + assert hook_info.custom_name == "Saved Hook" + assert hook_info.name == "Discord Hook" + assert hook_info.channel_id == "456" + main_module.get_data_from_hook_url.cache_clear() + + +@patch("discord_rss_bot.main.httpx2.get") +def test_resolve_final_feed_url_follows_redirects_with_httpx2(mock_get: MagicMock) -> None: + response = MagicMock(is_success=True) + response.url = "https://example.com/final.xml" + mock_get.return_value = response + + resolved_url, error = main_module.resolve_final_feed_url(" https://example.com/original.xml ") + + mock_get.assert_called_once_with("https://example.com/original.xml", follow_redirects=True, timeout=10.0) + assert resolved_url == "https://example.com/final.xml" + assert error is None def test_webhook_entries_webhook_not_found() -> None: