Files

175 lines
5.8 KiB
Python
Raw Permalink Normal View History

"""Unit tests for the services layer (families, naming utilities)."""
import pytest
from app.services.families import build_families, resolve_base_form
from app.services.naming import strip_roman_suffix, to_roman
# ---------------------------------------------------------------------------
# Minimal Evolution stand-in — only the two fields the services touch
# ---------------------------------------------------------------------------
class Evo:
"""Lightweight stand-in for app.models.evolution.Evolution."""
def __init__(self, from_id: int, to_id: int) -> None:
self.from_pokemon_id = from_id
self.to_pokemon_id = to_id
# ---------------------------------------------------------------------------
# build_families
# ---------------------------------------------------------------------------
class TestBuildFamilies:
def test_empty_evolutions_returns_empty_dict(self):
assert build_families([]) == {}
def test_linear_chain(self):
# A(1) → B(2) → C(3)
evos = [Evo(1, 2), Evo(2, 3)]
families = build_families(evos)
assert set(families[1]) == {1, 2, 3}
assert set(families[2]) == {1, 2, 3}
assert set(families[3]) == {1, 2, 3}
def test_branching_evolutions(self):
# Eevee-like: 1 → 2, 1 → 3, 1 → 4
evos = [Evo(1, 2), Evo(1, 3), Evo(1, 4)]
families = build_families(evos)
assert set(families[1]) == {1, 2, 3, 4}
assert set(families[2]) == {1, 2, 3, 4}
assert set(families[4]) == {1, 2, 3, 4}
def test_disjoint_chains_are_separate_families(self):
# Chain 1→2 and independent chain 3→4
evos = [Evo(1, 2), Evo(3, 4)]
families = build_families(evos)
assert set(families[1]) == {1, 2}
assert set(families[3]) == {3, 4}
assert 3 not in set(families[1])
assert 1 not in set(families[3])
def test_shedinja_case(self):
# Nincada(1) → Ninjask(2) and Nincada(1) → Shedinja(3)
evos = [Evo(1, 2), Evo(1, 3)]
families = build_families(evos)
assert set(families[1]) == {1, 2, 3}
assert set(families[3]) == {1, 2, 3}
def test_pokemon_not_in_any_evolution_not_in_result(self):
evos = [Evo(1, 2)]
families = build_families(evos)
assert 99 not in families
def test_all_family_members_have_identical_family_list(self):
evos = [Evo(10, 11), Evo(11, 12)]
families = build_families(evos)
assert set(families[10]) == set(families[11]) == set(families[12])
# ---------------------------------------------------------------------------
# resolve_base_form
# ---------------------------------------------------------------------------
class TestResolveBaseForm:
def test_pokemon_not_in_any_evolution_returns_itself(self):
assert resolve_base_form(99, []) == 99
def test_base_form_returns_itself(self):
# A(1) → B(2): base of 1 is still 1
evos = [Evo(1, 2)]
assert resolve_base_form(1, evos) == 1
def test_final_form_returns_base(self):
# A(1) → B(2) → C(3): base of 3 is 1
evos = [Evo(1, 2), Evo(2, 3)]
assert resolve_base_form(3, evos) == 1
def test_middle_form_returns_base(self):
# A(1) → B(2) → C(3): base of 2 is 1
evos = [Evo(1, 2), Evo(2, 3)]
assert resolve_base_form(2, evos) == 1
def test_branching_evolution_base(self):
# 1 → 2, 1 → 3: base of both 2 and 3 is 1
evos = [Evo(1, 2), Evo(1, 3)]
assert resolve_base_form(2, evos) == 1
assert resolve_base_form(3, evos) == 1
def test_shedinja_resolves_to_nincada(self):
# Nincada(1) → Ninjask(2), Nincada(1) → Shedinja(3)
evos = [Evo(1, 2), Evo(1, 3)]
assert resolve_base_form(3, evos) == 1
def test_empty_evolutions_returns_self(self):
assert resolve_base_form(42, []) == 42
# ---------------------------------------------------------------------------
# to_roman
# ---------------------------------------------------------------------------
class TestToRoman:
@pytest.mark.parametrize(
"n, expected",
[
(1, "I"),
(2, "II"),
(3, "III"),
(4, "IV"),
(5, "V"),
(6, "VI"),
(9, "IX"),
(10, "X"),
(11, "XI"),
(14, "XIV"),
(40, "XL"),
(50, "L"),
(90, "XC"),
(100, "C"),
],
)
def test_converts_integer_to_roman(self, n: int, expected: str):
assert to_roman(n) == expected
def test_typical_genlocke_sequence(self):
# Lineage names: Heracles I, II, III, IV, V
assert [to_roman(i) for i in range(1, 6)] == ["I", "II", "III", "IV", "V"]
# ---------------------------------------------------------------------------
# strip_roman_suffix
# ---------------------------------------------------------------------------
class TestStripRomanSuffix:
def test_strips_roman_numeral_ii(self):
assert strip_roman_suffix("Heracles II") == "Heracles"
def test_strips_roman_numeral_iii(self):
assert strip_roman_suffix("Athena III") == "Athena"
def test_strips_roman_numeral_iv(self):
assert strip_roman_suffix("Nova IV") == "Nova"
def test_strips_roman_numeral_x(self):
assert strip_roman_suffix("Zeus X") == "Zeus"
def test_no_suffix_returns_unchanged(self):
assert strip_roman_suffix("Apollo") == "Apollo"
def test_name_with_i_suffix(self):
# Single "I" at end is a valid roman numeral suffix
assert strip_roman_suffix("Heracles I") == "Heracles"
def test_round_trip_with_to_roman(self):
base = "Heracles"
for n in range(1, 6):
suffixed = f"{base} {to_roman(n)}"
assert strip_roman_suffix(suffixed) == base