Ensure absolute URLs for media enclosures and skip zero-length enclosures in RSS/Atom feeds
All checks were successful
Deploy to Server / deploy (push) Successful in 9s

This commit is contained in:
Joakim Hellsén 2026-03-14 01:59:31 +01:00
commit 11244c669f
Signed by: Joakim Hellsén
SSH key fingerprint: SHA256:/9h/CsExpFp+PRhsfA0xznFx2CGfTT5R/kpuFfUgEQk
2 changed files with 120 additions and 6 deletions

View file

@ -831,10 +831,19 @@ class GameFeed(TTVDropsBaseFeed):
except TypeError, ValueError:
length = 0
if not length:
return []
mime: str = getattr(item, "box_art_mime_type", "")
mime_type: str = mime or "image/jpeg"
return [feedgenerator.Enclosure(image_url, str(length), mime_type)]
return [
feedgenerator.Enclosure(
self._absolute_url(image_url),
str(length),
mime_type,
),
]
return []
def feed_url(self) -> str:
@ -965,10 +974,19 @@ class DropCampaignFeed(TTVDropsBaseFeed):
except TypeError, ValueError:
length = 0
if not length:
return []
mime: str = getattr(item, "image_mime_type", "")
mime_type: str = mime or "image/jpeg"
return [feedgenerator.Enclosure(image_url, str(length), mime_type)]
return [
feedgenerator.Enclosure(
self._absolute_url(image_url),
str(length),
mime_type,
),
]
return []
def feed_url(self) -> str:
@ -1132,10 +1150,19 @@ class GameCampaignFeed(TTVDropsBaseFeed):
except TypeError, ValueError:
length = 0
if not length:
return []
mime: str = getattr(item, "image_mime_type", "")
mime_type: str = mime or "image/jpeg"
return [feedgenerator.Enclosure(image_url, str(length), mime_type)]
return [
feedgenerator.Enclosure(
self._absolute_url(image_url),
str(length),
mime_type,
),
]
return []
def feed_url(self, obj: Game) -> str:
@ -1307,8 +1334,7 @@ class RewardCampaignFeed(TTVDropsBaseFeed):
Returns:
list[feedgenerator.Enclosure]: A list of Enclosure objects if an image URL is available, otherwise an empty list.
"""
# Use image_url as enclosure if available
image_url: str = getattr(item, "image_url", "")
image_url: str = getattr(item, "image_best_url", "")
if image_url:
try:
size: int | None = getattr(item, "image_size_bytes", None)
@ -1316,10 +1342,19 @@ class RewardCampaignFeed(TTVDropsBaseFeed):
except TypeError, ValueError:
length = 0
if not length:
return []
mime: str = getattr(item, "image_mime_type", "")
mime_type: str = mime or "image/jpeg"
return [feedgenerator.Enclosure(image_url, str(length), mime_type)]
return [
feedgenerator.Enclosure(
self._absolute_url(image_url),
str(length),
mime_type,
),
]
return []
def feed_url(self) -> str:

View file

@ -272,6 +272,85 @@ class RSSFeedTestCase(TestCase):
assert 'type="text/xsl"' in content
assert 'media="screen"' in content
def test_campaign_and_game_feeds_use_absolute_media_enclosure_urls(self) -> None:
"""Campaign/game RSS+Atom enclosures should use absolute URLs for local media files."""
self.game.box_art = ""
self.game.box_art_file.save(
"box.png",
ContentFile(b"game-image-bytes"),
save=False,
)
self.game.box_art_size_bytes = len(b"game-image-bytes")
self.game.box_art_mime_type = "image/png"
self.game.save()
self.campaign.image_url = ""
self.campaign.image_file.save(
"campaign.png",
ContentFile(b"campaign-image-bytes"),
save=False,
)
self.campaign.image_size_bytes = len(b"campaign-image-bytes")
self.campaign.image_mime_type = "image/png"
self.campaign.save()
feed_urls: list[str] = [
reverse("twitch:game_feed"),
reverse("twitch:campaign_feed"),
reverse("twitch:game_campaign_feed", args=[self.game.twitch_id]),
reverse("twitch:game_feed_atom"),
reverse("twitch:campaign_feed_atom"),
reverse("twitch:game_campaign_feed_atom", args=[self.game.twitch_id]),
]
for url in feed_urls:
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
assert response.status_code == 200
content: str = response.content.decode("utf-8")
msg: str = (
f"Expected absolute media enclosure URLs for {url}, got: {content}"
)
assert "http://testserver/media/" in content, msg
assert 'url="/media/' not in content, msg
assert 'href="/media/' not in content, msg
def test_all_campaign_game_reward_feeds_skip_enclosures_when_length_unknown(
self,
) -> None:
"""RSS/Atom feeds should not emit enclosure elements with length=0."""
self.game.box_art = "https://example.com/box.png"
self.game.box_art_size_bytes = None
self.game.save()
self.campaign.image_url = "https://example.com/campaign.png"
self.campaign.image_size_bytes = None
self.campaign.save()
self.reward_campaign.image_url = "https://example.com/reward.png"
self.reward_campaign.save()
feed_urls: list[str] = [
reverse("twitch:game_feed"),
reverse("twitch:campaign_feed"),
reverse("twitch:game_campaign_feed", args=[self.game.twitch_id]),
reverse("twitch:reward_campaign_feed"),
reverse("twitch:game_feed_atom"),
reverse("twitch:campaign_feed_atom"),
reverse("twitch:game_campaign_feed_atom", args=[self.game.twitch_id]),
reverse("twitch:reward_campaign_feed_atom"),
]
for url in feed_urls:
response: _MonkeyPatchedWSGIResponse = self.client.get(url)
assert response.status_code == 200
content: str = response.content.decode("utf-8")
msg: str = (
f"Feed {url} unexpectedly emitted a zero-length enclosure: {content}"
)
assert 'length="0"' not in content, msg
def test_game_feed_enclosure_helpers(self) -> None:
"""Helper methods should return values from model fields."""
feed = GameFeed()