|
| 1 | +import pytest |
| 2 | + |
| 3 | +from mlbstatsapi.models.game.livedata.linescore.attributes import LinescoreOffense |
| 4 | +from mlbstatsapi.models.game.livedata.plays.play.attributes import PlayReviewDetails |
| 5 | +from mlbstatsapi.models.game.livedata.plays.play.playevent.playevent import PlayEvent |
| 6 | +from mlbstatsapi.models.game.livedata.plays.playbyinning.attributes import HitCoordinates |
| 7 | +from mlbstatsapi.models.game.livedata.plays.play.playrunner.attributes import RunnerMovement |
| 8 | + |
| 9 | + |
| 10 | +# These gamepk IDs are taken from a user-submitted error log where the MLB API payload |
| 11 | +# was rejected by Pydantic validation. The underlying issues are schema inconsistencies |
| 12 | +# in MLB's API responses (int where str expected, dict where str expected, null where bool expected). |
| 13 | + |
| 14 | +GAMEPKS_BASE_INT = [ |
| 15 | + 776160, 776165, 776219, 776252, 776286, 776320, 776336, 776351, 776386, 776420, |
| 16 | + 776498, 776659, 776759, 776770, 776903, 776937, 777091, 777135, 777191, 777265, |
| 17 | + 777305, 777445, 777488, 777514, 777555, 777570, 777650, 777722, |
| 18 | + # Additional gamepks reported later (same base=int issue) |
| 19 | + 744814, 744819, 744824, 744826, 744832, 744836, 744837, 744838, |
| 20 | + 745146, 745542, 745796, 745799, |
| 21 | + 747000, 747080, 747170, |
| 22 | +] |
| 23 | + |
| 24 | +GAMEPKS_ISOUT_NULL = [ |
| 25 | + 776320, 776545, |
| 26 | + # Additional gamepks reported later (same isOut=null issue) |
| 27 | + 744832, 744836, |
| 28 | +] |
| 29 | + |
| 30 | +GAMEPKS_UMPIRE_DICT = [ |
| 31 | + 776221, 776367, 776420, 776525, 776650, 776850, 776903, |
| 32 | + # Additional gamepks reported later (same umpire=dict issue) |
| 33 | + 744831, 747000, |
| 34 | +] |
| 35 | + |
| 36 | +GAMEPKS_ADDITIONAL_REVIEWS_LIST = [ |
| 37 | + 776259, 776386, 777213, 777544, 777555, |
| 38 | + # Additional gamepks reported later (same additionalReviews=list issue) |
| 39 | + 747000, |
| 40 | +] |
| 41 | + |
| 42 | +GAMEPKS_LINESCORE_OFFENSE_RUNNER_DICT = [ |
| 43 | + 776784, 777091, |
| 44 | + # Additional gamepks reported later (same offense.*=person dict issue) |
| 45 | + 744814, 747080, |
| 46 | +] |
| 47 | + |
| 48 | +GAMEPKS_HIT_COORDS_NULL = [ |
| 49 | + 778077, |
| 50 | +] |
| 51 | + |
| 52 | + |
| 53 | +@pytest.mark.parametrize("gamepk", GAMEPKS_BASE_INT) |
| 54 | +def test_gamepk_play_event_base_coerces_int_to_str(gamepk: int): |
| 55 | + # path in log: liveData.plays.*.playEvents.*.base is int |
| 56 | + evt = PlayEvent(details={}, index=0, isPitch=True, type="pitch", base=1) |
| 57 | + assert evt.base == "1" |
| 58 | + |
| 59 | + |
| 60 | +@pytest.mark.parametrize("gamepk", GAMEPKS_ISOUT_NULL) |
| 61 | +def test_gamepk_runner_movement_is_out_coerces_null_to_false(gamepk: int): |
| 62 | + # path in log: liveData.plays.*.runners.*.movement.isOut is null |
| 63 | + mv = RunnerMovement(isOut=None) |
| 64 | + assert mv.is_out is False |
| 65 | + |
| 66 | + |
| 67 | +@pytest.mark.parametrize("gamepk", GAMEPKS_UMPIRE_DICT) |
| 68 | +def test_gamepk_play_event_umpire_accepts_person_object(gamepk: int): |
| 69 | + # path in log: liveData.plays.*.playEvents.*.umpire is a dict {id, link} |
| 70 | + evt = PlayEvent( |
| 71 | + details={}, |
| 72 | + index=0, |
| 73 | + isPitch=True, |
| 74 | + type="pitch", |
| 75 | + umpire={"id": 484499, "link": "/api/v1/people/484499"}, |
| 76 | + ) |
| 77 | + assert evt.umpire is not None |
| 78 | + |
| 79 | + |
| 80 | +@pytest.mark.parametrize("gamepk", GAMEPKS_ADDITIONAL_REVIEWS_LIST) |
| 81 | +def test_gamepk_review_details_additional_reviews_accepts_list(gamepk: int): |
| 82 | + # path in log: liveData.plays.allPlays.*.reviewDetails.additionalReviews is a list |
| 83 | + rd = PlayReviewDetails( |
| 84 | + isOverturned=False, |
| 85 | + inProgress=False, |
| 86 | + reviewType="NA", |
| 87 | + additionalReviews=[{"isOverturned": False, "reviewType": "NA", "challengeTeamId": 120}], |
| 88 | + ) |
| 89 | + assert isinstance(rd.additional_reviews, list) |
| 90 | + |
| 91 | + |
| 92 | +@pytest.mark.parametrize("gamepk", GAMEPKS_LINESCORE_OFFENSE_RUNNER_DICT) |
| 93 | +def test_gamepk_linescore_offense_baserunners_accept_person_object(gamepk: int): |
| 94 | + # path in log: liveData.linescore.offense.first/second/third is a dict person object |
| 95 | + offense = LinescoreOffense( |
| 96 | + team={"id": 120, "link": "/api/v1/teams/120"}, |
| 97 | + first={"id": 682928, "fullName": "Runner One", "link": "/api/v1/people/682928"}, |
| 98 | + second=None, |
| 99 | + third=None, |
| 100 | + ) |
| 101 | + assert offense.first is not None |
| 102 | + |
| 103 | + |
| 104 | +@pytest.mark.parametrize("gamepk", GAMEPKS_HIT_COORDS_NULL) |
| 105 | +def test_gamepk_hit_coordinates_accept_null_x_y(gamepk: int): |
| 106 | + coords = HitCoordinates(x=None, y=None) |
| 107 | + assert coords.x is None |
| 108 | + assert coords.y is None |
| 109 | + |
| 110 | + |
0 commit comments