import pytest from conftest import DB, Response from hircine.db.models import Namespace, Tag @pytest.fixture def query_comic_filter(execute_filter): query = """ query comics($filter: ComicFilterInput) { comics(filter: $filter) { __typename count edges { id title } } } """ return execute_filter(query) @pytest.fixture def query_string_filter(execute_filter): query = """ query artists($filter: ArtistFilterInput) { artists(filter: $filter) { __typename count edges { id name } } } """ return execute_filter(query) @pytest.fixture def query_tag_filter(execute_filter): query = """ query tags($filter: TagFilterInput) { tags(filter: $filter) { __typename count edges { id name } } } """ return execute_filter(query) def id_list(edges): return sorted([int(edge["id"]) for edge in edges]) @pytest.mark.parametrize( "filter,ids", [ ( {"include": {"name": {"contains": "robin"}}}, [3, 4], ), ({"exclude": {"name": {"contains": "smith"}}}, [2, 3]), ( { "exclude": {"name": {"contains": "robin"}}, "include": {"name": {"contains": "smith"}}, }, [1], ), ], ids=[ "includes", "excludes", "includes and excludes", ], ) @pytest.mark.anyio async def test_string_filter(query_string_filter, gen_artist, filter, ids): await DB.add_all(*gen_artist) response = Response(await query_string_filter(filter)) response.assert_is("ArtistFilterResult") assert id_list(response.edges) == ids @pytest.mark.parametrize( "filter,empty_response", [ ({"include": {"name": {"contains": ""}}}, False), ({"include": {"name": {}}}, False), ({"exclude": {"name": {"contains": ""}}}, True), ({"exclude": {"name": {}}}, False), ], ids=[ "string (include)", "field (include)", "string (exclude)", "field (exclude)", ], ) @pytest.mark.anyio async def test_string_filter_handles_empty( query_string_filter, gen_artist, filter, empty_response ): artists = await DB.add_all(*gen_artist) response = Response(await query_string_filter(filter)) response.assert_is("ArtistFilterResult") if empty_response: assert response.edges == [] assert response.count == 0 else: assert len(response.edges) == len(artists) assert response.count == len(artists) @pytest.mark.parametrize( "filter", [ {"include": {}}, {"exclude": {}}, ], ids=[ "include", "exclude", ], ) @pytest.mark.anyio async def test_filter_handles_empty_field(query_string_filter, gen_artist, filter): artists = await DB.add_all(*gen_artist) response = Response(await query_string_filter(filter)) response.assert_is("ArtistFilterResult") assert len(response.edges) == len(artists) assert response.count == len(artists) @pytest.mark.parametrize( "filter,ids", [ ( {"include": {"artists": {"any": 1}}}, [1, 3], ), ( {"include": {"artists": {"all": 1}}}, [1, 3], ), ( {"include": {"artists": {"any": [1, 4]}}}, [1, 3, 4], ), ( {"include": {"artists": {"all": [1, 4]}}}, [3], ), ( {"exclude": {"artists": {"any": 1}}}, [2, 4], ), ( {"exclude": {"artists": {"all": 1}}}, [2, 4], ), ( {"exclude": {"artists": {"any": [1, 4]}}}, [2], ), ( {"exclude": {"artists": {"all": [1, 4]}}}, [1, 2, 4], ), ( { "include": {"artists": {"any": [1]}}, "exclude": {"artists": {"all": [4]}}, }, [1], ), ( { "include": {"artists": {"any": [1, 4]}}, "exclude": {"artists": {"all": [1, 4]}}, }, [1, 4], ), ( { "include": {"artists": {"any": [1, 4], "all": [1, 2]}}, }, [1], ), ], ids=[ "includes any (single)", "includes all (single)", "includes any (list)", "includes all (list)", "excludes any (single)", "excludes all (single)", "excludes any (list)", "excludes all (list)", "includes and excludes (single)", "includes and excludes (list)", "includes any and all", ], ) @pytest.mark.anyio async def test_assoc_filter(query_comic_filter, gen_comic, filter, ids): await DB.add_all(*gen_comic) response = Response(await query_comic_filter(filter)) response.assert_is("ComicFilterResult") assert id_list(response.edges) == ids @pytest.mark.parametrize( "filter,empty_response", [ ({"include": {"artists": {"any": []}}}, True), ({"include": {"artists": {"all": []}}}, True), ({"include": {"artists": {}}}, False), ({"exclude": {"artists": {"any": []}}}, False), ({"exclude": {"artists": {"all": []}}}, False), ({"exclude": {"artists": {}}}, False), ({"include": {"tags": {"any": ""}}}, True), ({"include": {"tags": {"any": ":"}}}, True), ], ids=[ "list (include any)", "list (include all)", "field (include)", "list (exclude any)", "list (exclude all)", "field (exclude)", "string (tags)", "specifier (tags)", ], ) @pytest.mark.anyio async def test_assoc_filter_handles_empty( query_comic_filter, gen_comic, filter, empty_response ): comics = await DB.add_all(*gen_comic) response = Response(await query_comic_filter(filter)) response.assert_is("ComicFilterResult") if empty_response: assert response.edges == [] assert response.count == 0 else: assert len(response.edges) == len(comics) assert response.count == len(comics) @pytest.mark.parametrize( "filter,ids", [ ( {"include": {"tags": {"any": "1:"}}}, [1, 2, 3], ), ( {"include": {"tags": {"any": ":2"}}}, [1, 4], ), ( {"include": {"tags": {"exact": ["1:3", "2:1"]}}}, [2], ), ( {"include": {"tags": {"exact": ["1:"]}}}, [3], ), ( {"include": {"tags": {"exact": [":4"]}}}, [3], ), ( {"exclude": {"tags": {"all": ["1:4", "1:1"]}}}, [2, 3, 4], ), ( {"exclude": {"tags": {"exact": ["2:1", "2:2", "2:3"]}}}, [1, 2, 3], ), ( {"exclude": {"tags": {"exact": ["1:"]}}}, [1, 2, 4], ), ( {"exclude": {"tags": {"exact": [":4"]}}}, [1, 2, 4], ), ], ids=[ "includes any namespace", "includes any tag", "includes exact tags", "includes exact namespace", "includes exact tag", "excludes all tags", "includes exact tags", "includes exact namespace", "includes exact tag", ], ) @pytest.mark.anyio async def test_assoc_tag_filter(query_comic_filter, gen_comic, filter, ids): await DB.add_all(*gen_comic) response = Response(await query_comic_filter(filter)) response.assert_is("ComicFilterResult") assert id_list(response.edges) == ids @pytest.mark.parametrize( "filter,ids", [ ( {"include": {"favourite": True}}, [1], ), ( {"include": {"rating": {"any": "EXPLICIT"}}}, [3], ), ( {"include": {"category": {"any": "MANGA"}}}, [1, 2], ), ( {"include": {"censorship": {"any": "MOSAIC"}}}, [3], ), ( {"exclude": {"favourite": True}}, [2, 3, 4], ), ( {"exclude": {"rating": {"any": ["EXPLICIT", "QUESTIONABLE"]}}}, [1, 4], ), ], ids=[ "includes favourite", "includes rating", "includes category", "includes censorship", "excludes favourite", "excludes ratings", ], ) @pytest.mark.anyio async def test_field_filter(query_comic_filter, gen_comic, filter, ids): await DB.add_all(*gen_comic) response = Response(await query_comic_filter(filter)) response.assert_is("ComicFilterResult") assert id_list(response.edges) == ids @pytest.mark.parametrize( "filter,ids", [ ( {"include": {"rating": {"empty": True}}}, [100], ), ( {"include": {"rating": {"empty": False}}}, [1, 2], ), ( {"exclude": {"rating": {"empty": True}}}, [1, 2], ), ( {"exclude": {"rating": {"empty": False}}}, [100], ), ], ids=[ "includes rating empty", "includes rating not empty", "excludes rating empty", "excludes rating not empty", ], ) @pytest.mark.anyio async def test_field_presence(query_comic_filter, gen_comic, empty_comic, filter, ids): await DB.add(next(gen_comic)) await DB.add(next(gen_comic)) await DB.add(empty_comic) response = Response(await query_comic_filter(filter)) response.assert_is("ComicFilterResult") assert id_list(response.edges) == ids @pytest.mark.parametrize( "filter,ids", [ ( {"include": {"artists": {"empty": True}}}, [100], ), ( {"include": {"artists": {"empty": False}}}, [1, 2], ), ( {"exclude": {"artists": {"empty": True}}}, [1, 2], ), ( {"exclude": {"artists": {"empty": False}}}, [100], ), ( {"include": {"tags": {"empty": True}}}, [100], ), ( {"include": {"tags": {"empty": False}}}, [1, 2], ), ( {"exclude": {"tags": {"empty": True}}}, [1, 2], ), ( {"exclude": {"tags": {"empty": False}}}, [100], ), ], ids=[ "includes artist empty", "includes artist not empty", "excludes artist empty", "excludes artist not empty", "includes tags empty", "includes tags not empty", "excludes tags empty", "excludes tags not empty", ], ) @pytest.mark.anyio async def test_assoc_presence(query_comic_filter, gen_comic, empty_comic, filter, ids): await DB.add(next(gen_comic)) await DB.add(next(gen_comic)) await DB.add(empty_comic) response = Response(await query_comic_filter(filter)) response.assert_is("ComicFilterResult") assert id_list(response.edges) == ids @pytest.mark.parametrize( "filter,ids", [ ( {"include": {"namespaces": {"any": 1}}}, [1, 2], ), ( {"include": {"namespaces": {"all": [1, 2]}}}, [2], ), ( {"include": {"namespaces": {"exact": [1]}}}, [1], ), ( {"exclude": {"namespaces": {"any": 2}}}, [1], ), ( {"exclude": {"namespaces": {"exact": [1]}}}, [2], ), ], ids=[ "includes any namespace", "includes all namespace", "includes exact namespaces", "excludes any namespace", "excludes exact namespaces", ], ) @pytest.mark.anyio async def test_tag_assoc_filter(query_tag_filter, gen_namespace, gen_tag, filter, ids): foo = await DB.add(Namespace(id=1, name="foo")) bar = await DB.add(Namespace(id=2, name="bar")) await DB.add(Tag(id=1, name="small", namespaces=[foo])) await DB.add(Tag(id=2, name="large", namespaces=[foo, bar])) response = Response(await query_tag_filter(filter)) response.assert_is("TagFilterResult") assert id_list(response.edges) == ids