diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index f7865c3..ef20d03 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -49,10 +49,10 @@ jobs: docker buildx inspect --bootstrap - name: Lint Python code - run: ruff check --exit-non-zero-on-fix + run: ruff check --exit-non-zero-on-fix --verbose - name: Check Python formatting - run: ruff format --check + run: ruff format --check --verbose - name: Lint Dockerfile run: docker build --check . diff --git a/discord_rss_bot/custom_message.py b/discord_rss_bot/custom_message.py index e5f1b14..46224cf 100644 --- a/discord_rss_bot/custom_message.py +++ b/discord_rss_bot/custom_message.py @@ -155,7 +155,7 @@ def replace_tags_in_text_message(entry: Entry, reader: Reader) -> str: entry_updated: str = entry.updated.strftime("%Y-%m-%d %H:%M:%S") if entry.updated else "Never" list_of_replacements: list[dict[str, str]] = [ - {"{{feed_author}}": feed.authors_str or ""}, + {"{{feed_author}}": feed.author or ""}, {"{{feed_added}}": feed_added}, {"{{feed_last_exception}}": feed_last_exception}, {"{{feed_last_updated}}": feed_last_updated}, @@ -168,7 +168,7 @@ def replace_tags_in_text_message(entry: Entry, reader: Reader) -> str: {"{{feed_user_title}}": feed.user_title or ""}, {"{{feed_version}}": feed.version or ""}, {"{{entry_added}}": entry_added}, - {"{{entry_author}}": entry.authors_str or ""}, + {"{{entry_author}}": entry.author or ""}, {"{{entry_content}}": content}, {"{{entry_content_raw}}": entry.content[0].value if entry.content else ""}, {"{{entry_id}}": entry.id or ""}, @@ -318,7 +318,7 @@ def replace_tags_in_embed(feed: Feed, entry: Entry, reader: Reader) -> CustomEmb embed.title = "" list_of_replacements: list[dict[str, str]] = [ - {"{{feed_author}}": feed.authors_str or ""}, + {"{{feed_author}}": feed.author or ""}, {"{{feed_added}}": feed_added or ""}, {"{{feed_last_exception}}": feed_last_exception}, {"{{feed_last_updated}}": feed_last_updated or ""}, @@ -331,7 +331,7 @@ def replace_tags_in_embed(feed: Feed, entry: Entry, reader: Reader) -> CustomEmb {"{{feed_user_title}}": feed.user_title or ""}, {"{{feed_version}}": feed.version or ""}, {"{{entry_added}}": entry_added or ""}, - {"{{entry_author}}": entry.authors_str or ""}, + {"{{entry_author}}": entry.author or ""}, {"{{entry_content}}": content or ""}, {"{{entry_content_raw}}": entry.content[0].value if entry.content else ""}, {"{{entry_id}}": entry.id}, diff --git a/discord_rss_bot/feeds.py b/discord_rss_bot/feeds.py index 833bf33..87cd162 100644 --- a/discord_rss_bot/feeds.py +++ b/discord_rss_bot/feeds.py @@ -22,11 +22,9 @@ from urllib.parse import parse_qs from urllib.parse import urljoin from urllib.parse import urlparse -import httpx2 +import httpx import tldextract from fastapi import HTTPException -from httpx2 import HTTPError -from httpx2 import Response from markdownify import markdownify from playwright.sync_api import Browser from playwright.sync_api import Page @@ -704,7 +702,7 @@ def get_webhook_files(webhook: DiscordWebhook) -> list[WebhookFile]: # noqa: C9 return files -def get_retry_after_seconds(response: Response) -> float | None: +def get_retry_after_seconds(response: httpx.Response) -> float | None: """Return Discord's retry delay for a rate-limited response when available.""" response_json: JsonObject = get_response_json(response) retry_after: JsonValue = response_json.get("retry_after") @@ -729,7 +727,7 @@ def request_discord_webhook( files: list[WebhookFile] | None, timeout: float, rate_limit_retry: bool, -) -> Response: +) -> httpx.Response: """Send a Discord webhook request with optional multipart files. Returns: @@ -744,7 +742,7 @@ def request_discord_webhook( else: request_kwargs["json"] = payload - response: Response = httpx2.request(method, url, **request_kwargs) + response: httpx.Response = httpx.request(method, url, **request_kwargs) if not rate_limit_retry or response.status_code != 429: # noqa: PLR2004 return response @@ -753,11 +751,11 @@ def request_discord_webhook( return response time.sleep(max(0.0, retry_after)) - return httpx2.request(method, url, **request_kwargs) + return httpx.request(method, url, **request_kwargs) -def send_webhook_message(webhook: DiscordWebhook, payload: JsonObject) -> Response: - """Execute a Discord webhook message create request using httpx2. +def send_webhook_message(webhook: DiscordWebhook, payload: JsonObject) -> httpx.Response: + """Execute a Discord webhook message create request using httpx. Returns: Discord API response. @@ -779,11 +777,11 @@ def edit_sent_webhook_message( message_id: str, webhook: DiscordWebhook, payload: JsonObject, -) -> Response: +) -> httpx.Response: """Edit an already-sent Discord webhook message. Returns: - Response: Discord API response. + httpx.Response: Discord API response. """ clean_webhook_url, params = get_webhook_query_params(webhook_url, payload, webhook=webhook, wait=True) return request_discord_webhook( @@ -940,13 +938,8 @@ def update_sent_webhook_record_for_entry( now: str = datetime.datetime.now(tz=datetime.UTC).isoformat() try: - response: Response = edit_sent_webhook_message( - webhook_url=webhook_url_value, - message_id=message_id_value, - webhook=webhook, - payload=edit_payload, - ) - except (AssertionError, RequestException, HTTPError, OSError, ValueError) as e: + response = edit_sent_webhook_message(webhook_url_value, message_id_value, webhook, edit_payload) + except (AssertionError, RequestException, httpx.HTTPError, OSError, ValueError) as e: logger.exception("Failed to edit Discord webhook message %s for entry %s", message_id_value, entry.id) return ( { @@ -1491,13 +1484,13 @@ def fetch_ttvdrops_campaign_media_items(entry: Entry) -> list[JsonObject]: return [] try: - response: Response = httpx2.get(api_url, follow_redirects=True, timeout=10.0) + response: httpx.Response = httpx.get(api_url, follow_redirects=True, timeout=10.0) if response.status_code != 200: # noqa: PLR2004 logger.warning("Failed to fetch ttvdrops campaign data from %s: %s", api_url, response.text[:500]) return [] response_json = cast("JsonValue", response.json()) - except (HTTPError, ValueError, TypeError): + except (httpx.HTTPError, ValueError, TypeError): logger.exception("Failed to fetch ttvdrops campaign data from %s", api_url) return [] @@ -1766,7 +1759,7 @@ def send_to_discord(reader: Reader | None = None, feed: Feed | None = None, *, d ) try: update_sent_webhooks_for_modified_entries(effective_reader, modified_entries) - except (AssertionError, ReaderError, RequestException, HTTPError, OSError, ValueError): + except (AssertionError, ReaderError, RequestException, httpx.HTTPError, OSError, ValueError): logger.exception("Failed to update saved Discord webhooks for modified feed entries.") # Loop through the unread entries. @@ -1833,7 +1826,7 @@ def execute_webhook( request_payload: JsonObject = get_webhook_request_payload(webhook) payload: JsonObject = get_webhook_message_payload(webhook) - response: Response = send_webhook_message(webhook, request_payload) + response: httpx.Response = send_webhook_message(webhook, request_payload) logger.debug("Discord webhook response for entry %s: status=%s", entry.id, response.status_code) if response.status_code not in {200, 204}: msg: str = f"Error sending entry to Discord: {response.text}\n{pprint.pformat(request_payload)}" diff --git a/discord_rss_bot/filter/evaluator.py b/discord_rss_bot/filter/evaluator.py index 01851db..30ca4ca 100644 --- a/discord_rss_bot/filter/evaluator.py +++ b/discord_rss_bot/filter/evaluator.py @@ -245,7 +245,7 @@ def get_entry_fields(entry: Entry) -> dict[str, str]: "title": entry.title or "", "summary": entry.summary or "", "content": content_value, - "author": entry.authors_str or "", + "author": entry.author or "", } diff --git a/discord_rss_bot/main.py b/discord_rss_bot/main.py index a27199b..d3ee932 100644 --- a/discord_rss_bot/main.py +++ b/discord_rss_bot/main.py @@ -18,7 +18,7 @@ from typing import Annotated from typing import TypedDict from typing import cast -import httpx2 +import httpx import sentry_sdk import uvicorn from apscheduler.schedulers.asyncio import AsyncIOScheduler @@ -30,8 +30,7 @@ from fastapi import Request from fastapi.responses import HTMLResponse from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates -from httpx2 import HTTPError -from httpx2 import Response +from httpx import Response from markdownify import markdownify from reader import Entry from reader import EntryNotFoundError @@ -83,7 +82,6 @@ from discord_rss_bot.settings import get_reader if TYPE_CHECKING: from collections.abc import AsyncGenerator from collections.abc import Iterable - from pathlib import Path from reader.types import JSONType @@ -1883,7 +1881,7 @@ def create_html_for_feed( # noqa: C901, PLR0914 html += f"""
{blacklisted}{whitelisted}{from_another_feed}

{entry.title}

-{feed_link}{f"By {entry.authors_str} @" if entry.authors_str else ""}{published} - {to_discord_html} +{feed_link}{f"By {entry.author} @" if entry.author else ""}{published} - {to_discord_html} {text} {video_embed_html} @@ -1941,8 +1939,8 @@ def get_data_from_hook_url(hook_name: str, hook_url: str) -> WebhookInfo: return our_hook try: - response: Response = httpx2.get(clean_hook_url, timeout=10.0) - except HTTPError as e: + response: Response = httpx.get(clean_hook_url, timeout=10.0) + except httpx.HTTPError as e: logger.warning("Failed to fetch webhook metadata for %s: %s", clean_hook_url, e) return our_hook @@ -2211,7 +2209,7 @@ async def update_feed( try: update_sent_webhooks_for_modified_entries(reader, modified_entries) - except (AssertionError, ReaderError, HTTPError, OSError, ValueError): + except (AssertionError, ReaderError, httpx.HTTPError, OSError, ValueError): logger.exception("Failed to update saved Discord webhooks for manually updated feed: %s", feed_url) logger.info("Manually updated feed: %s", feed_url) @@ -2232,18 +2230,18 @@ async def manual_backup( Returns: RedirectResponse: Redirect to the index page with a success or error message. """ - backup_path: Path | None = get_backup_path() + backup_path = get_backup_path() if backup_path is None: - message: str = "Git backup is not configured. Set GIT_BACKUP_PATH environment variable to enable backups." + message = "Git backup is not configured. Set GIT_BACKUP_PATH environment variable to enable backups." logger.warning("Manual git backup attempted but GIT_BACKUP_PATH is not configured") return RedirectResponse(url=f"/?message={urllib.parse.quote(message)}", status_code=303) try: commit_state_change(reader, "Manual backup triggered from web UI") - message: str = "Successfully created git backup!" + message = "Successfully created git backup!" logger.info("Manual git backup completed successfully") except Exception as e: - message: str = f"Failed to create git backup: {e}" + message = f"Failed to create git backup: {e}" logger.exception("Manual git backup failed") return RedirectResponse(url=f"/?message={urllib.parse.quote(message)}", status_code=303) @@ -2421,8 +2419,8 @@ def resolve_final_feed_url(url: str) -> tuple[str, str | None]: return clean_url, "URL is invalid" try: - response: Response = httpx2.get(clean_url, follow_redirects=True, timeout=10.0) - except HTTPError as e: + response: Response = httpx.get(clean_url, follow_redirects=True, timeout=10.0) + except httpx.HTTPError as e: return clean_url, str(e) if not response.is_success: diff --git a/discord_rss_bot/templates/_filter_preview.html b/discord_rss_bot/templates/_filter_preview.html index 6818bd0..3134153 100644 --- a/discord_rss_bot/templates/_filter_preview.html +++ b/discord_rss_bot/templates/_filter_preview.html @@ -33,7 +33,7 @@ {% endif %}

- {% if row.entry.authors_str %}By {{ row.entry.authors_str }} |{% endif %} + {% if row.entry.author %}By {{ row.entry.author }} |{% endif %} {{ row.published_label }}

diff --git a/discord_rss_bot/templates/custom.html b/discord_rss_bot/templates/custom.html index 69c5f5f..3146718 100644 --- a/discord_rss_bot/templates/custom.html +++ b/discord_rss_bot/templates/custom.html @@ -21,7 +21,7 @@ {% raw %} {{feed_author}} {% endraw %} - {{ feed.authors_str }} + {{ feed.author }}
  • @@ -114,7 +114,7 @@ {% raw %} {{entry_author}} {% endraw %} - {{ entry.authors_str }} + {{ entry.author }}
  • {% if entry.content %}
  • diff --git a/discord_rss_bot/templates/embed.html b/discord_rss_bot/templates/embed.html index 4bcc3a7..b50807e 100644 --- a/discord_rss_bot/templates/embed.html +++ b/discord_rss_bot/templates/embed.html @@ -14,7 +14,7 @@ {% raw %} {{feed_author}} {% endraw %} - {{feed.authors_str}} + {{feed.author}}
  • @@ -107,7 +107,7 @@ {% raw %} {{entry_author}} {% endraw %} - {{entry.authors_str}} + {{entry.author}}
  • {% if entry.content %}
  • diff --git a/discord_rss_bot/webhook.py b/discord_rss_bot/webhook.py index 43c8dba..6b78fcb 100644 --- a/discord_rss_bot/webhook.py +++ b/discord_rss_bot/webhook.py @@ -82,7 +82,7 @@ class DiscordWebhook: """Discord webhook request data. This intentionally mirrors the subset of `discord-webhook` used by the app - while leaving the actual HTTP transport to `httpx2`. + while leaving the actual HTTP transport to `httpx`. """ def __init__( # noqa: D107 diff --git a/pyproject.toml b/pyproject.toml index 3c594e0..c794fbe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ requires-python = ">=3.12" dependencies = [ "apscheduler>=3.11.0", "fastapi", - "httpx2", + "httpx", "jinja2", "lxml", "markdownify", diff --git a/tests/test_blacklist.py b/tests/test_blacklist.py index 35ae1e2..62d1af5 100644 --- a/tests/test_blacklist.py +++ b/tests/test_blacklist.py @@ -2,9 +2,7 @@ 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 @@ -14,7 +12,6 @@ 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: @@ -70,23 +67,6 @@ 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 38cf649..34f2c6b 100644 --- a/tests/test_custom_message.py +++ b/tests/test_custom_message.py @@ -42,7 +42,6 @@ def make_feed() -> SimpleNamespace: return SimpleNamespace( added=None, author="Feed Author", - authors_str="Entry Author", last_exception=None, last_updated=None, link="https://example.com/feed", @@ -61,7 +60,6 @@ def make_entry(summary: str) -> SimpleNamespace: return SimpleNamespace( added=None, author="Entry Author", - authors_str="Entry Author", content=[], feed=feed, feed_url=feed.url, @@ -188,41 +186,6 @@ 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 = '

    ' content = '

    ' diff --git a/tests/test_feeds.py b/tests/test_feeds.py index 34a4c98..08a7e4f 100644 --- a/tests/test_feeds.py +++ b/tests/test_feeds.py @@ -9,7 +9,6 @@ from pathlib import Path from typing import LiteralString from typing import cast from unittest.mock import MagicMock -from unittest.mock import call from unittest.mock import patch import pytest @@ -793,7 +792,7 @@ def test_get_ttvdrops_campaign_api_url_from_campaign_page() -> None: ("https://ttvdrops.lovinator.space/twitch/feed.xml?hide_paid=0&hide_paid=1", False), ], ) -@patch("discord_rss_bot.feeds.httpx2.get") +@patch("discord_rss_bot.feeds.httpx.get") def test_fetch_ttvdrops_campaign_media_items_extracts_reward_alt_text( mock_get: MagicMock, feed_url: str, @@ -1352,8 +1351,8 @@ def test_execute_webhook_does_not_record_when_feed_tracking_disabled(mock_send_w reader.set_tag.assert_not_called() -@patch("discord_rss_bot.feeds.httpx2.request") -def test_send_webhook_message_posts_components_with_httpx2(mock_request: MagicMock) -> None: +@patch("discord_rss_bot.feeds.httpx.request") +def test_send_webhook_message_posts_components_with_httpx(mock_request: MagicMock) -> None: response = MagicMock(status_code=200, text='{"id": "message-1"}') mock_request.return_value = response components: list[feeds.JsonValue] = [ @@ -1384,7 +1383,7 @@ def test_send_webhook_message_posts_components_with_httpx2(mock_request: MagicMo } -@patch("discord_rss_bot.feeds.httpx2.request") +@patch("discord_rss_bot.feeds.httpx.request") def test_send_webhook_message_uploads_files_as_multipart(mock_request: MagicMock) -> None: response = MagicMock(status_code=200, text='{"id": "message-2"}') mock_request.return_value = response @@ -1401,64 +1400,6 @@ def test_send_webhook_message_uploads_files_as_multipart(mock_request: MagicMock assert "json" not in mock_request.call_args.kwargs -@patch("discord_rss_bot.feeds.time.sleep") -@patch("discord_rss_bot.feeds.httpx2.request") -def test_request_discord_webhook_retries_rate_limit_with_httpx2( - mock_request: MagicMock, - mock_sleep: MagicMock, -) -> None: - rate_limited_response = MagicMock(status_code=429, headers={}) - rate_limited_response.json.return_value = {"retry_after": 0.25} - success_response = MagicMock(status_code=200) - mock_request.side_effect = [rate_limited_response, success_response] - payload: JsonObject = {"content": "Retry entry"} - request_call = call( - "POST", - "https://discord.com/api/webhooks/123/abc", - params={"wait": "true"}, - timeout=30.0, - json=payload, - ) - - result = feeds.request_discord_webhook( - "POST", - "https://discord.com/api/webhooks/123/abc", - payload=payload, - params={"wait": "true"}, - files=None, - timeout=30.0, - rate_limit_retry=True, - ) - - assert result is success_response - assert mock_request.call_args_list == [request_call, request_call] - mock_sleep.assert_called_once_with(0.25) - - -@patch("discord_rss_bot.feeds.httpx2.request") -def test_edit_sent_webhook_message_patches_message_with_httpx2(mock_request: MagicMock) -> None: - response = MagicMock(status_code=200, text='{"id": "message-3"}') - mock_request.return_value = response - payload: JsonObject = {"content": "Updated entry"} - webhook = feeds.DiscordWebhook(url="https://discord.com/api/webhooks/123/abc") - - result = feeds.edit_sent_webhook_message( - "https://discord.com/api/webhooks/123/abc?thread_id=456", - "message-3", - webhook, - payload, - ) - - assert result is response - mock_request.assert_called_once_with( - "PATCH", - "https://discord.com/api/webhooks/123/abc/messages/message-3", - params={"thread_id": "456", "wait": "true"}, - timeout=30.0, - json=payload, - ) - - @patch("discord_rss_bot.feeds.edit_sent_webhook_message") @patch("discord_rss_bot.feeds.create_webhook_for_entry") def test_update_sent_webhooks_for_modified_entries_edits_changed_payload( @@ -1521,7 +1462,7 @@ def test_update_sent_webhooks_for_modified_entries_edits_changed_payload( assert updated_count == 1 mock_edit_sent_webhook_message.assert_called_once() - edit_payload = mock_edit_sent_webhook_message.call_args.kwargs["payload"] + edit_payload = mock_edit_sent_webhook_message.call_args.args[3] assert edit_payload == {"content": "New title"} records = state["sent_webhooks"] assert isinstance(records, list) @@ -1592,7 +1533,7 @@ def test_update_sent_webhook_record_preserves_existing_embed_image_when_updated_ assert record_changed is True assert message_was_edited is True - edit_payload = mock_edit_sent_webhook_message.call_args.kwargs["payload"] + edit_payload = mock_edit_sent_webhook_message.call_args.args[3] assert isinstance(edit_payload["embeds"], list) assert edit_payload["embeds"][0]["image"] == previous_image assert isinstance(updated_record["payload"], dict) diff --git a/tests/test_main.py b/tests/test_main.py index d638da2..8cd59ff 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -7,7 +7,6 @@ from dataclasses import dataclass from dataclasses import field from datetime import UTC from datetime import datetime -from types import SimpleNamespace from typing import TYPE_CHECKING from typing import cast from unittest.mock import MagicMock @@ -25,7 +24,7 @@ if TYPE_CHECKING: from pathlib import Path import pytest - from httpx2 import Response + from httpx import Response from reader import Entry from reader import Reader @@ -331,7 +330,6 @@ def test_blacklist_preview_uses_50_entry_limit() -> None: title: str summary: str author: str - authors_str: str link: str published: datetime | None content: list[DummyContent] = field(default_factory=lambda: [DummyContent("content")]) @@ -349,7 +347,6 @@ def test_blacklist_preview_uses_50_entry_limit() -> None: title=f"Entry {index}", summary=f"Summary {index}", author="Author", - authors_str="Author", link=f"https://example.com/entry-{index}", published=datetime(2024, 1, 1, tzinfo=UTC), ), @@ -406,7 +403,6 @@ def test_blacklist_preview_shows_labeled_field_values_for_substring_match() -> N title: str summary: str author: str - authors_str: str link: str published: datetime | None content: list[DummyContent] = field(default_factory=list) @@ -422,8 +418,7 @@ def test_blacklist_preview_shows_labeled_field_values_for_substring_match() -> N feed=self.feed, title="World of Warcraft", summary="

    Massive MMO news update

    ", - author="Legacy Blizzard Author", - authors_str="Blizzard Author One, Blizzard Author Two", + author="Blizzard", link="https://example.com/wow-1", published=datetime(2024, 1, 1, tzinfo=UTC), content=[DummyContent("

    The expansion launches soon.

    ")], @@ -465,90 +460,10 @@ 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}" @@ -1545,8 +1460,7 @@ 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 = "Legacy Author" - authors_str: str = "Author One, Author Two" + author: str = "Author" summary: str = "Summary" content: list[DummyContent] = field(default_factory=lambda: [DummyContent("Content")]) published: None = None @@ -1585,41 +1499,6 @@ 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: diff --git a/tests/test_update_interval.py b/tests/test_update_interval.py index 0d9f948..26c5421 100644 --- a/tests/test_update_interval.py +++ b/tests/test_update_interval.py @@ -8,7 +8,7 @@ from fastapi.testclient import TestClient from discord_rss_bot.main import app if TYPE_CHECKING: - from httpx2 import Response + from httpx import Response client: TestClient = TestClient(app) webhook_name: str = "Test Webhook for Update Interval"