diff --git a/app/routers_v2/cards.py b/app/routers_v2/cards.py index b814c51..6ef3617 100644 --- a/app/routers_v2/cards.py +++ b/app/routers_v2/cards.py @@ -16,7 +16,7 @@ from ..db_engine import ( DoesNotExist, ) from ..dependencies import oauth2_scheme, valid_token -from ..services.refractor_init import _determine_card_type, initialize_card_evolution +from ..services.refractor_init import _determine_card_type, initialize_card_refractor router = APIRouter(prefix="/api/v2/cards", tags=["cards"]) @@ -227,7 +227,7 @@ async def v1_cards_post(cards: CardModel, token: str = Depends(oauth2_scheme)): try: this_player = Player.get_by_id(x.player_id) card_type = _determine_card_type(this_player) - initialize_card_evolution(x.player_id, x.team_id, card_type) + initialize_card_refractor(x.player_id, x.team_id, card_type) except Exception: logging.exception( "refractor hook: unexpected error for player_id=%s team_id=%s", diff --git a/app/routers_v2/teams.py b/app/routers_v2/teams.py index e630c9a..6d1146a 100644 --- a/app/routers_v2/teams.py +++ b/app/routers_v2/teams.py @@ -1530,8 +1530,8 @@ async def delete_team(team_id, token: str = Depends(oauth2_scheme)): raise HTTPException(status_code=500, detail=f"Team {team_id} was not deleted") -@router.get("/{team_id}/evolutions") -async def list_team_evolutions( +@router.get("/{team_id}/refractors") +async def list_team_refractors( team_id: int, card_type: Optional[str] = Query(default=None), tier: Optional[int] = Query(default=None), diff --git a/app/services/refractor_init.py b/app/services/refractor_init.py index 9e49c9f..6f3780d 100644 --- a/app/services/refractor_init.py +++ b/app/services/refractor_init.py @@ -3,7 +3,7 @@ WP-10: Pack opening hook — refractor_card_state initialization. Public API ---------- -initialize_card_evolution(player_id, team_id, card_type) +initialize_card_refractor(player_id, team_id, card_type) Get-or-create a RefractorCardState for the (player_id, team_id) pair. Returns the state instance on success, or None if initialization fails (missing track, integrity error, etc.). Never raises. @@ -53,7 +53,7 @@ def _determine_card_type(player) -> str: return "batter" -def initialize_card_evolution( +def initialize_card_refractor( player_id: int, team_id: int, card_type: str, diff --git a/migrations/2026-03-23_rename_evolution_to_refractor.sql b/migrations/2026-03-23_rename_evolution_to_refractor.sql index c2a3d3f..48c6f0b 100644 --- a/migrations/2026-03-23_rename_evolution_to_refractor.sql +++ b/migrations/2026-03-23_rename_evolution_to_refractor.sql @@ -13,3 +13,7 @@ ALTER TABLE evolution_track RENAME TO refractor_track; ALTER TABLE evolution_card_state RENAME TO refractor_card_state; ALTER TABLE evolution_tier_boost RENAME TO refractor_tier_boost; ALTER TABLE evolution_cosmetic RENAME TO refractor_cosmetic; + +-- Rename indexes to match new table names +ALTER INDEX IF EXISTS evolution_card_state_player_team_uniq RENAME TO refractor_card_state_player_team_uniq; +ALTER INDEX IF EXISTS evolution_tier_boost_track_tier_type_target_uniq RENAME TO refractor_tier_boost_track_tier_type_target_uniq; diff --git a/tests/test_refractor_init.py b/tests/test_refractor_init.py index d92f80f..d8748bf 100644 --- a/tests/test_refractor_init.py +++ b/tests/test_refractor_init.py @@ -1,7 +1,7 @@ """ Tests for WP-10: refractor_card_state initialization on pack opening. -Covers `app/services/refractor_init.py` — the `initialize_card_evolution` +Covers `app/services/refractor_init.py` — the `initialize_card_refractor` function that creates an RefractorCardState row when a card is first acquired. Test strategy: @@ -30,7 +30,7 @@ from app.db_engine import ( RefractorTrack, Player, ) -from app.services.refractor_init import _determine_card_type, initialize_card_evolution +from app.services.refractor_init import _determine_card_type, initialize_card_refractor # --------------------------------------------------------------------------- @@ -154,12 +154,12 @@ class TestDetermineCardType: # --------------------------------------------------------------------------- -# Integration tests — initialize_card_evolution +# Integration tests — initialize_card_refractor # --------------------------------------------------------------------------- class TestInitializeCardEvolution: - """Integration tests for initialize_card_evolution against in-memory SQLite. + """Integration tests for initialize_card_refractor against in-memory SQLite. Each test relies on the conftest autouse fixture to get a clean database. We create tracks for all three card types so the function can always find @@ -170,7 +170,7 @@ class TestInitializeCardEvolution: def seed_tracks(self): """Create one RefractorTrack per card_type before each test. - initialize_card_evolution does a DB lookup for a track matching the + initialize_card_refractor does a DB lookup for a track matching the card_type. If no track exists the function must not crash (it should log and return None), but having tracks present lets us verify the happy path for all three types without repeating setup in every test. @@ -189,7 +189,7 @@ class TestInitializeCardEvolution: - track matches the player's card_type (batter here) """ player = _make_player(rarity, "2B") - state = initialize_card_evolution(player.player_id, team.id, "batter") + state = initialize_card_refractor(player.player_id, team.id, "batter") assert state is not None assert state.player_id == player.player_id @@ -208,7 +208,7 @@ class TestInitializeCardEvolution: """ player = _make_player(rarity, "SS") # First call creates the state - state1 = initialize_card_evolution(player.player_id, team.id, "batter") + state1 = initialize_card_refractor(player.player_id, team.id, "batter") assert state1 is not None # Simulate partial evolution progress @@ -217,7 +217,7 @@ class TestInitializeCardEvolution: state1.save() # Second call (duplicate card) must not reset progress - state2 = initialize_card_evolution(player.player_id, team.id, "batter") + state2 = initialize_card_refractor(player.player_id, team.id, "batter") assert state2 is not None # Exactly one row in the database @@ -246,8 +246,8 @@ class TestInitializeCardEvolution: player_a = _make_player(rarity, "LF") player_b = _make_player(rarity, "RF") - state_a = initialize_card_evolution(player_a.player_id, team.id, "batter") - state_b = initialize_card_evolution(player_b.player_id, team.id, "batter") + state_a = initialize_card_refractor(player_a.player_id, team.id, "batter") + state_b = initialize_card_refractor(player_b.player_id, team.id, "batter") assert state_a is not None assert state_b is not None @@ -264,7 +264,7 @@ class TestInitializeCardEvolution: state links to the sp track, not the batter track. """ player = _make_player(rarity, "SP") - state = initialize_card_evolution(player.player_id, team.id, "sp") + state = initialize_card_refractor(player.player_id, team.id, "sp") assert state is not None assert state.track_id == self.sp_track.id @@ -272,7 +272,7 @@ class TestInitializeCardEvolution: def test_rp_card_gets_rp_track(self, rarity, team): """A relief pitcher (RP or CP) is assigned the 'rp' RefractorTrack.""" player = _make_player(rarity, "RP") - state = initialize_card_evolution(player.player_id, team.id, "rp") + state = initialize_card_refractor(player.player_id, team.id, "rp") assert state is not None assert state.track_id == self.rp_track.id @@ -291,7 +291,7 @@ class TestInitializeCardEvolution: # Delete the sp track to simulate missing seed data self.sp_track.delete_instance() - result = initialize_card_evolution(player.player_id, team.id, "sp") + result = initialize_card_refractor(player.player_id, team.id, "sp") assert result is None def test_card_type_from_pos1_batter(self, rarity, team): @@ -302,7 +302,7 @@ class TestInitializeCardEvolution: """ player = _make_player(rarity, "3B") card_type = _determine_card_type(player) - state = initialize_card_evolution(player.player_id, team.id, card_type) + state = initialize_card_refractor(player.player_id, team.id, card_type) assert state is not None assert state.track_id == self.batter_track.id @@ -311,7 +311,7 @@ class TestInitializeCardEvolution: """_determine_card_type is wired correctly for a starting pitcher.""" player = _make_player(rarity, "SP") card_type = _determine_card_type(player) - state = initialize_card_evolution(player.player_id, team.id, card_type) + state = initialize_card_refractor(player.player_id, team.id, card_type) assert state is not None assert state.track_id == self.sp_track.id @@ -320,7 +320,7 @@ class TestInitializeCardEvolution: """_determine_card_type correctly routes CP to the rp track.""" player = _make_player(rarity, "CP") card_type = _determine_card_type(player) - state = initialize_card_evolution(player.player_id, team.id, card_type) + state = initialize_card_refractor(player.player_id, team.id, card_type) assert state is not None assert state.track_id == self.rp_track.id diff --git a/tests/test_refractor_state_api.py b/tests/test_refractor_state_api.py index f7a8689..6394219 100644 --- a/tests/test_refractor_state_api.py +++ b/tests/test_refractor_state_api.py @@ -1,7 +1,7 @@ """Integration tests for the refractor card state API endpoints (WP-07). Tests cover: - GET /api/v2/teams/{team_id}/evolutions + GET /api/v2/teams/{team_id}/refractors GET /api/v2/refractor/cards/{card_id} All tests require a live PostgreSQL connection (POSTGRES_HOST env var) and @@ -24,7 +24,7 @@ Object graph built by fixtures Test matrix ----------- - test_list_team_evolutions -- baseline: returns count + items for a team + test_list_team_refractors -- baseline: returns count + items for a team test_list_filter_by_card_type -- card_type query param filters by track.card_type test_list_filter_by_tier -- tier query param filters by current_tier test_list_pagination -- page/per_page params slice results correctly @@ -288,19 +288,19 @@ def client(): # --------------------------------------------------------------------------- -# Tests: GET /api/v2/teams/{team_id}/evolutions +# Tests: GET /api/v2/teams/{team_id}/refractors # --------------------------------------------------------------------------- @_skip_no_pg -def test_list_team_evolutions(client, seeded_data): - """GET /teams/{id}/evolutions returns count=1 and one item for the seeded state. +def test_list_team_refractors(client, seeded_data): + """GET /teams/{id}/refractors returns count=1 and one item for the seeded state. Verifies the basic list response shape: a dict with 'count' and 'items', and that the single item contains player_id, team_id, and current_tier. """ team_id = seeded_data["team_id"] - resp = client.get(f"/api/v2/teams/{team_id}/evolutions", headers=AUTH_HEADER) + resp = client.get(f"/api/v2/teams/{team_id}/refractors", headers=AUTH_HEADER) assert resp.status_code == 200 data = resp.json() assert data["count"] == 1 @@ -337,7 +337,7 @@ def test_list_filter_by_card_type(client, seeded_data, pg_conn): team_id = seeded_data["team_id"] resp_batter = client.get( - f"/api/v2/teams/{team_id}/evolutions?card_type=batter", headers=AUTH_HEADER + f"/api/v2/teams/{team_id}/refractors?card_type=batter", headers=AUTH_HEADER ) assert resp_batter.status_code == 200 batter_data = resp_batter.json() @@ -345,7 +345,7 @@ def test_list_filter_by_card_type(client, seeded_data, pg_conn): assert batter_data["items"][0]["player_id"] == seeded_data["player_id"] resp_sp = client.get( - f"/api/v2/teams/{team_id}/evolutions?card_type=sp", headers=AUTH_HEADER + f"/api/v2/teams/{team_id}/refractors?card_type=sp", headers=AUTH_HEADER ) assert resp_sp.status_code == 200 sp_data = resp_sp.json() @@ -377,13 +377,13 @@ def test_list_filter_by_tier(client, seeded_data, pg_conn): team_id = seeded_data["team_id"] resp_t1 = client.get( - f"/api/v2/teams/{team_id}/evolutions?tier=1", headers=AUTH_HEADER + f"/api/v2/teams/{team_id}/refractors?tier=1", headers=AUTH_HEADER ) assert resp_t1.status_code == 200 assert resp_t1.json()["count"] == 0 resp_t2 = client.get( - f"/api/v2/teams/{team_id}/evolutions?tier=2", headers=AUTH_HEADER + f"/api/v2/teams/{team_id}/refractors?tier=2", headers=AUTH_HEADER ) assert resp_t2.status_code == 200 t2_data = resp_t2.json() @@ -426,14 +426,14 @@ def test_list_pagination(client, seeded_data, pg_conn): team_id = seeded_data["team_id"] resp1 = client.get( - f"/api/v2/teams/{team_id}/evolutions?page=1&per_page=1", headers=AUTH_HEADER + f"/api/v2/teams/{team_id}/refractors?page=1&per_page=1", headers=AUTH_HEADER ) assert resp1.status_code == 200 data1 = resp1.json() assert len(data1["items"]) == 1 resp2 = client.get( - f"/api/v2/teams/{team_id}/evolutions?page=2&per_page=1", headers=AUTH_HEADER + f"/api/v2/teams/{team_id}/refractors?page=2&per_page=1", headers=AUTH_HEADER ) assert resp2.status_code == 200 data2 = resp2.json() @@ -596,13 +596,13 @@ def test_auth_required(client, seeded_data): """Both endpoints return 401 when no Bearer token is provided. Verifies that the valid_token dependency is enforced on: - GET /api/v2/teams/{id}/evolutions + GET /api/v2/teams/{id}/refractors GET /api/v2/refractor/cards/{id} """ team_id = seeded_data["team_id"] card_id = seeded_data["card_id"] - resp_list = client.get(f"/api/v2/teams/{team_id}/evolutions") + resp_list = client.get(f"/api/v2/teams/{team_id}/refractors") assert resp_list.status_code == 401 resp_card = client.get(f"/api/v2/refractor/cards/{card_id}")