paper-dynasty-database/tests/test_refractor_models.py
Cal Corum bf3a8ca0d5 chore: remove unused RefractorTierBoost and RefractorCosmetic tables
Speculative schema from initial Refractor design that was never used —
boosts are hardcoded in refractor_boost.py and tier visuals are embedded
in CSS templates. Both tables have zero rows on dev and prod.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 22:27:26 -05:00

216 lines
8.0 KiB
Python

"""
Tests for refractor-related models and BattingSeasonStats.
Covers WP-01 acceptance criteria:
- RefractorTrack: CRUD and unique-name constraint
- RefractorCardState: CRUD, defaults, unique-(player,team) constraint,
and FK resolution back to RefractorTrack
- BattingSeasonStats: CRUD with defaults, unique-(player, team, season),
and in-place stat accumulation
Each test class is self-contained: fixtures from conftest.py supply the
minimal parent rows needed to satisfy FK constraints, and every assertion
targets a single, clearly-named behaviour so failures are easy to trace.
"""
import pytest
from peewee import IntegrityError
from playhouse.shortcuts import model_to_dict
from app.db_engine import (
BattingSeasonStats,
RefractorCardState,
RefractorTrack,
)
# ---------------------------------------------------------------------------
# RefractorTrack
# ---------------------------------------------------------------------------
class TestRefractorTrack:
"""Tests for the RefractorTrack model.
RefractorTrack defines a named progression path (formula +
tier thresholds) for a card type. The name column carries a
UNIQUE constraint so that accidental duplicates are caught at
the database level.
"""
def test_create_track(self, track):
"""Creating a track persists all fields and they round-trip correctly.
Reads back via model_to_dict (recurse=False) to verify the raw
column values, not Python-object representations, match what was
inserted.
"""
data = model_to_dict(track, recurse=False)
assert data["name"] == "Batter Track"
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_track_unique_name(self, track):
"""Inserting a second track with the same name raises IntegrityError.
The UNIQUE constraint on RefractorTrack.name must prevent two
tracks from sharing the same identifier, as the name is used as
a human-readable key throughout the evolution system.
"""
with pytest.raises(IntegrityError):
RefractorTrack.create(
name="Batter Track", # duplicate
card_type="sp",
formula="outs * 3",
t1_threshold=10,
t2_threshold=40,
t3_threshold=120,
t4_threshold=240,
)
# ---------------------------------------------------------------------------
# RefractorCardState
# ---------------------------------------------------------------------------
class TestRefractorCardState:
"""Tests for RefractorCardState, which tracks per-player refractor progress.
Each row represents one card (player) owned by one team, linked to a
specific RefractorTrack. The model records the current tier (0-4),
accumulated progress value, and whether the card is fully evolved.
"""
def test_create_card_state(self, player, team, track):
"""Creating a card state stores all fields and defaults are correct.
Defaults under test:
current_tier → 0 (fresh card, no tier unlocked yet)
current_value → 0.0 (no formula progress accumulated)
fully_evolved → False (evolution is not complete at creation)
last_evaluated_at → None (never evaluated yet)
"""
state = RefractorCardState.create(player=player, team=team, track=track)
fetched = RefractorCardState.get_by_id(state.id)
assert fetched.player_id == player.player_id
assert fetched.team_id == team.id
assert fetched.track_id == track.id
assert fetched.current_tier == 0
assert fetched.current_value == 0.0
assert fetched.fully_evolved is False
assert fetched.last_evaluated_at is None
def test_card_state_unique_player_team(self, player, team, track):
"""A second card state for the same (player, team) pair raises IntegrityError.
The unique index on (player, team) enforces that each player card
has at most one refractor state per team roster slot, preventing
duplicate refractor progress rows for the same physical card.
"""
RefractorCardState.create(player=player, team=team, track=track)
with pytest.raises(IntegrityError):
RefractorCardState.create(player=player, team=team, track=track)
def test_card_state_fk_track(self, player, team, track):
"""Accessing card_state.track returns the original RefractorTrack instance.
This confirms the FK is correctly wired and that Peewee resolves
the relationship, returning an object with the same primary key and
name as the track used during creation.
"""
state = RefractorCardState.create(player=player, team=team, track=track)
fetched = RefractorCardState.get_by_id(state.id)
resolved_track = fetched.track
assert resolved_track.id == track.id
assert resolved_track.name == "Batter Track"
# ---------------------------------------------------------------------------
# BattingSeasonStats
# ---------------------------------------------------------------------------
class TestBattingSeasonStats:
"""Tests for BattingSeasonStats, the per-season batting accumulation table.
Each row aggregates game-by-game batting stats for one player on one
team in one season. The three-column unique constraint prevents
double-counting and ensures a single authoritative row for each
(player, team, season) combination.
"""
def test_create_season_stats(self, player, team):
"""Creating a stats row with explicit values stores everything correctly.
Also verifies the integer stat defaults (all 0) for columns that
are not provided, which is the initial state before any games are
processed.
"""
stats = BattingSeasonStats.create(
player=player,
team=team,
season=11,
games=5,
pa=20,
ab=18,
hits=6,
doubles=1,
triples=0,
hr=2,
bb=2,
hbp=0,
strikeouts=4,
rbi=5,
runs=3,
sb=1,
cs=0,
)
fetched = BattingSeasonStats.get_by_id(stats.id)
assert fetched.player_id == player.player_id
assert fetched.team_id == team.id
assert fetched.season == 11
assert fetched.games == 5
assert fetched.pa == 20
assert fetched.hits == 6
assert fetched.hr == 2
assert fetched.strikeouts == 4
# Nullable meta fields
assert fetched.last_game is None
assert fetched.last_updated_at is None
def test_season_stats_unique_constraint(self, player, team):
"""A second row for the same (player, team, season) raises IntegrityError.
The unique index on these three columns guarantees that each
player-team-season combination has exactly one accumulation row,
preventing duplicate stat aggregation that would inflate totals.
"""
BattingSeasonStats.create(player=player, team=team, season=11)
with pytest.raises(IntegrityError):
BattingSeasonStats.create(player=player, team=team, season=11)
def test_season_stats_increment(self, player, team):
"""Manually incrementing hits on an existing row persists the change.
Simulates the common pattern used by the stats accumulator:
fetch the row, add the game delta, save. Verifies that save()
writes back to the database and that subsequent reads reflect the
updated value.
"""
stats = BattingSeasonStats.create(
player=player,
team=team,
season=11,
hits=10,
)
stats.hits += 3
stats.save()
refreshed = BattingSeasonStats.get_by_id(stats.id)
assert refreshed.hits == 13