more recipe search tests

This commit is contained in:
smilerz
2022-02-13 13:53:07 -06:00
parent baa2aa51da
commit bf54680178
10 changed files with 408 additions and 102 deletions

View File

@@ -1,58 +1,78 @@
import itertools
import json
from datetime import timedelta
import pytest
from django.conf import settings
from django.contrib import auth
from django.urls import reverse
from django.utils import timezone
from django_scopes import scope, scopes_disabled
from cookbook.models import Food, Recipe, SearchFields
from cookbook.tests.factories import (FoodFactory, IngredientFactory, KeywordFactory,
RecipeBookEntryFactory, RecipeFactory, UnitFactory)
# TODO recipe name/description/instructions/keyword/book/food test search with icontains, istarts with/ full text(?? probably when word changes based on conjugation??), trigram, unaccent
from cookbook.tests.conftest import transpose
from cookbook.tests.factories import (CookLogFactory, FoodFactory, IngredientFactory,
KeywordFactory, RecipeBookEntryFactory, RecipeFactory,
UnitFactory, ViewLogFactory)
# TODO test combining any/all of the above
# TODO search rating as user or when another user rated
# TODO search last cooked
# TODO changing lsat_viewed ## to return on search
# TODO test sort_by
# TODO test sort_by new X number of recipes are new within last Y days
# TODO test loading custom filter
# TODO test loading custom filter with overrided params
# TODO makenow with above filters
# TODO test search for number of times cooked (self vs others)
# TODO test including children
# TODO test search food/keywords including/excluding children
LIST_URL = 'api:recipe-list'
sqlite = settings.DATABASES['default']['ENGINE'] not in ['django.db.backends.postgresql_psycopg2', 'django.db.backends.postgresql']
@pytest.fixture
def accent():
return "àèìòù"
return "àbçđêf ğĦìĵķĽmñ öPqŕşŧ úvŵxyž"
@pytest.fixture
def unaccent():
return "aeiou"
return "abcdef ghijklmn opqrst uvwxyz"
@pytest.fixture
def user1(request, space_1, u1_s1):
def user1(request, space_1, u1_s1, unaccent):
user = auth.get_user(u1_s1)
params = {x[0]: x[1] for x in request.param}
try:
params = {x[0]: x[1] for x in request.param}
except AttributeError:
params = {}
result = 1
misspelled_result = 0
search_term = unaccent
if params.get('fuzzy_lookups', False):
user.searchpreference.lookup = True
misspelled_result = 1
if params.get('fuzzy_search', False):
user.searchpreference.trigram.set(SearchFields.objects.all())
misspelled_result = 1
if params.get('icontains', False):
user.searchpreference.icontains.set(SearchFields.objects.all())
search_term = 'ghijklmn'
if params.get('istartswith', False):
user.searchpreference.istartswith.set(SearchFields.objects.all())
search_term = 'abcdef'
if params.get('unaccent', False):
user.searchpreference.unaccent.set(SearchFields.objects.all())
misspelled_result *= 2
result *= 2
# full text vectors are hard coded to use unaccent - put this after unaccent to override result
if params.get('fulltext', False):
user.searchpreference.fulltext.set(SearchFields.objects.all())
# user.searchpreference.search = 'websearch'
search_term = 'ghijklmn uvwxyz'
result = 2
else:
result = 1
user.userpreference.save()
return (u1_s1, result, params)
user.searchpreference.save()
misspelled_term = transpose(search_term, number=3)
return (u1_s1, result, misspelled_result, search_term, misspelled_term, params)
@pytest.fixture
@@ -61,15 +81,21 @@ def recipes(space_1):
@pytest.fixture
def found_recipe(request, space_1, accent, unaccent):
def found_recipe(request, space_1, accent, unaccent, u1_s1, u2_s1):
user1 = auth.get_user(u1_s1)
user2 = auth.get_user(u2_s1)
days_3 = timezone.now() - timedelta(days=3)
days_15 = timezone.now() - timedelta(days=15)
days_30 = timezone.now() - timedelta(days=30)
recipe1 = RecipeFactory.create(space=space_1)
recipe2 = RecipeFactory.create(space=space_1)
recipe3 = RecipeFactory.create(space=space_1)
obj1 = None
obj2 = None
if request.param.get('food', None):
obj1 = FoodFactory.create(name=unaccent, space=space_1)
obj2 = FoodFactory.create(name=accent, space=space_1)
recipe1.steps.first().ingredients.add(IngredientFactory.create(food=obj1))
recipe2.steps.first().ingredients.add(IngredientFactory.create(food=obj2))
recipe3.steps.first().ingredients.add(IngredientFactory.create(food=obj1), IngredientFactory.create(food=obj2))
@@ -79,6 +105,10 @@ def found_recipe(request, space_1, accent, unaccent):
recipe1.keywords.add(obj1)
recipe2.keywords.add(obj2)
recipe3.keywords.add(obj1, obj2)
recipe1.name = unaccent
recipe2.name = accent
recipe1.save()
recipe2.save()
if request.param.get('book', None):
obj1 = RecipeBookEntryFactory.create(recipe=recipe1).book
obj2 = RecipeBookEntryFactory.create(recipe=recipe2).book
@@ -87,12 +117,51 @@ def found_recipe(request, space_1, accent, unaccent):
if request.param.get('unit', None):
obj1 = UnitFactory.create(name=unaccent, space=space_1)
obj2 = UnitFactory.create(name=accent, space=space_1)
recipe1.steps.first().ingredients.add(IngredientFactory.create(unit=obj1))
recipe2.steps.first().ingredients.add(IngredientFactory.create(unit=obj2))
recipe3.steps.first().ingredients.add(IngredientFactory.create(unit=obj1), IngredientFactory.create(unit=obj2))
if request.param.get('name', None):
recipe1.name = unaccent
recipe2.name = accent
recipe1.save()
recipe2.save()
if request.param.get('description', None):
recipe1.description = unaccent
recipe2.description = accent
recipe1.save()
recipe2.save()
if request.param.get('instruction', None):
i1 = recipe1.steps.first()
i2 = recipe2.steps.first()
i1.instruction = unaccent
i2.instruction = accent
i1.save()
i2.save()
if request.param.get('createdon', None):
recipe1.created_at = days_3
recipe2.created_at = days_30
recipe3.created_at = days_15
recipe1.save()
recipe2.save()
recipe3.save()
if request.param.get('viewedon', None):
ViewLogFactory.create(recipe=recipe1, created_by=user1, created_at=days_3, space=space_1)
ViewLogFactory.create(recipe=recipe2, created_by=user1, created_at=days_30, space=space_1)
ViewLogFactory.create(recipe=recipe3, created_by=user2, created_at=days_15, space=space_1)
if request.param.get('cookedon', None):
CookLogFactory.create(recipe=recipe1, created_by=user1, created_at=days_3, space=space_1)
CookLogFactory.create(recipe=recipe2, created_by=user1, created_at=days_30, space=space_1)
CookLogFactory.create(recipe=recipe3, created_by=user2, created_at=days_15, space=space_1)
if request.param.get('timescooked', None):
CookLogFactory.create_batch(5, recipe=recipe1, created_by=user1, space=space_1)
CookLogFactory.create(recipe=recipe2, created_by=user1, space=space_1)
CookLogFactory.create_batch(3, recipe=recipe3, created_by=user2, space=space_1)
if request.param.get('rating', None):
CookLogFactory.create(recipe=recipe1, created_by=user1, rating=5.0, space=space_1)
CookLogFactory.create(recipe=recipe2, created_by=user1, rating=1.0, space=space_1)
CookLogFactory.create(recipe=recipe3, created_by=user2, rating=3.0, space=space_1)
return (recipe1, recipe2, recipe3, obj1, obj2)
return (recipe1, recipe2, recipe3, obj1, obj2, request.param)
@pytest.mark.parametrize("found_recipe, param_type", [
@@ -101,7 +170,7 @@ def found_recipe(request, space_1, accent, unaccent):
({'book': True}, 'books'),
], indirect=['found_recipe'])
@pytest.mark.parametrize('operator', [('_or', 3, 0), ('_and', 1, 2), ])
def test_search_or_and_not(found_recipe, param_type, operator, recipes, user1, space_1):
def test_search_or_and_not(found_recipe, param_type, operator, recipes, u1_s1, space_1):
with scope(space=space_1):
param1 = f"{param_type}{operator[0]}={found_recipe[3].id}"
param2 = f"{param_type}{operator[0]}={found_recipe[4].id}"
@@ -163,9 +232,12 @@ def test_search_units(found_recipe, recipes, u1_s1, space_1):
assert found_recipe[2].id in [x['id'] for x in r['results']]
@pytest.mark.skipif(sqlite, reason="requires PostgreSQL")
@pytest.mark.parametrize("user1", itertools.product(
[('fuzzy_lookups', True), ('fuzzy_lookups', False)],
[('fuzzy_search', True), ('fuzzy_search', False)],
[
('fuzzy_search', True), ('fuzzy_search', False),
('fuzzy_lookups', True), ('fuzzy_lookups', False)
],
[('unaccent', True), ('unaccent', False)]
), indirect=['user1'])
@pytest.mark.parametrize("found_recipe, param_type", [
@@ -176,12 +248,99 @@ def test_search_units(found_recipe, recipes, u1_s1, space_1):
def test_fuzzy_lookup(found_recipe, recipes, param_type, user1, space_1):
with scope(space=space_1):
list_url = f'api:{param_type}-list'
param1 = "query=aeiou"
param2 = "query=aoieu"
param1 = f"query={user1[3]}"
param2 = f"query={user1[4]}"
# test fuzzy off - also need search settings on/off
r = json.loads(user1[0].get(reverse(list_url) + f'?{param1}&limit=2').content)
assert len([x['id'] for x in r['results'] if x['id'] in [found_recipe[3].id, found_recipe[4].id]]) == user1[1]
r = json.loads(user1[0].get(reverse(list_url) + f'?{param2}').content)
assert len([x['id'] for x in r['results'] if x['id'] in [found_recipe[3].id, found_recipe[4].id]]) == user1[1]
r = json.loads(user1[0].get(reverse(list_url) + f'?{param2}&limit=10').content)
assert len([x['id'] for x in r['results'] if x['id'] in [found_recipe[3].id, found_recipe[4].id]]) == user1[2]
@pytest.mark.skipif(sqlite, reason="requires PostgreSQL")
@pytest.mark.parametrize("user1", itertools.product(
[
('fuzzy_search', True), ('fuzzy_search', False),
('fulltext', True), ('fulltext', False),
('icontains', True), ('icontains', False),
('istartswith', True), ('istartswith', False),
],
[('unaccent', True), ('unaccent', False)]
), indirect=['user1'])
@pytest.mark.parametrize("found_recipe", [
({'name': True}),
({'description': True}),
({'instruction': True}),
({'keyword': True}),
({'food': True}),
], indirect=['found_recipe'])
def test_search_string(found_recipe, recipes, user1, space_1):
with scope(space=space_1):
param1 = f"query={user1[3]}"
param2 = f"query={user1[4]}"
r = json.loads(user1[0].get(reverse(LIST_URL) + f'?{param1}').content)
assert len([x['id'] for x in r['results'] if x['id'] in [found_recipe[0].id, found_recipe[1].id]]) == user1[1]
r = json.loads(user1[0].get(reverse(LIST_URL) + f'?{param2}').content)
assert len([x['id'] for x in r['results'] if x['id'] in [found_recipe[0].id, found_recipe[1].id]]) == user1[2]
@pytest.mark.parametrize("found_recipe, param_type, result", [
({'viewedon': True}, 'viewedon', (1, 1)),
({'cookedon': True}, 'cookedon', (1, 1)),
({'createdon': True}, 'createdon', (2, 12)), # created dates are not filtered by user
], indirect=['found_recipe'])
def test_search_date(found_recipe, recipes, param_type, result, u1_s1, u2_s1, space_1):
date = (timezone.now() - timedelta(days=15)).strftime("%Y-%m-%d")
param1 = f"?{param_type}={date}"
param2 = f"?{param_type}=-{date}"
r = json.loads(u1_s1.get(reverse(LIST_URL) + f'{param1}').content)
assert r['count'] == result[0]
assert found_recipe[0].id in [x['id'] for x in r['results']]
r = json.loads(u1_s1.get(reverse(LIST_URL) + f'{param2}').content)
assert r['count'] == result[1]
assert found_recipe[1].id in [x['id'] for x in r['results']]
# test today's date returns for lte and gte searches
r = json.loads(u2_s1.get(reverse(LIST_URL) + f'{param1}').content)
assert r['count'] == result[0]
assert found_recipe[2].id in [x['id'] for x in r['results']]
r = json.loads(u2_s1.get(reverse(LIST_URL) + f'{param2}').content)
assert r['count'] == result[1]
assert found_recipe[2].id in [x['id'] for x in r['results']]
@pytest.mark.parametrize("found_recipe, param_type", [
({'rating': True}, 'rating'),
({'timescooked': True}, 'timescooked'),
], indirect=['found_recipe'])
def test_search_count(found_recipe, recipes, param_type, u1_s1, u2_s1, space_1):
param1 = f'?{param_type}=3'
param2 = f'?{param_type}=-3'
param3 = f'?{param_type}=0'
r = json.loads(u1_s1.get(reverse(LIST_URL) + param1).content)
assert r['count'] == 1
assert found_recipe[0].id in [x['id'] for x in r['results']]
r = json.loads(u1_s1.get(reverse(LIST_URL) + param2).content)
assert r['count'] == 1
assert found_recipe[1].id in [x['id'] for x in r['results']]
# test search for not rated/cooked
r = json.loads(u1_s1.get(reverse(LIST_URL) + param3).content)
assert r['count'] == 11
assert (found_recipe[0].id or found_recipe[1].id) not in [x['id'] for x in r['results']]
# test matched returns for lte and gte searches
r = json.loads(u2_s1.get(reverse(LIST_URL) + param1).content)
assert r['count'] == 1
assert found_recipe[2].id in [x['id'] for x in r['results']]
r = json.loads(u2_s1.get(reverse(LIST_URL) + param2).content)
assert r['count'] == 1
assert found_recipe[2].id in [x['id'] for x in r['results']]