import sqlite3 from pathlib import Path import pytest from engine.character_creator import CharacterCreator from engine.character_library import CharacterLibrary from engine.defs import DefRegistry DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs" @pytest.fixture() def registry(): return DefRegistry.load(DEFS_DIR) @pytest.fixture() def library(): return CharacterLibrary(sqlite3.connect(":memory:")) # --- species / gender selection ----------------------------------------------------------- def test_set_species_defaults_gender_to_the_first_valid_option(registry): creator = CharacterCreator(registry) assert creator.set_species("human") is True assert creator.species_id == "human" assert creator.gender == "male" def test_set_species_rejects_unknown_species(registry): creator = CharacterCreator(registry) assert creator.set_species("dragon") is False assert creator.species_id is None def test_set_gender_accepts_a_valid_option(registry): creator = CharacterCreator(registry) creator.set_species("human") assert creator.set_gender("nonbinary") is True assert creator.gender == "nonbinary" def test_set_gender_rejects_option_not_valid_for_species(registry): creator = CharacterCreator(registry) creator.set_species("alien_tri") # genders: alpha/beta/gamma assert creator.set_gender("male") is False assert creator.gender == "alpha" # unchanged, still the auto-picked default def test_set_gender_before_species_is_a_no_op(registry): creator = CharacterCreator(registry) assert creator.set_gender("male") is False assert creator.gender is None def test_switching_species_resets_gender_if_no_longer_valid(registry): creator = CharacterCreator(registry) creator.set_species("human") creator.set_gender("nonbinary") creator.set_species("bot") # monogender, "none" - "nonbinary" isn't valid here assert creator.gender == "none" def test_switching_species_keeps_gender_if_still_valid(registry): creator = CharacterCreator(registry) creator.set_species("human") creator.set_gender("female") creator.set_species("dog") # dog also has "male"/"female" assert creator.gender == "female" # --- skill point budget --------------------------------------------------------------------- def test_starts_with_the_full_budget_unspent(registry): creator = CharacterCreator(registry) assert creator.spent_skill_points() == 0 assert creator.remaining_skill_points() == creator.skill_points_budget def test_increase_skill_spends_a_point(registry): creator = CharacterCreator(registry) assert creator.increase_skill("stealth") is True assert creator.bio["stealth"] == 1 assert creator.remaining_skill_points() == creator.skill_points_budget - 1 def test_increase_skill_fails_once_budget_exhausted(registry): creator = CharacterCreator(registry, skill_points_budget=2) creator.increase_skill("stealth") creator.increase_skill("stealth") assert creator.increase_skill("athletics") is False assert creator.bio["athletics"] == 0 def test_increase_skill_fails_past_max_level(registry): creator = CharacterCreator(registry, skill_points_budget=99, max_skill_level=2) creator.increase_skill("stealth") creator.increase_skill("stealth") assert creator.increase_skill("stealth") is False assert creator.bio["stealth"] == 2 def test_increase_skill_rejects_unknown_skill(registry): creator = CharacterCreator(registry) assert creator.increase_skill("lockpicking") is False def test_decrease_skill_refunds_a_point(registry): creator = CharacterCreator(registry) creator.increase_skill("stealth") assert creator.decrease_skill("stealth") is True assert creator.bio["stealth"] == 0 assert creator.remaining_skill_points() == creator.skill_points_budget def test_decrease_skill_below_zero_is_a_no_op(registry): creator = CharacterCreator(registry) assert creator.decrease_skill("stealth") is False # --- body part preview ------------------------------------------------------------------------ def test_preview_body_parts_empty_before_species_chosen(registry): creator = CharacterCreator(registry) assert creator.preview_body_parts() == [] def test_preview_body_parts_matches_species_defaults(registry): creator = CharacterCreator(registry) creator.set_species("dog") slots = {p.slot for p in creator.preview_body_parts()} assert slots == {"head", "torso", "front_left_leg", "front_right_leg", "back_left_leg", "back_right_leg"} # --- readiness / build_character ---------------------------------------------------------------- def test_not_ready_without_name_species_and_gender(registry): creator = CharacterCreator(registry) assert creator.is_ready() is False creator.set_name("Vex") assert creator.is_ready() is False creator.set_species("human") assert creator.is_ready() is True # species auto-fills a valid gender def test_build_character_returns_none_when_not_ready(registry): creator = CharacterCreator(registry) creator.set_name("Vex") assert creator.build_character() is None def test_build_character_produces_a_character_with_chosen_traits(registry): creator = CharacterCreator(registry) creator.set_name(" Vex ") creator.set_species("reptilian_quad") creator.increase_skill("athletics") character = creator.build_character() assert character is not None assert character.name == "Vex" # whitespace trimmed assert character.species_id == "reptilian_quad" assert character.gender == "none" assert character.bio["athletics"] == 1 assert {p.slot for p in character.default_body_parts} == { "head", "torso", "left_arm", "right_arm", "left_arm_2", "right_arm_2", "left_leg", "right_leg", } # --- confirm: saves into the library ------------------------------------------------------------- def test_confirm_saves_into_the_library(registry, library): creator = CharacterCreator(registry) creator.set_name("Vex") creator.set_species("human") character = creator.confirm(library, "acct-1", "char-1") assert character is not None loaded = library.load_character("acct-1", "char-1", registry) assert loaded is not None assert loaded.name == "Vex" assert loaded.species_id == "human" def test_confirm_does_nothing_when_not_ready(registry, library): creator = CharacterCreator(registry) assert creator.confirm(library, "acct-1", "char-1") is None assert library.load_character("acct-1", "char-1", registry) is None