fix: remove evolution models from WP-02 PR (#82)
Evolution models (EvolutionTrack, EvolutionCardState, EvolutionTierBoost, EvolutionCosmetic), their re-export module, and tests were included in this PR without disclosure. Removed to keep this PR scoped to PlayerSeasonStats (WP-02) only per review feedback. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
4bfd878486
commit
8dfc5ef371
@ -1156,69 +1156,6 @@ if not SKIP_TABLE_CREATION:
|
|||||||
db.create_tables([ScoutOpportunity, ScoutClaim], safe=True)
|
db.create_tables([ScoutOpportunity, ScoutClaim], safe=True)
|
||||||
|
|
||||||
|
|
||||||
class EvolutionTrack(BaseModel):
|
|
||||||
name = CharField()
|
|
||||||
card_type = CharField() # batter / sp / rp
|
|
||||||
formula = CharField()
|
|
||||||
t1_threshold = IntegerField()
|
|
||||||
t2_threshold = IntegerField()
|
|
||||||
t3_threshold = IntegerField()
|
|
||||||
t4_threshold = IntegerField()
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
database = db
|
|
||||||
table_name = "evolution_track"
|
|
||||||
|
|
||||||
|
|
||||||
class EvolutionCardState(BaseModel):
|
|
||||||
player = ForeignKeyField(Player)
|
|
||||||
team = ForeignKeyField(Team)
|
|
||||||
track = ForeignKeyField(EvolutionTrack)
|
|
||||||
current_tier = IntegerField(default=0) # valid range: 0–4
|
|
||||||
current_value = FloatField(default=0.0)
|
|
||||||
fully_evolved = BooleanField(default=False)
|
|
||||||
last_evaluated_at = DateTimeField(null=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
database = db
|
|
||||||
table_name = "evolution_card_state"
|
|
||||||
|
|
||||||
|
|
||||||
ecs_index = ModelIndex(
|
|
||||||
EvolutionCardState,
|
|
||||||
(EvolutionCardState.player, EvolutionCardState.team),
|
|
||||||
unique=True,
|
|
||||||
)
|
|
||||||
EvolutionCardState.add_index(ecs_index)
|
|
||||||
|
|
||||||
|
|
||||||
class EvolutionTierBoost(BaseModel):
|
|
||||||
"""Phase 2 stub — minimal model, schema to be defined in phase 2."""
|
|
||||||
|
|
||||||
card_state = ForeignKeyField(EvolutionCardState)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
database = db
|
|
||||||
table_name = "evolution_tier_boost"
|
|
||||||
|
|
||||||
|
|
||||||
class EvolutionCosmetic(BaseModel):
|
|
||||||
"""Phase 2 stub — minimal model, schema to be defined in phase 2."""
|
|
||||||
|
|
||||||
card_state = ForeignKeyField(EvolutionCardState)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
database = db
|
|
||||||
table_name = "evolution_cosmetic"
|
|
||||||
|
|
||||||
|
|
||||||
if not SKIP_TABLE_CREATION:
|
|
||||||
db.create_tables(
|
|
||||||
[EvolutionTrack, EvolutionCardState, EvolutionTierBoost, EvolutionCosmetic],
|
|
||||||
safe=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
db.close()
|
db.close()
|
||||||
|
|
||||||
# scout_db = SqliteDatabase(
|
# scout_db = SqliteDatabase(
|
||||||
|
|||||||
@ -1,12 +0,0 @@
|
|||||||
"""Evolution ORM models.
|
|
||||||
|
|
||||||
Models are defined in db_engine alongside all other Peewee models; this
|
|
||||||
module re-exports them so callers can import from `app.models.evolution`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
from ..db_engine import ( # noqa: F401
|
|
||||||
EvolutionTrack,
|
|
||||||
EvolutionCardState,
|
|
||||||
EvolutionTierBoost,
|
|
||||||
EvolutionCosmetic,
|
|
||||||
)
|
|
||||||
@ -1,338 +0,0 @@
|
|||||||
"""Tests for evolution Peewee models (WP-01).
|
|
||||||
|
|
||||||
Unit tests verify model structure and defaults on unsaved instances without
|
|
||||||
touching a database. Integration tests use an in-memory SQLite database to
|
|
||||||
verify table creation, FK relationships, and unique constraints.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from peewee import SqliteDatabase, IntegrityError
|
|
||||||
from playhouse.shortcuts import model_to_dict
|
|
||||||
|
|
||||||
from app.models.evolution import (
|
|
||||||
EvolutionTrack,
|
|
||||||
EvolutionCardState,
|
|
||||||
EvolutionTierBoost,
|
|
||||||
EvolutionCosmetic,
|
|
||||||
)
|
|
||||||
from app.db_engine import Rarity, Event, Cardset, MlbPlayer, Player, Team
|
|
||||||
|
|
||||||
# All models that must exist in the test database (dependency order).
|
|
||||||
_TEST_MODELS = [
|
|
||||||
Rarity,
|
|
||||||
Event,
|
|
||||||
Cardset,
|
|
||||||
MlbPlayer,
|
|
||||||
Player,
|
|
||||||
Team,
|
|
||||||
EvolutionTrack,
|
|
||||||
EvolutionCardState,
|
|
||||||
EvolutionTierBoost,
|
|
||||||
EvolutionCosmetic,
|
|
||||||
]
|
|
||||||
|
|
||||||
_test_db = SqliteDatabase(":memory:", pragmas={"foreign_keys": 1})
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
|
||||||
def setup_test_db():
|
|
||||||
"""Bind all models to an in-memory SQLite database, create tables, and
|
|
||||||
tear them down after each test so each test starts from a clean state."""
|
|
||||||
_test_db.bind(_TEST_MODELS)
|
|
||||||
_test_db.create_tables(_TEST_MODELS)
|
|
||||||
yield _test_db
|
|
||||||
_test_db.drop_tables(list(reversed(_TEST_MODELS)), safe=True)
|
|
||||||
|
|
||||||
|
|
||||||
# ── Fixture helpers ────────────────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
def make_rarity():
|
|
||||||
return Rarity.create(value=1, name="Common", color="#ffffff")
|
|
||||||
|
|
||||||
|
|
||||||
def make_cardset():
|
|
||||||
return Cardset.create(name="2025", description="2025 Season", total_cards=100)
|
|
||||||
|
|
||||||
|
|
||||||
def make_player(cardset, rarity):
|
|
||||||
return Player.create(
|
|
||||||
player_id=1,
|
|
||||||
p_name="Test Player",
|
|
||||||
cost=100,
|
|
||||||
image="test.png",
|
|
||||||
mlbclub="BOS",
|
|
||||||
franchise="Boston",
|
|
||||||
cardset=cardset,
|
|
||||||
set_num=1,
|
|
||||||
rarity=rarity,
|
|
||||||
pos_1="OF",
|
|
||||||
description="Test",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def make_team():
|
|
||||||
return Team.create(
|
|
||||||
abbrev="TEST",
|
|
||||||
sname="Test",
|
|
||||||
lname="Test Team",
|
|
||||||
gmid=123456789,
|
|
||||||
gmname="testuser",
|
|
||||||
gsheet="https://example.com",
|
|
||||||
wallet=1000,
|
|
||||||
team_value=1000,
|
|
||||||
collection_value=1000,
|
|
||||||
season=1,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def make_track(card_type="batter"):
|
|
||||||
return EvolutionTrack.create(
|
|
||||||
name="Batter",
|
|
||||||
card_type=card_type,
|
|
||||||
formula="pa+tb*2",
|
|
||||||
t1_threshold=37,
|
|
||||||
t2_threshold=149,
|
|
||||||
t3_threshold=448,
|
|
||||||
t4_threshold=896,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ── Unit: model field validation ───────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
class TestEvolutionTrackFields:
|
|
||||||
"""model_to_dict works on unsaved EvolutionTrack instances and all fields
|
|
||||||
are accessible with the correct values."""
|
|
||||||
|
|
||||||
def test_model_to_dict_unsaved(self):
|
|
||||||
"""All EvolutionTrack fields appear in model_to_dict on an unsaved instance."""
|
|
||||||
track = EvolutionTrack(
|
|
||||||
name="Batter",
|
|
||||||
card_type="batter",
|
|
||||||
formula="pa+tb*2",
|
|
||||||
t1_threshold=37,
|
|
||||||
t2_threshold=149,
|
|
||||||
t3_threshold=448,
|
|
||||||
t4_threshold=896,
|
|
||||||
)
|
|
||||||
data = model_to_dict(track, recurse=False)
|
|
||||||
assert data["name"] == "Batter"
|
|
||||||
assert data["card_type"] == "batter"
|
|
||||||
assert data["formula"] == "pa+tb*2"
|
|
||||||
assert data["t1_threshold"] == 37
|
|
||||||
assert data["t2_threshold"] == 149
|
|
||||||
assert data["t3_threshold"] == 448
|
|
||||||
assert data["t4_threshold"] == 896
|
|
||||||
|
|
||||||
def test_all_threshold_fields_present(self):
|
|
||||||
"""EvolutionTrack exposes all four tier threshold columns."""
|
|
||||||
fields = EvolutionTrack._meta.fields
|
|
||||||
for col in ("t1_threshold", "t2_threshold", "t3_threshold", "t4_threshold"):
|
|
||||||
assert col in fields, f"Missing column: {col}"
|
|
||||||
|
|
||||||
|
|
||||||
class TestEvolutionCardStateFields:
|
|
||||||
"""model_to_dict works on unsaved EvolutionCardState instances and
|
|
||||||
default values match the spec."""
|
|
||||||
|
|
||||||
def test_model_to_dict_defaults(self):
|
|
||||||
"""Defaults: current_tier=0, current_value=0.0, fully_evolved=False,
|
|
||||||
last_evaluated_at=None."""
|
|
||||||
state = EvolutionCardState()
|
|
||||||
data = model_to_dict(state, recurse=False)
|
|
||||||
assert data["current_tier"] == 0
|
|
||||||
assert data["current_value"] == 0.0
|
|
||||||
assert data["fully_evolved"] is False
|
|
||||||
assert data["last_evaluated_at"] is None
|
|
||||||
|
|
||||||
def test_no_progress_since_field(self):
|
|
||||||
"""EvolutionCardState must not have a progress_since field (removed from spec)."""
|
|
||||||
assert "progress_since" not in EvolutionCardState._meta.fields
|
|
||||||
|
|
||||||
|
|
||||||
class TestEvolutionStubFields:
|
|
||||||
"""Phase 2 stub models are importable and respond to model_to_dict."""
|
|
||||||
|
|
||||||
def test_tier_boost_importable(self):
|
|
||||||
assert EvolutionTierBoost is not None
|
|
||||||
|
|
||||||
def test_cosmetic_importable(self):
|
|
||||||
assert EvolutionCosmetic is not None
|
|
||||||
|
|
||||||
def test_tier_boost_model_to_dict_unsaved(self):
|
|
||||||
"""model_to_dict on an unsaved EvolutionTierBoost returns a dict."""
|
|
||||||
data = model_to_dict(EvolutionTierBoost(), recurse=False)
|
|
||||||
assert isinstance(data, dict)
|
|
||||||
|
|
||||||
def test_cosmetic_model_to_dict_unsaved(self):
|
|
||||||
"""model_to_dict on an unsaved EvolutionCosmetic returns a dict."""
|
|
||||||
data = model_to_dict(EvolutionCosmetic(), recurse=False)
|
|
||||||
assert isinstance(data, dict)
|
|
||||||
|
|
||||||
|
|
||||||
# ── Unit: constraint definitions ──────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
class TestTierConstraints:
|
|
||||||
"""current_tier defaults to 0 and valid tier values (0-4) can be saved."""
|
|
||||||
|
|
||||||
def test_tier_zero_is_default(self):
|
|
||||||
"""EvolutionCardState.current_tier defaults to 0 on create."""
|
|
||||||
rarity = make_rarity()
|
|
||||||
cardset = make_cardset()
|
|
||||||
player = make_player(cardset, rarity)
|
|
||||||
team = make_team()
|
|
||||||
track = make_track()
|
|
||||||
state = EvolutionCardState.create(player=player, team=team, track=track)
|
|
||||||
assert state.current_tier == 0
|
|
||||||
|
|
||||||
def test_tier_four_is_valid(self):
|
|
||||||
"""Tier 4 (fully evolved cap) can be persisted without error."""
|
|
||||||
rarity = make_rarity()
|
|
||||||
cardset = make_cardset()
|
|
||||||
player = make_player(cardset, rarity)
|
|
||||||
team = make_team()
|
|
||||||
track = make_track()
|
|
||||||
state = EvolutionCardState.create(
|
|
||||||
player=player, team=team, track=track, current_tier=4
|
|
||||||
)
|
|
||||||
assert state.current_tier == 4
|
|
||||||
|
|
||||||
|
|
||||||
class TestUniqueConstraint:
|
|
||||||
"""Unique index on (player_id, team_id) is enforced at the DB level."""
|
|
||||||
|
|
||||||
def test_duplicate_player_team_raises(self):
|
|
||||||
"""A second EvolutionCardState for the same (player, team) raises IntegrityError,
|
|
||||||
even when a different track is used."""
|
|
||||||
rarity = make_rarity()
|
|
||||||
cardset = make_cardset()
|
|
||||||
player = make_player(cardset, rarity)
|
|
||||||
team = make_team()
|
|
||||||
track1 = make_track("batter")
|
|
||||||
track2 = EvolutionTrack.create(
|
|
||||||
name="SP",
|
|
||||||
card_type="sp",
|
|
||||||
formula="ip+k",
|
|
||||||
t1_threshold=10,
|
|
||||||
t2_threshold=40,
|
|
||||||
t3_threshold=120,
|
|
||||||
t4_threshold=240,
|
|
||||||
)
|
|
||||||
EvolutionCardState.create(player=player, team=team, track=track1)
|
|
||||||
with pytest.raises(IntegrityError):
|
|
||||||
EvolutionCardState.create(player=player, team=team, track=track2)
|
|
||||||
|
|
||||||
def test_same_player_different_teams_allowed(self):
|
|
||||||
"""One EvolutionCardState per team is allowed for the same player."""
|
|
||||||
rarity = make_rarity()
|
|
||||||
cardset = make_cardset()
|
|
||||||
player = make_player(cardset, rarity)
|
|
||||||
team1 = make_team()
|
|
||||||
team2 = Team.create(
|
|
||||||
abbrev="TM2",
|
|
||||||
sname="T2",
|
|
||||||
lname="Team Two",
|
|
||||||
gmid=987654321,
|
|
||||||
gmname="user2",
|
|
||||||
gsheet="https://example.com",
|
|
||||||
wallet=1000,
|
|
||||||
team_value=1000,
|
|
||||||
collection_value=1000,
|
|
||||||
season=1,
|
|
||||||
)
|
|
||||||
track = make_track()
|
|
||||||
EvolutionCardState.create(player=player, team=team1, track=track)
|
|
||||||
state2 = EvolutionCardState.create(player=player, team=team2, track=track)
|
|
||||||
assert state2.id is not None
|
|
||||||
|
|
||||||
|
|
||||||
# ── Integration: table creation ────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
class TestTableCreation:
|
|
||||||
"""All four evolution tables are created in the test DB and are queryable."""
|
|
||||||
|
|
||||||
def test_evolution_track_table_exists(self):
|
|
||||||
assert EvolutionTrack.select().count() == 0
|
|
||||||
|
|
||||||
def test_evolution_card_state_table_exists(self):
|
|
||||||
assert EvolutionCardState.select().count() == 0
|
|
||||||
|
|
||||||
def test_evolution_tier_boost_table_exists(self):
|
|
||||||
assert EvolutionTierBoost.select().count() == 0
|
|
||||||
|
|
||||||
def test_evolution_cosmetic_table_exists(self):
|
|
||||||
assert EvolutionCosmetic.select().count() == 0
|
|
||||||
|
|
||||||
|
|
||||||
# ── Integration: FK enforcement ────────────────────────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
class TestFKEnforcement:
|
|
||||||
"""FK columns resolve to the correct related instances."""
|
|
||||||
|
|
||||||
def test_card_state_player_fk_resolves(self):
|
|
||||||
"""EvolutionCardState.player_id matches the Player we inserted."""
|
|
||||||
rarity = make_rarity()
|
|
||||||
cardset = make_cardset()
|
|
||||||
player = make_player(cardset, rarity)
|
|
||||||
team = make_team()
|
|
||||||
track = make_track()
|
|
||||||
state = EvolutionCardState.create(player=player, team=team, track=track)
|
|
||||||
fetched = EvolutionCardState.get_by_id(state.id)
|
|
||||||
assert fetched.player_id == player.player_id
|
|
||||||
|
|
||||||
def test_card_state_team_fk_resolves(self):
|
|
||||||
"""EvolutionCardState.team_id matches the Team we inserted."""
|
|
||||||
rarity = make_rarity()
|
|
||||||
cardset = make_cardset()
|
|
||||||
player = make_player(cardset, rarity)
|
|
||||||
team = make_team()
|
|
||||||
track = make_track()
|
|
||||||
state = EvolutionCardState.create(player=player, team=team, track=track)
|
|
||||||
fetched = EvolutionCardState.get_by_id(state.id)
|
|
||||||
assert fetched.team_id == team.id
|
|
||||||
|
|
||||||
def test_card_state_track_fk_resolves(self):
|
|
||||||
"""EvolutionCardState.track_id matches the EvolutionTrack we inserted."""
|
|
||||||
rarity = make_rarity()
|
|
||||||
cardset = make_cardset()
|
|
||||||
player = make_player(cardset, rarity)
|
|
||||||
team = make_team()
|
|
||||||
track = make_track()
|
|
||||||
state = EvolutionCardState.create(player=player, team=team, track=track)
|
|
||||||
fetched = EvolutionCardState.get_by_id(state.id)
|
|
||||||
assert fetched.track_id == track.id
|
|
||||||
|
|
||||||
|
|
||||||
# ── Integration: model_to_dict on saved instances ──────────────────────────
|
|
||||||
|
|
||||||
|
|
||||||
class TestModelToDictOnSaved:
|
|
||||||
"""model_to_dict() works correctly on saved instances of all four models."""
|
|
||||||
|
|
||||||
def test_evolution_track_saved(self):
|
|
||||||
"""Saved EvolutionTrack round-trips through model_to_dict correctly."""
|
|
||||||
track = make_track()
|
|
||||||
data = model_to_dict(track, recurse=False)
|
|
||||||
assert data["name"] == "Batter"
|
|
||||||
assert data["card_type"] == "batter"
|
|
||||||
assert data["formula"] == "pa+tb*2"
|
|
||||||
assert data["t1_threshold"] == 37
|
|
||||||
|
|
||||||
def test_evolution_card_state_saved(self):
|
|
||||||
"""Saved EvolutionCardState round-trips through model_to_dict correctly."""
|
|
||||||
rarity = make_rarity()
|
|
||||||
cardset = make_cardset()
|
|
||||||
player = make_player(cardset, rarity)
|
|
||||||
team = make_team()
|
|
||||||
track = make_track()
|
|
||||||
state = EvolutionCardState.create(
|
|
||||||
player=player, team=team, track=track, current_value=42.5, current_tier=2
|
|
||||||
)
|
|
||||||
data = model_to_dict(state, recurse=False)
|
|
||||||
assert data["current_value"] == 42.5
|
|
||||||
assert data["current_tier"] == 2
|
|
||||||
assert data["fully_evolved"] is False
|
|
||||||
Loading…
Reference in New Issue
Block a user