Archive Twitch chat badges

This commit is contained in:
Joakim Hellsén 2026-01-16 00:28:25 +01:00
commit 6842581656
No known key found for this signature in database
14 changed files with 1394 additions and 1 deletions

View file

@ -0,0 +1,199 @@
"""Tests for chat badge views."""
from __future__ import annotations
from typing import TYPE_CHECKING
import pytest
from django.urls import reverse
from twitch.models import ChatBadge
from twitch.models import ChatBadgeSet
if TYPE_CHECKING:
from django.test import Client
@pytest.mark.django_db
class TestBadgeListView:
"""Tests for the badge list view."""
def test_badge_list_empty(self, client: Client) -> None:
"""Test badge list view with no badges."""
response = client.get(reverse("twitch:badge_list"))
assert response.status_code == 200
assert "No badge sets found" in response.content.decode()
def test_badge_list_displays_sets(self, client: Client) -> None:
"""Test that badge sets are displayed."""
badge_set1 = ChatBadgeSet.objects.create(set_id="vip")
badge_set2 = ChatBadgeSet.objects.create(set_id="subscriber")
response = client.get(reverse("twitch:badge_list"))
assert response.status_code == 200
content = response.content.decode()
assert badge_set1.set_id in content
assert badge_set2.set_id in content
def test_badge_list_displays_badge_count(self, client: Client) -> None:
"""Test that badge version count is displayed."""
badge_set = ChatBadgeSet.objects.create(set_id="bits")
ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="Bits 1",
description="1 Bit",
)
ChatBadge.objects.create(
badge_set=badge_set,
badge_id="100",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="Bits 100",
description="100 Bits",
)
response = client.get(reverse("twitch:badge_list"))
assert response.status_code == 200
content = response.content.decode()
# Should show version count (the template uses "versions" not "version")
assert "2" in content
assert "versions" in content
@pytest.mark.django_db
class TestBadgeSetDetailView:
"""Tests for the badge set detail view."""
def test_badge_set_detail_not_found(self, client: Client) -> None:
"""Test 404 when badge set doesn't exist."""
response = client.get(reverse("twitch:badge_set_detail", kwargs={"set_id": "nonexistent"}))
assert response.status_code == 404
def test_badge_set_detail_displays_badges(self, client: Client) -> None:
"""Test that badge versions are displayed."""
badge_set = ChatBadgeSet.objects.create(set_id="moderator")
badge = ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="Moderator",
description="Channel Moderator",
click_action="visit_url",
click_url="https://help.twitch.tv",
)
response = client.get(reverse("twitch:badge_set_detail", kwargs={"set_id": "moderator"}))
assert response.status_code == 200
content = response.content.decode()
assert badge.title in content
assert badge.description in content
assert badge.badge_id in content
assert badge.image_url_4x in content
def test_badge_set_detail_displays_metadata(self, client: Client) -> None:
"""Test that badge set metadata is displayed."""
badge_set = ChatBadgeSet.objects.create(set_id="vip")
ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="VIP",
description="VIP Badge",
)
response = client.get(reverse("twitch:badge_set_detail", kwargs={"set_id": "vip"}))
assert response.status_code == 200
content = response.content.decode()
assert "vip" in content
assert "Total Versions:" in content
assert "1" in content
def test_badge_set_detail_json_data(self, client: Client) -> None:
"""Test that JSON data is displayed."""
badge_set = ChatBadgeSet.objects.create(set_id="test_set")
ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="Test",
description="Test Badge",
)
response = client.get(reverse("twitch:badge_set_detail", kwargs={"set_id": "test_set"}))
assert response.status_code == 200
content = response.content.decode()
# Should include JSON data section
assert "Badge Set Data (JSON)" in content
assert "test_set" in content
@pytest.mark.django_db
class TestBadgeSearch:
"""Tests for badge search functionality."""
def test_search_finds_badge_sets(self, client: Client) -> None:
"""Test that search finds badge sets."""
ChatBadgeSet.objects.create(set_id="vip")
ChatBadgeSet.objects.create(set_id="subscriber")
response = client.get(reverse("twitch:search"), {"q": "vip"})
assert response.status_code == 200
content = response.content.decode()
assert "Badge Sets" in content
assert "vip" in content
def test_search_finds_badges_by_title(self, client: Client) -> None:
"""Test that search finds badges by title."""
badge_set = ChatBadgeSet.objects.create(set_id="test")
ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="Moderator Badge",
description="Test description",
)
response = client.get(reverse("twitch:search"), {"q": "Moderator"})
assert response.status_code == 200
content = response.content.decode()
assert "Chat Badges" in content
assert "Moderator Badge" in content
def test_search_finds_badges_by_description(self, client: Client) -> None:
"""Test that search finds badges by description."""
badge_set = ChatBadgeSet.objects.create(set_id="test")
ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="Test Badge",
description="Unique description text",
)
response = client.get(reverse("twitch:search"), {"q": "Unique description"})
assert response.status_code == 200
content = response.content.decode()
assert "Chat Badges" in content or "Test Badge" in content

View file

@ -0,0 +1,314 @@
"""Tests for chat badge models and functionality."""
from __future__ import annotations
import pytest
from django.db import IntegrityError
from pydantic import ValidationError
from twitch.models import ChatBadge
from twitch.models import ChatBadgeSet
from twitch.schemas import ChatBadgeSetSchema
from twitch.schemas import ChatBadgeVersionSchema
from twitch.schemas import GlobalChatBadgesResponse
@pytest.mark.django_db
class TestChatBadgeSetModel:
"""Tests for the ChatBadgeSet model."""
def test_create_badge_set(self) -> None:
"""Test creating a new badge set."""
badge_set = ChatBadgeSet.objects.create(set_id="vip")
assert badge_set.set_id == "vip"
assert badge_set.added_at is not None
assert badge_set.updated_at is not None
assert str(badge_set) == "vip"
def test_unique_set_id(self) -> None:
"""Test that set_id must be unique."""
ChatBadgeSet.objects.create(set_id="vip")
with pytest.raises(IntegrityError):
ChatBadgeSet.objects.create(set_id="vip")
def test_badge_set_ordering(self) -> None:
"""Test that badge sets are ordered by set_id."""
ChatBadgeSet.objects.create(set_id="subscriber")
ChatBadgeSet.objects.create(set_id="bits")
ChatBadgeSet.objects.create(set_id="vip")
badge_sets = list(ChatBadgeSet.objects.all())
assert badge_sets[0].set_id == "bits"
assert badge_sets[1].set_id == "subscriber"
assert badge_sets[2].set_id == "vip"
@pytest.mark.django_db
class TestChatBadgeModel:
"""Tests for the ChatBadge model."""
def test_create_badge(self) -> None:
"""Test creating a new badge."""
badge_set = ChatBadgeSet.objects.create(set_id="vip")
badge = ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="VIP",
description="VIP Badge",
click_action="visit_url",
click_url="https://help.twitch.tv",
)
assert badge.badge_set == badge_set
assert badge.badge_id == "1"
assert badge.title == "VIP"
assert badge.description == "VIP Badge"
assert badge.click_action == "visit_url"
assert badge.click_url == "https://help.twitch.tv"
assert str(badge) == "vip/1: VIP"
def test_unique_badge_set_and_id(self) -> None:
"""Test that badge_set and badge_id combination must be unique."""
badge_set = ChatBadgeSet.objects.create(set_id="vip")
ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="VIP",
description="VIP Badge",
)
with pytest.raises(IntegrityError):
ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="VIP Duplicate",
description="Duplicate Badge",
)
def test_different_badge_ids_same_set(self) -> None:
"""Test that different badge_ids can exist in the same set."""
badge_set = ChatBadgeSet.objects.create(set_id="bits")
badge1 = ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="Bits 1",
description="1 Bit",
)
badge2 = ChatBadge.objects.create(
badge_set=badge_set,
badge_id="100",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="Bits 100",
description="100 Bits",
)
assert badge1.badge_id == "1"
assert badge2.badge_id == "100"
assert badge1.badge_set == badge2.badge_set
def test_nullable_click_fields(self) -> None:
"""Test that click_action and click_url can be null."""
badge_set = ChatBadgeSet.objects.create(set_id="moderator")
badge = ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="Moderator",
description="Channel Moderator",
click_action=None,
click_url=None,
)
assert badge.click_action is None
assert badge.click_url is None
def test_badge_cascade_delete(self) -> None:
"""Test that badges are deleted when their badge set is deleted."""
badge_set = ChatBadgeSet.objects.create(set_id="test_set")
ChatBadge.objects.create(
badge_set=badge_set,
badge_id="1",
image_url_1x="https://example.com/1x.png",
image_url_2x="https://example.com/2x.png",
image_url_4x="https://example.com/4x.png",
title="Test Badge",
description="Test Description",
)
assert ChatBadge.objects.filter(badge_set=badge_set).count() == 1
badge_set.delete()
assert ChatBadge.objects.filter(badge_set__set_id="test_set").count() == 0
class TestChatBadgeSchemas:
"""Tests for chat badge Pydantic schemas."""
def test_chat_badge_version_schema_valid(self) -> None:
"""Test that ChatBadgeVersionSchema validates correct data."""
data = {
"id": "1",
"image_url_1x": "https://static-cdn.jtvnw.net/badges/v1/example/1",
"image_url_2x": "https://static-cdn.jtvnw.net/badges/v1/example/2",
"image_url_4x": "https://static-cdn.jtvnw.net/badges/v1/example/3",
"title": "VIP",
"description": "VIP Badge",
"click_action": "visit_url",
"click_url": "https://help.twitch.tv",
}
schema = ChatBadgeVersionSchema.model_validate(data)
assert schema.badge_id == "1"
assert schema.title == "VIP"
assert schema.click_action == "visit_url"
def test_chat_badge_version_schema_nullable_fields(self) -> None:
"""Test that nullable fields in ChatBadgeVersionSchema work correctly."""
data = {
"id": "1",
"image_url_1x": "https://static-cdn.jtvnw.net/badges/v1/example/1",
"image_url_2x": "https://static-cdn.jtvnw.net/badges/v1/example/2",
"image_url_4x": "https://static-cdn.jtvnw.net/badges/v1/example/3",
"title": "VIP",
"description": "VIP Badge",
"click_action": None,
"click_url": None,
}
schema = ChatBadgeVersionSchema.model_validate(data)
assert schema.click_action is None
assert schema.click_url is None
def test_chat_badge_version_schema_missing_required(self) -> None:
"""Test that ChatBadgeVersionSchema raises error on missing required fields."""
data = {
"id": "1",
"title": "VIP",
# Missing required image URLs and description
}
with pytest.raises(ValidationError):
ChatBadgeVersionSchema.model_validate(data)
def test_chat_badge_set_schema_valid(self) -> None:
"""Test that ChatBadgeSetSchema validates correct data."""
data = {
"set_id": "vip",
"versions": [
{
"id": "1",
"image_url_1x": "https://static-cdn.jtvnw.net/badges/v1/example/1",
"image_url_2x": "https://static-cdn.jtvnw.net/badges/v1/example/2",
"image_url_4x": "https://static-cdn.jtvnw.net/badges/v1/example/3",
"title": "VIP",
"description": "VIP Badge",
"click_action": "visit_url",
"click_url": "https://help.twitch.tv",
},
],
}
schema = ChatBadgeSetSchema.model_validate(data)
assert schema.set_id == "vip"
assert len(schema.versions) == 1
assert schema.versions[0].badge_id == "1"
def test_chat_badge_set_schema_multiple_versions(self) -> None:
"""Test that ChatBadgeSetSchema handles multiple badge versions."""
data = {
"set_id": "bits",
"versions": [
{
"id": "1",
"image_url_1x": "https://example.com/1/1x.png",
"image_url_2x": "https://example.com/1/2x.png",
"image_url_4x": "https://example.com/1/4x.png",
"title": "Bits 1",
"description": "1 Bit",
"click_action": None,
"click_url": None,
},
{
"id": "100",
"image_url_1x": "https://example.com/100/1x.png",
"image_url_2x": "https://example.com/100/2x.png",
"image_url_4x": "https://example.com/100/4x.png",
"title": "Bits 100",
"description": "100 Bits",
"click_action": None,
"click_url": None,
},
],
}
schema = ChatBadgeSetSchema.model_validate(data)
assert schema.set_id == "bits"
assert len(schema.versions) == 2
assert schema.versions[0].badge_id == "1"
assert schema.versions[1].badge_id == "100"
def test_global_chat_badges_response_valid(self) -> None:
"""Test that GlobalChatBadgesResponse validates correct API response."""
data = {
"data": [
{
"set_id": "vip",
"versions": [
{
"id": "1",
"image_url_1x": "https://static-cdn.jtvnw.net/badges/v1/example/1",
"image_url_2x": "https://static-cdn.jtvnw.net/badges/v1/example/2",
"image_url_4x": "https://static-cdn.jtvnw.net/badges/v1/example/3",
"title": "VIP",
"description": "VIP Badge",
"click_action": "visit_url",
"click_url": "https://help.twitch.tv",
},
],
},
],
}
response = GlobalChatBadgesResponse.model_validate(data)
assert len(response.data) == 1
assert response.data[0].set_id == "vip"
def test_global_chat_badges_response_empty(self) -> None:
"""Test that GlobalChatBadgesResponse validates empty response."""
data = {"data": []}
response = GlobalChatBadgesResponse.model_validate(data)
assert len(response.data) == 0
def test_chat_badge_schema_extra_forbidden(self) -> None:
"""Test that extra fields are forbidden in schemas."""
data = {
"id": "1",
"image_url_1x": "https://example.com/1x.png",
"image_url_2x": "https://example.com/2x.png",
"image_url_4x": "https://example.com/4x.png",
"title": "VIP",
"description": "VIP Badge",
"extra_field": "should_fail", # This should cause validation to fail
}
with pytest.raises(ValidationError) as exc_info:
ChatBadgeVersionSchema.model_validate(data)
# Check that the error is about the extra field
assert "extra_field" in str(exc_info.value)