Skip to content

Commit 5886f4e

Browse files
committed
coverage for atomic operations method config resolution
1 parent d0706b4 commit 5886f4e

File tree

4 files changed

+273
-34
lines changed

4 files changed

+273
-34
lines changed

tests/fixtures/app.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
from pathlib import Path
2+
from typing import Type
23

34
import uvicorn
45
from fastapi import APIRouter, FastAPI
56
from pytest import fixture # noqa PT013
67

78
from fastapi_jsonapi import RoutersJSONAPI, init
89
from fastapi_jsonapi.atomic import AtomicOperations
10+
from fastapi_jsonapi.views.detail_view import DetailViewBase
11+
from fastapi_jsonapi.views.list_view import ListViewBase
912
from tests.fixtures.views import (
1013
DetailViewBaseGeneric,
1114
ListViewBaseGeneric,
@@ -191,3 +194,36 @@ def app(app_plain: FastAPI):
191194
reload=True,
192195
app_dir=str(CURRENT_DIR),
193196
)
197+
198+
199+
def build_app_custom(
200+
model,
201+
schema,
202+
schema_in_patch=None,
203+
schema_in_post=None,
204+
path: str = "/misc",
205+
resource_type: str = "misc",
206+
class_list: Type[ListViewBase] = ListViewBaseGeneric,
207+
class_detail: Type[DetailViewBase] = DetailViewBaseGeneric,
208+
) -> FastAPI:
209+
router: APIRouter = APIRouter()
210+
211+
RoutersJSONAPI(
212+
router=router,
213+
path=path,
214+
tags=["Misc"],
215+
class_list=class_list,
216+
class_detail=class_detail,
217+
schema=schema,
218+
resource_type=resource_type,
219+
schema_in_patch=schema_in_patch,
220+
schema_in_post=schema_in_post,
221+
model=model,
222+
)
223+
224+
app = build_app_plain()
225+
app.include_router(router, prefix="")
226+
227+
atomic = AtomicOperations()
228+
app.include_router(atomic.router, prefix="")
229+
return app

tests/fixtures/views.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,15 @@
1616
from tests.fixtures.db_connection import async_session_dependency
1717

1818

19-
class SessionDependency(BaseModel):
20-
session: AsyncSession = Depends(async_session_dependency)
21-
19+
class ArbitraryModelBase(BaseModel):
2220
class Config:
2321
arbitrary_types_allowed = True
2422

2523

24+
class SessionDependency(ArbitraryModelBase):
25+
session: AsyncSession = Depends(async_session_dependency)
26+
27+
2628
def common_handler(view: ViewBase, dto: SessionDependency) -> Dict:
2729
return {"session": dto.session}
2830

tests/test_api/test_api_sqla_with_includes.py

Lines changed: 2 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from typing import Dict, List
66
from uuid import UUID, uuid4
77

8-
from fastapi import APIRouter, FastAPI, status
8+
from fastapi import FastAPI, status
99
from httpx import AsyncClient
1010
from pydantic import BaseModel, Field, root_validator, validator
1111
from pytest import fixture, mark, param # noqa PT013
@@ -15,9 +15,8 @@
1515
from fastapi_jsonapi.exceptions import BadRequest
1616
from fastapi_jsonapi.schema_builder import SchemaBuilder
1717
from fastapi_jsonapi.views.view_base import ViewBase
18-
from tests.fixtures.app import build_app_plain
18+
from tests.fixtures.app import build_app_custom
1919
from tests.fixtures.entities import build_workplace, create_user
20-
from tests.fixtures.views import DetailViewBaseGeneric, ListViewBaseGeneric
2120
from tests.misc.utils import fake
2221
from tests.models import (
2322
Computer,
@@ -51,34 +50,6 @@ def association_key(data: dict):
5150
return data["type"], data["id"]
5251

5352

54-
def build_app_custom(
55-
model,
56-
schema,
57-
schema_in_patch=None,
58-
schema_in_post=None,
59-
resource_type: str = "misc",
60-
) -> FastAPI:
61-
router: APIRouter = APIRouter()
62-
63-
RoutersJSONAPI(
64-
router=router,
65-
path="/misc",
66-
tags=["Misc"],
67-
class_detail=DetailViewBaseGeneric,
68-
class_list=ListViewBaseGeneric,
69-
schema=schema,
70-
resource_type=resource_type,
71-
schema_in_patch=schema_in_patch,
72-
schema_in_post=schema_in_post,
73-
model=model,
74-
)
75-
76-
app = build_app_plain()
77-
app.include_router(router, prefix="")
78-
79-
return app
80-
81-
8253
async def test_root(client: AsyncClient):
8354
response = await client.get("/docs")
8455
assert response.status_code == status.HTTP_200_OK
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
from typing import Dict
2+
3+
import pytest
4+
from fastapi import Depends, Query, status
5+
from httpx import AsyncClient
6+
from pytest_asyncio import fixture
7+
8+
from fastapi_jsonapi.misc.sqla.generics.base import DetailViewBaseGeneric, ListViewBaseGeneric
9+
from fastapi_jsonapi.views.utils import (
10+
HTTPMethod,
11+
HTTPMethodConfig,
12+
)
13+
from tests.fixtures.app import build_app_custom
14+
from tests.fixtures.views import ArbitraryModelBase, SessionDependency, common_handler
15+
from tests.misc.utils import fake
16+
from tests.models import User
17+
from tests.schemas import (
18+
UserAttributesBaseSchema,
19+
UserInSchema,
20+
UserPatchSchema,
21+
UserSchema,
22+
)
23+
24+
pytestmark = pytest.mark.asyncio
25+
26+
27+
class CustomDependencyForCreate:
28+
KEY = "spam_create"
29+
30+
def __init__(self, query_spam: str = Query(..., alias=KEY)):
31+
self.query_spam = query_spam
32+
33+
34+
class CustomDependencyForUpdate:
35+
KEY = "spam_update"
36+
37+
def __init__(self, query_spam: str = Query(..., alias=KEY)):
38+
self.query_spam = query_spam
39+
40+
41+
class CustomDependencyForDelete:
42+
KEY = "spam_delete"
43+
44+
def __init__(self, query_spam: str = Query(..., alias=KEY)):
45+
self.query_spam = query_spam
46+
47+
48+
class UserCreateCustomDependency(ArbitraryModelBase):
49+
dep: CustomDependencyForCreate = Depends()
50+
51+
52+
class UserUpdateCustomDependency(ArbitraryModelBase):
53+
dep: CustomDependencyForUpdate = Depends()
54+
55+
56+
class UserDeleteCustomDependency(ArbitraryModelBase):
57+
dep: CustomDependencyForDelete = Depends()
58+
59+
60+
class UserCustomListView(ListViewBaseGeneric):
61+
method_dependencies: Dict[HTTPMethod, HTTPMethodConfig] = {
62+
HTTPMethod.ALL: HTTPMethodConfig(
63+
dependencies=SessionDependency,
64+
prepare_data_layer_kwargs=common_handler,
65+
),
66+
HTTPMethod.POST: HTTPMethodConfig(
67+
dependencies=UserCreateCustomDependency,
68+
),
69+
}
70+
71+
72+
class UserCustomDetailView(DetailViewBaseGeneric):
73+
method_dependencies: Dict[HTTPMethod, HTTPMethodConfig] = {
74+
HTTPMethod.ALL: HTTPMethodConfig(
75+
dependencies=SessionDependency,
76+
prepare_data_layer_kwargs=common_handler,
77+
),
78+
HTTPMethod.PATCH: HTTPMethodConfig(
79+
dependencies=UserUpdateCustomDependency,
80+
),
81+
HTTPMethod.DELETE: HTTPMethodConfig(
82+
dependencies=UserDeleteCustomDependency,
83+
),
84+
}
85+
86+
87+
class TestDependenciesResolver:
88+
@pytest.fixture(scope="class")
89+
def resource_type(self):
90+
return "user_custom_deps"
91+
92+
@pytest.fixture(scope="class")
93+
def app_w_deps(self, resource_type):
94+
app = build_app_custom(
95+
model=User,
96+
schema=UserSchema,
97+
schema_in_post=UserInSchema,
98+
schema_in_patch=UserPatchSchema,
99+
resource_type=resource_type,
100+
class_list=UserCustomListView,
101+
class_detail=UserCustomDetailView,
102+
)
103+
return app
104+
105+
@fixture(scope="class")
106+
async def client(self, app_w_deps):
107+
app = app_w_deps
108+
async with AsyncClient(app=app, base_url="http://test") as client:
109+
yield client
110+
111+
async def send_and_validate_atomic(
112+
self,
113+
client: AsyncClient,
114+
data_atomic: dict,
115+
expected_body: dict,
116+
expected_status=status.HTTP_422_UNPROCESSABLE_ENTITY,
117+
):
118+
response = await client.post("/operations", json=data_atomic)
119+
assert response.status_code == expected_status, response.text
120+
response_data = response.json()
121+
# TODO: JSON:API exception
122+
assert response_data == expected_body
123+
124+
async def test_on_create_atomic(
125+
self,
126+
client: AsyncClient,
127+
resource_type: str,
128+
):
129+
user = UserAttributesBaseSchema(
130+
name=fake.name(),
131+
age=fake.pyint(min_value=13, max_value=99),
132+
email=fake.email(),
133+
)
134+
data_atomic_request = {
135+
"atomic:operations": [
136+
{
137+
"op": "add",
138+
"data": {
139+
"type": resource_type,
140+
"attributes": user.dict(),
141+
},
142+
},
143+
],
144+
}
145+
# TODO: JSON:API exception
146+
expected_response_data = {
147+
"detail": [
148+
{
149+
"loc": ["query", CustomDependencyForCreate.KEY],
150+
"msg": "field required",
151+
"type": "value_error.missing",
152+
},
153+
],
154+
}
155+
await self.send_and_validate_atomic(
156+
client=client,
157+
data_atomic=data_atomic_request,
158+
expected_body=expected_response_data,
159+
)
160+
161+
async def test_on_update_atomic(
162+
self,
163+
client: AsyncClient,
164+
resource_type: str,
165+
user_1: User,
166+
):
167+
user = UserAttributesBaseSchema(
168+
name=fake.name(),
169+
age=fake.pyint(min_value=13, max_value=99),
170+
email=fake.email(),
171+
)
172+
data_atomic_request = {
173+
"atomic:operations": [
174+
{
175+
"op": "update",
176+
"id": user_1.id,
177+
"data": {
178+
"type": resource_type,
179+
"attributes": user.dict(),
180+
},
181+
},
182+
],
183+
} # TODO: JSON:API exception
184+
expected_response_data = {
185+
"detail": [
186+
{
187+
"loc": ["query", CustomDependencyForUpdate.KEY],
188+
"msg": "field required",
189+
"type": "value_error.missing",
190+
},
191+
],
192+
}
193+
await self.send_and_validate_atomic(
194+
client=client,
195+
data_atomic=data_atomic_request,
196+
expected_body=expected_response_data,
197+
)
198+
199+
async def test_on_delete_atomic(
200+
self,
201+
client: AsyncClient,
202+
resource_type: str,
203+
user_1: User,
204+
):
205+
data_atomic_request = {
206+
"atomic:operations": [
207+
{
208+
"op": "remove",
209+
"ref": {
210+
"id": user_1.id,
211+
"type": resource_type,
212+
},
213+
},
214+
],
215+
}
216+
# TODO: JSON:API exception
217+
expected_response_data = {
218+
"detail": [
219+
{
220+
"loc": ["query", CustomDependencyForDelete.KEY],
221+
"msg": "field required",
222+
"type": "value_error.missing",
223+
},
224+
],
225+
}
226+
await self.send_and_validate_atomic(
227+
client=client,
228+
data_atomic=data_atomic_request,
229+
expected_body=expected_response_data,
230+
)

0 commit comments

Comments
 (0)