Files
old-saburly-wagtail-web/env/lib/python3.10/site-packages/wagtail/search/tests/test_queries.py
2024-08-27 20:33:44 +02:00

333 lines
12 KiB
Python

from django.test import SimpleTestCase, TestCase
from wagtail.search.query import And, Or, Phrase, PlainText
from wagtail.search.utils import (
balanced_reduce,
normalise_query_string,
parse_query_string,
separate_filters_from_query,
)
class TestQueryStringNormalisation(TestCase):
def test_truncation(self):
test_querystring = "a" * 1000
result = normalise_query_string(test_querystring)
self.assertEqual(len(result), 255)
def test_no_truncation(self):
test_querystring = "a" * 10
result = normalise_query_string(test_querystring)
self.assertEqual(len(result), 10)
class TestSeparateFiltersFromQuery(SimpleTestCase):
def test_only_query(self):
filters, query = separate_filters_from_query("hello world")
self.assertDictEqual(filters.dict(), {})
self.assertEqual(query, "hello world")
def test_filter(self):
filters, query = separate_filters_from_query("author:foo")
self.assertDictEqual(filters.dict(), {"author": "foo"})
self.assertEqual(query, "")
def test_filter_with_quotation_mark(self):
filters, query = separate_filters_from_query('author:"foo bar"')
self.assertDictEqual(filters.dict(), {"author": "foo bar"})
self.assertEqual(query, "")
def test_filter_and_query(self):
filters, query = separate_filters_from_query("author:foo hello world")
self.assertDictEqual(filters.dict(), {"author": "foo"})
self.assertEqual(query, "hello world")
def test_filter_with_quotation_mark_and_query(self):
filters, query = separate_filters_from_query('author:"foo bar" hello world')
self.assertDictEqual(filters.dict(), {"author": "foo bar"})
self.assertEqual(query, "hello world")
def test_filter_with_unclosed_quotation_mark_and_query(self):
filters, query = separate_filters_from_query('author:"foo bar hello world')
self.assertDictEqual(filters.dict(), {})
self.assertEqual(query, 'author:"foo bar hello world')
def test_two_filters_and_query(self):
filters, query = separate_filters_from_query(
'author:"foo bar" hello world bar:beer'
)
self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "beer"})
self.assertEqual(query, "hello world")
def test_two_filters_with_quotation_marks_and_query(self):
filters, query = separate_filters_from_query(
'author:"foo bar" hello world bar:"two beers"'
)
self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "two beers"})
self.assertEqual(query, "hello world")
filters, query = separate_filters_from_query(
"author:'foo bar' hello world bar:'two beers'"
)
self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "two beers"})
self.assertEqual(query, "hello world")
def test_return_list_of_multiple_instances_for_same_filter_key(self):
filters, query = separate_filters_from_query(
'foo:test1 hello world foo:test2 foo:"test3" foo2:test4'
)
self.assertDictEqual(filters.dict(), {"foo": "test3", "foo2": "test4"})
self.assertListEqual(filters.getlist("foo"), ["test1", "test2", "test3"])
self.assertEqual(query, "hello world")
class TestParseQueryString(SimpleTestCase):
def test_simple_query(self):
filters, query = parse_query_string("hello world")
self.assertDictEqual(filters.dict(), {})
self.assertEqual(repr(query), repr(PlainText("hello world")))
def test_with_phrase(self):
filters, query = parse_query_string('"hello world"')
self.assertDictEqual(filters.dict(), {})
self.assertEqual(repr(query), repr(Phrase("hello world")))
filters, query = parse_query_string("'hello world'")
self.assertDictEqual(filters.dict(), {})
self.assertEqual(repr(query), repr(Phrase("hello world")))
def test_with_simple_and_phrase(self):
filters, query = parse_query_string('this is simple "hello world"')
self.assertDictEqual(filters.dict(), {})
self.assertEqual(
repr(query), repr(And([PlainText("this is simple"), Phrase("hello world")]))
)
filters, query = parse_query_string("this is simple 'hello world'")
self.assertDictEqual(filters.dict(), {})
self.assertEqual(
repr(query), repr(And([PlainText("this is simple"), Phrase("hello world")]))
)
def test_operator(self):
filters, query = parse_query_string(
'this is simple "hello world"', operator="or"
)
self.assertDictEqual(filters.dict(), {})
self.assertEqual(
repr(query),
repr(
Or([PlainText("this is simple", operator="or"), Phrase("hello world")])
),
)
filters, query = parse_query_string(
"this is simple 'hello world'", operator="or"
)
self.assertDictEqual(filters.dict(), {})
self.assertEqual(
repr(query),
repr(
Or([PlainText("this is simple", operator="or"), Phrase("hello world")])
),
)
def test_with_phrase_unclosed(self):
filters, query = parse_query_string('"hello world')
self.assertDictEqual(filters.dict(), {})
self.assertEqual(repr(query), repr(Phrase("hello world")))
filters, query = parse_query_string("'hello world")
self.assertDictEqual(filters.dict(), {})
self.assertEqual(repr(query), repr(Phrase("hello world")))
def test_phrase_with_filter(self):
filters, query = parse_query_string('"hello world" author:"foo bar" bar:beer')
self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "beer"})
self.assertEqual(repr(query), repr(Phrase("hello world")))
filters, query = parse_query_string("'hello world' author:'foo bar' bar:beer")
self.assertDictEqual(filters.dict(), {"author": "foo bar", "bar": "beer"})
self.assertEqual(repr(query), repr(Phrase("hello world")))
def test_long_queries(self):
filters, query = parse_query_string("0" * 60_000)
self.assertEqual(filters.dict(), {})
self.assertEqual(repr(query), repr(PlainText("0" * 60_000)))
filters, _ = parse_query_string(f'{"a" * 60_000}:"foo bar"')
self.assertEqual(filters.dict(), {"a" * 60_000: "foo bar"})
def test_long_filter_value(self):
filters, _ = parse_query_string(f'foo:ba{"r" * 60_000}')
self.assertEqual(filters.dict(), {"foo": f'ba{"r" * 60_000}'})
def test_joined_filters(self):
filters, query = parse_query_string("foo:bar:baz")
self.assertEqual(filters.dict(), {"foo": "bar"})
self.assertEqual(repr(query), repr(PlainText(":baz")))
filters, query = parse_query_string("foo:'bar':baz")
self.assertEqual(filters.dict(), {"foo": "bar"})
self.assertEqual(repr(query), repr(PlainText(":baz")))
filters, query = parse_query_string("foo:'bar:baz'")
self.assertEqual(filters.dict(), {"foo": "bar:baz"})
def test_multiple_phrases(self):
filters, query = parse_query_string('"hello world" "hi earth"')
self.assertEqual(
repr(query), repr(And([Phrase("hello world"), Phrase("hi earth")]))
)
filters, query = parse_query_string("'hello world' 'hi earth'")
self.assertEqual(
repr(query), repr(And([Phrase("hello world"), Phrase("hi earth")]))
)
def test_mixed_phrases_with_filters(self):
filters, query = parse_query_string(
""""lord of the rings" army_1:"elves" army_2:'humans'"""
)
self.assertDictEqual(filters.dict(), {"army_1": "elves", "army_2": "humans"})
self.assertEqual(
repr(query),
repr(Phrase("lord of the rings")),
)
class TestBalancedReduce(SimpleTestCase):
# For simple values, this should behave exactly the same as Pythons reduce()
# So I've copied its tests: https://github.com/python/cpython/blob/21cdb711e3b1975398c54141e519ead02670610e/Lib/test/test_functools.py#L771
def test_reduce(self):
class Squares:
def __init__(self, max):
self.max = max
self.sofar = []
def __len__(self):
return len(self.sofar)
def __getitem__(self, i):
if not 0 <= i < self.max:
raise IndexError
n = len(self.sofar)
while n <= i:
self.sofar.append(n * n)
n += 1
return self.sofar[i]
def add(x, y):
return x + y
self.assertEqual(balanced_reduce(add, ["a", "b", "c"], ""), "abc")
self.assertEqual(
balanced_reduce(add, [["a", "c"], [], ["d", "w"]], []), ["a", "c", "d", "w"]
)
self.assertEqual(balanced_reduce(lambda x, y: x * y, range(2, 8), 1), 5040)
self.assertEqual(
balanced_reduce(lambda x, y: x * y, range(2, 21), 1), 2432902008176640000
)
self.assertEqual(balanced_reduce(add, Squares(10)), 285)
self.assertEqual(balanced_reduce(add, Squares(10), 0), 285)
self.assertEqual(balanced_reduce(add, Squares(0), 0), 0)
self.assertRaises(TypeError, balanced_reduce)
self.assertRaises(TypeError, balanced_reduce, 42, 42)
self.assertRaises(TypeError, balanced_reduce, 42, 42, 42)
self.assertEqual(
balanced_reduce(42, "1"), "1"
) # func is never called with one item
self.assertEqual(
balanced_reduce(42, "", "1"), "1"
) # func is never called with one item
self.assertRaises(TypeError, balanced_reduce, 42, (42, 42))
self.assertRaises(
TypeError, balanced_reduce, add, []
) # arg 2 must not be empty sequence with no initial value
self.assertRaises(TypeError, balanced_reduce, add, "")
self.assertRaises(TypeError, balanced_reduce, add, ())
self.assertRaises(TypeError, balanced_reduce, add, object())
class TestFailingIter:
def __iter__(self):
raise RuntimeError
self.assertRaises(RuntimeError, balanced_reduce, add, TestFailingIter())
self.assertIsNone(balanced_reduce(add, [], None))
self.assertEqual(balanced_reduce(add, [], 42), 42)
class BadSeq:
def __getitem__(self, index):
raise ValueError
self.assertRaises(ValueError, balanced_reduce, 42, BadSeq())
# Test reduce()'s use of iterators.
def test_iterator_usage(self):
class SequenceClass:
def __init__(self, n):
self.n = n
def __getitem__(self, i):
if 0 <= i < self.n:
return i
else:
raise IndexError
from operator import add
self.assertEqual(balanced_reduce(add, SequenceClass(5)), 10)
self.assertEqual(balanced_reduce(add, SequenceClass(5), 42), 52)
self.assertRaises(TypeError, balanced_reduce, add, SequenceClass(0))
self.assertEqual(balanced_reduce(add, SequenceClass(0), 42), 42)
self.assertEqual(balanced_reduce(add, SequenceClass(1)), 0)
self.assertEqual(balanced_reduce(add, SequenceClass(1), 42), 42)
d = {"one": 1, "two": 2, "three": 3}
self.assertEqual(balanced_reduce(add, d), "".join(d.keys()))
# This test is specific to balanced_reduce
def test_is_balanced(self):
# Tests that balanced_reduce returns the object as a balanced tree
class CombinedNode:
def __init__(self, a, b):
self.a = a
self.b = b
def __repr__(self):
return f"({self.a} {self.b})"
self.assertEqual(
repr(
balanced_reduce(CombinedNode, ["A", "B", "C", "D", "E", "F", "G", "H"])
),
"(((A B) (C D)) ((E F) (G H)))",
# Note: functools.reduce will return '(((((((A B) C) D) E) F) G) H)'
)