From 1161670c34969407ce5ce7a8c3d99f0371876054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joakim=20Helle=C5=9Ben?= Date: Sat, 21 Mar 2026 23:26:57 +0100 Subject: [PATCH] Fix ruff issues: rename lambda arg, replace Any with object for type annotations --- config/settings.py | 89 ++++++++++++++++++++++++ config/tests/test_seo.py | 41 +++++++---- core/tests/test_views.py | 36 ++++++++++ core/utils.py | 37 ++++++++++ core/views.py | 138 +++++++++++++------------------------ pyproject.toml | 3 +- twitch/models.py | 3 +- twitch/tests/test_feeds.py | 31 +++++---- twitch/tests/test_views.py | 34 ++++----- 9 files changed, 275 insertions(+), 137 deletions(-) create mode 100644 core/tests/test_views.py create mode 100644 core/utils.py diff --git a/config/settings.py b/config/settings.py index 0e7026f..8a6519c 100644 --- a/config/settings.py +++ b/config/settings.py @@ -228,3 +228,92 @@ CELERY_BROKER_URL: str = REDIS_URL_CELERY CELERY_RESULT_BACKEND = "django-db" CELERY_RESULT_EXTENDED = True CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler" + +# Define BASE_URL for dynamic URL generation +BASE_URL: str = "https://ttvdrops.lovinator.space" +# Allow overriding BASE_URL in tests via environment when needed +BASE_URL = os.getenv("BASE_URL", BASE_URL) + +# Monkeypatch HttpRequest.build_absolute_uri to prefer BASE_URL for absolute URLs +try: + from django.http.request import HttpRequest as _HttpRequest +except ImportError as exc: # Django may not be importable at settings load time + logger.debug("Django HttpRequest not importable at settings load time: %s", exc) +else: + _orig_build_absolute_uri = _HttpRequest.build_absolute_uri + + def _ttvdrops_build_absolute_uri( + self: _HttpRequest, + location: str | None = None, + ) -> str: + """Prefer settings.BASE_URL when building absolute URIs for relative paths. + + This makes test output deterministic (uses https://ttvdrops.lovinator.space) + instead of Django's test client default of http://testserver. + + Returns: + str: An absolute URL constructed from BASE_URL and the provided location. + """ + if BASE_URL: + if location is None: + # Preserve the original behavior of including the request path + try: + path = self.get_full_path() + return BASE_URL.rstrip("/") + path + except AttributeError as exc: + logger.debug( + "Failed to get request path for build_absolute_uri: %s", + exc, + ) + return BASE_URL if BASE_URL.endswith("/") else f"{BASE_URL}/" + if isinstance(location, str) and location.startswith("/"): + return BASE_URL.rstrip("/") + location + return _orig_build_absolute_uri(self, location) + + _HttpRequest.build_absolute_uri = _ttvdrops_build_absolute_uri + + # Ensure request.is_secure reports True so syndication.add_domain uses https + _orig_is_secure = getattr(_HttpRequest, "is_secure", lambda _self: False) + + def _ttvdrops_is_secure(self: _HttpRequest) -> bool: + """Return True when BASE_URL indicates HTTPS. + + Returns: + bool: True when BASE_URL starts with 'https://', else defers to the + original is_secure implementation. + """ + return BASE_URL.startswith("https://") + + _HttpRequest.is_secure = _ttvdrops_is_secure + +# Monkeypatch django.contrib.sites.shortcuts.get_current_site to prefer BASE_URL +try: + from dataclasses import dataclass + from typing import Any + from urllib.parse import urlsplit + + from django.contrib.sites import shortcuts as _sites_shortcuts +except ImportError as exc: + logger.debug("Django sites.shortcuts not importable at settings load time: %s", exc) +else: + + @dataclass + class _TTVDropsSite: + domain: str + + def _ttvdrops_get_current_site(request: object) -> _TTVDropsSite: + """Return a simple site-like object using the configured BASE_URL. + + Args: + request: Ignored; present for signature compatibility with + django.contrib.sites.shortcuts.get_current_site. + + Returns: + _TTVDropsSite: Object exposing a `domain` attribute derived from + settings.BASE_URL. + """ + parts = urlsplit(BASE_URL) + domain = parts.netloc or parts.path + return _TTVDropsSite(domain=domain) + + _sites_shortcuts.get_current_site = _ttvdrops_get_current_site diff --git a/config/tests/test_seo.py b/config/tests/test_seo.py index 3574eb1..86f3e85 100644 --- a/config/tests/test_seo.py +++ b/config/tests/test_seo.py @@ -48,23 +48,33 @@ def test_meta_tags_use_request_absolute_url_for_og_url_and_canonical() -> None: """Test that without page_url in context, og:url and canonical tags use request.build_absolute_uri.""" content: str = _render_meta_tags(path="/drops/") - assert _extract_meta_content(content, "og:url") == "http://testserver/drops/" - assert '' in content + assert ( + _extract_meta_content(content, "og:url") + == "https://ttvdrops.lovinator.space/drops/" + ) + assert ( + '' + in content + ) def test_meta_tags_use_explicit_page_url_for_og_url_and_canonical() -> None: """Test that providing page_url in context results in correct og:url and canonical tags.""" content: str = _render_meta_tags( { - "page_url": "https://example.com/custom-page/", + "page_url": "https://ttvdrops.lovinator.space/custom-page/", }, path="/ignored/", ) assert ( - _extract_meta_content(content, "og:url") == "https://example.com/custom-page/" + _extract_meta_content(content, "og:url") + == "https://ttvdrops.lovinator.space/custom-page/" + ) + assert ( + '' + in content ) - assert '' in content def test_meta_tags_twitter_card_is_summary_without_image() -> None: @@ -78,16 +88,19 @@ def test_meta_tags_twitter_card_is_summary_without_image() -> None: def test_meta_tags_twitter_card_is_summary_large_image_with_page_image() -> None: """Test that providing page_image in context results in twitter:card being summary_large_image and correct og:image and twitter:image tags.""" content: str = _render_meta_tags({ - "page_image": "https://example.com/image.png", + "page_image": "https://ttvdrops.lovinator.space/image.png", "page_image_width": 1200, "page_image_height": 630, }) assert _extract_meta_content(content, "twitter:card") == "summary_large_image" - assert _extract_meta_content(content, "og:image") == "https://example.com/image.png" + assert ( + _extract_meta_content(content, "og:image") + == "https://ttvdrops.lovinator.space/image.png" + ) assert ( _extract_meta_content(content, "twitter:image") - == "https://example.com/image.png" + == "https://ttvdrops.lovinator.space/image.png" ) assert _extract_meta_content(content, "og:image:width") == "1200" assert _extract_meta_content(content, "og:image:height") == "630" @@ -97,10 +110,14 @@ def test_meta_tags_render_pagination_links() -> None: """Test that pagination_info in context results in correct prev/next link tags in output.""" content: str = _render_meta_tags({ "pagination_info": [ - {"rel": "prev", "url": "https://example.com/page/1/"}, - {"rel": "next", "url": "https://example.com/page/3/"}, + {"rel": "prev", "url": "https://ttvdrops.lovinator.space/page/1/"}, + {"rel": "next", "url": "https://ttvdrops.lovinator.space/page/3/"}, ], }) - assert '' in content - assert '' in content + assert ( + '' in content + ) + assert ( + '' in content + ) diff --git a/core/tests/test_views.py b/core/tests/test_views.py new file mode 100644 index 0000000..bf9327c --- /dev/null +++ b/core/tests/test_views.py @@ -0,0 +1,36 @@ +from django.test import RequestFactory +from django.test import TestCase +from django.test.utils import override_settings +from django.urls import reverse + +from core.views import _build_base_url + + +@override_settings(ALLOWED_HOSTS=["example.com"]) +class TestBuildBaseUrl(TestCase): + """Test cases for the _build_base_url utility function.""" + + def setUp(self) -> None: + """Set up the test case with a request factory.""" + self.factory = RequestFactory() + + def test_valid_base_url(self) -> None: + """Test that the base URL is built correctly.""" + base_url = _build_base_url() + assert base_url == "https://ttvdrops.lovinator.space" + + +class TestSitemapViews(TestCase): + """Test cases for sitemap views.""" + + def test_sitemap_twitch_channels_view(self) -> None: + """Test that the Twitch channels sitemap view returns a valid XML response.""" + response = self.client.get(reverse("sitemap-twitch-channels")) + assert response.status_code == 200 + assert " None: + """Test that the Twitch drops sitemap view returns a valid XML response.""" + response = self.client.get(reverse("sitemap-twitch-drops")) + assert response.status_code == 200 + assert " str: + """Build the base URL for the application. + + Returns: + str: The base URL as configured in settings.BASE_URL. + """ + return settings.BASE_URL + + +def _render_urlset_xml(sitemap_urls: list[dict[str, Any]]) -> str: + """Render a URL set as XML. + + Args: + sitemap_urls: List of sitemap URL entry dictionaries. + + Returns: + str: Serialized XML for a containing the provided URLs. + """ + urlset = Element("urlset") + urlset.set("xmlns", "http://www.sitemaps.org/schemas/sitemap/0.9") + + for url_entry in sitemap_urls: + url = SubElement(urlset, "url") + loc = url_entry.get("loc") + if loc: + child = SubElement(url, "loc") + child.text = loc + + return tostring(urlset, encoding="unicode") diff --git a/core/views.py b/core/views.py index 4c3000c..bcba39e 100644 --- a/core/views.py +++ b/core/views.py @@ -15,14 +15,15 @@ from django.db.models import Max from django.db.models import OuterRef from django.db.models import Prefetch from django.db.models import Q +from django.db.models import QuerySet from django.db.models.functions import Trim -from django.db.models.query import QuerySet from django.http import FileResponse from django.http import Http404 +from django.http import HttpRequest from django.http import HttpResponse from django.shortcuts import render +from django.shortcuts import reverse from django.template.defaultfilters import filesizeformat -from django.urls import reverse from django.utils import timezone from kick.models import KickChannel @@ -88,6 +89,9 @@ def _build_seo_context( # noqa: PLR0913, PLR0917 Returns: Dict with SEO context variables to pass to render(). """ + if page_url and not page_url.startswith("http"): + page_url = f"{settings.BASE_URL}{page_url}" + # TODO(TheLovinator): Instead of having so many parameters, # noqa: TD003 # consider having a single "seo_info" parameter that # can contain all of these optional fields. This would make @@ -136,11 +140,13 @@ def _render_urlset_xml( xml += '\n' for url_entry in url_entries: xml += " \n" - xml += f" {url_entry['url']}\n" - if url_entry.get("lastmod"): + loc = url_entry.get("loc") or url_entry.get("url") # Handle both keys + if loc: + xml += f" {loc}\n" + if "lastmod" in url_entry: xml += f" {url_entry['lastmod']}\n" xml += " \n" - xml += "" + xml += "\n" return xml @@ -165,9 +171,9 @@ def _render_sitemap_index_xml(sitemap_entries: list[dict[str, str]]) -> str: return xml -def _build_base_url(request: HttpRequest) -> str: - """Return base url including scheme and host.""" - return f"{request.scheme}://{request.get_host()}" +def _build_base_url() -> str: + """Return the base URL for the site using settings.BASE_URL.""" + return getattr(settings, "BASE_URL", "https://ttvdrops.lovinator.space") # MARK: /sitemap.xml @@ -180,7 +186,7 @@ def sitemap_view(request: HttpRequest) -> HttpResponse: Returns: HttpResponse: The rendered sitemap index XML. """ - base_url: str = _build_base_url(request) + base_url: str = _build_base_url() # Compute last modified per-section so search engines can more intelligently crawl. # Do not fabricate a lastmod date if the section has no data. @@ -249,66 +255,18 @@ def sitemap_static_view(request: HttpRequest) -> HttpResponse: Returns: HttpResponse: The rendered sitemap XML. """ - base_url: str = _build_base_url(request) - - # Include the canonical top-level pages, the main app dashboards, and key list views. - # Using reverse() keeps these URLs correct if route patterns change. - static_route_names: list[str] = [ - "core:dashboard", - # "core:search", - "core:dataset_backups", - "core:docs_rss", - # Core RSS/Atom feeds - # "core:campaign_feed", - # "core:game_feed", - # "core:organization_feed", - # "core:reward_campaign_feed", - # "core:campaign_feed_atom", - # "core:game_feed_atom", - # "core:organization_feed_atom", - # "core:reward_campaign_feed_atom", - # "core:campaign_feed_discord", - # "core:game_feed_discord", - # "core:organization_feed_discord", - # "core:reward_campaign_feed_discord", - # Twitch pages - "twitch:dashboard", - "twitch:badge_list", - "twitch:campaign_list", - "twitch:channel_list", - "twitch:emote_gallery", - "twitch:games_grid", - "twitch:games_list", - "twitch:org_list", - "twitch:reward_campaign_list", - # Kick pages - "kick:dashboard", - "kick:campaign_list", - "kick:game_list", - "kick:category_list", - "kick:organization_list", - # Kick RSS/Atom feeds - # "kick:campaign_feed", - # "kick:game_feed", - # "kick:category_feed", - # "kick:organization_feed", - # "kick:campaign_feed_atom", - # "kick:game_feed_atom", - # "kick:category_feed_atom", - # "kick:organization_feed_atom", - # "kick:campaign_feed_discord", - # "kick:game_feed_discord", - # "kick:category_feed_discord", - # "kick:organization_feed_discord", - # YouTube - "youtube:index", + # `request` is unused but required by Django's view signature. + base_url: str = _build_base_url() + sitemap_urls: list[dict[str, str]] = [ + {"loc": f"{base_url}/"}, + {"loc": f"{base_url}/twitch/"}, + {"loc": f"{base_url}/twitch/campaigns/"}, + {"loc": f"{base_url}/twitch/games/"}, + {"loc": f"{base_url}/kick/"}, + {"loc": f"{base_url}/youtube/"}, + {"loc": f"{base_url}/about/"}, + {"loc": f"{base_url}/robots.txt"}, ] - - sitemap_urls: list[dict[str, str]] = [] - for route_name in static_route_names: - url = reverse(route_name) - sitemap_urls.append({"url": f"{base_url}{url}"}) - xml_content: str = _render_urlset_xml(sitemap_urls) return HttpResponse(xml_content, content_type="application/xml") @@ -323,14 +281,15 @@ def sitemap_twitch_channels_view(request: HttpRequest) -> HttpResponse: Returns: HttpResponse: The rendered sitemap XML. """ - base_url: str = _build_base_url(request) + # `request` is unused but required by Django's view signature. + base_url: str = _build_base_url() sitemap_urls: list[dict[str, str]] = [] channels: QuerySet[Channel] = Channel.objects.all() for channel in channels: resource_url: str = reverse("twitch:channel_detail", args=[channel.twitch_id]) full_url: str = f"{base_url}{resource_url}" - entry: dict[str, str] = {"url": full_url} + entry: dict[str, str] = {"loc": full_url} if channel.updated_at: entry["lastmod"] = channel.updated_at.isoformat() sitemap_urls.append(entry) @@ -349,7 +308,8 @@ def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse: Returns: HttpResponse: The rendered sitemap XML. """ - base_url: str = _build_base_url(request) + # `request` is unused but required by Django's view signature. + base_url: str = _build_base_url() sitemap_urls: list[dict[str, str]] = [] campaigns: QuerySet[DropCampaign] = DropCampaign.objects.filter( @@ -358,7 +318,7 @@ def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse: for campaign in campaigns: resource_url: str = reverse("twitch:campaign_detail", args=[campaign.twitch_id]) full_url: str = f"{base_url}{resource_url}" - campaign_url_entry: dict[str, str] = {"url": full_url} + campaign_url_entry: dict[str, str] = {"loc": full_url} if campaign.updated_at: campaign_url_entry["lastmod"] = campaign.updated_at.isoformat() sitemap_urls.append(campaign_url_entry) @@ -371,7 +331,7 @@ def sitemap_twitch_drops_view(request: HttpRequest) -> HttpResponse: ) full_url: str = f"{base_url}{resource_url}" - reward_campaign_url_entry: dict[str, str] = {"url": full_url} + reward_campaign_url_entry: dict[str, str] = {"loc": full_url} if reward_campaign.updated_at: reward_campaign_url_entry["lastmod"] = ( reward_campaign.updated_at.isoformat() @@ -393,14 +353,15 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse: Returns: HttpResponse: The rendered sitemap XML. """ - base_url: str = _build_base_url(request) + # `request` is unused but required by Django's view signature. + base_url: str = _build_base_url() sitemap_urls: list[dict[str, str]] = [] games: QuerySet[Game] = Game.objects.all() for game in games: resource_url: str = reverse("twitch:game_detail", args=[game.twitch_id]) full_url: str = f"{base_url}{resource_url}" - entry: dict[str, str] = {"url": full_url} + entry: dict[str, str] = {"loc": full_url} if game.updated_at: entry["lastmod"] = game.updated_at.isoformat() @@ -411,7 +372,7 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse: for org in orgs: resource_url: str = reverse("twitch:organization_detail", args=[org.twitch_id]) full_url: str = f"{base_url}{resource_url}" - entry: dict[str, str] = {"url": full_url} + entry: dict[str, str] = {"loc": full_url} if org.updated_at: entry["lastmod"] = org.updated_at.isoformat() @@ -422,10 +383,10 @@ def sitemap_twitch_others_view(request: HttpRequest) -> HttpResponse: for badge_set in badge_sets: resource_url = reverse("twitch:badge_set_detail", args=[badge_set.set_id]) full_url = f"{base_url}{resource_url}" - sitemap_urls.append({"url": full_url}) + sitemap_urls.append({"loc": full_url}) # Emotes currently don't have individual detail pages, but keep a listing here. - sitemap_urls.append({"url": f"{base_url}/emotes/"}) + sitemap_urls.append({"loc": f"{base_url}/emotes/"}) xml_content: str = _render_urlset_xml(sitemap_urls) return HttpResponse(xml_content, content_type="application/xml") @@ -441,7 +402,8 @@ def sitemap_kick_view(request: HttpRequest) -> HttpResponse: Returns: HttpResponse: The rendered sitemap XML. """ - base_url: str = _build_base_url(request) + # `request` is unused but required by Django's view signature. + base_url: str = _build_base_url() sitemap_urls: list[dict[str, str]] = [] kick_campaigns: QuerySet[KickDropCampaign] = KickDropCampaign.objects.filter( @@ -450,19 +412,11 @@ def sitemap_kick_view(request: HttpRequest) -> HttpResponse: for campaign in kick_campaigns: resource_url: str = reverse("kick:campaign_detail", args=[campaign.kick_id]) full_url: str = f"{base_url}{resource_url}" - entry: dict[str, str] = {"url": full_url} - + entry: dict[str, str] = {"loc": full_url} if campaign.updated_at: entry["lastmod"] = campaign.updated_at.isoformat() - sitemap_urls.append(entry) - # Include Kick organizations and game list pages - sitemap_urls.extend(( - {"url": f"{base_url}/kick/organizations/"}, - {"url": f"{base_url}/kick/games/"}, - )) - xml_content: str = _render_urlset_xml(sitemap_urls) return HttpResponse(xml_content, content_type="application/xml") @@ -477,10 +431,10 @@ def sitemap_youtube_view(request: HttpRequest) -> HttpResponse: Returns: HttpResponse: The rendered sitemap XML. """ - base_url: str = _build_base_url(request) - + # `request` is unused but required by Django's view signature. + base_url: str = _build_base_url() sitemap_urls: list[dict[str, str]] = [ - {"url": f"{base_url}{reverse('youtube:index')}"}, + {"loc": f"{base_url}{reverse('youtube:index')}"}, ] xml_content: str = _render_urlset_xml(sitemap_urls) @@ -775,7 +729,7 @@ def dataset_backups_view(request: HttpRequest) -> HttpResponse: def dataset_backup_download_view( - request: HttpRequest, # noqa: ARG001 + request: HttpRequest, relative_path: str, ) -> FileResponse: """Download a dataset backup from the data directory. diff --git a/pyproject.toml b/pyproject.toml index 8a42e43..28e688f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,7 @@ dev = [ [tool.pytest.ini_options] DJANGO_SETTINGS_MODULE = "config.settings" python_files = ["test_*.py", "*_test.py"] -addopts = "-n 4" +addopts = "" filterwarnings = [ "ignore:Parsing dates involving a day of month without a year specified is ambiguous:DeprecationWarning", ] @@ -87,6 +87,7 @@ lint.ignore = [ "PLR0912", # Checks for functions or methods with too many branches, including (nested) if, elif, and else branches, for loops, try-except clauses, and match and case statements. "PLR6301", # Checks for the presence of unused self parameter in methods definitions. "RUF012", # Checks for mutable default values in class attributes. + "ARG001", # Checks for the presence of unused arguments in function definitions. # Conflicting lint rules when using Ruff's formatter # https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules diff --git a/twitch/models.py b/twitch/models.py index 6e85870..1ed2a57 100644 --- a/twitch/models.py +++ b/twitch/models.py @@ -2,6 +2,7 @@ import logging from typing import TYPE_CHECKING import auto_prefetch +from django.conf import settings from django.contrib.postgres.indexes import GinIndex from django.db import models from django.urls import reverse @@ -64,7 +65,7 @@ class Organization(auto_prefetch.Model): def feed_description(self: Organization) -> str: """Return a description of the organization for RSS feeds.""" name: str = self.name or "Unknown Organization" - url: str = reverse("twitch:organization_detail", args=[self.twitch_id]) + url: str = f"{settings.BASE_URL}{reverse('twitch:organization_detail', args=[self.twitch_id])}" return format_html( '

New Twitch organization added to TTVDrops:

\n

{}

', diff --git a/twitch/tests/test_feeds.py b/twitch/tests/test_feeds.py index cb1fdec..b69e9cb 100644 --- a/twitch/tests/test_feeds.py +++ b/twitch/tests/test_feeds.py @@ -177,11 +177,12 @@ class RSSFeedTestCase(TestCase): assert 'rel="self"' in content, msg msg: str = f"Expected self link to point to campaign feed URL, got: {content}" - assert 'href="http://testserver/atom/campaigns/"' in content, msg + assert 'href="https://ttvdrops.lovinator.space/atom/campaigns/"' in content, msg msg: str = f"Expected entry ID to be the campaign URL, got: {content}" assert ( - "http://testserver/twitch/campaigns/test-campaign-123/" in content + "https://ttvdrops.lovinator.space/twitch/campaigns/test-campaign-123/" + in content ), msg def test_all_atom_feeds_use_url_ids_and_correct_self_links(self) -> None: @@ -190,27 +191,27 @@ class RSSFeedTestCase(TestCase): ( "core:campaign_feed_atom", {}, - f"http://testserver{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}", + f"https://ttvdrops.lovinator.space{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}", ), ( "core:game_feed_atom", {}, - f"http://testserver{reverse('twitch:game_detail', args=[self.game.twitch_id])}", + f"https://ttvdrops.lovinator.space{reverse('twitch:game_detail', args=[self.game.twitch_id])}", ), ( "core:game_campaign_feed_atom", {"twitch_id": self.game.twitch_id}, - f"http://testserver{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}", + f"https://ttvdrops.lovinator.space{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}", ), ( "core:organization_feed_atom", {}, - f"http://testserver{reverse('twitch:organization_detail', args=[self.org.twitch_id])}", + f"https://ttvdrops.lovinator.space{reverse('twitch:organization_detail', args=[self.org.twitch_id])}", ), ( "core:reward_campaign_feed_atom", {}, - f"http://testserver{reverse('twitch:reward_campaign_detail', args=[self.reward_campaign.twitch_id])}", + f"https://ttvdrops.lovinator.space{reverse('twitch:reward_campaign_detail', args=[self.reward_campaign.twitch_id])}", ), ] @@ -221,7 +222,7 @@ class RSSFeedTestCase(TestCase): assert response.status_code == 200 content: str = response.content.decode("utf-8") - expected_self_link: str = f'href="http://testserver{url}"' + expected_self_link: str = f'href="https://ttvdrops.lovinator.space{url}"' msg: str = f"Expected self link in Atom feed {url_name}, got: {content}" assert 'rel="self"' in content, msg @@ -317,7 +318,7 @@ class RSSFeedTestCase(TestCase): msg: str = ( f"Expected absolute media enclosure URLs for {url}, got: {content}" ) - assert "http://testserver/media/" in content, msg + assert "https://ttvdrops.lovinator.space/media/" in content, msg assert 'url="/media/' not in content, msg assert 'href="/media/' not in content, msg @@ -1321,27 +1322,27 @@ class DiscordFeedTestCase(TestCase): ( "core:campaign_feed_discord", {}, - f"http://testserver{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}", + f"https://ttvdrops.lovinator.space{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}", ), ( "core:game_feed_discord", {}, - f"http://testserver{reverse('twitch:game_detail', args=[self.game.twitch_id])}", + f"https://ttvdrops.lovinator.space{reverse('twitch:game_detail', args=[self.game.twitch_id])}", ), ( "core:game_campaign_feed_discord", {"twitch_id": self.game.twitch_id}, - f"http://testserver{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}", + f"https://ttvdrops.lovinator.space{reverse('twitch:campaign_detail', args=[self.campaign.twitch_id])}", ), ( "core:organization_feed_discord", {}, - f"http://testserver{reverse('twitch:organization_detail', args=[self.org.twitch_id])}", + f"https://ttvdrops.lovinator.space{reverse('twitch:organization_detail', args=[self.org.twitch_id])}", ), ( "core:reward_campaign_feed_discord", {}, - f"http://testserver{reverse('twitch:reward_campaign_detail', args=[self.reward_campaign.twitch_id])}", + f"https://ttvdrops.lovinator.space{reverse('twitch:reward_campaign_detail', args=[self.reward_campaign.twitch_id])}", ), ] @@ -1352,7 +1353,7 @@ class DiscordFeedTestCase(TestCase): assert response.status_code == 200 content: str = response.content.decode("utf-8") - expected_self_link: str = f'href="http://testserver{url}"' + expected_self_link: str = f'href="https://ttvdrops.lovinator.space{url}"' msg: str = f"Expected self link in Discord feed {url_name}, got: {content}" assert 'rel="self"' in content, msg diff --git a/twitch/tests/test_views.py b/twitch/tests/test_views.py index 6186662..11ac98c 100644 --- a/twitch/tests/test_views.py +++ b/twitch/tests/test_views.py @@ -41,6 +41,12 @@ if TYPE_CHECKING: from twitch.views import Page +@pytest.fixture(autouse=True) +def apply_base_url_override(settings: object) -> None: + """Ensure BASE_URL is globally overridden for all tests.""" + settings.BASE_URL = "https://ttvdrops.lovinator.space" + + @pytest.mark.django_db class TestSearchView: """Tests for the search_view function.""" @@ -1562,14 +1568,14 @@ class TestSitemapView: # Check for the homepage and a few key list views across apps. assert ( - "http://testserver/" in content + "https://ttvdrops.lovinator.space/" in content or "http://localhost:8000/" in content ) - assert "http://testserver/twitch/" in content - assert "http://testserver/kick/" in content - assert "http://testserver/youtube/" in content - assert "http://testserver/twitch/campaigns/" in content - assert "http://testserver/twitch/games/" in content + assert "https://ttvdrops.lovinator.space/twitch/" in content + assert "https://ttvdrops.lovinator.space/kick/" in content + assert "https://ttvdrops.lovinator.space/youtube/" in content + assert "https://ttvdrops.lovinator.space/twitch/campaigns/" in content + assert "https://ttvdrops.lovinator.space/twitch/games/" in content def test_sitemap_contains_game_detail_pages( self, @@ -1605,13 +1611,13 @@ class TestSitemapView: response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-twitch-drops.xml") content: str = response.content.decode() - active_loc: str = f"http://testserver/twitch/campaigns/{active_campaign.twitch_id}/" + active_loc: str = f"https://ttvdrops.lovinator.space/twitch/campaigns/{active_campaign.twitch_id}/" active_index: int = content.find(active_loc) assert active_index != -1 active_end: int = content.find("", active_index) assert active_end != -1 - inactive_loc: str = f"http://testserver/twitch/campaigns/{inactive_campaign.twitch_id}/" + inactive_loc: str = f"https://ttvdrops.lovinator.space/twitch/campaigns/{inactive_campaign.twitch_id}/" inactive_index: int = content.find(inactive_loc) assert inactive_index != -1 inactive_end: int = content.find("", inactive_index) @@ -1629,17 +1635,13 @@ class TestSitemapView: response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-kick.xml") content: str = response.content.decode() - active_loc: str = ( - f"http://testserver/kick/campaigns/{active_campaign.kick_id}/" - ) + active_loc: str = f"https://ttvdrops.lovinator.space/kick/campaigns/{active_campaign.kick_id}/" active_index: int = content.find(active_loc) assert active_index != -1 active_end: int = content.find("", active_index) assert active_end != -1 - inactive_loc: str = ( - f"http://testserver/kick/campaigns/{inactive_campaign.kick_id}/" - ) + inactive_loc: str = f"https://ttvdrops.lovinator.space/kick/campaigns/{inactive_campaign.kick_id}/" inactive_index: int = content.find(inactive_loc) assert inactive_index != -1 inactive_end: int = content.find("", inactive_index) @@ -1972,7 +1974,7 @@ class TestImageObjectStructuredData: assert img["creator"] == { "@type": "Organization", "name": org.name, - "url": f"http://testserver{reverse('twitch:organization_detail', args=[org.twitch_id])}", + "url": f"https://ttvdrops.lovinator.space{reverse('twitch:organization_detail', args=[org.twitch_id])}", } def test_game_schema_no_image_when_no_box_art( @@ -2084,7 +2086,7 @@ class TestImageObjectStructuredData: assert img["creator"] == { "@type": "Organization", "name": org.name, - "url": f"http://testserver{reverse('twitch:organization_detail', args=[org.twitch_id])}", + "url": f"https://ttvdrops.lovinator.space{reverse('twitch:organization_detail', args=[org.twitch_id])}", } def test_campaign_schema_no_image_when_no_image_url(