Merge pull request 'feat: evolution track seed data and tests (WP-03) (#68)' (#83) from ai/paper-dynasty-database#68 into next-release
Some checks are pending
Build Docker Image / build (push) Waiting to run
Some checks are pending
Build Docker Image / build (push) Waiting to run
Reviewed-on: #83
This commit is contained in:
commit
223743d89f
0
app/seed/__init__.py
Normal file
0
app/seed/__init__.py
Normal file
5
app/seed/evolution_tracks.json
Normal file
5
app/seed/evolution_tracks.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
[
|
||||||
|
{"name": "Batter", "card_type": "batter", "formula": "pa+tb*2", "t1": 37, "t2": 149, "t3": 448, "t4": 896},
|
||||||
|
{"name": "Starting Pitcher", "card_type": "sp", "formula": "ip+k", "t1": 10, "t2": 40, "t3": 120, "t4": 240},
|
||||||
|
{"name": "Relief Pitcher", "card_type": "rp", "formula": "ip+k", "t1": 3, "t2": 12, "t3": 35, "t4": 70}
|
||||||
|
]
|
||||||
41
app/seed/evolution_tracks.py
Normal file
41
app/seed/evolution_tracks.py
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
"""Seed data fixture for EvolutionTrack.
|
||||||
|
|
||||||
|
Inserts the three universal evolution tracks (Batter, Starting Pitcher,
|
||||||
|
Relief Pitcher) if they do not already exist. Safe to call multiple times
|
||||||
|
thanks to get_or_create — depends on WP-01 (EvolutionTrack model) to run.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
_JSON_PATH = os.path.join(os.path.dirname(__file__), "evolution_tracks.json")
|
||||||
|
|
||||||
|
|
||||||
|
def load_tracks():
|
||||||
|
"""Return the locked list of evolution track dicts from the JSON fixture."""
|
||||||
|
with open(_JSON_PATH) as fh:
|
||||||
|
return json.load(fh)
|
||||||
|
|
||||||
|
|
||||||
|
def seed(model_class=None):
|
||||||
|
"""Insert evolution tracks that are not yet in the database.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
model_class: Peewee model with get_or_create support. Defaults to
|
||||||
|
``app.db_engine.EvolutionTrack`` (imported lazily so this module
|
||||||
|
can be imported before WP-01 lands).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of (instance, created) tuples from get_or_create.
|
||||||
|
"""
|
||||||
|
if model_class is None:
|
||||||
|
from app.db_engine import EvolutionTrack as model_class # noqa: PLC0415
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for track in load_tracks():
|
||||||
|
instance, created = model_class.get_or_create(
|
||||||
|
card_type=track["card_type"],
|
||||||
|
defaults=track,
|
||||||
|
)
|
||||||
|
results.append((instance, created))
|
||||||
|
return results
|
||||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
119
tests/test_evolution_seed.py
Normal file
119
tests/test_evolution_seed.py
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
"""Tests for the evolution track seed data fixture (WP-03).
|
||||||
|
|
||||||
|
Unit tests verify the JSON fixture is correctly formed without touching any
|
||||||
|
database. The integration test binds a minimal in-memory EvolutionTrack
|
||||||
|
model (mirroring the schema WP-01 will add to db_engine) to an in-memory
|
||||||
|
SQLite database, calls seed(), and verifies idempotency.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from peewee import CharField, IntegerField, Model, SqliteDatabase
|
||||||
|
|
||||||
|
from app.seed.evolution_tracks import load_tracks, seed
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Fixtures
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
_test_db = SqliteDatabase(":memory:")
|
||||||
|
|
||||||
|
|
||||||
|
class EvolutionTrackStub(Model):
|
||||||
|
"""Minimal EvolutionTrack model for integration tests.
|
||||||
|
|
||||||
|
Mirrors the schema that WP-01 will add to db_engine so the integration
|
||||||
|
test can run without WP-01 being merged.
|
||||||
|
"""
|
||||||
|
|
||||||
|
name = CharField()
|
||||||
|
card_type = CharField(unique=True)
|
||||||
|
formula = CharField()
|
||||||
|
t1 = IntegerField()
|
||||||
|
t2 = IntegerField()
|
||||||
|
t3 = IntegerField()
|
||||||
|
t4 = IntegerField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = _test_db
|
||||||
|
table_name = "evolution_track"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def _db():
|
||||||
|
"""Bind and create the stub table; drop it after each test."""
|
||||||
|
_test_db.connect(reuse_if_open=True)
|
||||||
|
_test_db.create_tables([EvolutionTrackStub])
|
||||||
|
yield
|
||||||
|
_test_db.drop_tables([EvolutionTrackStub])
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Unit tests — JSON fixture only, no database
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_three_tracks_in_seed_data():
|
||||||
|
"""load_tracks() must return exactly 3 evolution tracks."""
|
||||||
|
assert len(load_tracks()) == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_card_types_are_exactly_batter_sp_rp():
|
||||||
|
"""The set of card_type values must be exactly {'batter', 'sp', 'rp'}."""
|
||||||
|
types = {t["card_type"] for t in load_tracks()}
|
||||||
|
assert types == {"batter", "sp", "rp"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_thresholds_positive_and_ascending():
|
||||||
|
"""Each track must have t1 < t2 < t3 < t4, all positive."""
|
||||||
|
for track in load_tracks():
|
||||||
|
assert track["t1"] > 0
|
||||||
|
assert track["t1"] < track["t2"] < track["t3"] < track["t4"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_all_tracks_have_non_empty_formula():
|
||||||
|
"""Every track must have a non-empty formula string."""
|
||||||
|
for track in load_tracks():
|
||||||
|
assert isinstance(track["formula"], str) and track["formula"].strip()
|
||||||
|
|
||||||
|
|
||||||
|
def test_tier_thresholds_match_locked_values():
|
||||||
|
"""Threshold values must exactly match the locked design spec."""
|
||||||
|
tracks = {t["card_type"]: t for t in load_tracks()}
|
||||||
|
|
||||||
|
assert tracks["batter"]["t1"] == 37
|
||||||
|
assert tracks["batter"]["t2"] == 149
|
||||||
|
assert tracks["batter"]["t3"] == 448
|
||||||
|
assert tracks["batter"]["t4"] == 896
|
||||||
|
|
||||||
|
assert tracks["sp"]["t1"] == 10
|
||||||
|
assert tracks["sp"]["t2"] == 40
|
||||||
|
assert tracks["sp"]["t3"] == 120
|
||||||
|
assert tracks["sp"]["t4"] == 240
|
||||||
|
|
||||||
|
assert tracks["rp"]["t1"] == 3
|
||||||
|
assert tracks["rp"]["t2"] == 12
|
||||||
|
assert tracks["rp"]["t3"] == 35
|
||||||
|
assert tracks["rp"]["t4"] == 70
|
||||||
|
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Integration test — uses the stub model + in-memory SQLite
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_seed_is_idempotent():
|
||||||
|
"""Calling seed() twice must not create duplicate rows (get_or_create).
|
||||||
|
|
||||||
|
First call: all three tracks created (created=True for each).
|
||||||
|
Second call: all three already exist (created=False for each).
|
||||||
|
Both calls succeed without error.
|
||||||
|
"""
|
||||||
|
results_first = seed(model_class=EvolutionTrackStub)
|
||||||
|
assert len(results_first) == 3
|
||||||
|
assert all(created for _, created in results_first)
|
||||||
|
|
||||||
|
results_second = seed(model_class=EvolutionTrackStub)
|
||||||
|
assert len(results_second) == 3
|
||||||
|
assert not any(created for _, created in results_second)
|
||||||
|
|
||||||
|
assert EvolutionTrackStub.select().count() == 3
|
||||||
Loading…
Reference in New Issue
Block a user