Files
nuzlocke-tracker/backend/tests/test_schemas.py

307 lines
9.6 KiB
Python
Raw Normal View History

"""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