paper-dynasty-discord/tests/in_game/test_simulations.py
Cal Corum ee80cd72ae fix: apply Black formatting and resolve ruff lint violations
Run Black formatter across 83 files and fix 1514 ruff violations:
- E722: bare except → typed exceptions (17 fixes)
- E711/E712/E721: comparison style fixes with noqa for SQLAlchemy (44 fixes)
- F841: unused variable assignments (70 fixes)
- F541/F401: f-string and import cleanup (1383 auto-fixes)

Remaining 925 errors are all F403/F405 (star imports) — structural,
requires converting to explicit imports in a separate effort.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:37:46 -05:00

482 lines
15 KiB
Python

import pytest
from unittest.mock import patch, call
import discord
from in_game import simulations, data_cache
@pytest.fixture
def mock_batting_wrapper():
"""Create a mock BattingWrapper with test data."""
batting_card = data_cache.BattingCard(
player_id=123,
variant=0,
steal_low=9,
steal_high=12,
steal_auto=True,
steal_jump=0.25,
bunting="B",
hit_and_run="A",
running=13,
offense_col=1,
hand="R",
)
ratings_vl = data_cache.BattingRatings(
homerun=5.0,
bp_homerun=7.0,
triple=2.0,
double_three=1.0,
double_two=8.0,
double_pull=6.0,
single_two=4.0,
single_one=3.0,
single_center=12.0,
bp_single=5.0,
hbp=2.0,
walk=10.0,
strikeout=15.0,
lineout=3.0,
popout=6.0,
flyout_a=1.0,
flyout_bq=0.5,
flyout_lf_b=4.0,
flyout_rf_b=8.0,
groundout_a=2.0,
groundout_b=20.0,
groundout_c=15.0,
avg=0.285,
obp=0.375,
slg=0.455,
pull_rate=0.42,
center_rate=0.33,
slap_rate=0.25,
)
ratings_vr = data_cache.BattingRatings(
homerun=3.0,
bp_homerun=6.0,
triple=1.0,
double_three=0.5,
double_two=6.0,
double_pull=5.0,
single_two=3.5,
single_one=2.5,
single_center=10.0,
bp_single=4.0,
hbp=1.5,
walk=8.0,
strikeout=18.0,
lineout=4.0,
popout=7.0,
flyout_a=1.5,
flyout_bq=0.75,
flyout_lf_b=5.0,
flyout_rf_b=10.0,
groundout_a=2.5,
groundout_b=22.0,
groundout_c=16.0,
avg=0.265,
obp=0.345,
slg=0.425,
pull_rate=0.45,
center_rate=0.30,
slap_rate=0.25,
)
return data_cache.BattingWrapper(
card=batting_card, ratings_vl=ratings_vl, ratings_vr=ratings_vr
)
@pytest.fixture
def mock_pitching_wrapper():
"""Create a mock PitchingWrapper with test data."""
pitching_card = data_cache.PitchingCard(
player_id=456,
variant=0,
balk=1,
wild_pitch=2,
hold=8,
starter_rating=7,
relief_rating=5,
batting="B",
offense_col=1,
hand="L",
closer_rating=6,
)
ratings_vl = data_cache.PitchingRatings(
homerun=3.0,
bp_homerun=5.0,
triple=1.0,
double_three=0.5,
double_two=5.0,
double_cf=4.0,
single_two=3.0,
single_one=2.0,
single_center=8.0,
bp_single=4.0,
hbp=2.0,
walk=12.0,
strikeout=25.0,
flyout_lf_b=5.0,
flyout_cf_b=6.0,
flyout_rf_b=6.0,
groundout_a=3.0,
groundout_b=18.0,
xcheck_p=1.0,
xcheck_c=1.0,
xcheck_1b=1.0,
xcheck_2b=1.0,
xcheck_3b=1.0,
xcheck_ss=1.0,
xcheck_lf=1.0,
xcheck_cf=1.0,
xcheck_rf=1.0,
avg=0.245,
obp=0.335,
slg=0.385,
)
ratings_vr = data_cache.PitchingRatings(
homerun=4.0,
bp_homerun=6.0,
triple=1.5,
double_three=0.75,
double_two=7.0,
double_cf=5.0,
single_two=4.0,
single_one=3.0,
single_center=10.0,
bp_single=5.0,
hbp=1.5,
walk=10.0,
strikeout=22.0,
flyout_lf_b=6.0,
flyout_cf_b=7.0,
flyout_rf_b=8.0,
groundout_a=4.0,
groundout_b=20.0,
xcheck_p=1.5,
xcheck_c=1.5,
xcheck_1b=1.5,
xcheck_2b=1.5,
xcheck_3b=1.5,
xcheck_ss=1.5,
xcheck_lf=1.5,
xcheck_cf=1.5,
xcheck_rf=1.5,
avg=0.255,
obp=0.345,
slg=0.405,
)
return data_cache.PitchingWrapper(
card=pitching_card, ratings_vl=ratings_vl, ratings_vr=ratings_vr
)
@pytest.fixture
def mock_player_data():
"""Create mock player data for helper function calls."""
return {
"player_id": 123,
"p_name": "Test Player",
"description": "2024",
"image": "test_player_pitchingcard",
"image2": "test_player_battingcard",
}
class TestGetResult:
"""Test the get_result function which simulates plate appearance outcomes."""
@patch("random.choice")
@patch("random.choices")
def test_get_result_pitcher_chosen_left_handed_batter(
self, mock_choices, mock_choice, mock_pitching_wrapper, mock_batting_wrapper
):
"""Test get_result when pitcher is chosen and batter is left-handed."""
# Make the batter left-handed
mock_batting_wrapper.card.hand = "L"
mock_choice.return_value = "pitcher"
mock_choices.return_value = ["strikeout"]
result = simulations.get_result(mock_pitching_wrapper, mock_batting_wrapper)
mock_choice.assert_called_once_with(["pitcher", "batter"])
assert result == "strikeout"
# Verify the correct ratings were used (pitcher vs left-handed batter)
call_args = mock_choices.call_args
assert call_args is not None
# Should use pitcher's ratings_vl when facing left-handed batter
@patch("random.choice")
@patch("random.choices")
def test_get_result_pitcher_chosen_right_handed_batter(
self, mock_choices, mock_choice, mock_pitching_wrapper, mock_batting_wrapper
):
"""Test get_result when pitcher is chosen and batter is right-handed."""
# Batter is already right-handed in fixture
mock_choice.return_value = "pitcher"
mock_choices.return_value = ["groundout_b"]
result = simulations.get_result(mock_pitching_wrapper, mock_batting_wrapper)
mock_choice.assert_called_once_with(["pitcher", "batter"])
assert result == "groundout_b"
@patch("random.choice")
@patch("random.choices")
def test_get_result_batter_chosen_left_handed_pitcher(
self, mock_choices, mock_choice, mock_pitching_wrapper, mock_batting_wrapper
):
"""Test get_result when batter is chosen and pitcher is left-handed."""
# Pitcher is already left-handed in fixture
mock_choice.return_value = "batter"
mock_choices.return_value = ["homerun"]
result = simulations.get_result(mock_pitching_wrapper, mock_batting_wrapper)
mock_choice.assert_called_once_with(["pitcher", "batter"])
assert result == "homerun"
@patch("random.choice")
@patch("random.choices")
def test_get_result_batter_chosen_right_handed_pitcher(
self, mock_choices, mock_choice, mock_pitching_wrapper, mock_batting_wrapper
):
"""Test get_result when batter is chosen and pitcher is right-handed."""
# Make the pitcher right-handed
mock_pitching_wrapper.card.hand = "R"
mock_choice.return_value = "batter"
mock_choices.return_value = ["single_center"]
result = simulations.get_result(mock_pitching_wrapper, mock_batting_wrapper)
mock_choice.assert_called_once_with(["pitcher", "batter"])
assert result == "single_center"
@patch("random.choice")
@patch("random.choices")
def test_get_result_unused_fields_filtered_out(
self, mock_choices, mock_choice, mock_pitching_wrapper, mock_batting_wrapper
):
"""Test that unused statistical fields are filtered out from the random selection."""
mock_choice.return_value = "batter"
mock_choices.return_value = ["walk"]
result = simulations.get_result(mock_pitching_wrapper, mock_batting_wrapper)
# Get the call arguments to verify unused fields were removed
call_args = mock_choices.call_args
results_list = call_args[0][0] # First positional argument (results)
probs_list = call_args[0][1] # Second positional argument (probabilities)
# Verify unused fields are not in the results
unused_fields = ["avg", "obp", "slg", "pull_rate", "center_rate", "slap_rate"]
for field in unused_fields:
assert field not in results_list
# Verify we have valid baseball outcomes
assert len(results_list) == len(probs_list)
assert all(isinstance(prob, (int, float)) for prob in probs_list)
assert result == "walk"
class TestGetPosEmbeds:
"""Test the get_pos_embeds function which creates Discord embeds for matchups."""
@patch("in_game.simulations.get_result")
@patch("helpers.player_desc")
@patch("helpers.player_pcard")
@patch("helpers.player_bcard")
@patch("helpers.SBA_COLOR", "a6ce39")
def test_get_pos_embeds_structure(
self,
mock_bcard,
mock_pcard,
mock_desc,
mock_get_result,
mock_pitching_wrapper,
mock_batting_wrapper,
mock_player_data,
):
"""Test that get_pos_embeds returns the correct embed structure."""
# Setup mocks
mock_desc.side_effect = ["2024 Test Pitcher", "2024 Test Batter"]
mock_pcard.return_value = "pitcher_card_url"
mock_bcard.return_value = "batter_card_url"
mock_get_result.return_value = "strikeout"
pitcher_data = {"p_name": "Test Pitcher", "description": "2024"}
batter_data = {"p_name": "Test Batter", "description": "2024"}
embeds = simulations.get_pos_embeds(
pitcher_data, batter_data, mock_pitching_wrapper, mock_batting_wrapper
)
# Verify we get 3 embeds
assert len(embeds) == 3
assert all(isinstance(embed, discord.Embed) for embed in embeds)
# Check first embed (pitcher)
assert embeds[0].title == "2024 Test Pitcher vs. 2024 Test Batter"
assert embeds[0].image.url == "pitcher_card_url"
assert embeds[0].color.value == int("a6ce39", 16)
# Check second embed (batter)
assert embeds[1].image.url == "batter_card_url"
assert embeds[1].color.value == int("a6ce39", 16)
# Check third embed (result)
assert len(embeds[2].fields) == 3
assert embeds[2].fields[0].name == "Pitcher"
assert embeds[2].fields[0].value == "2024 Test Pitcher"
assert embeds[2].fields[1].name == "Batter"
assert embeds[2].fields[1].value == "2024 Test Batter"
assert embeds[2].fields[2].name == "Result"
assert embeds[2].fields[2].value == "strikeout"
assert embeds[2].fields[2].inline is False
# Verify helper functions were called correctly
mock_desc.assert_has_calls([call(pitcher_data), call(batter_data)])
mock_pcard.assert_called_once_with(pitcher_data)
mock_bcard.assert_called_once_with(batter_data)
mock_get_result.assert_called_once_with(
mock_pitching_wrapper, mock_batting_wrapper
)
@patch("in_game.simulations.get_result")
@patch("helpers.player_desc")
@patch("helpers.player_pcard")
@patch("helpers.player_bcard")
@patch("helpers.SBA_COLOR", "a6ce39")
def test_get_pos_embeds_different_results(
self,
mock_bcard,
mock_pcard,
mock_desc,
mock_get_result,
mock_pitching_wrapper,
mock_batting_wrapper,
mock_player_data,
):
"""Test get_pos_embeds with different simulation results."""
mock_desc.side_effect = ["Live Ace Pitcher", "Live Power Hitter"]
mock_pcard.return_value = "ace_pitcher_card"
mock_bcard.return_value = "power_hitter_card"
mock_get_result.return_value = "homerun"
pitcher_data = {"p_name": "Ace Pitcher", "description": "Live"}
batter_data = {"p_name": "Power Hitter", "description": "Live"}
embeds = simulations.get_pos_embeds(
pitcher_data, batter_data, mock_pitching_wrapper, mock_batting_wrapper
)
assert len(embeds) == 3
assert embeds[0].title == "Live Ace Pitcher vs. Live Power Hitter"
assert embeds[2].fields[2].value == "homerun"
class TestIntegration:
"""Integration tests that test the functions together with minimal mocking."""
def test_get_result_randomness_distribution(
self, mock_pitching_wrapper, mock_batting_wrapper
):
"""Test that get_result produces a reasonable distribution of outcomes over many iterations."""
# Run many simulations to test randomness
results = []
for _ in range(1000):
result = simulations.get_result(mock_pitching_wrapper, mock_batting_wrapper)
results.append(result)
# Verify we got various outcomes (not just one result)
unique_results = set(results)
assert len(unique_results) > 1
# Verify all results are valid baseball outcomes
valid_outcomes = {
"homerun",
"triple",
"double_two",
"double_three",
"double_pull",
"double_cf",
"single_center",
"single_one",
"single_two",
"bp_single",
"bp_homerun",
"walk",
"hbp",
"strikeout",
"lineout",
"popout",
"flyout_a",
"flyout_bq",
"flyout_lf_b",
"flyout_cf_b",
"flyout_rf_b",
"groundout_a",
"groundout_b",
"groundout_c",
"xcheck_p",
"xcheck_c",
"xcheck_1b",
"xcheck_2b",
"xcheck_3b",
"xcheck_ss",
"xcheck_lf",
"xcheck_cf",
"xcheck_rf",
}
for result in unique_results:
assert result in valid_outcomes
@patch("helpers.SBA_COLOR", "a6ce39")
def test_full_simulation_flow(self, mock_pitching_wrapper, mock_batting_wrapper):
"""Test the complete flow from player data to Discord embeds."""
pitcher_data = {
"p_name": "Test Pitcher",
"description": "2024",
"image": "test_pitcher_pitchingcard",
"image2": None,
}
batter_data = {
"p_name": "Test Batter",
"description": "2024",
"image": None,
"image2": "test_batter_battingcard",
}
# This will call the actual helper functions, so we need valid player data
with patch("helpers.player_desc") as mock_desc, patch(
"helpers.player_pcard"
) as mock_pcard, patch("helpers.player_bcard") as mock_bcard:
mock_desc.side_effect = ["2024 Test Pitcher", "2024 Test Batter"]
mock_pcard.return_value = "test_pitcher_pitchingcard"
mock_bcard.return_value = "test_batter_battingcard"
embeds = simulations.get_pos_embeds(
pitcher_data, batter_data, mock_pitching_wrapper, mock_batting_wrapper
)
# Verify the complete embed structure
assert len(embeds) == 3
assert embeds[0].title == "2024 Test Pitcher vs. 2024 Test Batter"
assert embeds[0].image.url == "test_pitcher_pitchingcard"
assert embeds[1].image.url == "test_batter_battingcard"
# The result should be one of many possible baseball outcomes
result_field = embeds[2].fields[2]
assert result_field.name == "Result"
assert isinstance(result_field.value, str)
assert len(result_field.value) > 0