summaryrefslogtreecommitdiffstatshomepage
path: root/tests/api/test_filter.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/api/test_filter.py')
-rw-r--r--tests/api/test_filter.py521
1 files changed, 521 insertions, 0 deletions
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