diff --git a/app/db_engine.py b/app/db_engine.py index 0ec6bec..bb9c9f0 100644 --- a/app/db_engine.py +++ b/app/db_engine.py @@ -1156,69 +1156,6 @@ if not SKIP_TABLE_CREATION: 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() # scout_db = SqliteDatabase( diff --git a/app/models/evolution.py b/app/models/evolution.py deleted file mode 100644 index 7763885..0000000 --- a/app/models/evolution.py +++ /dev/null @@ -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, -) diff --git a/tests/test_evolution_models.py b/tests/test_evolution_models.py deleted file mode 100644 index 1a6a4c9..0000000 --- a/tests/test_evolution_models.py +++ /dev/null @@ -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