diff --git a/twitch/migrations/0023_alter_dropcampaign_url_lengths.py b/twitch/migrations/0023_alter_dropcampaign_url_lengths.py new file mode 100644 index 0000000..10844fe --- /dev/null +++ b/twitch/migrations/0023_alter_dropcampaign_url_lengths.py @@ -0,0 +1,45 @@ +# Generated by Django 6.0.4 on 2026-05-09 + +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + """Allow longer campaign URLs returned by Twitch imports.""" + + dependencies = [ + ("twitch", "0022_dropcampaign_tw_drop_game_end_desc_idx"), + ] + + operations = [ + migrations.AlterField( + model_name="dropcampaign", + name="account_link_url", + field=models.URLField( + blank=True, + default="", + help_text="URL to link a Twitch account for the campaign.", + max_length=2000, + ), + ), + migrations.AlterField( + model_name="dropcampaign", + name="details_url", + field=models.URLField( + blank=True, + default="", + help_text="URL with campaign details.", + max_length=2000, + ), + ), + migrations.AlterField( + model_name="dropcampaign", + name="image_url", + field=models.URLField( + blank=True, + default="", + help_text="URL to an image representing the campaign.", + max_length=2000, + ), + ), + ] diff --git a/twitch/models.py b/twitch/models.py index 943e520..f8f3df4 100644 --- a/twitch/models.py +++ b/twitch/models.py @@ -600,21 +600,21 @@ class DropCampaign(auto_prefetch.Model): details_url = models.URLField( help_text="URL with campaign details.", - max_length=500, + max_length=2000, blank=True, default="", ) account_link_url = models.URLField( help_text="URL to link a Twitch account for the campaign.", - max_length=500, + max_length=2000, blank=True, default="", ) image_url = models.URLField( help_text="URL to an image representing the campaign.", - max_length=500, + max_length=2000, blank=True, default="", ) diff --git a/twitch/tests/test_better_import_drops.py b/twitch/tests/test_better_import_drops.py index 64fda50..c160634 100644 --- a/twitch/tests/test_better_import_drops.py +++ b/twitch/tests/test_better_import_drops.py @@ -857,6 +857,64 @@ class ImporterRobustnessTests(TestCase): campaign = DropCampaign.objects.get(twitch_id="campaign-null-image") assert not campaign.image_url + def test_allows_long_campaign_account_link_url(self) -> None: + """Ensure long Twitch accountLinkURL values fit the campaign schema.""" + command = Command() + long_account_link_url = ( + "https://example.com/link?" + "redirect=https%3A%2F%2Fexample.com%2Fcallback&state=" + ("x" * 520) + ) + + assert len(long_account_link_url) > 500 + assert DropCampaign._meta.get_field("account_link_url").max_length == 2000 # pyright: ignore[reportAttributeAccessIssue] + assert DropCampaign._meta.get_field("details_url").max_length == 2000 # pyright: ignore[reportAttributeAccessIssue] + assert DropCampaign._meta.get_field("image_url").max_length == 2000 # pyright: ignore[reportAttributeAccessIssue] + + payload: dict[str, object] = { + "data": { + "user": { + "id": "123", + "dropCampaign": { + "id": "campaign-long-account-link", + "name": "Long Account Link Campaign", + "description": "", + "startAt": "2026-05-01T00:00:00Z", + "endAt": "2026-05-02T00:00:00Z", + "accountLinkURL": long_account_link_url, + "detailsURL": "https://example.com/details", + "imageURL": "", + "status": "ACTIVE", + "self": { + "isAccountConnected": False, + "__typename": "DropCampaignSelfEdge", + }, + "game": { + "id": "g-long-account-link", + "displayName": "Test Game", + "boxArtURL": "https://example.com/box.png", + "__typename": "Game", + }, + "timeBasedDrops": [], + "__typename": "DropCampaign", + }, + "__typename": "User", + }, + }, + "extensions": {"operationName": "DropCampaignDetails"}, + } + + success, broken_dir = command.process_responses( + responses=[payload], + file_path=Path("long_account_link.json"), + options={}, + ) + + assert success is True + assert broken_dir is None + + campaign = DropCampaign.objects.get(twitch_id="campaign-long-account-link") + assert campaign.account_link_url == long_account_link_url + class ErrorOnlyResponseDetectionTests(TestCase): """Tests for detecting responses that only contain GraphQL errors without data."""