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

Reviewed-on: #83
This commit is contained in:
cal 2026-03-16 16:12:18 +00:00
commit 223743d89f
5 changed files with 165 additions and 0 deletions

0
app/seed/__init__.py Normal file
View File

View 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}
]

View 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
View File

View 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