This commit is contained in:
parent
4d53a46850
commit
415dd12fd9
16 changed files with 843 additions and 379 deletions
|
|
@ -2,6 +2,8 @@ from __future__ import annotations
|
|||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Any
|
||||
from unittest import skipIf
|
||||
|
||||
from django.db import connection
|
||||
|
|
@ -17,6 +19,9 @@ from twitch.models import Organization
|
|||
from twitch.models import TimeBasedDrop
|
||||
from twitch.schemas import DropBenefitSchema
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pytest_django.asserts import QuerySet
|
||||
|
||||
|
||||
class GetOrUpdateBenefitTests(TestCase):
|
||||
"""Tests for the _get_or_update_benefit method in better_import_drops.Command."""
|
||||
|
|
@ -24,7 +29,6 @@ class GetOrUpdateBenefitTests(TestCase):
|
|||
def test_defaults_distribution_type_when_missing(self) -> None:
|
||||
"""Ensure importer sets distribution_type to empty string when absent."""
|
||||
command = Command()
|
||||
command.benefit_cache = {}
|
||||
|
||||
benefit_schema: DropBenefitSchema = DropBenefitSchema.model_validate(
|
||||
{
|
||||
|
|
@ -324,7 +328,7 @@ class CampaignStructureDetectionTests(TestCase):
|
|||
command = Command()
|
||||
|
||||
# Inventory format with null dropCampaignsInProgress - should not detect as inventory_campaigns
|
||||
response = {
|
||||
response: dict[str, dict[str, dict[str, str | dict[str, str | None]]]] = {
|
||||
"data": {
|
||||
"currentUser": {
|
||||
"id": "123",
|
||||
|
|
@ -462,15 +466,19 @@ class OperationNameFilteringTests(TestCase):
|
|||
command.process_responses([inventory_payload], Path("inventory.json"), {})
|
||||
|
||||
# Verify we can filter by operation_names with JSON containment
|
||||
viewer_campaigns = DropCampaign.objects.filter(operation_names__contains=["ViewerDropsDashboard"])
|
||||
inventory_campaigns = DropCampaign.objects.filter(operation_names__contains=["Inventory"])
|
||||
viewer_campaigns: QuerySet[DropCampaign, DropCampaign] = DropCampaign.objects.filter(
|
||||
operation_names__contains=["ViewerDropsDashboard"],
|
||||
)
|
||||
inventory_campaigns: QuerySet[DropCampaign, DropCampaign] = DropCampaign.objects.filter(
|
||||
operation_names__contains=["Inventory"],
|
||||
)
|
||||
|
||||
assert len(viewer_campaigns) >= 1
|
||||
assert len(inventory_campaigns) >= 1
|
||||
|
||||
# Verify the correct campaigns are in each list
|
||||
viewer_ids = [c.twitch_id for c in viewer_campaigns]
|
||||
inventory_ids = [c.twitch_id for c in inventory_campaigns]
|
||||
viewer_ids: list[str] = [c.twitch_id for c in viewer_campaigns]
|
||||
inventory_ids: list[str] = [c.twitch_id for c in inventory_campaigns]
|
||||
|
||||
assert "viewer-campaign-1" in viewer_ids
|
||||
assert "inventory-campaign-1" in inventory_ids
|
||||
|
|
@ -532,7 +540,7 @@ class GameImportTests(TestCase):
|
|||
assert success is True
|
||||
assert broken_dir is None
|
||||
|
||||
game = Game.objects.get(twitch_id="497057")
|
||||
game: Game = Game.objects.get(twitch_id="497057")
|
||||
assert game.slug == "destiny-2"
|
||||
assert game.display_name == "Destiny 2"
|
||||
|
||||
|
|
@ -595,10 +603,8 @@ class ExampleJsonImportTests(TestCase):
|
|||
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"
|
||||
)
|
||||
image_url = "https://static-cdn.jtvnw.net/twitch-quests-assets/REWARD/903496ad-de97-41ff-ad97-12f099e20ea8.jpeg"
|
||||
assert benefit.image_asset_url == image_url
|
||||
|
||||
|
||||
class ImporterRobustnessTests(TestCase):
|
||||
|
|
@ -608,7 +614,7 @@ class ImporterRobustnessTests(TestCase):
|
|||
"""Ensure tuple payloads from json_repair don't crash the importer."""
|
||||
command = Command()
|
||||
|
||||
parsed = (
|
||||
parsed: tuple[dict[str, dict[str, dict[str, str | list[Any]]] | dict[str, str]], list[dict[str, str]]] = (
|
||||
{
|
||||
"data": {
|
||||
"currentUser": {
|
||||
|
|
@ -622,7 +628,7 @@ class ImporterRobustnessTests(TestCase):
|
|||
[{"json_repair": "log"}],
|
||||
)
|
||||
|
||||
normalized = command._normalize_responses(parsed)
|
||||
normalized: list[dict[str, Any]] = command._normalize_responses(parsed)
|
||||
assert isinstance(normalized, list)
|
||||
assert len(normalized) == 1
|
||||
assert normalized[0]["extensions"]["operationName"] == "ViewerDropsDashboard"
|
||||
|
|
@ -670,7 +676,7 @@ class ImporterRobustnessTests(TestCase):
|
|||
assert success is True
|
||||
assert broken_dir is None
|
||||
|
||||
campaign = DropCampaign.objects.get(twitch_id="campaign-null-image")
|
||||
campaign: DropCampaign = DropCampaign.objects.get(twitch_id="campaign-null-image")
|
||||
assert not campaign.image_url
|
||||
|
||||
|
||||
|
|
@ -679,7 +685,7 @@ class ErrorOnlyResponseDetectionTests(TestCase):
|
|||
|
||||
def test_detects_error_only_response_with_service_timeout(self) -> None:
|
||||
"""Ensure error-only response with service timeout is detected."""
|
||||
parsed_json = {
|
||||
parsed_json: dict[str, list[dict[str, str | list[str]]]] = {
|
||||
"errors": [
|
||||
{
|
||||
"message": "service timeout",
|
||||
|
|
@ -688,12 +694,13 @@ class ErrorOnlyResponseDetectionTests(TestCase):
|
|||
],
|
||||
}
|
||||
|
||||
result = detect_error_only_response(parsed_json)
|
||||
result: str | None = detect_error_only_response(parsed_json)
|
||||
|
||||
assert result == "error_only: service timeout"
|
||||
|
||||
def test_detects_error_only_response_with_null_data(self) -> None:
|
||||
"""Ensure error-only response with null data field is detected."""
|
||||
parsed_json = {
|
||||
parsed_json: dict[str, list[dict[str, str | list[str]]] | None] = {
|
||||
"errors": [
|
||||
{
|
||||
"message": "internal server error",
|
||||
|
|
@ -703,12 +710,13 @@ class ErrorOnlyResponseDetectionTests(TestCase):
|
|||
"data": None,
|
||||
}
|
||||
|
||||
result = detect_error_only_response(parsed_json)
|
||||
result: str | None = detect_error_only_response(parsed_json)
|
||||
|
||||
assert result == "error_only: internal server error"
|
||||
|
||||
def test_detects_error_only_response_with_empty_data(self) -> None:
|
||||
"""Ensure error-only response with empty data dict is allowed through."""
|
||||
parsed_json = {
|
||||
parsed_json: dict[str, list[dict[str, str]] | dict[str, None]] = {
|
||||
"errors": [
|
||||
{
|
||||
"message": "unauthorized",
|
||||
|
|
@ -717,13 +725,14 @@ class ErrorOnlyResponseDetectionTests(TestCase):
|
|||
"data": {},
|
||||
}
|
||||
|
||||
result = detect_error_only_response(parsed_json)
|
||||
result: str | None = detect_error_only_response(parsed_json)
|
||||
|
||||
# Empty dict {} is considered "data exists" so this should pass
|
||||
assert result is None
|
||||
|
||||
def test_detects_error_only_response_without_data_key(self) -> None:
|
||||
"""Ensure error-only response without data key is detected."""
|
||||
parsed_json = {
|
||||
parsed_json: dict[str, list[dict[str, str]]] = {
|
||||
"errors": [
|
||||
{
|
||||
"message": "missing data",
|
||||
|
|
@ -731,7 +740,7 @@ class ErrorOnlyResponseDetectionTests(TestCase):
|
|||
],
|
||||
}
|
||||
|
||||
result = detect_error_only_response(parsed_json)
|
||||
result: str | None = detect_error_only_response(parsed_json)
|
||||
assert result == "error_only: missing data"
|
||||
|
||||
def test_allows_response_with_both_errors_and_data(self) -> None:
|
||||
|
|
@ -749,12 +758,12 @@ class ErrorOnlyResponseDetectionTests(TestCase):
|
|||
},
|
||||
}
|
||||
|
||||
result = detect_error_only_response(parsed_json)
|
||||
result: str | None = detect_error_only_response(parsed_json)
|
||||
assert result is None
|
||||
|
||||
def test_allows_response_with_no_errors(self) -> None:
|
||||
"""Ensure normal responses without errors are not flagged."""
|
||||
parsed_json = {
|
||||
parsed_json: dict[str, dict[str, dict[str, list[None]]]] = {
|
||||
"data": {
|
||||
"currentUser": {
|
||||
"dropCampaigns": [],
|
||||
|
|
@ -762,12 +771,12 @@ class ErrorOnlyResponseDetectionTests(TestCase):
|
|||
},
|
||||
}
|
||||
|
||||
result = detect_error_only_response(parsed_json)
|
||||
result: str | None = detect_error_only_response(parsed_json)
|
||||
assert result is None
|
||||
|
||||
def test_detects_error_only_in_list_of_responses(self) -> None:
|
||||
"""Ensure error-only detection works with list of responses."""
|
||||
parsed_json = [
|
||||
parsed_json: list[dict[str, list[dict[str, str]]]] = [
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
|
|
@ -777,12 +786,12 @@ class ErrorOnlyResponseDetectionTests(TestCase):
|
|||
},
|
||||
]
|
||||
|
||||
result = detect_error_only_response(parsed_json)
|
||||
result: str | None = detect_error_only_response(parsed_json)
|
||||
assert result == "error_only: rate limit exceeded"
|
||||
|
||||
def test_handles_json_repair_tuple_format(self) -> None:
|
||||
"""Ensure error-only detection works with json_repair tuple format."""
|
||||
parsed_json = (
|
||||
parsed_json: tuple[dict[str, list[dict[str, str | list[str]]]], list[dict[str, str]]] = (
|
||||
{
|
||||
"errors": [
|
||||
{
|
||||
|
|
@ -794,26 +803,26 @@ class ErrorOnlyResponseDetectionTests(TestCase):
|
|||
[{"json_repair": "log"}],
|
||||
)
|
||||
|
||||
result = detect_error_only_response(parsed_json)
|
||||
result: str | None = detect_error_only_response(parsed_json)
|
||||
assert result == "error_only: service timeout"
|
||||
|
||||
def test_returns_none_for_non_dict_input(self) -> None:
|
||||
"""Ensure non-dict input is handled gracefully."""
|
||||
result = detect_error_only_response("invalid")
|
||||
result: str | None = detect_error_only_response("invalid")
|
||||
assert result is None
|
||||
|
||||
def test_returns_none_for_empty_errors_list(self) -> None:
|
||||
"""Ensure empty errors list is not flagged as error-only."""
|
||||
parsed_json = {
|
||||
parsed_json: dict[str, list[None]] = {
|
||||
"errors": [],
|
||||
}
|
||||
|
||||
result = detect_error_only_response(parsed_json)
|
||||
result: str | None = detect_error_only_response(parsed_json)
|
||||
assert result is None
|
||||
|
||||
def test_handles_error_without_message_field(self) -> None:
|
||||
"""Ensure errors without message field use default text."""
|
||||
parsed_json = {
|
||||
parsed_json: dict[str, list[dict[str, list[str]]]] = {
|
||||
"errors": [
|
||||
{
|
||||
"path": ["data"],
|
||||
|
|
@ -821,5 +830,5 @@ class ErrorOnlyResponseDetectionTests(TestCase):
|
|||
],
|
||||
}
|
||||
|
||||
result = detect_error_only_response(parsed_json)
|
||||
result: str | None = detect_error_only_response(parsed_json)
|
||||
assert result == "error_only: unknown error"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue