Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 5 additions & 78 deletions tests/external_tests/mlbdataadapter/test_mlbadapter.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import unittest
import requests
from unittest.mock import patch
from mlbstatsapi import MlbDataAdapter, TheMlbStatsApiException

from mlbstatsapi import MlbDataAdapter


class TestMlbAdapter(unittest.TestCase):
Expand All @@ -13,7 +12,6 @@ def setUpClass(cls) -> None:
def tearDownClass(cls) -> None:
pass


def test_mlbadapter_get_200(self):
"""mlbadapter should return 200 and data for sports endpoint"""

Expand All @@ -27,25 +25,17 @@ def test_mlbadapter_get_200(self):
self.assertTrue(result.data)

def test_mlbadapter_get_400(self):
"""mlbadapter should return 404, and result.data should be None"""
"""mlbadapter should return 404, and result.data should be empty"""

# invalid endpoint
# invalid endpoint
result = self.mlb_adapter.get(endpoint="teams/19990")

# result.status_code should be 404
self.assertEqual(result.status_code, 404)

# result.data should be None
# result.data should be empty
self.assertEqual(result.data, {})

@unittest.skip("External API no longer returns 500 for this endpoint - covered by mock tests")
def test_mlbadapter_get_500(self):
"""mlbadapter should raise TheMlbStatsApiException for 500"""

# bad endpoint should raise exception due to 500
with self.assertRaises(TheMlbStatsApiException):
result = self.mlb_adapter.get(endpoint="teams/133/stats?stats=vsPlayer&group=catching")

def test_mlbadapter_get_params(self):
"""mlbadapter should accept params and parse them to the url"""

Expand All @@ -60,66 +50,3 @@ def test_mlbadapter_get_params(self):

# result should have data
self.assertTrue(result.data)

class MlbAdapterMockTesting(unittest.TestCase):
@classmethod
def setUpClass(cls) -> None:
cls.mlb_adapter = MlbDataAdapter()
cls.response = requests.Response()

@classmethod
def tearDownClass(cls) -> None:
pass

def test_mlbadapter_mock_bad_json(self):
"""mlbadapter should raise TheMlbStatsApiException"""
# setting up mock
self.response.status_code = 200
self.response._content = '{"some bad json": sdfsd'.encode()

# params to pass
self.params = {"stats": "season", "group": "hitting"}

# patch mlbdataadapter to return bad JSON
with patch("mlbstatsapi.mlb_dataadapter.requests.get", return_value=self.response):

# mlb_adapter should raise exception due to bad JSON
with self.assertRaises(TheMlbStatsApiException):
result = self.mlb_adapter.get(endpoint="teams/133/stats", ep_params=self.params)

def test_mlbadapter_mock_404_json(self):
"""mlbadapter should raise TheMlbStatsApiException"""
# setting up mock
self.response.status_code = 404
self.response._content = '{ "messageNumber": 10, "message": "Object not found", "timestamp": "2022-10-13T18:16:41.886604Z", "traceId": null }'.encode()

# params to pass
self.params = { "stats": "standard", "group": "hitting" }

# patch mlbdataadapter to return 404 response
with patch("mlbstatsapi.mlb_dataadapter.requests.get", return_value=self.response):

# mlb_adapter should raise exception due to bad JSON
result = self.mlb_adapter.get(endpoint="teams/133/stats", ep_params=self.params)

# result.status_code should be 404
self.assertEqual(result.status_code, 404)

# result.data should be None
self.assertEqual(result.data, {})

def test_mlbadapter_mock_500_json(self):
"""mlbadapter should raise TheMlbStatsApiException"""
# setting up mock
self.response.status_code = 500
self.response._content = '{ "messageNumber" : 1, "message" : "Internal error occurred", "timestamp" : "2022-10-13T18:37:47.600274Z", "traceId" : "9318607c0b50f493e9056648614a5cea" }'.encode()

# params to pass
self.params = {"stats": "standard", "group": "hitting"}

# patch mlbdataadapter to return mocked response
with patch("mlbstatsapi.mlb_dataadapter.requests.get", return_value=self.response):

# mlb_adapter should raise exception due to 500 status code
with self.assertRaises(TheMlbStatsApiException):
result = self.mlb_adapter.get(endpoint="teams/133/stats", ep_params=self.params)
229 changes: 229 additions & 0 deletions tests/test_mlb_dataadapter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
"""Offline characterization tests for MlbDataAdapter.

These tests document current HTTP adapter behavior with requests-mock before
the transport is refactored in version 0.8.0. Known defects use strict xfail
markers so a future fix causes XPASS until the marker is removed.
"""

import requests
import pytest

from mlbstatsapi import MlbDataAdapter, MlbResult, TheMlbStatsApiException


BASE_URL = "https://statsapi.mlb.com/api/v1/"


@pytest.fixture
def adapter() -> MlbDataAdapter:
return MlbDataAdapter()


def test_successful_json_response_returns_exact_values(adapter, requests_mock):
payload = {
"copyright": "Copyright 2026 MLB Advanced Media, L.P.",
"sports": [{"id": 1, "name": "Major League Baseball"}],
}
requests_mock.get(
f"{BASE_URL}sports",
json=payload,
status_code=200,
reason="OK",
)

result = adapter.get(endpoint="sports")

assert result.status_code == 200
assert result.message == "OK"
assert result.data == {"sports": [{"id": 1, "name": "Major League Baseball"}]}


def test_ep_params_are_sent_as_query_parameters(adapter, requests_mock):
requests_mock.get(
f"{BASE_URL}teams/133/stats",
json={"stats": []},
status_code=200,
reason="OK",
)

result = adapter.get(
endpoint="teams/133/stats",
ep_params={"stats": "season", "group": "hitting", "season": 2022},
)

assert result.status_code == 200
assert requests_mock.called
assert requests_mock.last_request.qs == {
"stats": ["season"],
"group": ["hitting"],
"season": ["2022"],
}


def test_status_code_and_reason_are_preserved_on_result(adapter, requests_mock):
requests_mock.get(
f"{BASE_URL}sports",
json={"sports": []},
status_code=200,
reason="OK",
)

result = adapter.get(endpoint="sports")

assert result.status_code == 200
assert result.message == "OK"


def test_copyright_is_removed_from_result_data(adapter, requests_mock):
requests_mock.get(
f"{BASE_URL}sports",
json={
"copyright": "Copyright 2026 MLB Advanced Media, L.P.",
"sports": [],
},
status_code=200,
reason="OK",
)

result = adapter.get(endpoint="sports")

assert "copyright" not in result.data
assert result.data == {"sports": []}


def test_json_404_returns_empty_data(adapter, requests_mock):
requests_mock.get(
f"{BASE_URL}teams/19990",
json={
"messageNumber": 10,
"message": "Object not found",
"timestamp": "2022-10-13T18:16:41.886604Z",
"traceId": None,
},
status_code=404,
reason="Not Found",
)

result = adapter.get(endpoint="teams/19990")

assert result.status_code == 404
assert result.data == {}


def test_json_500_raises_the_mlb_stats_api_exception(adapter, requests_mock):
requests_mock.get(
f"{BASE_URL}teams/133/stats",
json={
"messageNumber": 1,
"message": "Internal error occurred",
"timestamp": "2022-10-13T18:37:47.600274Z",
"traceId": "9318607c0b50f493e9056648614a5cea",
},
status_code=500,
reason="Internal Server Error",
)

with pytest.raises(TheMlbStatsApiException, match=r"^500: Internal Server Error$") as exc_info:
adapter.get(
endpoint="teams/133/stats",
ep_params={"stats": "standard", "group": "hitting"},
)

assert str(exc_info.value) == "500: Internal Server Error"


def test_connection_failure_raises_request_failed(adapter, requests_mock):
requests_mock.get(
f"{BASE_URL}sports",
exc=requests.exceptions.ConnectionError("connection refused"),
)

with pytest.raises(TheMlbStatsApiException, match=r"^Request failed$") as exc_info:
adapter.get(endpoint="sports")

assert str(exc_info.value) == "Request failed"
assert isinstance(exc_info.value.__cause__, requests.exceptions.ConnectionError)


def test_invalid_json_on_successful_response_raises_bad_json(adapter, requests_mock):
requests_mock.get(
f"{BASE_URL}teams/133/stats",
text='{"some bad json": sdfsd',
status_code=200,
reason="OK",
headers={"Content-Type": "application/json"},
)

with pytest.raises(TheMlbStatsApiException, match=r"^Bad JSON in response$") as exc_info:
adapter.get(
endpoint="teams/133/stats",
ep_params={"stats": "season", "group": "hitting"},
)

assert str(exc_info.value) == "Bad JSON in response"


def test_mlb_result_optional_data_argument_omitted():
result = MlbResult(200, "OK")

assert result.status_code == 200
assert result.message == "OK"
assert result.data == {}


def test_mlb_result_optional_data_argument_explicit_dict():
result = MlbResult(404, "Not Found", {"ignored": True})

assert result.status_code == 404
assert result.message == "Not Found"
assert result.data == {"ignored": True}


def test_mlb_result_type_coercion():
result = MlbResult("200", 123, {"value": True})

assert result.status_code == 200
assert result.message == "123"


@pytest.mark.xfail(
strict=True,
reason=(
"Adapter always JSON-decodes before status handling; "
"fix/http-adapter-correctness should return empty data for empty successful bodies"
),
)
def test_empty_successful_response_returns_empty_data(adapter, requests_mock):
requests_mock.get(
f"{BASE_URL}sports",
text="",
status_code=204,
reason="No Content",
)

result = adapter.get(endpoint="sports")

assert result.status_code == 204
assert result.data == {}


@pytest.mark.xfail(
strict=True,
reason=(
"Adapter JSON-decodes before HTTP status handling; "
"fix/http-adapter-correctness should return empty data for non-JSON 404 bodies"
),
)
def test_non_json_404_returns_empty_data(adapter, requests_mock):
requests_mock.get(
f"{BASE_URL}teams/19990",
text="<html><body>Not Found</body></html>",
status_code=404,
reason="Not Found",
headers={"Content-Type": "text/html"},
)

result = adapter.get(endpoint="teams/19990")

assert result.status_code == 404
assert result.data == {}
Loading
Loading