From df6977e9bcfc6a76afc14ab4d06c4b6001084ee9 Mon Sep 17 00:00:00 2001 From: joereg4 Date: Thu, 2 Oct 2025 19:11:07 -0500 Subject: [PATCH 1/3] Fix formatting issues in pokemon_evolution.csv by removing trailing data in specific rows to ensure consistency. --- data/v2/csv/pokemon_evolution.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/v2/csv/pokemon_evolution.csv b/data/v2/csv/pokemon_evolution.csv index 61b2e4637..6bca72db0 100644 --- a/data/v2/csv/pokemon_evolution.csv +++ b/data/v2/csv/pokemon_evolution.csv @@ -11,7 +11,7 @@ id,evolved_species_id,evolution_trigger_id,trigger_item_id,minimum_level,gender_ 10,15,1,,10,,,,,,,,,,,,,,0,0,, 11,17,1,,18,,,,,,,,,,,,,,0,0,, 12,18,1,,36,,,,,,,,,,,,,,0,0,, -13,20,1,,20,,,,,,,,,,,,,,0,0,7,19 +13,20,1,,20,,,,,,,,,,,,,,0,0,, 14,22,1,,20,,,,,,,,,,,,,,0,0,, 15,24,1,,22,,,,,,,,,,,,,,0,0,, 16,25,1,,,,,,,,,220,,,,,,,0,0,, @@ -455,7 +455,7 @@ id,evolved_species_id,evolution_trigger_id,trigger_item_id,minimum_level,gender_ 458,555,3,885,,,,,,,,,,,,,,,0,0,, 459,867,9,,,,,,,,,,,,,,,,0,0,, 460,745,1,,25,,,,dusk,,,,,,,,,,0,0,, -461,20,1,,20,,,,night,,,,,,,,,,0,0,7,19 +461,20,1,,20,,,,night,,,,,,,,,,0,0,, 462,899,11,,,,,,,828,,,,,,,,,0,0,, 463,900,3,10001,,,,,,,,,,,,,,,0,0,9,123 464,901,3,10002,,,,,full-moon,,,,,,,,,,0,0,9,217 From 63695da1d1c6094d0582603c3b963a0fb21324f1 Mon Sep 17 00:00:00 2001 From: joereg4 Date: Thu, 9 Oct 2025 18:42:37 -0500 Subject: [PATCH 2/3] Refactor version and method data retrieval in serializers - Updated `LocationAreaDetailSerializer` and `PokemonDetailSerializer` to order version and method objects by ID. - Created mappings from version and method IDs to their serialized data for improved clarity and efficiency in data access. --- pokemon_v2/serializers.py | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/pokemon_v2/serializers.py b/pokemon_v2/serializers.py index d457e686a..ee33a2678 100644 --- a/pokemon_v2/serializers.py +++ b/pokemon_v2/serializers.py @@ -1192,11 +1192,16 @@ def get_method_rates(self, obj): ) def get_encounters(self, obj): # get versions for later use - version_objects = Version.objects.all() + version_objects = Version.objects.all().order_by("id") version_data = VersionSummarySerializer( version_objects, many=True, context=self.context ).data + # Create a mapping from version ID to serialized data + version_data_map = { + version_objects[i].id: version_data[i] for i in range(len(version_objects)) + } + # all encounters associated with location area all_encounters = Encounter.objects.filter(location_area=obj).order_by("pokemon") encounters_list = [] @@ -1218,7 +1223,7 @@ def get_encounters(self, obj): # each pokemon has multiple versions it could be encountered in for ver in poke_encounters.values("version").distinct(): version_detail = OrderedDict() - version_detail["version"] = version_data[ver["version"] - 1] + version_detail["version"] = version_data_map[ver["version"]] version_detail["max_chance"] = 0 version_detail["encounter_details"] = [] @@ -4705,15 +4710,26 @@ def get_pokemon_cries(self, obj): } ) def get_pokemon_moves(self, obj): - version_objects = VersionGroup.objects.all() + version_objects = VersionGroup.objects.all().order_by("id") version_data = VersionGroupSummarySerializer( version_objects, many=True, context=self.context ).data - method_objects = MoveLearnMethod.objects.all() + + # Create a mapping from version group ID to serialized data + version_data_map = { + version_objects[i].id: version_data[i] for i in range(len(version_objects)) + } + + method_objects = MoveLearnMethod.objects.all().order_by("id") method_data = MoveLearnMethodSummarySerializer( method_objects, many=True, context=self.context ).data + # Create a mapping from method ID to serialized data + method_data_map = { + method_objects[i].id: method_data[i] for i in range(len(method_objects)) + } + # Get moves related to this pokemon and pull out unique Move IDs. # Note that it's important to order by the same column we're using to # determine if the entries are unique. Otherwise distinct() will @@ -4742,11 +4758,11 @@ def get_pokemon_moves(self, obj): version_detail = OrderedDict() version_detail["level_learned_at"] = move["level"] - version_detail["version_group"] = version_data[ - move["version_group"] - 1 + version_detail["version_group"] = version_data_map[ + move["version_group"] ] - version_detail["move_learn_method"] = method_data[ - move["move_learn_method"] - 1 + version_detail["move_learn_method"] = method_data_map[ + move["move_learn_method"] ] version_detail["order"] = move["order"] From ca7b98cd8e87bf1fc477eef3693e38a70c2bb63b Mon Sep 17 00:00:00 2001 From: joereg4 Date: Tue, 21 Oct 2025 07:16:02 -0500 Subject: [PATCH 3/3] Add tests for location area and pokemon APIs with non-sequential version IDs - Implemented `test_location_area_api_with_non_sequential_version_ids` to ensure the location area API handles non-sequential version IDs correctly, addressing the IndexError from issue #1313. - Added `test_pokemon_api_with_non_sequential_ids` to verify the pokemon API functions properly with non-sequential version group and method IDs, also related to issue #1313. --- pokemon_v2/tests.py | 193 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/pokemon_v2/tests.py b/pokemon_v2/tests.py index 8e8eee0c8..4135fec0a 100644 --- a/pokemon_v2/tests.py +++ b/pokemon_v2/tests.py @@ -5797,3 +5797,196 @@ def test_case_insensitive_api(self): self.assertEqual(uppercase_response.status_code, status.HTTP_200_OK) self.assertEqual(lowercase_response.data, uppercase_response.data) + + def test_location_area_api_with_non_sequential_version_ids(self): + """ + Test that location area API works correctly with non-sequential version IDs. + This test reproduces the IndexError scenario that was fixed in issue #1313. + """ + # Create versions with non-sequential IDs to trigger the original bug + # We'll create versions with IDs 1, 5, 10 to simulate non-sequential ordering + version_group = self.setup_version_group_data(name="test version group") + + # Create versions with specific IDs by deleting and recreating with explicit IDs + Version.objects.all().delete() # Clear existing versions + version1 = Version.objects.create( + id=1, name="version1", version_group=version_group + ) + version5 = Version.objects.create( + id=5, name="version5", version_group=version_group + ) + version10 = Version.objects.create( + id=10, name="version10", version_group=version_group + ) + + # Create location area and encounter data + location = self.setup_location_data(name="test location") + location_area = self.setup_location_area_data( + location, name="test location area" + ) + + encounter_method = self.setup_encounter_method_data( + name="test encounter method" + ) + location_area_encounter_rate = self.setup_location_area_encounter_rate_data( + location_area, encounter_method, rate=20 + ) + + pokemon_species = self.setup_pokemon_species_data(name="test pokemon species") + pokemon = self.setup_pokemon_data( + name="test pokemon", pokemon_species=pokemon_species + ) + encounter_slot = self.setup_encounter_slot_data( + encounter_method, slot=1, rarity=30 + ) + + # Create encounters with different versions (including the non-sequential IDs) + encounter1 = self.setup_encounter_data( + pokemon=pokemon, + location_area=location_area, + encounter_slot=encounter_slot, + version=version1, # ID 1 + min_level=30, + max_level=35, + ) + + encounter2 = self.setup_encounter_data( + pokemon=pokemon, + location_area=location_area, + encounter_slot=encounter_slot, + version=version5, # ID 5 + min_level=32, + max_level=38, + ) + + encounter3 = self.setup_encounter_data( + pokemon=pokemon, + location_area=location_area, + encounter_slot=encounter_slot, + version=version10, # ID 10 + min_level=35, + max_level=40, + ) + + # Test the API endpoint - this should not raise IndexError + response = self.client.get(f"{API_V2}/location-area/{location_area.pk}/") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Verify the response contains the expected structure + self.assertIn("pokemon_encounters", response.data) + self.assertIsInstance(response.data["pokemon_encounters"], list) + + # Verify that version data is correctly mapped (not using array indexing) + pokemon_encounters = response.data["pokemon_encounters"] + self.assertGreater(len(pokemon_encounters), 0) + + # Check that version details are properly structured + for encounter in pokemon_encounters: + self.assertIn("version_details", encounter) + for version_detail in encounter["version_details"]: + self.assertIn("version", version_detail) + # The version should be a proper object, not None or malformed + self.assertIsInstance(version_detail["version"], dict) + self.assertIn("name", version_detail["version"]) + + def test_pokemon_api_with_non_sequential_ids(self): + """ + Test that pokemon API works correctly with non-sequential version group and method IDs. + This test reproduces the IndexError scenario that was fixed in issue #1313. + """ + # Create version groups with non-sequential IDs + VersionGroup.objects.all().delete() # Clear existing version groups + MoveLearnMethod.objects.all().delete() # Clear existing methods + + generation = self.setup_generation_data(name="test generation") + + # Create version groups with specific IDs + version_group1 = VersionGroup.objects.create( + id=1, name="version group 1", generation=generation, order=1 + ) + version_group5 = VersionGroup.objects.create( + id=5, name="version group 5", generation=generation, order=2 + ) + version_group10 = VersionGroup.objects.create( + id=10, name="version group 10", generation=generation, order=3 + ) + + # Create move learn methods with specific IDs + method1 = MoveLearnMethod.objects.create(id=1, name="method 1") + method5 = MoveLearnMethod.objects.create(id=5, name="method 5") + method10 = MoveLearnMethod.objects.create(id=10, name="method 10") + + # Create pokemon and move data with all required setup + pokemon_species = self.setup_pokemon_species_data(name="test pokemon species") + pokemon = self.setup_pokemon_data( + name="test pokemon", pokemon_species=pokemon_species + ) + pokemon_form = self.setup_pokemon_form_data( + pokemon=pokemon, name="test pokemon form" + ) + pokemon_ability = self.setup_pokemon_ability_data(pokemon=pokemon) + pokemon_stat = self.setup_pokemon_stat_data(pokemon=pokemon) + pokemon_type = self.setup_pokemon_type_data(pokemon=pokemon) + pokemon_item = self.setup_pokemon_item_data(pokemon=pokemon) + pokemon_sprites = self.setup_pokemon_sprites_data(pokemon=pokemon) + pokemon_cries = self.setup_pokemon_cries_data(pokemon, latest=True, legacy=True) + pokemon_game_index = self.setup_pokemon_game_index_data( + pokemon=pokemon, game_index=10 + ) + move = self.setup_move_data(name="test move") + + # Create pokemon moves with different version groups + # Note: setup_pokemon_move_data creates its own move_learn_method, so we'll create the moves manually + pokemon_move1 = PokemonMove.objects.create( + pokemon=pokemon, + move=move, + version_group=version_group1, # ID 1 + move_learn_method=method1, # ID 1 + level=10, + order=1, + ) + + pokemon_move2 = PokemonMove.objects.create( + pokemon=pokemon, + move=move, + version_group=version_group5, # ID 5 + move_learn_method=method5, # ID 5 + level=15, + order=2, + ) + + pokemon_move3 = PokemonMove.objects.create( + pokemon=pokemon, + move=move, + version_group=version_group10, # ID 10 + move_learn_method=method10, # ID 10 + level=20, + order=3, + ) + + # Test the API endpoint - this should not raise IndexError + response = self.client.get(f"{API_V2}/pokemon/{pokemon.pk}/") + + self.assertEqual(response.status_code, status.HTTP_200_OK) + + # Verify the response contains the expected structure + self.assertIn("moves", response.data) + self.assertIsInstance(response.data["moves"], list) + + # Verify that move data is correctly structured + moves = response.data["moves"] + self.assertGreater(len(moves), 0) + + # Check that version group details are properly structured + for move_data in moves: + self.assertIn("version_group_details", move_data) + for version_detail in move_data["version_group_details"]: + self.assertIn("version_group", version_detail) + self.assertIn("move_learn_method", version_detail) + + # The version group and method should be proper objects, not None or malformed + self.assertIsInstance(version_detail["version_group"], dict) + self.assertIsInstance(version_detail["move_learn_method"], dict) + self.assertIn("name", version_detail["version_group"]) + self.assertIn("name", version_detail["move_learn_method"])