Skip to content

Commit 36faa12

Browse files
committed
refactor: add tests for pagination and players resource
1 parent 8c4687c commit 36faa12

11 files changed

Lines changed: 782 additions & 435 deletions

.ruff.toml

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ ignore-names = ["env", "pages"]
5151

5252
[lint.per-file-ignores]
5353
"__init__.py" = [
54-
"RUF067", # Code is present in `__init__.py`
54+
"RUF067", # Code is present in `__init__.py`
5555
]
5656
"examples/*" = [
5757
"ICN003", # Import a member from the module instead of importing the member directly
@@ -61,20 +61,28 @@ ignore-names = ["env", "pages"]
6161
"T201", # `print()` statement detected
6262
]
6363
"exceptions.py" = [
64-
"E302", # Expected 2 blank lines before top-level definitions
65-
"E305", # Expected 2 blank lines after a class or function definition
64+
"E302", # Expected 2 blank lines before top-level definitions
65+
"E305", # Expected 2 blank lines after a class or function definition
6666
]
6767
"docs/*" = ["ALL"]
6868
"scripts/*" = [
69-
"T201", # `print()` statement detected
70-
"INP001", # Missing `__init__.py` file in a package directory
69+
"T201", # `print()` statement detected
70+
"INP001", # Missing `__init__.py` file in a package directory
71+
]
72+
"tests/*" = [
73+
"PLR0917", # Too many positional arguments
74+
"PLC1901", # Compare to empty string (e.g., `x == ""` instead of `not x`)
75+
"PLC2701", # Import of a private name from an external module
76+
"PLR2004", # Magic value used in a comparison
77+
"PLR6301", # Method could be a function, class method, or static method (doesn't use `self`)
78+
"PT011", # `pytest.raises()` is too broad, set the `match` parameter
79+
"PT030", # `pytest.warns()` is too broad, set the `match` parameter
7180
]
72-
"tests/*" = ["ALL"]
7381
"types.py" = [
74-
"E302", # Expected 2 blank lines before top-level definitions
75-
"F401", # Imported but unused
76-
"ICN003", # Import a member from the module instead of importing the member directly
77-
"PYI018", # Unused private `TypeVar`, `ParamSpec`, or `TypeVarTuple` declaration
82+
"E302", # Expected 2 blank lines before top-level definitions
83+
"F401", # Imported but unused
84+
"ICN003", # Import a member from the module instead of importing the member directly
85+
"PYI018", # Unused private `TypeVar`, `ParamSpec`, or `TypeVarTuple` declaration
7886
]
7987

8088
[lint.pyupgrade]

src/faceit/api/pagination.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ def _extract_unix_timestamp(
457457
if isinstance(page, dict):
458458
items = page.get(RAW_RESPONSE_ITEMS_KEY) or []
459459
return deep_get(items[-1], key) if items else None
460-
assert isinstance(page, ItemPage)
460+
assert isinstance(page, ItemPage) # Type narrowing for mypy on Python < 3.9
461461
return getattr(page.get_last(), attr, None)
462462

463463
@staticmethod

src/faceit/models/players/general.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,9 @@ def _prepare_skill_level(cls, data: typing.Any) -> typing.Any:
6262
return data
6363

6464
resolved = SkillLevel.get_level(game_id, skill_lvl)
65-
assert resolved is not None, (
66-
"`resolved` cannot be None because `game_id` was already validated "
67-
"to be present in `ELO_THRESHOLDS`"
68-
)
65+
# `resolved` cannot be None because `game_id` was already validated
66+
# to be present in `ELO_THRESHOLDS`
67+
assert resolved is not None
6968
data[cls._SKILL_LVL] = resolved
7069
return data
7170

tests/conftest.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,36 @@
1+
import typing
2+
from unittest.mock import AsyncMock, Mock
13
from uuid import uuid4
24

35
import pytest
46

7+
from faceit.api.data.players import AsyncPlayers, SyncPlayers
8+
59

610
@pytest.fixture
711
def valid_uuid() -> str:
812
return str(uuid4())
13+
14+
15+
@pytest.fixture
16+
def mock_sync_client() -> Mock:
17+
client = Mock()
18+
client.get.return_value = {"player_id": "p1"}
19+
return client
20+
21+
22+
@pytest.fixture
23+
def mock_async_client() -> Mock:
24+
client = Mock()
25+
client.get = AsyncMock(return_value={"player_id": "p1"})
26+
return client
27+
28+
29+
@pytest.fixture
30+
def sync_players_raw(mock_sync_client: Mock) -> SyncPlayers[typing.Any]:
31+
return SyncPlayers(client=mock_sync_client, raw=True)
32+
33+
34+
@pytest.fixture
35+
def async_players_raw(mock_async_client: Mock) -> AsyncPlayers[typing.Any]:
36+
return AsyncPlayers(client=mock_async_client, raw=True)

tests/test_custom_types.py

Lines changed: 21 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
"""NOTE TO DEVELOPERS:
2-
3-
These tests were generated with the assistance of AI and may require
4-
additional review or adjustments. Please verify that all test cases
5-
properly cover the expected behavior of the custom types, especially
6-
regarding edge cases and integration with `Pydantic`.
7-
"""
8-
91
import pytest
10-
from pydantic import AnyHttpUrl, BaseModel, ValidationError, TypeAdapter
2+
from pydantic import AnyHttpUrl, BaseModel, TypeAdapter, ValidationError
113

124
from faceit.models.custom_types import (
135
FaceitID,
@@ -26,7 +18,7 @@ def lang_adapter() -> TypeAdapter[LangFormattedAnyHttpUrl]:
2618

2719

2820
@pytest.mark.parametrize(
29-
"input_value,expected",
21+
("input_value", "expected"),
3022
[
3123
(f"https://example.com/{langph}/docs", "https://example.com/docs"),
3224
(f"http://{langph}/foo/bar", "http://foo/bar"),
@@ -39,7 +31,9 @@ def lang_adapter() -> TypeAdapter[LangFormattedAnyHttpUrl]:
3931
("", ""),
4032
],
4133
)
42-
def test_validate_success(input_value, expected, lang_adapter):
34+
def test_validate_success(
35+
input_value: str, expected: str, lang_adapter: TypeAdapter[LangFormattedAnyHttpUrl]
36+
) -> None:
4337
if expected == "" or expected.startswith("http"):
4438
assert (
4539
input_value == expected
@@ -52,33 +46,24 @@ def test_validate_success(input_value, expected, lang_adapter):
5246

5347

5448
class TestFaceitID:
55-
def test_valid_uuid(self, valid_uuid):
56-
# Test with a valid UUID string
57-
valid_uuid = valid_uuid
49+
def test_valid_uuid(self, valid_uuid: str) -> None:
5850
faceit_id = FaceitID._validate(valid_uuid)
5951
assert isinstance(faceit_id, FaceitID)
6052
assert str(faceit_id) == valid_uuid
6153

62-
def test_invalid_uuid(self):
63-
# Test with an invalid UUID string
54+
def test_invalid_uuid(self) -> None:
6455
with pytest.raises(ValueError, match="Invalid FaceitID:"):
6556
FaceitID._validate("not-a-uuid")
6657

67-
# Test with a non-string, non-UUID value
6858
with pytest.raises(AttributeError):
6959
FaceitID(123)
7060

71-
def test_suffix_handling(self, valid_uuid):
72-
# Test that the 'gui' suffix is NOT automatically handled
73-
# We need to manually remove it before validation
74-
valid_uuid = valid_uuid
61+
def test_suffix_handling(self, valid_uuid: str) -> None:
7562
suffixed_uuid = f"{valid_uuid}gui"
7663

77-
# This should fail because the suffix makes it an invalid UUID
7864
with pytest.raises(ValueError, match="is not a valid UUID format"):
7965
FaceitID._validate(suffixed_uuid)
8066

81-
# Manual handling of suffix
8267
if suffixed_uuid.endswith("gui"):
8368
cleaned_uuid = suffixed_uuid[:-3]
8469
faceit_id_from_cleaned = FaceitID._validate(cleaned_uuid)
@@ -87,41 +72,29 @@ def test_suffix_handling(self, valid_uuid):
8772

8873

8974
class TestFaceitTeamID:
90-
def test_valid_team_id(self, valid_uuid):
91-
# Test with a valid team ID (prefix + UUID)
92-
valid_uuid = valid_uuid
75+
def test_valid_team_id(self, valid_uuid: str) -> None:
9376
valid_team_id = f"team-{valid_uuid}"
9477

9578
team_id = FaceitTeamID._validate(valid_team_id)
9679
assert isinstance(team_id, FaceitTeamID)
9780
assert str(team_id) == valid_team_id
9881

99-
def test_missing_prefix(self, valid_uuid):
100-
# Test with a UUID without the required prefix
101-
valid_uuid = valid_uuid
102-
82+
def test_missing_prefix(self, valid_uuid: str) -> None:
10383
with pytest.raises(ValueError, match="must start with 'team-'"):
10484
FaceitTeamID._validate(valid_uuid)
10585

106-
def test_invalid_uuid_part(self):
107-
# Test with an invalid UUID part
86+
def test_invalid_uuid_part(self) -> None:
10887
with pytest.raises(ValueError, match="contains invalid UUID part"):
10988
FaceitTeamID._validate("team-not-a-valid-uuid")
11089

111-
def test_suffix_handling(self, valid_uuid):
112-
# Test that the 'gui' suffix is NOT automatically handled
113-
valid_uuid = valid_uuid
90+
def test_suffix_handling(self, valid_uuid: str) -> None:
11491
valid_team_id = f"team-{valid_uuid}"
11592
suffixed_team_id = f"{valid_team_id}gui"
93+
_ = FaceitTeamID._validate(valid_team_id)
11694

117-
# This should work
118-
team_id = FaceitTeamID._validate(valid_team_id)
119-
120-
# This should fail because the suffix makes the UUID part invalid
12195
with pytest.raises(ValueError, match="contains invalid UUID part"):
12296
FaceitTeamID._validate(suffixed_team_id)
12397

124-
# Manual handling of suffix
12598
if suffixed_team_id.endswith("gui"):
12699
cleaned_team_id = suffixed_team_id[:-3]
127100
team_id_from_cleaned = FaceitTeamID._validate(cleaned_team_id)
@@ -130,119 +103,89 @@ def test_suffix_handling(self, valid_uuid):
130103

131104

132105
class TestFaceitMatchID:
133-
def test_valid_match_id(self, valid_uuid):
134-
# Test with a valid match ID (prefix + UUID)
135-
valid_uuid = valid_uuid
106+
def test_valid_match_id(self, valid_uuid: str) -> None:
136107
valid_match_id = f"1-{valid_uuid}"
137108

138109
match_id = FaceitMatchID._validate(valid_match_id)
139110
assert isinstance(match_id, FaceitMatchID)
140111
assert str(match_id) == valid_match_id
141112

142-
def test_missing_prefix(self, valid_uuid):
143-
# Test with a UUID without the required prefix
144-
valid_uuid = valid_uuid
145-
113+
def test_missing_prefix(self, valid_uuid: str) -> None:
146114
with pytest.raises(ValueError, match="must start with '1-'"):
147115
FaceitMatchID._validate(valid_uuid)
148116

149-
def test_invalid_uuid_part(self):
150-
# Test with an invalid UUID part
117+
def test_invalid_uuid_part(self) -> None:
151118
with pytest.raises(ValueError, match="contains invalid UUID part"):
152119
FaceitMatchID._validate("1-not-a-valid-uuid")
153120

154-
def test_suffix_handling(self, valid_uuid):
155-
# Test that the 'gui' suffix is NOT automatically handled
156-
valid_uuid = valid_uuid
121+
def test_suffix_handling(self, valid_uuid: str) -> None:
157122
valid_match_id = f"1-{valid_uuid}"
158123
suffixed_match_id = f"{valid_match_id}gui"
124+
_ = FaceitMatchID._validate(valid_match_id)
159125

160-
# This should work
161-
match_id = FaceitMatchID._validate(valid_match_id)
162-
163-
# This should fail because the suffix makes the UUID part invalid
164126
with pytest.raises(ValueError, match="contains invalid UUID part"):
165127
FaceitMatchID._validate(suffixed_match_id)
166128

167-
# Manual handling of suffix
168129
if suffixed_match_id.endswith("gui"):
169130
cleaned_match_id = suffixed_match_id[:-3]
170131
match_id_from_cleaned = FaceitMatchID._validate(cleaned_match_id)
171132
assert isinstance(match_id_from_cleaned, FaceitMatchID)
172133
assert str(match_id_from_cleaned) == valid_match_id
173134

174135

175-
# Test Pydantic integration
176136
class TestPydanticIntegration:
177-
def test_faceit_id_in_model(self, valid_uuid):
137+
def test_faceit_id_in_model(self, valid_uuid: str) -> None:
178138
class UserModel(BaseModel):
179139
id: FaceitID
180140

181-
# Valid UUID
182-
valid_uuid = valid_uuid
183141
user = UserModel(id=valid_uuid)
184142
assert isinstance(user.id, FaceitID)
185143
assert str(user.id) == valid_uuid
186144

187-
# UUID with suffix - Pydantic automatically handles this
188145
suffixed_uuid = f"{valid_uuid}gui"
189146
user = UserModel(id=suffixed_uuid)
190147
assert isinstance(user.id, FaceitID)
191-
# The suffix should be automatically removed
192148
assert str(user.id) == valid_uuid
193149

194-
# Invalid UUID
195150
with pytest.raises(ValidationError):
196151
UserModel(id="not-a-uuid")
197152

198-
def test_faceit_team_id_in_model(self, valid_uuid):
153+
def test_faceit_team_id_in_model(self, valid_uuid: str) -> None:
199154
class TeamModel(BaseModel):
200155
id: FaceitTeamID
201156

202-
# Valid team ID
203-
valid_uuid = valid_uuid
204157
valid_team_id = f"team-{valid_uuid}"
205158
team = TeamModel(id=valid_team_id)
206159
assert isinstance(team.id, FaceitTeamID)
207160
assert str(team.id) == valid_team_id
208161

209-
# Team ID with suffix - Pydantic automatically handles this
210162
suffixed_team_id = f"{valid_team_id}gui"
211163
team = TeamModel(id=suffixed_team_id)
212164
assert isinstance(team.id, FaceitTeamID)
213-
# The suffix should be automatically removed
214165
assert str(team.id) == valid_team_id
215166

216-
# Missing prefix
217167
with pytest.raises(ValidationError):
218168
TeamModel(id=valid_uuid)
219169

220-
# Invalid UUID part
221170
with pytest.raises(ValidationError):
222171
TeamModel(id="team-not-a-valid-uuid")
223172

224-
def test_faceit_match_id_in_model(self, valid_uuid):
173+
def test_faceit_match_id_in_model(self, valid_uuid: str) -> None:
225174
class MatchModel(BaseModel):
226175
id: FaceitMatchID
227176

228-
# Valid match ID
229-
valid_uuid = valid_uuid
230177
valid_match_id = f"1-{valid_uuid}"
231178
match = MatchModel(id=valid_match_id)
232179
assert isinstance(match.id, FaceitMatchID)
233180
assert str(match.id) == valid_match_id
234181

235-
# Match ID with suffix - Pydantic automatically handles this
236182
suffixed_match_id = f"{valid_match_id}gui"
237183
match = MatchModel(id=suffixed_match_id)
238184
assert isinstance(match.id, FaceitMatchID)
239-
# The suffix should be automatically removed
240185
assert str(match.id) == valid_match_id
241186

242-
# Missing prefix
243187
with pytest.raises(ValidationError):
244188
MatchModel(id=valid_uuid)
245189

246-
# Invalid UUID part
247190
with pytest.raises(ValidationError):
248191
MatchModel(id="1-not-a-valid-uuid")

0 commit comments

Comments
 (0)