From d1d654ebac2d51e3841675faeb56480e440f622f Mon Sep 17 00:00:00 2001 From: Wolfgang Müller Date: Tue, 5 Mar 2024 18:08:09 +0100 Subject: Initial commit --- tests/api/test_filter.py | 521 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 521 insertions(+) create mode 100644 tests/api/test_filter.py (limited to 'tests/api/test_filter.py') diff --git a/tests/api/test_filter.py b/tests/api/test_filter.py new file mode 100644 index 0000000..67a953f --- /dev/null +++ b/tests/api/test_filter.py @@ -0,0 +1,521 @@ +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 -- cgit v1.2.3-2-gb3c3