strat-gameplay-webapp/backend/tests/unit/config/test_league_configs.py
Cal Corum beb939b32a CLAUDE: Fix all unit test failures and implement 100% test requirement
Test Fixes (609/609 passing):
- Fixed DiceSystem API to accept team_id/player_id parameters for audit trails
- Fixed dice roll history timing issue in test
- Fixed terminal client mock to match resolve_play signature (X-Check params)
- Fixed result chart test mocks with missing pitching fields
- Fixed flaky test by using groundball_a (exists in both batting/pitching)

Documentation Updates:
- Added Testing Policy section to backend/CLAUDE.md
- Added Testing Policy section to tests/CLAUDE.md
- Documented 100% unit test requirement before commits
- Added git hook setup instructions

Git Hook System:
- Created .git-hooks/pre-commit script (enforces 100% test pass)
- Created .git-hooks/install-hooks.sh (easy installation)
- Created .git-hooks/README.md (hook documentation)
- Hook automatically runs all unit tests before each commit
- Blocks commits if any test fails

All 609 unit tests now passing (100%)
Integration tests have known asyncpg connection issues (documented)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-04 19:35:21 -06:00

211 lines
6.9 KiB
Python

"""
Unit tests for league configuration system.
Tests:
- Config loading and registry
- Config immutability
- Abstract method implementations
- League-specific settings
"""
import pytest
from pydantic import ValidationError
from app.config import (
BaseGameConfig,
SbaConfig,
PdConfig,
LEAGUE_CONFIGS,
get_league_config
)
class TestBaseGameConfig:
"""Tests for BaseGameConfig abstract class."""
def test_cannot_instantiate_abstract_base(self):
"""BaseGameConfig cannot be instantiated directly."""
with pytest.raises(TypeError):
BaseGameConfig(league_id="test")
class TestSbaConfig:
"""Tests for SBA league configuration."""
def test_sba_config_creation(self):
"""Can create SBA config instance."""
config = SbaConfig()
assert config.league_id == "sba"
assert config.version == "1.0.0"
def test_sba_basic_rules(self):
"""SBA uses standard baseball rules."""
config = SbaConfig()
assert config.innings == 9
assert config.outs_per_inning == 3
assert config.strikes_for_out == 3
assert config.balls_for_walk == 4
def test_sba_result_chart_name(self):
"""SBA uses sba_standard_v1 chart."""
config = SbaConfig()
assert config.get_result_chart_name() == "sba_standard_v1"
def test_sba_supports_manual_selection(self):
"""SBA supports manual result selection."""
config = SbaConfig()
assert config.supports_manual_result_selection() is True
def test_sba_api_url(self):
"""SBA API URL is correct."""
config = SbaConfig()
assert config.get_api_base_url() == "https://api.sba.manticorum.com"
def test_sba_player_selection_mode(self):
"""SBA uses manual player selection."""
config = SbaConfig()
assert config.player_selection_mode == "manual"
def test_sba_config_is_immutable(self):
"""Config cannot be modified after creation."""
config = SbaConfig()
with pytest.raises(ValidationError):
config.innings = 7
class TestPdConfig:
"""Tests for PD league configuration."""
def test_pd_config_creation(self):
"""Can create PD config instance."""
config = PdConfig()
assert config.league_id == "pd"
assert config.version == "1.0.0"
def test_pd_basic_rules(self):
"""PD uses standard baseball rules."""
config = PdConfig()
assert config.innings == 9
assert config.outs_per_inning == 3
assert config.strikes_for_out == 3
assert config.balls_for_walk == 4
def test_pd_result_chart_name(self):
"""PD uses pd_standard_v1 chart."""
config = PdConfig()
assert config.get_result_chart_name() == "pd_standard_v1"
def test_pd_supports_manual_selection(self):
"""PD supports manual result selection (though auto is also available)."""
config = PdConfig()
assert config.supports_manual_result_selection() is True
def test_pd_api_url(self):
"""PD API URL is correct."""
config = PdConfig()
assert config.get_api_base_url() == "https://pd.manticorum.com/api/"
def test_pd_player_selection_mode(self):
"""PD uses flexible player selection."""
config = PdConfig()
assert config.player_selection_mode == "flexible"
def test_pd_scouting_model_enabled(self):
"""PD has scouting model enabled."""
config = PdConfig()
assert config.use_scouting_model is True
def test_pd_cardset_validation_enabled(self):
"""PD has cardset validation enabled."""
config = PdConfig()
assert config.cardset_validation is True
def test_pd_advanced_analytics_enabled(self):
"""PD has advanced analytics enabled."""
config = PdConfig()
assert config.detailed_analytics is True
assert config.wpa_calculation is True
def test_pd_config_is_immutable(self):
"""Config cannot be modified after creation."""
config = PdConfig()
with pytest.raises(ValidationError):
config.use_scouting_model = False
class TestLeagueRegistry:
"""Tests for league config registry and getter."""
def test_registry_contains_both_leagues(self):
"""Registry has both SBA and PD configs."""
assert "sba" in LEAGUE_CONFIGS
assert "pd" in LEAGUE_CONFIGS
assert len(LEAGUE_CONFIGS) == 2
def test_registry_configs_are_correct_type(self):
"""Registry contains correct config types."""
assert isinstance(LEAGUE_CONFIGS["sba"], SbaConfig)
assert isinstance(LEAGUE_CONFIGS["pd"], PdConfig)
def test_get_sba_config(self):
"""Can get SBA config via getter."""
config = get_league_config("sba")
assert isinstance(config, SbaConfig)
assert config.league_id == "sba"
def test_get_pd_config(self):
"""Can get PD config via getter."""
config = get_league_config("pd")
assert isinstance(config, PdConfig)
assert config.league_id == "pd"
def test_get_invalid_league_raises(self):
"""Getting invalid league raises ValueError."""
with pytest.raises(ValueError) as exc_info:
get_league_config("invalid")
assert "Unknown league: invalid" in str(exc_info.value)
assert "sba" in str(exc_info.value)
assert "pd" in str(exc_info.value)
def test_registry_returns_same_instance(self):
"""Registry returns singleton instances."""
config1 = get_league_config("sba")
config2 = get_league_config("sba")
assert config1 is config2
class TestConfigDifferences:
"""Tests for differences between league configs."""
def test_different_result_charts(self):
"""SBA and PD use different result charts."""
sba = get_league_config("sba")
pd = get_league_config("pd")
assert sba.get_result_chart_name() != pd.get_result_chart_name()
def test_different_api_urls(self):
"""SBA and PD have different API URLs."""
sba = get_league_config("sba")
pd = get_league_config("pd")
assert sba.get_api_base_url() != pd.get_api_base_url()
def test_different_player_selection_modes(self):
"""SBA and PD have different selection modes."""
sba = get_league_config("sba")
pd = get_league_config("pd")
assert sba.player_selection_mode == "manual"
assert pd.player_selection_mode == "flexible"
def test_pd_has_extra_features(self):
"""PD has features that SBA doesn't."""
pd = get_league_config("pd")
sba = get_league_config("sba")
# PD-specific attributes
assert hasattr(pd, 'use_scouting_model')
assert hasattr(pd, 'cardset_validation')
assert hasattr(pd, 'detailed_analytics')
# SBA doesn't have these (or they're False/None)
assert not hasattr(sba, 'use_scouting_model')
assert not hasattr(sba, 'cardset_validation')