425 lines
15 KiB
Python
425 lines
15 KiB
Python
import pytest
|
|
import random
|
|
from unittest.mock import Mock, patch, call
|
|
import discord
|
|
|
|
from in_game import simulations, data_cache
|
|
from tests.factory import session_fixture
|
|
|
|
|
|
@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 |