diff --git a/pyproject.toml b/pyproject.toml index 28e688f..0b8e299 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,14 +5,20 @@ description = "Get notified when a new drop is available on Twitch." readme = "README.md" requires-python = ">=3.14" dependencies = [ + "celery[redis]", "colorama", "dateparser", "django-auto-prefetch", + "django-celery-beat", + "django-celery-results", "django-debug-toolbar", "django-silk", "django", + "flower", "gunicorn", + "hiredis", "httpx", + "index-now-for-python", "json-repair", "pillow", "platformdirs", @@ -20,17 +26,11 @@ dependencies = [ "pydantic", "pygments", "python-dotenv", + "redis", "sentry-sdk", "setproctitle", - "tqdm", - "index-now-for-python", "sitemap-parser", - "celery[redis]", - "django-celery-results", - "django-celery-beat", - "flower", - "hiredis", - "redis", + "tqdm", ] @@ -41,9 +41,9 @@ dev = [ "hypothesis[django]", "pytest-cov", "pytest-django", + "pytest-randomly", "pytest-xdist[psutil]", "pytest", - "pytest-randomly", ] [tool.pytest.ini_options] diff --git a/twitch/feeds.py b/twitch/feeds.py index 6a8678c..498db75 100644 --- a/twitch/feeds.py +++ b/twitch/feeds.py @@ -692,7 +692,7 @@ class OrganizationRSSFeed(TTVDropsBaseFeed): feed_type = BrowserFriendlyRss201rev2Feed title: str = "TTVDrops Twitch Organizations" - link: str = "/organizations/" + link: str = "/twitch/organizations/" description: str = "Latest organizations on TTVDrops" _limit: int | None = None @@ -921,7 +921,7 @@ class DropCampaignFeed(TTVDropsBaseFeed): """RSS feed for latest drop campaigns.""" title: str = "Twitch Drop Campaigns" - link: str = "/campaigns/" + link: str = "/" description: str = "Latest Twitch drop campaigns on TTVDrops" item_guid_is_permalink = True diff --git a/twitch/migrations/0007_rename_operation_name_to_operation_names.py b/twitch/migrations/0007_rename_operation_name_to_operation_names.py index 55985e2..63190e1 100644 --- a/twitch/migrations/0007_rename_operation_name_to_operation_names.py +++ b/twitch/migrations/0007_rename_operation_name_to_operation_names.py @@ -5,7 +5,7 @@ from django.db import migrations from django.db import models -def migrate_operation_name_to_list(apps, schema_editor) -> None: # noqa: ANN001, ARG001 +def migrate_operation_name_to_list(apps, schema_editor) -> None: # noqa: ANN001 """Convert operation_name string values to operation_names list.""" DropCampaign = apps.get_model("twitch", "DropCampaign") for campaign in DropCampaign.objects.all(): @@ -14,7 +14,7 @@ def migrate_operation_name_to_list(apps, schema_editor) -> None: # noqa: ANN001 campaign.save(update_fields=["operation_names"]) -def reverse_operation_names_to_string(apps, schema_editor) -> None: # noqa: ARG001, ANN001 +def reverse_operation_names_to_string(apps, schema_editor) -> None: # noqa: ANN001 """Convert operation_names list back to operation_name string (first item only).""" DropCampaign = apps.get_model("twitch", "DropCampaign") for campaign in DropCampaign.objects.all(): diff --git a/twitch/migrations/0016_mark_all_drops_fully_imported.py b/twitch/migrations/0016_mark_all_drops_fully_imported.py index 9fd5630..54b4003 100644 --- a/twitch/migrations/0016_mark_all_drops_fully_imported.py +++ b/twitch/migrations/0016_mark_all_drops_fully_imported.py @@ -1,7 +1,7 @@ from django.db import migrations -def mark_all_drops_fully_imported(apps, schema_editor) -> None: # noqa: ANN001, ARG001 +def mark_all_drops_fully_imported(apps, schema_editor) -> None: # noqa: ANN001 """Marks all existing DropCampaigns as fully imported. This was needed to ensure that the Twitch API view only returns campaigns that are ready for display. diff --git a/twitch/tests/test_feeds.py b/twitch/tests/test_feeds.py index b69e9cb..afd7a83 100644 --- a/twitch/tests/test_feeds.py +++ b/twitch/tests/test_feeds.py @@ -1410,3 +1410,33 @@ class DiscordFeedTestCase(TestCase): f"Expected Discord timestamp format <t:UNIX_TIMESTAMP:R> in content, got: {content}" ) assert "()" not in content + + def test_feed_links_return_200(self) -> None: + """Test that all links in the feeds return 200 OK.""" + feed_urls: list[str] = [ + reverse("core:organization_feed"), + reverse("core:game_feed"), + reverse("core:organization_feed_atom"), + ] + + for feed_url in feed_urls: + response: _MonkeyPatchedWSGIResponse = self.client.get(feed_url) + assert response.status_code == 200, f"Feed {feed_url} did not return 200." + + # Extract all links from the feed content + content: str = response.content.decode("utf-8") + links: list[str] = re.findall(r'href=["\'](https?://.*?)["\']', content) + + for link in links: + skip_links: list[str] = [ + "rss_styles.xslt", + "twitch.tv", + ] + + if any(skip in link for skip in skip_links): + # Skip testing rss_styles.xslt, external Twitch links, and internal links that may not exist in test environment + continue + link_response: _MonkeyPatchedWSGIResponse = self.client.get(link) + + msg: str = f"Link {link} in feed {feed_url} did not return 200, got {link_response.status_code}." + assert link_response.status_code == 200, msg diff --git a/twitch/views.py b/twitch/views.py index 595bd42..2b492f8 100644 --- a/twitch/views.py +++ b/twitch/views.py @@ -1883,7 +1883,7 @@ def export_campaigns_json(request: HttpRequest) -> HttpResponse: return response -def export_games_csv(request: HttpRequest) -> HttpResponse: # noqa: ARG001 # noqa: ARG001 +def export_games_csv(request: HttpRequest) -> HttpResponse: """Export games to CSV format. Args: @@ -1923,7 +1923,7 @@ def export_games_csv(request: HttpRequest) -> HttpResponse: # noqa: ARG001 # n return response -def export_games_json(request: HttpRequest) -> HttpResponse: # noqa: ARG001 # noqa: ARG001 +def export_games_json(request: HttpRequest) -> HttpResponse: """Export games to JSON format. Args: @@ -1958,7 +1958,7 @@ def export_games_json(request: HttpRequest) -> HttpResponse: # noqa: ARG001 # return response -def export_organizations_csv(request: HttpRequest) -> HttpResponse: # noqa: ARG001 +def export_organizations_csv(request: HttpRequest) -> HttpResponse: """Export organizations to CSV format. Args: @@ -1987,7 +1987,7 @@ def export_organizations_csv(request: HttpRequest) -> HttpResponse: # noqa: ARG return response -def export_organizations_json(request: HttpRequest) -> HttpResponse: # noqa: ARG001 +def export_organizations_json(request: HttpRequest) -> HttpResponse: """Export organizations to JSON format. Args: