Skip to content

Commit 790a769

Browse files
committed
MPT-18701 Improve base model tests
1 parent 1602b14 commit 790a769

File tree

4 files changed

+135
-31
lines changed

4 files changed

+135
-31
lines changed

mpt_api_client/models/model.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re
22
from collections import UserList
33
from collections.abc import Iterable
4-
from typing import Any, ClassVar, Self, get_args, get_origin, override
4+
from typing import Any, Self, get_args, get_origin, override
55

66
from mpt_api_client.http.types import Response
77
from mpt_api_client.models.meta import Meta
@@ -169,7 +169,6 @@ def _process_value(self, value: Any, target_class: Any = None) -> Any: # noqa:
169169
class Model(BaseModel):
170170
"""Provides a resource to interact with api data using fluent interfaces."""
171171

172-
_data_key: ClassVar[str | None] = None
173172
id: str
174173

175174
def __init__(
@@ -192,16 +191,14 @@ def new(cls, resource_data: ResourceData | None = None, meta: Meta | None = None
192191

193192
@classmethod
194193
def from_response(cls, response: Response) -> Self:
195-
"""Creates a collection from a response.
194+
"""Creates a Model from a response.
196195
197196
Args:
198197
response: The httpx response object.
199198
"""
200199
response_data = response.json()
201200
if isinstance(response_data, dict):
202201
response_data.pop("$meta", None)
203-
if cls._data_key:
204-
response_data = response_data.get(cls._data_key)
205202
if not isinstance(response_data, dict):
206203
raise TypeError("Response data must be a dict.")
207204
meta = Meta.from_response(response)

tests/unit/conftest.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
class DummyModel(Model):
1111
"""Dummy resource for testing."""
1212

13-
_data_key = None
14-
1513

1614
@pytest.fixture
1715
def http_client():

tests/unit/models/resource/test_resource_custom_key.py

Lines changed: 0 additions & 17 deletions
This file was deleted.

tests/unit/models/resource/test_resource.py renamed to tests/unit/models/test_model.py

Lines changed: 133 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from httpx import Response
33

44
from mpt_api_client.models import Meta, Model
5+
from mpt_api_client.models.model import BaseModel, ModelList, to_snake_case # noqa: WPS347
56

67

78
class AgreementDummy(Model): # noqa: WPS431
@@ -20,6 +21,30 @@ class AgreementWithContactDummy(Model):
2021
contact: ContactDummy
2122

2223

24+
class TypedListItemDummy(BaseModel):
25+
"""Dummy item for typed list tests."""
26+
27+
name: str
28+
29+
30+
class TypedListContainerDummy(BaseModel):
31+
"""Dummy container with a typed list field."""
32+
33+
entries: list[TypedListItemDummy]
34+
35+
36+
class DictTypedContainerDummy(BaseModel):
37+
"""Dummy container with a dict-typed field."""
38+
39+
metadata: dict[str, str]
40+
41+
42+
class ScalarListContainerDummy(BaseModel):
43+
"""Dummy container with a list[str] field."""
44+
45+
tags: list[str]
46+
47+
2348
@pytest.fixture
2449
def meta_data():
2550
return {"pagination": {"limit": 10, "offset": 20, "total": 100}, "ignored": ["one"]} # noqa: WPS226
@@ -156,9 +181,11 @@ def test_append():
156181
agreement.parameters.ordering.append(new_param) # act
157182

158183
assert agreement.id == "AGR-123"
159-
assert agreement.parameters.ordering[0].external_id == "contact"
160-
assert agreement.parameters.ordering[1].external_id == "address"
161-
assert agreement.parameters.ordering[2].external_id == "email"
184+
assert [agr_param.external_id for agr_param in agreement.parameters.ordering] == [
185+
"contact",
186+
"address",
187+
"email",
188+
]
162189
agreement_data["parameters"]["ordering"].append(new_param)
163190
assert agreement.to_dict() == agreement_data
164191

@@ -177,8 +204,10 @@ def test_overwrite_list():
177204
agreement.parameters.ordering = ordering_parameters # act
178205

179206
assert agreement.id == "AGR-123"
180-
assert agreement.parameters.ordering[0].external_id == "contact"
181-
assert agreement.parameters.ordering[1].external_id == "address"
207+
assert [agr_param.external_id for agr_param in agreement.parameters.ordering] == [
208+
"contact",
209+
"address",
210+
]
182211
assert agreement.to_dict() == agreement_data
183212

184213

@@ -197,6 +226,103 @@ def test_advanced_mapping():
197226
agreement.parameters.ordering = ordering_parameters # act
198227

199228
assert isinstance(agreement.contact, ContactDummy)
200-
assert agreement.parameters.ordering[0].external_id == "contact"
201-
assert agreement.parameters.ordering[1].external_id == "address"
229+
assert [agr_param.external_id for agr_param in agreement.parameters.ordering] == [
230+
"contact",
231+
"address",
232+
]
202233
assert agreement.to_dict() == agreement_data
234+
235+
236+
def test_to_snake_case_already_snake():
237+
result = to_snake_case("already_snake") # act
238+
239+
assert result == "already_snake"
240+
241+
242+
def test_model_list_extend():
243+
ml = ModelList([{"id": "1"}])
244+
245+
ml.extend([{"id": "2"}, {"id": "3"}]) # act
246+
247+
assert [ml_item.id for ml_item in ml] == ["1", "2", "3"]
248+
249+
250+
def test_model_list_insert():
251+
ml = ModelList([{"id": "1"}, {"id": "3"}])
252+
253+
ml.insert(1, {"id": "2"}) # act
254+
255+
assert [ml_item.id for ml_item in ml] == ["1", "2", "3"]
256+
257+
258+
def test_model_list_process_item_nested_list():
259+
nested = [{"id": "a"}, {"id": "b"}]
260+
261+
ml = ModelList([nested]) # act
262+
263+
assert isinstance(ml[0], ModelList)
264+
assert ml[0][0].id == "a"
265+
266+
267+
def test_model_list_process_item_scalar():
268+
ml = ModelList(["a", "b", "c"]) # act
269+
270+
assert ml == ["a", "b", "c"]
271+
272+
273+
def test_base_model_getattr_from_dict():
274+
model = BaseModel(foo="bar")
275+
276+
result = model.__getattr__("foo") # noqa: PLC2801
277+
278+
assert result == "bar"
279+
280+
281+
def test_base_model_setattr_private():
282+
model = BaseModel(foo="bar")
283+
284+
model._private = "secret" # noqa: SLF001 # act
285+
286+
assert model._private == "secret" # noqa: SLF001
287+
288+
289+
def test_to_dict_excludes_private_attrs():
290+
model = BaseModel(foo="bar")
291+
model._private = "secret" # noqa: SLF001
292+
293+
result = model.to_dict()
294+
295+
assert result == {"foo": "bar"}
296+
assert "_private" not in result
297+
298+
299+
def test_process_value_typed_list():
300+
container = TypedListContainerDummy(entries=[{"name": "one"}, {"name": "two"}]) # act
301+
302+
assert all(isinstance(entry, TypedListItemDummy) for entry in container.entries)
303+
assert [entry.name for entry in container.entries] == ["one", "two"]
304+
305+
306+
def test_process_value_existing_base_model():
307+
nested = BaseModel(value="test")
308+
model = BaseModel()
309+
310+
model.nested = nested # act
311+
312+
assert model.nested is nested
313+
314+
315+
def test_process_value_non_list_target():
316+
container = DictTypedContainerDummy()
317+
318+
container.metadata = [{"id": "1"}] # act
319+
320+
assert isinstance(container.metadata, ModelList)
321+
assert container.metadata[0].id == "1"
322+
323+
324+
def test_process_value_scalar_list_elements():
325+
container = ScalarListContainerDummy(tags=["a", "b", "c"]) # act
326+
327+
assert isinstance(container.tags, ModelList)
328+
assert list(container.tags) == ["a", "b", "c"]

0 commit comments

Comments
 (0)