From 23f3f4c7973938206b412bae855967cbd79d5128 Mon Sep 17 00:00:00 2001 From: Doctor Date: Sat, 24 Feb 2024 00:50:15 +0300 Subject: [PATCH] Add tags to manga type --- schema.graphql | 7 +++ src/app/adapters/graphql/apps/manga/types.py | 4 +- src/app/core/domain/manga/loaders.py | 18 ++++-- .../adapters/graphql/manga/fields/__init__.py | 0 .../graphql/manga/fields/test_manga_tags.py | 58 +++++++++++++++++++ tests/conftest.py | 7 +++ 6 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 tests/adapters/graphql/manga/fields/__init__.py create mode 100644 tests/adapters/graphql/manga/fields/test_manga_tags.py diff --git a/schema.graphql b/schema.graphql index 5db6aa8..99b0bb1 100644 --- a/schema.graphql +++ b/schema.graphql @@ -29,6 +29,7 @@ type Manga { titleSlug: String! createdAt: DateTime! updatedAt: DateTime! + tags: [MangaTag!]! } union MangaCreateError = ValidationErrors @@ -56,6 +57,12 @@ type MangaPagePaginationResult { pageInfo: PagePaginationInfo! } +type MangaTag { + id: ID! + name: String! + slug: String! +} + input MangaTagFilter { include: [String!] = null exclude: [String!] = null diff --git a/src/app/adapters/graphql/apps/manga/types.py b/src/app/adapters/graphql/apps/manga/types.py index d03417f..44e6f2e 100644 --- a/src/app/adapters/graphql/apps/manga/types.py +++ b/src/app/adapters/graphql/apps/manga/types.py @@ -17,14 +17,14 @@ class MangaTagGQL(DTOMixin[MangaTag]): id: strawberry.ID name: str - name_slug: str + slug: str @classmethod def from_dto(cls, model: MangaTag) -> Self: return cls( id=strawberry.ID(str(model.id)), name=model.name, - name_slug=model.name_slug, + slug=model.name_slug, ) diff --git a/src/app/core/domain/manga/loaders.py b/src/app/core/domain/manga/loaders.py index cb1fe92..8d56127 100644 --- a/src/app/core/domain/manga/loaders.py +++ b/src/app/core/domain/manga/loaders.py @@ -47,12 +47,18 @@ async def execute( self, keys: Sequence[UUID], ) -> Sequence[Sequence[MangaTag]]: - stmt = select( - manga_manga_tag_secondary_table.c.manga_id, - MangaTag, - ).join( - manga_manga_tag_secondary_table, - manga_manga_tag_secondary_table.c.tag_id == MangaTag.id, + stmt = ( + select( + manga_manga_tag_secondary_table.c.manga_id, + MangaTag, + ) + .join( + manga_manga_tag_secondary_table, + manga_manga_tag_secondary_table.c.tag_id == MangaTag.id, + ) + .order_by( + MangaTag.name_slug, + ) ) tags = collections.defaultdict(list) for manga_id, tag in await self._session.execute(stmt): diff --git a/tests/adapters/graphql/manga/fields/__init__.py b/tests/adapters/graphql/manga/fields/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/adapters/graphql/manga/fields/test_manga_tags.py b/tests/adapters/graphql/manga/fields/test_manga_tags.py new file mode 100644 index 0000000..ccc0126 --- /dev/null +++ b/tests/adapters/graphql/manga/fields/test_manga_tags.py @@ -0,0 +1,58 @@ +import pytest +from sqlalchemy.ext.asyncio import AsyncSession + +from app.db.models import Manga +from tests.adapters.graphql.client import GraphQLClient +from tests.factories import MangaTagFactory + +pytestmark = pytest.mark.anyio + +_QUERY = """query ($id: ID!) { + manga(id: $id) { + id + tags { + __typename + id + name + slug + } + } +} +""" + + +def _tpl(manga: object) -> object: + return { + "data": {"manga": manga}, + } + + +async def test_ok( + collection_size: int, + session: AsyncSession, + manga: Manga, + graphql_client: GraphQLClient, +) -> None: + manga.tags = MangaTagFactory.build_batch(size=collection_size) + manga.tags.sort(key=lambda tag: tag.name_slug) + session.add(manga) + await session.flush() + + response = await graphql_client.query( + query=_QUERY, + variables={"id": str(manga.id)}, + ) + assert response == _tpl( + { + "id": str(manga.id), + "tags": [ + { + "__typename": "MangaTag", + "id": str(tag.id), + "name": tag.name, + "slug": tag.name_slug, + } + for tag in manga.tags + ], + }, + ) diff --git a/tests/conftest.py b/tests/conftest.py index ff48385..c057436 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,13 @@ import pkgutil from collections.abc import AsyncIterator from datetime import datetime +from typing import cast import aioinject import dotenv import httpx import pytest +from _pytest.fixtures import SubRequest from aioinject import Object from asgi_lifespan import LifespanManager from fastapi import FastAPI @@ -81,3 +83,8 @@ async def resolver( @pytest.fixture def now() -> datetime: return utc_now() + + +@pytest.fixture(params=[0, 1, 10]) +def collection_size(request: SubRequest) -> int: + return cast(int, request.param)