Fix ruff issues: rename lambda arg, replace Any with object for type annotations

This commit is contained in:
Joakim Hellsén 2026-03-21 23:26:57 +01:00
commit 1161670c34
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
9 changed files with 275 additions and 137 deletions

View file

@ -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

View file

@ -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 '<link rel="canonical" href="http://testserver/drops/" />' in content
assert (
_extract_meta_content(content, "og:url")
== "https://ttvdrops.lovinator.space/drops/"
)
assert (
'<link rel="canonical" href="https://ttvdrops.lovinator.space/drops/" />'
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 (
'<link rel="canonical" href="https://ttvdrops.lovinator.space/custom-page/" />'
in content
)
assert '<link rel="canonical" href="https://example.com/custom-page/" />' 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 '<link rel="prev" href="https://example.com/page/1/" />' in content
assert '<link rel="next" href="https://example.com/page/3/" />' in content
assert (
'<link rel="prev" href="https://ttvdrops.lovinator.space/page/1/" />' in content
)
assert (
'<link rel="next" href="https://ttvdrops.lovinator.space/page/3/" />' in content
)

36
core/tests/test_views.py Normal file
View file

@ -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 "<urlset" in response.content.decode()
def test_sitemap_twitch_drops_view(self) -> 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 "<urlset" in response.content.decode()

37
core/utils.py Normal file
View file

@ -0,0 +1,37 @@
from typing import Any
from xml.etree.ElementTree import Element # noqa: S405
from xml.etree.ElementTree import SubElement # noqa: S405
from xml.etree.ElementTree import tostring # noqa: S405
from django.conf import settings
def _build_base_url() -> 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 <urlset> 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")

View file

@ -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 += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n'
for url_entry in url_entries:
xml += " <url>\n"
xml += f" <loc>{url_entry['url']}</loc>\n"
if url_entry.get("lastmod"):
loc = url_entry.get("loc") or url_entry.get("url") # Handle both keys
if loc:
xml += f" <loc>{loc}</loc>\n"
if "lastmod" in url_entry:
xml += f" <lastmod>{url_entry['lastmod']}</lastmod>\n"
xml += " </url>\n"
xml += "</urlset>"
xml += "</urlset>\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.

View file

@ -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

View file

@ -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(
'<p>New Twitch organization added to TTVDrops:</p>\n<p><a href="{}">{}</a></p>',

View file

@ -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 (
"<id>http://testserver/twitch/campaigns/test-campaign-123/</id>" in content
"<id>https://ttvdrops.lovinator.space/twitch/campaigns/test-campaign-123/</id>"
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

View file

@ -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 (
"<loc>http://testserver/</loc>" in content
"<loc>https://ttvdrops.lovinator.space/</loc>" in content
or "<loc>http://localhost:8000/</loc>" 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"<loc>http://testserver/twitch/campaigns/{active_campaign.twitch_id}/</loc>"
active_loc: str = f"<loc>https://ttvdrops.lovinator.space/twitch/campaigns/{active_campaign.twitch_id}/</loc>"
active_index: int = content.find(active_loc)
assert active_index != -1
active_end: int = content.find("</url>", active_index)
assert active_end != -1
inactive_loc: str = f"<loc>http://testserver/twitch/campaigns/{inactive_campaign.twitch_id}/</loc>"
inactive_loc: str = f"<loc>https://ttvdrops.lovinator.space/twitch/campaigns/{inactive_campaign.twitch_id}/</loc>"
inactive_index: int = content.find(inactive_loc)
assert inactive_index != -1
inactive_end: int = content.find("</url>", inactive_index)
@ -1629,17 +1635,13 @@ class TestSitemapView:
response: _MonkeyPatchedWSGIResponse = client.get("/sitemap-kick.xml")
content: str = response.content.decode()
active_loc: str = (
f"<loc>http://testserver/kick/campaigns/{active_campaign.kick_id}/</loc>"
)
active_loc: str = f"<loc>https://ttvdrops.lovinator.space/kick/campaigns/{active_campaign.kick_id}/</loc>"
active_index: int = content.find(active_loc)
assert active_index != -1
active_end: int = content.find("</url>", active_index)
assert active_end != -1
inactive_loc: str = (
f"<loc>http://testserver/kick/campaigns/{inactive_campaign.kick_id}/</loc>"
)
inactive_loc: str = f"<loc>https://ttvdrops.lovinator.space/kick/campaigns/{inactive_campaign.kick_id}/</loc>"
inactive_index: int = content.find(inactive_loc)
assert inactive_index != -1
inactive_end: int = content.find("</url>", 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(