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>
211 lines
6.9 KiB
Python
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')
|