Skip to content

Commit a28046f

Browse files
committed
huge user api test refactoring, create generic class for test
1 parent e570ff2 commit a28046f

File tree

5 files changed

+292
-187
lines changed

5 files changed

+292
-187
lines changed

tests/common_user_api_test.py

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
from typing import Literal
2+
3+
from fastapi import FastAPI
4+
from httpx import AsyncClient
5+
from pydantic import BaseModel
6+
from sqlalchemy import select
7+
from sqlalchemy.ext.asyncio import AsyncSession
8+
from starlette import status
9+
10+
from tests.misc.utils import fake
11+
from tests.models import User
12+
from tests.schemas import UserAttributesBaseSchema
13+
14+
FIELD_CUSTOM_NAME = "custom_name"
15+
16+
17+
class CustomNameAttributeModel(BaseModel):
18+
custom_name: str
19+
20+
21+
class CustomNameAttributesJSONAPI(BaseModel):
22+
attributes: CustomNameAttributeModel
23+
24+
25+
class ValidateCustomNameEqualsBase:
26+
STATUS_ON_ERROR = status.HTTP_400_BAD_REQUEST
27+
28+
def __init__(self, expected_value):
29+
self.expected_value = expected_value
30+
31+
async def validate(self, custom_name: str) -> Literal[True]:
32+
raise NotImplementedError
33+
34+
35+
class BaseGenericUserCreateUpdateWithBodyDependency:
36+
FIELD_CUSTOM_NAME = FIELD_CUSTOM_NAME
37+
validator_create = ValidateCustomNameEqualsBase(None)
38+
validator_update = ValidateCustomNameEqualsBase(None)
39+
40+
def validate_field_not_passed_response(self, response):
41+
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY, response.text
42+
response_data = response.json()
43+
assert response_data == {
44+
"detail": [
45+
{
46+
"loc": ["body", "data", "attributes", self.FIELD_CUSTOM_NAME],
47+
"msg": "field required",
48+
"type": "value_error.missing",
49+
},
50+
],
51+
}
52+
53+
def validate_field_value_invalid_response(self, response, validator: ValidateCustomNameEqualsBase):
54+
assert response.status_code == validator.STATUS_ON_ERROR, response.text
55+
response_data = response.json()
56+
assert response_data["detail"].pop("error")
57+
assert response_data == {
58+
"detail": {
59+
"expected_value": validator.expected_value,
60+
},
61+
}
62+
63+
async def validate_user_creation_on_error_key_not_passed(
64+
self,
65+
app: FastAPI,
66+
client: AsyncClient,
67+
resource_type: str,
68+
user_attributes: UserAttributesBaseSchema,
69+
):
70+
attributes_data = user_attributes.dict()
71+
data_user_create = {
72+
"data": {
73+
"type": resource_type,
74+
"attributes": attributes_data,
75+
},
76+
}
77+
url = app.url_path_for(f"create_{resource_type}_list")
78+
response = await client.post(url, json=data_user_create)
79+
self.validate_field_not_passed_response(response)
80+
81+
async def validate_user_creation_test_error_value_passed_but_invalid(
82+
self,
83+
app: FastAPI,
84+
client: AsyncClient,
85+
resource_type: str,
86+
user_attributes: UserAttributesBaseSchema,
87+
):
88+
attributes_data = user_attributes.dict()
89+
attributes_data[self.FIELD_CUSTOM_NAME] = fake.word()
90+
data_user_create = {
91+
"data": {
92+
"type": resource_type,
93+
"attributes": attributes_data,
94+
},
95+
}
96+
url = app.url_path_for(f"create_{resource_type}_list")
97+
response = await client.post(url, json=data_user_create)
98+
self.validate_field_value_invalid_response(response, self.validator_create)
99+
100+
async def validate_user_update_error_key_not_passed(
101+
self,
102+
app: FastAPI,
103+
client: AsyncClient,
104+
user: User,
105+
resource_type: str,
106+
user_attributes: UserAttributesBaseSchema,
107+
):
108+
attributes_data = user_attributes.dict()
109+
data_user_update = {
110+
"data": {
111+
"id": user.id,
112+
"type": resource_type,
113+
"attributes": attributes_data,
114+
},
115+
}
116+
url = app.url_path_for(f"update_{resource_type}_detail", obj_id=user.id)
117+
response = await client.patch(url, json=data_user_update)
118+
self.validate_field_not_passed_response(response)
119+
120+
async def validate_user_update_error_value_passed_but_invalid(
121+
self,
122+
app: FastAPI,
123+
client: AsyncClient,
124+
user: User,
125+
resource_type: str,
126+
user_attributes: UserAttributesBaseSchema,
127+
):
128+
attributes_data = user_attributes.dict()
129+
attributes_data[self.FIELD_CUSTOM_NAME] = fake.word()
130+
data_user_create = {
131+
"data": {
132+
"type": resource_type,
133+
"attributes": attributes_data,
134+
},
135+
}
136+
url = app.url_path_for(f"update_{resource_type}_detail", obj_id=user.id)
137+
response = await client.patch(url, json=data_user_create)
138+
self.validate_field_value_invalid_response(response, self.validator_update)
139+
140+
async def validate_generic_user_create_works(
141+
self,
142+
app: FastAPI,
143+
client: AsyncClient,
144+
async_session: AsyncSession,
145+
resource_type: str,
146+
user_attributes: UserAttributesBaseSchema,
147+
):
148+
data_user_attributes = user_attributes.dict()
149+
data_user_attributes[self.FIELD_CUSTOM_NAME] = self.validator_create.expected_value
150+
data_user_create = {
151+
"data": {
152+
"type": resource_type,
153+
"attributes": data_user_attributes,
154+
},
155+
}
156+
url = app.url_path_for(f"create_{resource_type}_list")
157+
response = await client.post(url, json=data_user_create)
158+
assert response.status_code == status.HTTP_201_CREATED, response.text
159+
user = await async_session.scalar(
160+
select(User).where(
161+
*(
162+
# all filters
163+
getattr(User, key) == value
164+
# iterate obj by key + value
165+
for key, value in user_attributes
166+
),
167+
),
168+
)
169+
assert isinstance(user, User)
170+
response_data = response.json()
171+
user_created_data = response_data["data"]
172+
assert user_created_data["id"] == str(user.id)
173+
assert user_created_data["attributes"] == user_attributes.dict()
174+
175+
async def validate_generic_user_update_works(
176+
self,
177+
app: FastAPI,
178+
client: AsyncClient,
179+
async_session: AsyncSession,
180+
resource_type: str,
181+
user_attributes: UserAttributesBaseSchema,
182+
user: User,
183+
):
184+
for field_name, value in user_attributes:
185+
assert getattr(user, field_name) != value
186+
187+
data_user_attributes = user_attributes.dict()
188+
data_user_attributes[self.FIELD_CUSTOM_NAME] = self.validator_update.expected_value
189+
data_user_update = {
190+
"data": {
191+
"id": user.id,
192+
"type": resource_type,
193+
"attributes": data_user_attributes,
194+
},
195+
}
196+
url = app.url_path_for(f"update_{resource_type}_detail", obj_id=user.id)
197+
response = await client.patch(url, json=data_user_update)
198+
assert response.status_code == status.HTTP_200_OK, response.text
199+
await async_session.refresh(user)
200+
response_data = response.json()
201+
user_created_data = response_data["data"]
202+
assert user_created_data["id"] == str(user.id)
203+
assert user_created_data["attributes"] == user_attributes.dict()

tests/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
)
4949
from tests.fixtures.user import ( # noqa
5050
user_attributes,
51+
user_attributes_factory,
5152
)
5253
from tests.fixtures.views import ( # noqa
5354
DetailViewBaseGeneric,

tests/fixtures/user.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,18 @@
55

66

77
@pytest.fixture()
8-
def user_attributes():
9-
user_attributes = UserAttributesBaseSchema(
10-
name=fake.name(),
11-
age=fake.pyint(),
12-
)
13-
return user_attributes
8+
def user_attributes_factory():
9+
def factory():
10+
user_attributes = UserAttributesBaseSchema(
11+
name=fake.name(),
12+
age=fake.pyint(min_value=13, max_value=99),
13+
email=fake.email(),
14+
)
15+
return user_attributes
16+
17+
return factory
18+
19+
20+
@pytest.fixture()
21+
def user_attributes(user_attributes_factory):
22+
return user_attributes_factory()

0 commit comments

Comments
 (0)