Mi2dRPGamEng/tests/test_character_library.py

183 lines
7.3 KiB
Python

import sqlite3
from pathlib import Path
import pytest
from engine.character import Ailment, BodyPart, Character, ClothingPiece, HeldItem, Organ
from engine.character_library import CharacterLibrary
from engine.defs import DefRegistry
from engine.inventory import Inventory
from engine.item import ItemInstance
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:"))
def build_character(registry) -> Character:
character = Character(
entity_id="ignored-on-save", # library keys on (account_id, character_id), not entity_id
name="Vex",
species_id="reptilian_quad",
gender="none",
body_type="stocky",
blood_percentage=88.0,
strength=14,
body_parts=[
BodyPart(
"torso", "reptilian_torso", integrity=70.0,
ailments=[Ailment("poisoned", "Poisoned", severity=0.3, progress=12.0)],
organs=[Organ("reptilian_heart", integrity=60.0)],
),
],
default_body_parts=registry.new_default_body_parts("reptilian_quad"),
clothing=[ClothingPiece("torso", "leather_jacket")],
default_clothing=[],
held_items=[HeldItem("right_hand", "chronowand", attachments=["scope_basic"])],
implants=["chronoimplant"],
mental_stats={"insanity": 4.0},
)
character.bio["stealth"] = 5
backpack = Inventory(4, 4)
backpack.place(ItemInstance("rope-1", "sandwich"), registry.get_item_def("sandwich"), 0, 0)
character.clothing[0] = ClothingPiece("torso", "backpack_basic")
character.clothing[0].inventory = backpack
character.inventory = Inventory(6, 5)
character.inventory.place(ItemInstance("staff-1", "staff_oak"), registry.get_item_def("staff_oak"), 0, 0)
return character
def test_save_and_load_roundtrip_preserves_identity_and_species(registry, library):
character = build_character(registry)
library.save_character("account-1", "char-1", character)
loaded = library.load_character("account-1", "char-1", registry)
assert loaded is not None
assert loaded.name == "Vex"
assert loaded.species_id == "reptilian_quad"
assert loaded.gender == "none"
assert loaded.body_type == "stocky"
assert loaded.hair_color is None
assert loaded.blood_percentage == pytest.approx(88.0)
assert loaded.strength == 14
assert loaded.position is None # portable template, not tied to any world
def test_roundtrip_preserves_body_parts_ailments_and_organs(registry, library):
character = build_character(registry)
library.save_character("account-1", "char-1", character)
loaded = library.load_character("account-1", "char-1", registry)
torso = loaded.get_body_part("torso")
assert torso.part_def_id == "reptilian_torso"
assert torso.integrity == pytest.approx(70.0)
assert [a.id for a in torso.ailments] == ["poisoned"]
assert torso.ailments[0].progress == pytest.approx(12.0)
heart = next(o for o in torso.organs if o.organ_def_id == "reptilian_heart")
assert heart.integrity == pytest.approx(60.0)
# default (non-overridden) parts and their organs came back too
head = loaded.get_body_part("head")
assert head.part_def_id == "reptilian_head"
assert {o.organ_def_id for o in head.organs} == {"reptilian_brain", "reptilian_eyes", "reptilian_ears"}
def test_roundtrip_preserves_clothing_and_its_granted_inventory(registry, library):
character = build_character(registry)
library.save_character("account-1", "char-1", character)
loaded = library.load_character("account-1", "char-1", registry)
backpack = loaded.get_clothing("torso")
assert backpack.item_def_id == "backpack_basic"
assert backpack.inventory is not None
assert {p.item.item_id for p in backpack.inventory.items()} == {"rope-1"}
def test_roundtrip_preserves_held_items_and_attachments(registry, library):
character = build_character(registry)
library.save_character("account-1", "char-1", character)
loaded = library.load_character("account-1", "char-1", registry)
held = loaded.get_held_item("right_hand")
assert held.item_def_id == "chronowand"
assert held.attachments == ["scope_basic"]
def test_roundtrip_preserves_implants(registry, library):
character = build_character(registry)
library.save_character("account-1", "char-1", character)
loaded = library.load_character("account-1", "char-1", registry)
assert loaded.implants == ["chronoimplant"]
def test_roundtrip_preserves_bio_and_mental_stats(registry, library):
character = build_character(registry)
library.save_character("account-1", "char-1", character)
loaded = library.load_character("account-1", "char-1", registry)
assert loaded.bio["stealth"] == 5
assert loaded.mental_stats == {"insanity": 4.0}
def test_roundtrip_preserves_top_level_inventory(registry, library):
character = build_character(registry)
library.save_character("account-1", "char-1", character)
loaded = library.load_character("account-1", "char-1", registry)
placed = {p.item.item_id: (p.x, p.y) for p in loaded.inventory.items()}
assert placed == {"staff-1": (0, 0)}
def test_load_character_returns_none_when_not_found(registry, library):
assert library.load_character("account-1", "no-such-char", registry) is None
def test_list_characters_returns_id_name_species_for_an_account(registry, library):
library.save_character("account-1", "char-1", build_character(registry))
other = build_character(registry)
other.name = "Kess"
library.save_character("account-1", "char-2", other)
library.save_character("account-2", "char-3", build_character(registry)) # different account
listed = library.list_characters("account-1")
assert {(cid, name) for cid, name, species in listed if species} == {("char-1", "Vex"), ("char-2", "Kess")}
def test_delete_character_removes_it_and_leaves_others(registry, library):
library.save_character("account-1", "char-1", build_character(registry))
library.save_character("account-1", "char-2", build_character(registry))
library.delete_character("account-1", "char-1")
assert library.load_character("account-1", "char-1", registry) is None
assert library.load_character("account-1", "char-2", registry) is not None
def test_saving_twice_under_the_same_id_overwrites_not_duplicates(registry, library):
character = build_character(registry)
library.save_character("account-1", "char-1", character)
character.blood_percentage = 50.0
library.save_character("account-1", "char-1", character)
loaded = library.load_character("account-1", "char-1", registry)
assert loaded.blood_percentage == pytest.approx(50.0)
assert len(library.list_characters("account-1")) == 1
def test_same_character_id_isolated_between_accounts(registry, library):
a = build_character(registry)
a.name = "Account A's Vex"
b = build_character(registry)
b.name = "Account B's Vex"
library.save_character("account-a", "char-1", a)
library.save_character("account-b", "char-1", b)
assert library.load_character("account-a", "char-1", registry).name == "Account A's Vex"
assert library.load_character("account-b", "char-1", registry).name == "Account B's Vex"