Refactor DropCampaign ACL check and add regression tests for campaign details import

This commit is contained in:
Joakim Hellsén 2026-01-11 23:57:46 +01:00
commit 9f34641e9b
No known key found for this signature in database
3 changed files with 335 additions and 3 deletions

265
example.json Normal file
View file

@ -0,0 +1,265 @@
[
{
"data": {
"user": {
"id": "17658559",
"dropCampaign": {
"id": "3b965979-ecd2-11f0-876e-0a58a9feac02",
"self": {
"isAccountConnected": true,
"__typename": "DropCampaignSelfEdge"
},
"allow": {
"channels": null,
"isEnabled": false,
"__typename": "DropCampaignACL"
},
"accountLinkURL": "https://link.smite2.com/",
"description": "Viewers will receive 50 Wandering Market Coins for each two hours spent viewing participating streams. Watch to earn 7 drops for a total of 350 Wandering Market Coins for the week!",
"detailsURL": "https://www.smite2.com/news/closed-alpha-twitch-drops/",
"endAt": "2026-01-17T10:58:59.999Z",
"eventBasedDrops": [],
"game": {
"id": "2094865572",
"slug": "smite-2",
"displayName": "SMITE 2",
"__typename": "Game"
},
"imageURL": "https://static-cdn.jtvnw.net/twitch-quests-assets/CAMPAIGN/47db66e8-933c-484f-ab5a-30ba09093098.png",
"name": "Jan Drops Week 2",
"owner": {
"id": "51a157a0-674a-4863-b120-7bb6ee2466a8",
"name": "Hi-Rez Studios",
"__typename": "Organization"
},
"startAt": "2026-01-10T11:00:00Z",
"status": "ACTIVE",
"timeBasedDrops": [
{
"id": "933c8f91-ecd2-11f0-b3fd-0a58a9feac02",
"requiredSubs": 0,
"benefitEdges": [
{
"benefit": {
"id": "ccb3fb7f-e59b-11ef-aef0-0a58a9feac02",
"createdAt": "2025-02-07T21:37:58.881Z",
"entitlementLimit": 1,
"game": {
"id": "2094865572",
"name": "SMITE 2",
"__typename": "Game"
},
"imageAssetURL": "https://static-cdn.jtvnw.net/twitch-quests-assets/REWARD/903496ad-de97-41ff-ad97-12f099e20ea8.jpeg",
"isIosAvailable": false,
"name": "Market Coins Bundle 1",
"ownerOrganization": {
"id": "51a157a0-674a-4863-b120-7bb6ee2466a8",
"name": "Hi-Rez Studios",
"__typename": "Organization"
},
"distributionType": "DIRECT_ENTITLEMENT",
"__typename": "DropBenefit"
},
"entitlementLimit": 1,
"__typename": "DropBenefitEdge"
}
],
"endAt": "2026-01-17T10:58:59.999Z",
"name": "Market Coins Bundle 1",
"preconditionDrops": null,
"requiredMinutesWatched": 120,
"startAt": "2026-01-10T11:00:00Z",
"__typename": "TimeBasedDrop"
},
{
"id": "9909373d-ecd2-11f0-92b1-0a58a9feac02",
"requiredSubs": 0,
"benefitEdges": [
{
"benefit": {
"id": "ccb3fb7f-e59b-11ef-aef0-0a58a9feac02",
"createdAt": "2025-02-07T21:37:58.881Z",
"entitlementLimit": 1,
"game": {
"id": "2094865572",
"name": "SMITE 2",
"__typename": "Game"
},
"imageAssetURL": "https://static-cdn.jtvnw.net/twitch-quests-assets/REWARD/903496ad-de97-41ff-ad97-12f099e20ea8.jpeg",
"isIosAvailable": false,
"name": "Market Coins Bundle 1",
"ownerOrganization": {
"id": "51a157a0-674a-4863-b120-7bb6ee2466a8",
"name": "Hi-Rez Studios",
"__typename": "Organization"
},
"distributionType": "DIRECT_ENTITLEMENT",
"__typename": "DropBenefit"
},
"entitlementLimit": 1,
"__typename": "DropBenefitEdge"
}
],
"endAt": "2026-01-17T10:58:59.999Z",
"name": "Market Coins Bundle 2",
"preconditionDrops": null,
"requiredMinutesWatched": 240,
"startAt": "2026-01-10T11:00:00Z",
"__typename": "TimeBasedDrop"
},
{
"id": "a5289489-ecd2-11f0-b098-0a58a9feac02",
"requiredSubs": 0,
"benefitEdges": [
{
"benefit": {
"id": "ccb3fb7f-e59b-11ef-aef0-0a58a9feac02",
"createdAt": "2025-02-07T21:37:58.881Z",
"entitlementLimit": 1,
"game": {
"id": "2094865572",
"name": "SMITE 2",
"__typename": "Game"
},
"imageAssetURL": "https://static-cdn.jtvnw.net/twitch-quests-assets/REWARD/903496ad-de97-41ff-ad97-12f099e20ea8.jpeg",
"isIosAvailable": false,
"name": "Market Coins Bundle 1",
"ownerOrganization": {
"id": "51a157a0-674a-4863-b120-7bb6ee2466a8",
"name": "Hi-Rez Studios",
"__typename": "Organization"
},
"distributionType": "DIRECT_ENTITLEMENT",
"__typename": "DropBenefit"
},
"entitlementLimit": 1,
"__typename": "DropBenefitEdge"
}
],
"endAt": "2026-01-17T10:58:59.999Z",
"name": "Market Coins Bundle 3",
"preconditionDrops": null,
"requiredMinutesWatched": 360,
"startAt": "2026-01-10T11:00:00Z",
"__typename": "TimeBasedDrop"
},
{
"id": "ab5ea171-ecd2-11f0-9e33-0a58a9feac02",
"requiredSubs": 0,
"benefitEdges": [
{
"benefit": {
"id": "ccb3fb7f-e59b-11ef-aef0-0a58a9feac02",
"createdAt": "2025-02-07T21:37:58.881Z",
"entitlementLimit": 1,
"game": {
"id": "2094865572",
"name": "SMITE 2",
"__typename": "Game"
},
"imageAssetURL": "https://static-cdn.jtvnw.net/twitch-quests-assets/REWARD/903496ad-de97-41ff-ad97-12f099e20ea8.jpeg",
"isIosAvailable": false,
"name": "Market Coins Bundle 1",
"ownerOrganization": {
"id": "51a157a0-674a-4863-b120-7bb6ee2466a8",
"name": "Hi-Rez Studios",
"__typename": "Organization"
},
"distributionType": "DIRECT_ENTITLEMENT",
"__typename": "DropBenefit"
},
"entitlementLimit": 1,
"__typename": "DropBenefitEdge"
}
],
"endAt": "2026-01-17T10:58:59.999Z",
"name": "Market Coins Bundle 4",
"preconditionDrops": null,
"requiredMinutesWatched": 480,
"startAt": "2026-01-10T11:00:00Z",
"__typename": "TimeBasedDrop"
},
{
"id": "b19b7afb-ecd2-11f0-bbd3-0a58a9feac02",
"requiredSubs": 0,
"benefitEdges": [
{
"benefit": {
"id": "ccb3fb7f-e59b-11ef-aef0-0a58a9feac02",
"createdAt": "2025-02-07T21:37:58.881Z",
"entitlementLimit": 1,
"game": {
"id": "2094865572",
"name": "SMITE 2",
"__typename": "Game"
},
"imageAssetURL": "https://static-cdn.jtvnw.net/twitch-quests-assets/REWARD/903496ad-de97-41ff-ad97-12f099e20ea8.jpeg",
"isIosAvailable": false,
"name": "Market Coins Bundle 1",
"ownerOrganization": {
"id": "51a157a0-674a-4863-b120-7bb6ee2466a8",
"name": "Hi-Rez Studios",
"__typename": "Organization"
},
"distributionType": "DIRECT_ENTITLEMENT",
"__typename": "DropBenefit"
},
"entitlementLimit": 1,
"__typename": "DropBenefitEdge"
}
],
"endAt": "2026-01-17T10:58:59.999Z",
"name": "Market Coins Bundle 5",
"preconditionDrops": null,
"requiredMinutesWatched": 600,
"startAt": "2026-01-10T11:00:00Z",
"__typename": "TimeBasedDrop"
},
{
"id": "b82db8e0-ecd2-11f0-8c96-0a58a9feac02",
"requiredSubs": 0,
"benefitEdges": [
{
"benefit": {
"id": "ccb3fb7f-e59b-11ef-aef0-0a58a9feac02",
"createdAt": "2025-02-07T21:37:58.881Z",
"entitlementLimit": 1,
"game": {
"id": "2094865572",
"name": "SMITE 2",
"__typename": "Game"
},
"imageAssetURL": "https://static-cdn.jtvnw.net/twitch-quests-assets/REWARD/903496ad-de97-41ff-ad97-12f099e20ea8.jpeg",
"isIosAvailable": false,
"name": "Market Coins Bundle 1",
"ownerOrganization": {
"id": "51a157a0-674a-4863-b120-7bb6ee2466a8",
"name": "Hi-Rez Studios",
"__typename": "Organization"
},
"distributionType": "DIRECT_ENTITLEMENT",
"__typename": "DropBenefit"
},
"entitlementLimit": 1,
"__typename": "DropBenefitEdge"
}
],
"endAt": "2026-01-17T10:58:59.999Z",
"name": "Market Coins Bundle 6",
"preconditionDrops": null,
"requiredMinutesWatched": 720,
"startAt": "2026-01-10T11:00:00Z",
"__typename": "TimeBasedDrop"
}
],
"__typename": "DropCampaign"
},
"__typename": "User"
}
},
"extensions": {
"durationMilliseconds": 48,
"operationName": "DropCampaignDetails"
}
}
]

View file

@ -694,7 +694,7 @@ class Command(BaseCommand):
) )
# Process allowed channels from the campaign's ACL # Process allowed channels from the campaign's ACL
if drop_campaign.allow and drop_campaign.allow.channels: if drop_campaign.allow:
self._process_allowed_channels( self._process_allowed_channels(
campaign_obj=campaign_obj, campaign_obj=campaign_obj,
allow_schema=drop_campaign.allow, allow_schema=drop_campaign.allow,

View file

@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
import json
from pathlib import Path from pathlib import Path
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@ -7,15 +8,16 @@ from django.test import TestCase
from twitch.management.commands.better_import_drops import Command from twitch.management.commands.better_import_drops import Command
from twitch.models import Channel from twitch.models import Channel
from twitch.models import DropBenefit
from twitch.models import DropCampaign from twitch.models import DropCampaign
from twitch.models import Game from twitch.models import Game
from twitch.models import Organization
from twitch.models import TimeBasedDrop
from twitch.schemas import DropBenefitSchema from twitch.schemas import DropBenefitSchema
if TYPE_CHECKING: if TYPE_CHECKING:
from debug_toolbar.panels.templates.panel import QuerySet from debug_toolbar.panels.templates.panel import QuerySet
from twitch.models import DropBenefit
class GetOrUpdateBenefitTests(TestCase): class GetOrUpdateBenefitTests(TestCase):
"""Tests for the _get_or_update_benefit method in better_import_drops.Command.""" """Tests for the _get_or_update_benefit method in better_import_drops.Command."""
@ -540,3 +542,68 @@ class GameImportTests(TestCase):
game = Game.objects.get(twitch_id="497057") game = Game.objects.get(twitch_id="497057")
assert game.slug == "destiny-2" assert game.slug == "destiny-2"
assert game.display_name == "Destiny 2" assert game.display_name == "Destiny 2"
class ExampleJsonImportTests(TestCase):
"""Regression tests based on the real-world `example.json` payload."""
def test_imports_drop_campaign_details_and_persists_urls(self) -> None:
"""Ensure `imageURL` and other URL-ish fields are persisted from DropCampaignDetails."""
command = Command()
command.pre_fill_cache()
repo_root: Path = Path(__file__).resolve().parents[2]
example_path: Path = repo_root / "example.json"
responses = json.loads(example_path.read_text(encoding="utf-8"))
success, broken_dir = command.process_responses(
responses=responses,
file_path=example_path,
options={},
)
assert success is True
assert broken_dir is None
campaign: DropCampaign = DropCampaign.objects.get(twitch_id="3b965979-ecd2-11f0-876e-0a58a9feac02")
# Core campaign fields
assert campaign.name == "Jan Drops Week 2"
assert "Viewers will receive 50 Wandering Market Coins" in campaign.description
assert campaign.details_url == "https://www.smite2.com/news/closed-alpha-twitch-drops/"
assert campaign.account_link_url == "https://link.smite2.com/"
# The regression: ensure imageURL makes it into DropCampaign.image_url
assert (
campaign.image_url
== "https://static-cdn.jtvnw.net/twitch-quests-assets/CAMPAIGN/47db66e8-933c-484f-ab5a-30ba09093098.png"
)
# Allow ACL normalization
assert campaign.allow_is_enabled is False
assert campaign.allow_channels.count() == 0
# Operation name provenance
assert campaign.operation_name == "DropCampaignDetails"
# Related game/org normalization
game: Game = Game.objects.get(twitch_id="2094865572")
assert game.display_name == "SMITE 2"
assert game.slug == "smite-2"
org: Organization = Organization.objects.get(twitch_id="51a157a0-674a-4863-b120-7bb6ee2466a8")
assert org.name == "Hi-Rez Studios"
assert game.owners.filter(pk=org.pk).exists()
# Drops + benefits
assert TimeBasedDrop.objects.filter(campaign=campaign).count() == 6
first_drop: TimeBasedDrop = TimeBasedDrop.objects.get(twitch_id="933c8f91-ecd2-11f0-b3fd-0a58a9feac02")
assert first_drop.name == "Market Coins Bundle 1"
assert first_drop.required_minutes_watched == 120
assert DropBenefit.objects.count() == 1
benefit: DropBenefit = DropBenefit.objects.get(twitch_id="ccb3fb7f-e59b-11ef-aef0-0a58a9feac02")
assert (
benefit.image_asset_url
== "https://static-cdn.jtvnw.net/twitch-quests-assets/REWARD/903496ad-de97-41ff-ad97-12f099e20ea8.jpeg"
)