paper-dynasty-database/tests/test_card_pricing.py
Cal Corum 64be5eabdc feat: add initial test suite with pytest (#28)
- Add SQLITE_DB_PATH env var to db_engine.py for test isolation
- Create tests/conftest.py with in-memory SQLite fixture and sample data helpers
- Add tests/test_dependencies.py: unit tests for valid_token, mround, param_char, get_req_url
- Add tests/test_card_pricing.py: tests for Player.change_on_sell/buy and get_all_pos
- Add tests/test_api_packs.py: integration tests for GET/POST/DELETE /api/v2/packs
- Add requirements-test.txt with pytest and httpx
- Add test job to CI workflow (build now requires tests to pass first)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 16:35:53 -06:00

117 lines
4.5 KiB
Python

"""
Unit tests for card pricing logic in Player model.
Player.change_on_sell() and Player.change_on_buy() are critical business logic
used whenever cards are traded. These tests verify the price update math and
the floor/ceiling behaviour.
"""
import math
import pytest
class TestPlayerChangeOnSell:
"""Tests for Player.change_on_sell() — price decreases 5% on each sale."""
def test_sell_reduces_cost_by_5_percent(self, sample_player):
"""Selling a card should reduce its cost to floor(cost * 0.95)."""
sample_player.cost = 100
sample_player.change_on_sell()
assert sample_player.cost == math.floor(100 * 0.95) # 95
def test_sell_saves_to_db(self, sample_player):
"""change_on_sell() should persist the new price to the database."""
from app.db_engine import Player
sample_player.cost = 200
sample_player.change_on_sell()
refreshed = Player.get_by_id(sample_player.pk)
assert refreshed.cost == math.floor(200 * 0.95) # 190
def test_sell_floors_at_1(self, sample_player):
"""Price should never drop below 1, even at very low starting values."""
sample_player.cost = 1
sample_player.change_on_sell()
assert sample_player.cost == 1
def test_sell_large_price(self, sample_player):
"""Large prices should still apply the 5% reduction correctly."""
sample_player.cost = 10000
sample_player.change_on_sell()
assert sample_player.cost == math.floor(10000 * 0.95) # 9500
def test_sell_rounds_down(self, sample_player):
"""floor() means fractional results are rounded down, not up."""
sample_player.cost = 21 # 21 * 0.95 = 19.95 → floor → 19
sample_player.change_on_sell()
assert sample_player.cost == 19
class TestPlayerChangeOnBuy:
"""Tests for Player.change_on_buy() — price increases 10% on each purchase."""
def test_buy_increases_cost_by_10_percent(self, sample_player):
"""Buying a card should increase its cost to ceil(cost * 1.1)."""
sample_player.cost = 100
sample_player.change_on_buy()
assert sample_player.cost == math.ceil(100 * 1.1) # 110
def test_buy_saves_to_db(self, sample_player):
"""change_on_buy() should persist the new price to the database."""
from app.db_engine import Player
sample_player.cost = 200
sample_player.change_on_buy()
refreshed = Player.get_by_id(sample_player.pk)
assert refreshed.cost == math.ceil(200 * 1.1) # 220
def test_buy_from_low_price(self, sample_player):
"""Low prices should still apply the 10% increase."""
sample_player.cost = 1
sample_player.change_on_buy()
assert sample_player.cost == math.ceil(1 * 1.1) # 2 (ceil of 1.1)
def test_buy_rounds_up(self, sample_player):
"""ceil() means fractional results are rounded up."""
sample_player.cost = 9 # 9 * 1.1 = 9.9 → ceil → 10
sample_player.change_on_buy()
assert sample_player.cost == 10
def test_buy_large_price(self, sample_player):
"""Large prices should still apply the 10% increase correctly."""
sample_player.cost = 5000
sample_player.change_on_buy()
assert sample_player.cost == math.ceil(5000 * 1.1) # 5500
class TestPlayerGetAllPos:
"""Tests for Player.get_all_pos() — returns non-null, non-CP position list."""
def test_returns_primary_position(self, sample_player):
"""A player with only pos_1 set should return a list with one position."""
sample_player.pos_1 = "1B"
assert sample_player.get_all_pos() == ["1B"]
def test_excludes_cp_position(self, sample_player):
"""CP (closing pitcher) is excluded from the position list."""
sample_player.pos_1 = "SP"
sample_player.pos_2 = "CP"
positions = sample_player.get_all_pos()
assert "CP" not in positions
assert "SP" in positions
def test_excludes_null_positions(self, sample_player):
"""None positions should not appear in the result."""
sample_player.pos_1 = "CF"
sample_player.pos_2 = None
assert sample_player.get_all_pos() == ["CF"]
def test_multiple_positions(self, sample_player):
"""Players with multiple eligible positions should return all of them."""
sample_player.pos_1 = "1B"
sample_player.pos_2 = "OF"
sample_player.pos_3 = "DH"
positions = sample_player.get_all_pos()
assert positions == ["1B", "OF", "DH"]