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>
482 lines
15 KiB
Python
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
|