Skip to content

Commit a128dac

Browse files
committed
added tests for update
1 parent cc6bfbf commit a128dac

File tree

3 files changed

+137
-39
lines changed

3 files changed

+137
-39
lines changed

fastapi_jsonapi/data_layers/sqla_orm.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from typing import TYPE_CHECKING, Any, Iterable, List, Optional, Tuple, Type
44

55
from sqlalchemy import delete, func, select
6-
from sqlalchemy.exc import DBAPIError, IntegrityError, NoResultFound
6+
from sqlalchemy.exc import DBAPIError, IntegrityError, MissingGreenlet, NoResultFound
77
from sqlalchemy.ext.asyncio import AsyncSession, AsyncSessionTransaction
88
from sqlalchemy.inspection import inspect
99
from sqlalchemy.orm import joinedload, selectinload
@@ -185,6 +185,18 @@ async def apply_relationships(self, obj: TypeModel, data_create: BaseJSONAPIItem
185185
related_id_field=relationship_info.id_field_name,
186186
id_value=relationship_in.data.id,
187187
)
188+
189+
try:
190+
hasattr(obj, relation_name)
191+
except MissingGreenlet:
192+
raise InternalServerError(
193+
detail=(
194+
f"Error of loading the {relation_name!r} relationship. "
195+
f"Please add this relationship to include query parameter explicitly."
196+
),
197+
parameter="include",
198+
)
199+
188200
# todo: relation name may be different?
189201
setattr(obj, relation_name, related_data)
190202

tests/schemas.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,6 @@ class SelfRelationshipSchema(BaseModel):
410410

411411

412412
class CascadeCaseSchema(BaseModel):
413-
name: str
414413
parent_item: Optional["CascadeCaseSchema"] = Field(
415414
relationship=RelationshipInfo(
416415
resource_type="cascade_case",

tests/test_api/test_api_sqla_with_includes.py

Lines changed: 124 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import json
22
import logging
33
from collections import defaultdict
4+
from contextlib import suppress
45
from datetime import datetime, timezone
56
from itertools import chain, zip_longest
67
from json import dumps, loads
@@ -18,6 +19,7 @@
1819
from sqlalchemy.orm import InstrumentedAttribute
1920
from starlette.datastructures import QueryParams
2021

22+
from fastapi_jsonapi.api import RoutersJSONAPI
2123
from fastapi_jsonapi.views.view_base import ViewBase
2224
from tests.common import is_postgres_tests
2325
from tests.fixtures.app import build_alphabet_app, build_app_custom
@@ -45,6 +47,7 @@
4547
Workplace,
4648
)
4749
from tests.schemas import (
50+
CascadeCaseSchema,
4851
CustomUserAttributesSchema,
4952
CustomUUIDItemAttributesSchema,
5053
PostAttributesBaseSchema,
@@ -1475,43 +1478,6 @@ async def test_create_with_relationship_to_the_same_table(self):
14751478
"meta": None,
14761479
}
14771480

1478-
async def test_cascade_delete(self, async_session: AsyncSession):
1479-
resource_type = "cascade_case"
1480-
app = build_app_custom(
1481-
model=CascadeCase,
1482-
schema=SelfRelationshipSchema,
1483-
resource_type=resource_type,
1484-
)
1485-
1486-
top_item = CascadeCase()
1487-
sub_item_1 = CascadeCase(parent_item=top_item)
1488-
sub_item_2 = CascadeCase(parent_item=top_item)
1489-
async_session.add_all(
1490-
[
1491-
top_item,
1492-
sub_item_1,
1493-
sub_item_2,
1494-
],
1495-
)
1496-
await async_session.commit()
1497-
1498-
assert sub_item_1.parent_item_id == top_item.id
1499-
assert sub_item_2.parent_item_id == top_item.id
1500-
1501-
async with AsyncClient(app=app, base_url="http://test") as client:
1502-
url = app.url_path_for(f"delete_{resource_type}_detail", obj_id=top_item.id)
1503-
1504-
res = await client.delete(url)
1505-
assert res.status_code == status.HTTP_204_NO_CONTENT, res.text
1506-
1507-
top_item_stmt = select(CascadeCase).where(CascadeCase.id == top_item.id)
1508-
top_item = (await async_session.execute(top_item_stmt)).one_or_none()
1509-
assert top_item is None
1510-
1511-
sub_items_stmt = select(CascadeCase).where(CascadeCase.id.in_([sub_item_1.id, sub_item_2.id]))
1512-
sub_items = (await async_session.execute(sub_items_stmt)).all()
1513-
assert sub_items == []
1514-
15151481
async def test_create_with_timestamp_and_fetch(self, async_session: AsyncSession):
15161482
resource_type = "contains_timestamp_model"
15171483

@@ -1782,6 +1748,87 @@ async def test_select_custom_fields(
17821748
"meta": None,
17831749
}
17841750

1751+
@mark.parametrize("check_type", ["ok", "fail"])
1752+
async def test_update_to_many_relationships(self, async_session: AsyncSession, check_type: Literal["ok", "fail"]):
1753+
resource_type = "cascade_case"
1754+
with suppress(KeyError):
1755+
RoutersJSONAPI.all_jsonapi_routers.pop(resource_type)
1756+
1757+
app = build_app_custom(
1758+
model=CascadeCase,
1759+
schema=CascadeCaseSchema,
1760+
resource_type=resource_type,
1761+
)
1762+
1763+
top_item = CascadeCase()
1764+
new_top_item = CascadeCase()
1765+
sub_item_1 = CascadeCase(parent_item=top_item)
1766+
sub_item_2 = CascadeCase(parent_item=top_item)
1767+
async_session.add_all(
1768+
[
1769+
top_item,
1770+
new_top_item,
1771+
sub_item_1,
1772+
sub_item_2,
1773+
],
1774+
)
1775+
await async_session.commit()
1776+
1777+
assert sub_item_1.parent_item_id == top_item.id
1778+
assert sub_item_2.parent_item_id == top_item.id
1779+
1780+
async with AsyncClient(app=app, base_url="http://test") as client:
1781+
params = None
1782+
if check_type == "ok":
1783+
params = {"include": "sub_items"}
1784+
1785+
update_body = {
1786+
"type": resource_type,
1787+
"data": {
1788+
"id": new_top_item.id,
1789+
"attributes": {},
1790+
"relationships": {
1791+
"sub_items": {
1792+
"data": [
1793+
{
1794+
"type": resource_type,
1795+
"id": sub_item_1.id,
1796+
},
1797+
{
1798+
"type": resource_type,
1799+
"id": sub_item_2.id,
1800+
},
1801+
],
1802+
},
1803+
},
1804+
},
1805+
}
1806+
url = app.url_path_for(f"update_{resource_type}_detail", obj_id=new_top_item.id)
1807+
1808+
res = await client.patch(url, params=params, json=update_body)
1809+
1810+
if check_type == "ok":
1811+
assert res.status_code == status.HTTP_200_OK, res.text
1812+
1813+
await async_session.refresh(sub_item_1)
1814+
await async_session.refresh(sub_item_2)
1815+
await async_session.refresh(top_item)
1816+
assert sub_item_1.parent_item_id == new_top_item.id
1817+
assert sub_item_1.parent_item_id == new_top_item.id
1818+
else:
1819+
assert res.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR, res.text
1820+
assert res.json() == {
1821+
"errors": [
1822+
{
1823+
"detail": "Error of loading the 'sub_items' relationship. "
1824+
"Please add this relationship to include query parameter explicitly.",
1825+
"source": {"parameter": "include"},
1826+
"status_code": status.HTTP_500_INTERNAL_SERVER_ERROR,
1827+
"title": "Internal Server Error",
1828+
},
1829+
],
1830+
}
1831+
17851832

17861833
class TestPatchObjectRelationshipsToOne:
17871834
async def test_ok_when_foreign_key_of_related_object_is_nullable(
@@ -2285,6 +2332,46 @@ async def test_select_custom_fields(
22852332
"meta": {"count": 2, "totalPages": 1},
22862333
}
22872334

2335+
async def test_cascade_delete(self, async_session: AsyncSession):
2336+
resource_type = "cascade_case"
2337+
with suppress(KeyError):
2338+
RoutersJSONAPI.all_jsonapi_routers.pop(resource_type)
2339+
2340+
app = build_app_custom(
2341+
model=CascadeCase,
2342+
schema=CascadeCaseSchema,
2343+
resource_type=resource_type,
2344+
)
2345+
2346+
top_item = CascadeCase()
2347+
sub_item_1 = CascadeCase(parent_item=top_item)
2348+
sub_item_2 = CascadeCase(parent_item=top_item)
2349+
async_session.add_all(
2350+
[
2351+
top_item,
2352+
sub_item_1,
2353+
sub_item_2,
2354+
],
2355+
)
2356+
await async_session.commit()
2357+
2358+
assert sub_item_1.parent_item_id == top_item.id
2359+
assert sub_item_2.parent_item_id == top_item.id
2360+
2361+
async with AsyncClient(app=app, base_url="http://test") as client:
2362+
url = app.url_path_for(f"delete_{resource_type}_detail", obj_id=top_item.id)
2363+
2364+
res = await client.delete(url)
2365+
assert res.status_code == status.HTTP_204_NO_CONTENT, res.text
2366+
2367+
top_item_stmt = select(CascadeCase).where(CascadeCase.id == top_item.id)
2368+
top_item = (await async_session.execute(top_item_stmt)).one_or_none()
2369+
assert top_item is None
2370+
2371+
sub_items_stmt = select(CascadeCase).where(CascadeCase.id.in_([sub_item_1.id, sub_item_2.id]))
2372+
sub_items = (await async_session.execute(sub_items_stmt)).all()
2373+
assert sub_items == []
2374+
22882375

22892376
class TestOpenApi:
22902377
def test_openapi_method_ok(self, app: FastAPI):

0 commit comments

Comments
 (0)