Skip to content

Commit 7de4d37

Browse files
mahenzonCosmoV
authored andcommitted
create tests for validators (TDD)
1 parent 51cdac2 commit 7de4d37

File tree

5 files changed

+193
-2
lines changed

5 files changed

+193
-2
lines changed

tests/fixtures/app.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Type
33

44
import pytest
5+
import uvicorn
56
from fastapi import APIRouter, FastAPI
67

78
from fastapi_jsonapi import RoutersJSONAPI, init
@@ -19,6 +20,7 @@
1920
ParentToChildAssociation,
2021
Post,
2122
PostComment,
23+
Task,
2224
User,
2325
UserBio,
2426
)
@@ -36,6 +38,9 @@
3638
PostInSchema,
3739
PostPatchSchema,
3840
PostSchema,
41+
TaskInSchema,
42+
TaskPatchSchema,
43+
TaskSchema,
3944
UserBioSchema,
4045
UserInSchema,
4146
UserPatchSchema,
@@ -161,6 +166,19 @@ def add_routers(app_plain: FastAPI):
161166
schema_in_post=ComputerInSchema,
162167
)
163168

169+
RoutersJSONAPI(
170+
router=router,
171+
path="/tasks",
172+
tags=["Task"],
173+
class_detail=DetailViewBaseGeneric,
174+
class_list=ListViewBaseGeneric,
175+
model=Task,
176+
schema=TaskSchema,
177+
resource_type="task",
178+
schema_in_patch=TaskPatchSchema,
179+
schema_in_post=TaskInSchema,
180+
)
181+
164182
atomic = AtomicOperations()
165183

166184
app_plain.include_router(router, prefix="")

tests/models.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,15 @@ def __repr__(self):
222222
return f"{self.__class__.__name__}(id={self.id}, name={self.name!r}, user_id={self.user_id})"
223223

224224

225+
class Task(Base):
226+
__tablename__ = "tasks"
227+
id = Column(Integer, primary_key=True)
228+
task_ids = Column(JSON, nullable=True, unique=False)
229+
230+
231+
# uuid below
232+
233+
225234
class CustomUUIDType(TypeDecorator):
226235
cache_ok = True
227236

tests/schemas.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from typing import Dict, List, Optional
22
from uuid import UUID
33

4+
from pydantic import validator
5+
46
from fastapi_jsonapi.schema_base import BaseModel, Field, RelationshipInfo
57

68

@@ -354,6 +356,40 @@ class Config:
354356
id: int
355357

356358

359+
# task
360+
class TaskBaseSchema(BaseModel):
361+
class Config:
362+
orm_mode = True
363+
364+
task_ids: Optional[list[str]] = None
365+
366+
# noinspection PyMethodParameters
367+
@validator("task_ids", pre=True)
368+
def task_ids_validator(cls, value: Optional[list[str]]):
369+
"""
370+
return `[]`, if value is None
371+
both on get and on create
372+
"""
373+
return value or []
374+
375+
376+
class TaskPatchSchema(TaskBaseSchema):
377+
"""Task PATCH schema."""
378+
379+
380+
class TaskInSchema(TaskBaseSchema):
381+
"""Task create schema."""
382+
383+
384+
class TaskSchema(TaskBaseSchema):
385+
"""Task item schema."""
386+
387+
id: int
388+
389+
390+
# uuid below
391+
392+
357393
class IdCastSchema(BaseModel):
358394
id: UUID = Field(client_can_set_id=True)
359395

tests/test_api/test_validators.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import pytest
2+
from fastapi import FastAPI, status
3+
from httpx import AsyncClient
4+
from pytest_asyncio import fixture
5+
from sqlalchemy.ext.asyncio import AsyncSession
6+
7+
from tests.models import Task
8+
from tests.schemas import TaskBaseSchema
9+
10+
pytestmark = pytest.mark.asyncio
11+
12+
13+
@fixture()
14+
async def task_with_none_ids(
15+
async_session: AsyncSession,
16+
) -> Task:
17+
task = Task(task_ids=None)
18+
async_session.add(task)
19+
await async_session.commit()
20+
21+
yield task
22+
23+
await async_session.delete(task)
24+
await async_session.commit()
25+
26+
27+
@pytest.fixture()
28+
def resource_type():
29+
return "task"
30+
31+
32+
class TestTaskValidators:
33+
async def test_base_model_root_validator_get_one(
34+
self,
35+
app: FastAPI,
36+
client: AsyncClient,
37+
resource_type: str,
38+
task_with_none_ids: Task,
39+
):
40+
url = app.url_path_for(f"get_{resource_type}_detail", obj_id=task_with_none_ids.id)
41+
res = await client.get(url)
42+
assert res.status_code == status.HTTP_200_OK, res.text
43+
response_data = res.json()
44+
attributes = response_data["data"].pop("attributes")
45+
assert response_data == {
46+
"data": {
47+
"id": str(task_with_none_ids.id),
48+
"type": resource_type,
49+
},
50+
"jsonapi": {"version": "1.0"},
51+
"meta": None,
52+
}
53+
assert attributes == {
54+
# not `None`! schema validator returns empty list `[]`
55+
# "task_ids": None,
56+
"task_ids": [],
57+
}
58+
assert attributes == TaskBaseSchema.from_orm(task_with_none_ids)
59+
60+
async def test_base_model_root_validator_get_list(
61+
self,
62+
app: FastAPI,
63+
client: AsyncClient,
64+
resource_type: str,
65+
task_with_none_ids: Task,
66+
):
67+
url = app.url_path_for(f"get_{resource_type}_list")
68+
res = await client.get(url)
69+
assert res.status_code == status.HTTP_200_OK, res.text
70+
response_data = res.json()
71+
assert response_data == {
72+
"data": [
73+
{
74+
"id": str(task_with_none_ids.id),
75+
"type": resource_type,
76+
"attributes": {
77+
# not `None`! schema validator returns empty list `[]`
78+
# "task_ids": None,
79+
"task_ids": [],
80+
},
81+
},
82+
],
83+
"jsonapi": {
84+
"version": "1.0",
85+
},
86+
"meta": {
87+
"count": 1,
88+
"totalPages": 1,
89+
},
90+
}
91+
92+
async def test_base_model_root_validator_create(
93+
self,
94+
app: FastAPI,
95+
client: AsyncClient,
96+
resource_type: str,
97+
async_session: AsyncSession,
98+
):
99+
task_data = {
100+
# should be converted to [] by schema on create
101+
"task_ids": None,
102+
}
103+
data_create = {
104+
"data": {
105+
"type": resource_type,
106+
"attributes": task_data,
107+
},
108+
}
109+
url = app.url_path_for(f"create_{resource_type}_list")
110+
res = await client.post(url, json=data_create)
111+
assert res.status_code == status.HTTP_201_CREATED, res.text
112+
response_data: dict = res.json()
113+
task_id = response_data["data"].pop("id")
114+
task = await async_session.get(Task, int(task_id))
115+
assert isinstance(task, Task)
116+
# we sent request with `None`, but value in db is `[]`
117+
# because validator converted data before object creation
118+
assert task.task_ids == []
119+
assert response_data == {
120+
"data": {
121+
"type": resource_type,
122+
"attributes": {
123+
# should be empty list
124+
"task_ids": [],
125+
},
126+
},
127+
"jsonapi": {"version": "1.0"},
128+
"meta": None,
129+
}

tests/test_atomic/test_update_objects.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@
22

33
import pytest
44
from httpx import AsyncClient
5-
from pytest import mark # noqa
65
from sqlalchemy.ext.asyncio import AsyncSession
76
from starlette import status
87

98
from tests.misc.utils import fake
109
from tests.models import Computer, User, UserBio
1110
from tests.schemas import UserAttributesBaseSchema, UserBioAttributesBaseSchema
1211

13-
pytestmark = mark.asyncio
12+
pytestmark = pytest.mark.asyncio
1413

1514
logging.basicConfig(level=logging.DEBUG)
1615

0 commit comments

Comments
 (0)