"""Unit tests for Pydantic schemas.""" import pytest from pydantic import ValidationError from app.schemas.base import CamelModel from app.schemas.boss import BossReorderItem, BossReorderRequest, BossResultCreate from app.schemas.encounter import EncounterCreate, EncounterUpdate from app.schemas.game import ( GameCreate, GameUpdate, RouteReorderItem, RouteReorderRequest, ) from app.schemas.genlocke import GenlockeCreate from app.schemas.pokemon import EvolutionCreate, PokemonCreate from app.schemas.run import RunCreate, RunUpdate class TestCamelModel: def test_snake_case_field_name_accepted(self): class M(CamelModel): game_id: int assert M(game_id=1).game_id == 1 def test_camel_case_alias_accepted(self): class M(CamelModel): game_id: int assert M(**{"gameId": 1}).game_id == 1 def test_serializes_to_camel_case(self): class M(CamelModel): game_id: int is_shiny: bool data = M(game_id=1, is_shiny=True).model_dump(by_alias=True) assert data == {"gameId": 1, "isShiny": True} def test_snake_case_not_in_serialized_output(self): class M(CamelModel): version_group_id: int data = M(version_group_id=5).model_dump(by_alias=True) assert "version_group_id" not in data assert "versionGroupId" in data def test_from_attributes(self): class FakeOrm: game_id = 42 class M(CamelModel): game_id: int assert M.model_validate(FakeOrm()).game_id == 42 class TestRunCreate: def test_valid_minimum(self): run = RunCreate(game_id=1, name="Nuzlocke #1") assert run.game_id == 1 assert run.name == "Nuzlocke #1" assert run.rules == {} assert run.naming_scheme is None def test_camel_case_input(self): run = RunCreate(**{"gameId": 5, "name": "Run"}) assert run.game_id == 5 def test_missing_game_id_raises(self): with pytest.raises(ValidationError): RunCreate(name="Run") def test_missing_name_raises(self): with pytest.raises(ValidationError): RunCreate(game_id=1) def test_rules_accepts_arbitrary_data(self): run = RunCreate(game_id=1, name="x", rules={"duplicatesClause": True}) assert run.rules["duplicatesClause"] is True def test_naming_scheme_accepted(self): run = RunCreate(game_id=1, name="x", naming_scheme="nature") assert run.naming_scheme == "nature" class TestRunUpdate: def test_all_fields_optional(self): update = RunUpdate() assert update.name is None assert update.status is None assert update.rules is None assert update.naming_scheme is None def test_partial_update(self): update = RunUpdate(name="New Name") assert update.name == "New Name" assert update.status is None def test_hof_encounter_ids(self): update = RunUpdate(hof_encounter_ids=[1, 2, 3]) assert update.hof_encounter_ids == [1, 2, 3] class TestGameCreate: def test_valid_minimum(self): game = GameCreate(name="Pokemon Red", slug="red", generation=1, region="Kanto") assert game.name == "Pokemon Red" assert game.slug == "red" assert game.generation == 1 assert game.region == "Kanto" def test_optional_fields_default_none(self): game = GameCreate(name="Pokemon Red", slug="red", generation=1, region="Kanto") assert game.category is None assert game.box_art_url is None assert game.release_year is None assert game.color is None def test_missing_required_field_raises(self): with pytest.raises(ValidationError): GameCreate(name="Pokemon Red", slug="red", generation=1) # missing region def test_camel_case_input(self): game = GameCreate( **{ "name": "Gold", "slug": "gold", "generation": 2, "region": "Johto", "boxArtUrl": "/art.png", } ) assert game.box_art_url == "/art.png" class TestGameUpdate: def test_all_fields_optional(self): assert GameUpdate().name is None def test_partial_update(self): update = GameUpdate(name="New Name", generation=3) assert update.name == "New Name" assert update.generation == 3 assert update.region is None class TestEncounterCreate: def test_valid_minimum(self): enc = EncounterCreate(route_id=1, pokemon_id=25, status="caught") assert enc.route_id == 1 assert enc.pokemon_id == 25 assert enc.status == "caught" assert enc.is_shiny is False assert enc.nickname is None def test_camel_case_input(self): enc = EncounterCreate( **{"routeId": 1, "pokemonId": 25, "status": "caught", "isShiny": True} ) assert enc.route_id == 1 assert enc.is_shiny is True def test_missing_pokemon_id_raises(self): with pytest.raises(ValidationError): EncounterCreate(route_id=1, status="caught") def test_missing_status_raises(self): with pytest.raises(ValidationError): EncounterCreate(route_id=1, pokemon_id=25) def test_origin_accepted(self): enc = EncounterCreate(route_id=1, pokemon_id=1, status="caught", origin="gift") assert enc.origin == "gift" class TestEncounterUpdate: def test_all_fields_optional(self): update = EncounterUpdate() assert update.nickname is None assert update.status is None assert update.faint_level is None assert update.death_cause is None assert update.current_pokemon_id is None class TestBossResultCreate: def test_valid_minimum(self): result = BossResultCreate(boss_battle_id=1, result="win") assert result.boss_battle_id == 1 assert result.result == "win" assert result.attempts == 1 def test_attempts_default_one(self): assert BossResultCreate(boss_battle_id=1, result="loss").attempts == 1 def test_custom_attempts(self): assert ( BossResultCreate(boss_battle_id=1, result="win", attempts=3).attempts == 3 ) def test_missing_boss_battle_id_raises(self): with pytest.raises(ValidationError): BossResultCreate(result="win") class TestBossReorderRequest: def test_nested_items_accepted(self): req = BossReorderRequest(bosses=[BossReorderItem(id=1, order=2)]) assert req.bosses[0].id == 1 assert req.bosses[0].order == 2 def test_dict_input_coerced(self): req = BossReorderRequest(**{"bosses": [{"id": 3, "order": 1}]}) assert req.bosses[0].id == 3 def test_empty_list_accepted(self): assert BossReorderRequest(bosses=[]).bosses == [] class TestRouteReorderRequest: def test_nested_items_accepted(self): req = RouteReorderRequest(routes=[RouteReorderItem(id=10, order=1)]) assert req.routes[0].id == 10 def test_dict_input_coerced(self): req = RouteReorderRequest(**{"routes": [{"id": 5, "order": 3}]}) assert req.routes[0].order == 3 class TestGenlockeCreate: def test_valid_minimum(self): gc = GenlockeCreate(name="My Genlocke", game_ids=[1, 2, 3]) assert gc.name == "My Genlocke" assert gc.game_ids == [1, 2, 3] assert gc.genlocke_rules == {} assert gc.nuzlocke_rules == {} assert gc.naming_scheme is None def test_missing_name_raises(self): with pytest.raises(ValidationError): GenlockeCreate(game_ids=[1, 2]) def test_missing_game_ids_raises(self): with pytest.raises(ValidationError): GenlockeCreate(name="My Genlocke") def test_camel_case_input(self): gc = GenlockeCreate(**{"name": "x", "gameIds": [1], "namingScheme": "types"}) assert gc.naming_scheme == "types" class TestPokemonCreate: def test_valid_minimum(self): p = PokemonCreate( pokeapi_id=25, national_dex=25, name="Pikachu", types=["electric"] ) assert p.name == "Pikachu" assert p.types == ["electric"] assert p.sprite_url is None def test_multi_type(self): p = PokemonCreate( pokeapi_id=6, national_dex=6, name="Charizard", types=["fire", "flying"] ) assert p.types == ["fire", "flying"] def test_missing_required_raises(self): with pytest.raises(ValidationError): PokemonCreate(pokeapi_id=1, national_dex=1, name="x") # missing types class TestEvolutionCreate: def test_valid_minimum(self): evo = EvolutionCreate(from_pokemon_id=1, to_pokemon_id=2, trigger="level-up") assert evo.from_pokemon_id == 1 assert evo.to_pokemon_id == 2 assert evo.trigger == "level-up" assert evo.min_level is None assert evo.item is None def test_all_optional_fields(self): evo = EvolutionCreate( from_pokemon_id=1, to_pokemon_id=2, trigger="use-item", min_level=16, item="fire-stone", held_item=None, condition="day", region="Kanto", ) assert evo.min_level == 16 assert evo.item == "fire-stone" assert evo.region == "Kanto" def test_missing_trigger_raises(self): with pytest.raises(ValidationError): EvolutionCreate(from_pokemon_id=1, to_pokemon_id=2) def test_camel_case_input(self): evo = EvolutionCreate( **{"fromPokemonId": 1, "toPokemonId": 2, "trigger": "level-up"} ) assert evo.from_pokemon_id == 1