Hide paid ttvdrops images and update lint checks
Some checks failed
Test and build Docker image / docker (push) Failing after 3s

This commit is contained in:
Joakim Hellsén 2026-05-31 00:24:56 +02:00
commit 1065838ef7
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
11 changed files with 151 additions and 38 deletions

View file

@ -59,7 +59,7 @@ def pytest_sessionstart(session: pytest.Session) -> None:
# the worker-specific location.
settings_module: ModuleType | None = sys.modules.get("discord_rss_bot.settings")
if settings_module is not None:
settings_module.data_dir = str(worker_data_dir)
settings_module.data_dir = str(worker_data_dir) # pyright: ignore[reportAttributeAccessIssue]
get_reader_attr = getattr(settings_module, "get_reader", None)
if get_reader_attr is not None and hasattr(get_reader_attr, "cache_clear"):
get_reader = cast("CachedReaderFactory", get_reader_attr)

View file

@ -102,6 +102,6 @@ def test_pytest_collection_modifyitems_noops_when_real_git_backup_tests_enabled(
config.getoption.return_value = True
items: list[MagicMock] = [MagicMock()]
hooks.pytest_collection_modifyitems(config=config, items=items)
hooks.pytest_collection_modifyitems(config=config, items=items) # pyright: ignore[reportArgumentType]
config.getoption.assert_called_once_with("--run-real-git-backup-tests")

View file

@ -781,8 +781,24 @@ def test_get_ttvdrops_campaign_api_url_from_campaign_page() -> None:
assert api_url == "https://ttvdrops.lovinator.space/twitch/api/v1/campaigns/93ba35ae-5bfc-43fe-88ac-49a0aabb2fe2/"
@pytest.mark.parametrize(
("feed_url", "include_paid_reward"),
[
("https://ttvdrops.lovinator.space/twitch/feed.xml", True),
("https://ttvdrops.lovinator.space/twitch/feed.xml?hide_paid=0", True),
("https://ttvdrops.lovinator.space/twitch/feed.xml?hide_paid=true", True),
("https://ttvdrops.lovinator.space/twitch/feed.xml?hide_paid=1", False),
("https://ttvdrops.lovinator.space/twitch/feed.xml?lang=en&hide_paid=1", False),
("https://ttvdrops.lovinator.space/twitch/feed.xml?hide_paid=0&hide_paid=1", False),
],
)
@patch("discord_rss_bot.feeds.httpx.get")
def test_fetch_ttvdrops_campaign_media_items_extracts_reward_alt_text(mock_get: MagicMock) -> None:
def test_fetch_ttvdrops_campaign_media_items_extracts_reward_alt_text(
mock_get: MagicMock,
feed_url: str,
*,
include_paid_reward: bool,
) -> None:
response = MagicMock()
response.status_code = 200
response.json.return_value = {
@ -796,22 +812,38 @@ def test_fetch_ttvdrops_campaign_media_items_extracts_reward_alt_text(mock_get:
{"image_url": "javascript:alert(1)"},
],
},
{
"name": "Paid drop",
"required_minutes_watched": 0,
"required_subs": 2,
"benefits": [
{"name": "Pay2win reward", "image_url": "/media/benefits/images/paid-reward.png"},
],
},
],
}
mock_get.return_value = response
entry = MagicMock()
entry.link = "https://ttvdrops.lovinator.space/twitch/campaigns/93ba35ae-5bfc-43fe-88ac-49a0aabb2fe2/"
entry.id = "entry-4"
entry.feed.url = "https://example.com/feed.xml"
entry.feed.url = feed_url
media_items = feeds.fetch_ttvdrops_campaign_media_items(entry)
assert media_items == [
expected_media_items: list[JsonObject] = [
{
"url": "https://ttvdrops.lovinator.space/media/benefits/images/reward.png",
"description": "120 minutes watched: Skulbladi",
},
]
if include_paid_reward:
expected_media_items.append(
{
"url": "https://ttvdrops.lovinator.space/media/benefits/images/paid-reward.png",
"description": "2 subscriptions: Pay2win reward",
},
)
assert media_items == expected_media_items
mock_get.assert_called_once_with(
"https://ttvdrops.lovinator.space/twitch/api/v1/campaigns/93ba35ae-5bfc-43fe-88ac-49a0aabb2fe2/",
follow_redirects=True,
@ -819,6 +851,78 @@ def test_fetch_ttvdrops_campaign_media_items_extracts_reward_alt_text(mock_get:
)
def test_extract_ttvdrops_media_gallery_items_includes_paid_rewards_by_default() -> None:
media_items = feeds.extract_ttvdrops_media_gallery_items(
{
"drops": [
{
"required_subs": 1,
"benefits": [{"name": "Paid reward", "image_url": "/media/paid.png"}],
},
],
},
)
assert media_items == [
{
"url": "https://ttvdrops.lovinator.space/media/paid.png",
"description": "1 subscriptions: Paid reward",
},
]
def test_extract_ttvdrops_media_gallery_items_hide_paid_omits_non_watch_rewards() -> None:
media_items = feeds.extract_ttvdrops_media_gallery_items(
{
"drops": [
{
"required_minutes_watched": 30,
"benefits": [{"name": "Watch reward", "image_url": "/media/watch.png"}],
},
{
"required_minutes_watched": 0,
"required_subs": 1,
"benefits": [{"name": "Paid reward", "image_url": "/media/paid.png"}],
},
{
"benefits": [{"name": "Unknown reward", "image_url": "/media/unknown.png"}],
},
],
},
hide_paid=True,
)
assert media_items == [
{
"url": "https://ttvdrops.lovinator.space/media/watch.png",
"description": "30 minutes watched: Watch reward",
},
]
def test_extract_ttvdrops_media_gallery_items_extracts_nested_watch_rewards() -> None:
media_items = feeds.extract_ttvdrops_media_gallery_items(
{
"campaign": {
"drops": [
{
"required_minutes_watched": 45,
"rewards": [{"name": "Nested reward", "image_url": "/media/nested.png"}],
},
],
},
},
hide_paid=True,
)
assert media_items == [
{
"url": "https://ttvdrops.lovinator.space/media/nested.png",
"description": "45 minutes watched: Nested reward",
},
]
def test_capture_full_page_screenshot_uses_thread_when_loop_running() -> None:
"""Capture should offload sync Playwright work when called from an active event loop."""
with patch("discord_rss_bot.feeds._capture_full_page_screenshot_sync", return_value=b"png") as mock_capture_sync:

View file

@ -4,7 +4,6 @@ import contextlib
import json
import shutil
import subprocess # noqa: S404
from pathlib import Path
from typing import TYPE_CHECKING
from typing import cast
from unittest.mock import MagicMock
@ -196,8 +195,8 @@ def test_export_state_creates_state_json(tmp_path: Path) -> None:
data = cast("JsonObject", json.loads(state_file.read_text(encoding="utf-8")))
assert "feeds" in data
assert "webhooks" in data
assert data["feeds"][0]["url"] == "https://example.com/feed.rss"
assert data["feeds"][0]["webhook"] == "https://discord.com/api/webhooks/123/abc"
assert data["feeds"][0]["url"] == "https://example.com/feed.rss" # type: ignore
assert data["feeds"][0]["webhook"] == "https://discord.com/api/webhooks/123/abc" # type: ignore
def test_export_state_omits_empty_tags(tmp_path: Path) -> None:
@ -227,7 +226,7 @@ def test_export_state_omits_empty_tags(tmp_path: Path) -> None:
data = cast("JsonObject", json.loads((backup_path / "state.json").read_text()))
# Only "url" key should be present (no empty-value tags)
assert list(data["feeds"][0].keys()) == ["url"]
assert list(data["feeds"][0].keys()) == ["url"] # type: ignore
def test_commit_state_change_noop_when_not_configured(monkeypatch: pytest.MonkeyPatch) -> None:
@ -575,7 +574,7 @@ def test_embed_backup_end_to_end(monkeypatch: pytest.MonkeyPatch, tmp_path: Path
state_data = cast("JsonObject", json.loads(state_file.read_text(encoding="utf-8")))
# Find our test feed in the state
test_feed_data = next((feed for feed in state_data["feeds"] if feed["url"] == test_feed_url), None)
test_feed_data = next((feed for feed in state_data["feeds"] if feed["url"] == test_feed_url), None) # type: ignore
assert test_feed_data is not None, f"Test feed not found in state.json: {state_data}"
# The embed settings are stored as a nested dict under custom_embed tag

View file

@ -174,7 +174,7 @@ class TestCreateHoyolabWebhook:
entry = make_entry(link=None)
entry = typing.cast("Entry", entry)
webhook = create_hoyolab_webhook("https://discord.test/webhook", entry, post_data)
webhook = create_hoyolab_webhook("https://discord.test/webhook", entry, post_data) # type: ignore
assert webhook is webhook_instance
mock_webhook_cls.assert_called_once_with(url="https://discord.test/webhook", rate_limit_retry=True)
@ -222,7 +222,7 @@ class TestCreateHoyolabWebhook:
entry = make_entry()
entry = typing.cast("Entry", entry)
webhook = create_hoyolab_webhook("https://discord.test/webhook", entry, post_data)
webhook = create_hoyolab_webhook("https://discord.test/webhook", entry, post_data) # type: ignore
assert webhook is webhook_instance
webhook_instance.remove_embeds.assert_not_called()

View file

@ -438,7 +438,7 @@ def test_blacklist_preview_shows_labeled_field_values_for_substring_match() -> N
stub_reader = StubReader()
app.dependency_overrides[get_reader_dependency] = lambda: stub_reader
try:
try: # noqa: PLW0717
with patch("discord_rss_bot.main.create_html_for_feed", return_value="<div>Rendered</div>"):
response: Response = client.get(
url="/blacklist_preview",
@ -656,7 +656,7 @@ def test_sent_webhooks_view_shows_saved_records() -> None:
app.dependency_overrides[get_reader_dependency] = StubReader
try:
try: # noqa: PLW0717
response: Response = client.get(url="/sent_webhooks")
assert response.status_code == 200, f"/sent_webhooks failed: {response.text}"
@ -1224,7 +1224,7 @@ def test_post_entry_uses_feed_url_to_disambiguate_duplicate_ids() -> None:
app.dependency_overrides[get_reader_dependency] = StubReader
no_redirect_client = TestClient(app, follow_redirects=False)
try:
try: # noqa: PLW0717
with patch("discord_rss_bot.main.send_entry_to_discord", side_effect=fake_send_entry_to_discord):
response: Response = no_redirect_client.get(
url="/post_entry",
@ -1960,7 +1960,7 @@ def test_webhook_entries_mass_update_preview_shows_old_and_new_urls() -> None:
return []
app.dependency_overrides[get_reader_dependency] = StubReader
try:
try: # noqa: PLW0717
with (
patch(
"discord_rss_bot.main.get_data_from_hook_url",
@ -2040,7 +2040,7 @@ def test_bulk_change_feed_urls_updates_matching_feeds() -> None:
app.dependency_overrides[get_reader_dependency] = lambda: stub_reader
no_redirect_client = TestClient(app, follow_redirects=False)
try:
try: # noqa: PLW0717
with patch(
"discord_rss_bot.main.resolve_final_feed_url",
side_effect=lambda url: (url.replace("old.example.com", "new.example.com"), None),
@ -2095,7 +2095,7 @@ def test_webhook_entries_mass_update_preview_fragment_endpoint() -> None:
return self._feeds
app.dependency_overrides[get_reader_dependency] = StubReader
try:
try: # noqa: PLW0717
with patch(
"discord_rss_bot.main.resolve_final_feed_url",
side_effect=lambda url: (url.replace("old.example.com", "new.example.com"), None),
@ -2168,7 +2168,7 @@ def test_bulk_change_feed_urls_force_update_overwrites_conflict() -> None: # no
app.dependency_overrides[get_reader_dependency] = lambda: stub_reader
no_redirect_client = TestClient(app, follow_redirects=False)
try:
try: # noqa: PLW0717
with patch(
"discord_rss_bot.main.resolve_final_feed_url",
side_effect=lambda url: (url.replace("old.example.com", "new.example.com"), None),
@ -2242,7 +2242,7 @@ def test_bulk_change_feed_urls_force_update_ignores_resolution_error() -> None:
app.dependency_overrides[get_reader_dependency] = lambda: stub_reader
no_redirect_client = TestClient(app, follow_redirects=False)
try:
try: # noqa: PLW0717
with patch(
"discord_rss_bot.main.resolve_final_feed_url",
return_value=("https://new.example.com/rss/a.xml", "HTTP 404"),