|
1 | 1 | import json
|
2 | 2 | import logging
|
3 | 3 | from collections import defaultdict
|
| 4 | +from contextlib import suppress |
4 | 5 | from datetime import datetime, timezone
|
5 | 6 | from itertools import chain, zip_longest
|
6 | 7 | from json import dumps, loads
|
|
18 | 19 | from sqlalchemy.orm import InstrumentedAttribute
|
19 | 20 | from starlette.datastructures import QueryParams
|
20 | 21 |
|
| 22 | +from fastapi_jsonapi.api import RoutersJSONAPI |
21 | 23 | from fastapi_jsonapi.views.view_base import ViewBase
|
22 | 24 | from tests.common import is_postgres_tests
|
23 | 25 | from tests.fixtures.app import build_alphabet_app, build_app_custom
|
|
31 | 33 | from tests.models import (
|
32 | 34 | Alpha,
|
33 | 35 | Beta,
|
| 36 | + CascadeCase, |
34 | 37 | Computer,
|
35 | 38 | ContainsTimestamp,
|
36 | 39 | CustomUUIDItem,
|
|
44 | 47 | Workplace,
|
45 | 48 | )
|
46 | 49 | from tests.schemas import (
|
| 50 | + CascadeCaseSchema, |
47 | 51 | CustomUserAttributesSchema,
|
48 | 52 | CustomUUIDItemAttributesSchema,
|
49 | 53 | PostAttributesBaseSchema,
|
@@ -1744,6 +1748,87 @@ async def test_select_custom_fields(
|
1744 | 1748 | "meta": None,
|
1745 | 1749 | }
|
1746 | 1750 |
|
| 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 | + |
1747 | 1832 |
|
1748 | 1833 | class TestPatchObjectRelationshipsToOne:
|
1749 | 1834 | async def test_ok_when_foreign_key_of_related_object_is_nullable(
|
@@ -2247,6 +2332,46 @@ async def test_select_custom_fields(
|
2247 | 2332 | "meta": {"count": 2, "totalPages": 1},
|
2248 | 2333 | }
|
2249 | 2334 |
|
| 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 | + |
2250 | 2375 |
|
2251 | 2376 | class TestOpenApi:
|
2252 | 2377 | def test_openapi_method_ok(self, app: FastAPI):
|
|
0 commit comments