combo/tests/test_search.py

1233 lines
49 KiB
Python

import json
import os
import pytest
import mock
from django.conf import settings
from django.contrib.auth.models import AnonymousUser, User, Group
from django.contrib.contenttypes.models import ContentType
from django.db import connection
from django.test import override_settings
from django.test.client import RequestFactory
from django.test.utils import CaptureQueriesContext
from django.urls import reverse
from django.utils.http import urlencode
from requests.exceptions import ConnectionError
from requests.models import Response
from combo.apps.search.engines import engines
from combo.apps.search.models import SearchCell, IndexedCell
from combo.apps.search.utils import index_site, search_site
from combo.data.models import Page, JsonCell, TextCell, MenuCell, LinkCell, PageSnapshot
from .test_manager import login
pytestmark = pytest.mark.django_db
SEARCH_SERVICES = {
'search1': {
'label': 'Search 1',
'url': 'http://www.example.net/search/?q=%(q)s',
},
'search_tmpl': {
'label': 'Search with template',
'url': '[search_url]?q=%(q)s',
},
'search_alternate_key': {
'label': 'Search with alternate key',
'url': 'http://www.example.net/search/?q=%(q)s',
'data_key': 'results',
},
}
TEMPLATE_VARS = {'search_url': 'http://search.example.net/'}
class SearchServices(object):
def __init__(self, search_services):
self.search_services = search_services
def __enter__(self):
settings.COMBO_SEARCH_SERVICES = self.search_services
def __exit__(self, *args, **kwargs):
settings.COMBO_SEARCH_SERVICES = {}
def test_search_cell(app):
page = Page(title='Search', slug='search_page', template_name='standard')
page.save()
cell = SearchCell(page=page, placeholder='content', order=0)
cell._search_services = {'data': ['search1']}
cell.input_placeholder = 'my placeholder'
cell.save()
# unknown cell pk
resp = app.get('/ajax/search/0/search1/?q=foo', status=404)
with SearchServices(SEARCH_SERVICES):
resp = cell.render({})
assert 'input' in resp
assert 'id="combo-search-input-%s"' % cell.pk in resp
assert 'autofocus' not in resp
assert 'placeholder="my placeholder"' in resp
cell.autofocus = True
cell.save()
resp = cell.render({})
assert 'autofocus' in resp
cell.slug = 'var-name'
context = {'request': RequestFactory().get('/?q_var_name=searchme')}
resp = cell.render(context)
assert "$input.val('searchme');" in resp
with mock.patch('combo.apps.search.models.requests.get') as requests_get:
response = {'err': 0, 'data': []}
mock_json = mock.Mock()
mock_json.json.return_value = response
requests_get.return_value = mock_json
resp = app.get('/ajax/search/%s/search1/?q=foo' % cell.pk, status=200)
assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=foo'
assert '<li>' not in resp.text
assert 'no result found' in resp.text
resp = app.get('/ajax/search/%s/search1/?q=foo%%23bar' % cell.pk, status=200)
assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=foo%23bar'
assert '<li>' not in resp.text
assert 'no result found' in resp.text
response['data'] = [{'url': 'http://test', 'text': 'barbarbar'}]
resp = app.get('/ajax/search/%s/search1/?q=foo' % cell.pk, status=200)
assert resp.text.count('<li>') == 1
assert '<li><a href="http://test">barbarbar</a>' in resp.text
assert 'no result found' not in resp.text
response['data'] = [
{'url': 'http://test', 'text': 'barbarbar', 'description': 'this is <b>html</b>'}
]
resp = app.get('/ajax/search/%s/search1/?q=foo' % cell.pk, status=200)
assert resp.text.count('<li>') == 1
assert '<li><a href="http://test">barbarbar</a>' in resp.text
assert 'this is <b>html</b>' in resp.text
assert 'no result found' not in resp.text
resp = app.get('/ajax/search/%s/search1/?q=' % cell.pk, status=200)
assert '<li>' not in resp.text
assert 'no result found' not in resp.text
cell._search_services = {'data': ['search_alternate_key']}
cell.save()
response = {'results': [{'url': 'http://test', 'text': 'barbarbar'}]}
mock_json.json.return_value = response
resp = app.get('/ajax/search/%s/search_alternate_key/?q=foo' % cell.pk, status=200)
assert resp.text.count('<li>') == 1
assert '<li><a href="http://test">barbarbar</a>' in resp.text
# search engine does not return valid JSON
class FakedResponse(mock.Mock):
def json(self):
return json.loads(self.content)
requests_get.return_value = FakedResponse(content='notjson', status_code=200)
resp = app.get('/ajax/search/%s/search_alternate_key/?q=bar' % cell.pk, status=200)
assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=bar'
assert '<li>' not in resp.text
assert 'no result found' in resp.text
requests_get.return_value = FakedResponse(content='500withbadjson', status_code=500)
resp = app.get('/ajax/search/%s/search_alternate_key/?q=foo' % cell.pk, status=200)
assert requests_get.call_args[0][0] == 'http://www.example.net/search/?q=foo'
assert '<li>' not in resp.text
assert 'no result found' in resp.text
with override_settings(TEMPLATE_VARS=TEMPLATE_VARS):
cell._search_services = {'data': ['search_tmpl']}
cell.save()
with mock.patch('combo.apps.search.models.requests.get') as requests_get:
response = {'err': 0, 'data': []}
mock_json = mock.Mock()
mock_json.json.return_value = response
requests_get.return_value = mock_json
resp = app.get('/ajax/search/%s/search_tmpl/?q=foo' % cell.pk, status=200)
assert requests_get.call_args[0][0] == 'http://search.example.net/?q=foo'
# TEMPLATE_VARS are accessible in template
cell.slug = 'searchfoo'
cell.save()
templates_settings = [settings.TEMPLATES[0].copy()]
templates_settings[0]['DIRS'] = [
'%s/templates-1' % os.path.abspath(os.path.dirname(__file__))
]
with override_settings(TEMPLATES=templates_settings):
resp = app.get('/ajax/search/%s/search_tmpl/?q=bar' % cell.pk, status=200)
assert requests_get.call_args[0][0] == 'http://search.example.net/?q=bar'
assert 'searchfoo results.data=[]' in resp.text
assert 'search_url=http://search.example.net/' in resp.text
def test_search_cell_urlsplit(settings, app):
settings.KNOWN_SERVICES = {
'passerelle': {
'default': {
'title': 'test',
'url': 'http://example.org',
'secret': 'combo',
'orig': 'combo',
'backoffice-menu-url': 'http://example.org/manage/',
}
}
}
settings.COMBO_SEARCH_SERVICES = {
'search1': {
'label': 'Search 1',
'url': 'http://example.org/search/?q={{ q }}', # template var
'signature': 'foo',
},
}
page = Page.objects.create(title='Search', slug='search_page', template_name='standard')
cell = SearchCell.objects.create(
page=page, placeholder='content', order=0, _search_services={'data': ['search1']}
)
with mock.patch('combo.apps.search.models.requests.get') as requests_get:
response = {'err': 0, 'data': []}
mock_json = mock.Mock()
mock_json.json.return_value = response
requests_get.return_value = mock_json
resp = app.get('/ajax/search/%s/search1/?q=foo?' % cell.pk, status=200)
assert requests_get.call_args[0][0] == 'http://example.org/search/?q=foo?'
assert requests_get.call_args[1]['remote_service'] == 'auto'
with mock.patch('requests.Session.request') as mock_request:
response = {'err': 0, 'data': []}
mock_json = mock.Mock(status_code=200)
mock_json.json.return_value = response
mock_request.return_value = mock_json
resp = app.get('/ajax/search/%s/search1/?q=foo?' % cell.pk, status=200)
assert mock_request.call_args[0][1] == 'http://example.org/search/?orig=combo&q=foo?'
assert '<li>' not in resp.text
assert 'no result found' in resp.text
def test_search_global_context(app):
with SearchServices(SEARCH_SERVICES):
page = Page(title='Search', slug='search_page', template_name='standard')
page.save()
cell = SearchCell(page=page, placeholder='content', order=0)
cell._search_services = {'data': ['search1']}
cell.save()
assert cell.varname == ''
cell.slug = 'search-item'
cell.save()
assert cell.varname == 'search_item'
jsoncell = JsonCell(page=page, placeholder='content', order=0)
jsoncell.url = 'http://www.example.net/search/[search_item]/'
jsoncell.save()
url = (
reverse(
'combo-public-ajax-page-cell',
kwargs={'page_pk': page.id, 'cell_reference': jsoncell.get_reference()},
)
+ '?search_item=foo'
)
with mock.patch('combo.utils.requests.get') as requests_get:
data = {'data': []}
requests_get.return_value = mock.Mock(json=lambda: data, status_code=200)
resp = app.get(url)
assert requests_get.call_args[0][0] == 'http://www.example.net/search/foo/'
def test_search_percent_sign(app):
services = {
'search2': {
'label': 'Search 2',
'url': 'http://www.example.net/search/?email=test%40example.net&q=%(q)s',
'hit_url_template': 'http://example.net/{{id}}/',
'hit_label_template': '{{a}} {{b}}',
'hit_description_template': 'description {{a}}',
}
}
with SearchServices(services):
page = Page(title='Search', slug='search_page', template_name='standard')
page.save()
cell = SearchCell(page=page, placeholder='content', order=0)
cell._search_services = {'data': ['search2']}
cell.save()
with mock.patch('combo.apps.search.models.requests.get') as requests_get:
response = {
'err': 0,
'data': [{'id': '123', 'a': 'A', 'b': 'B'}],
}
mock_json = mock.Mock()
mock_json.json.return_value = response
requests_get.return_value = mock_json
resp = app.get('/ajax/search/%s/search2/?q=foo' % cell.pk, status=200)
assert resp.text.count('<li>') == 1
assert '<li><a href="http://example.net/123/">A B</a>' in resp.text
assert '<div>description A</div>' in resp.text
def test_search_custom_templates(app):
services = {
'search2': {
'label': 'Search 2',
'url': 'http://www.example.net/search/?q=%(q)s',
'hit_url_template': 'http://example.net/{{id}}/',
'hit_label_template': '{{a}} {{b}}',
'hit_description_template': 'description {{a}}',
}
}
with SearchServices(services):
page = Page(title='Search', slug='search_page', template_name='standard')
page.save()
cell = SearchCell(page=page, placeholder='content', order=0)
cell._search_services = {'data': ['search2']}
cell.save()
with mock.patch('combo.apps.search.models.requests.get') as requests_get:
response = {
'err': 0,
'data': [{'id': '123', 'a': 'A', 'b': 'B'}],
}
mock_json = mock.Mock()
mock_json.json.return_value = response
requests_get.return_value = mock_json
resp = app.get('/ajax/search/%s/search2/?q=foo' % cell.pk, status=200)
assert resp.text.count('<li>') == 1
assert '<li><a href="http://example.net/123/">A B</a>' in resp.text
assert '<div>description A</div>' in resp.text
def test_search_cell_visibility(settings, app):
page = Page.objects.create(title='example page', slug='example-page')
settings.COMBO_SEARCH_SERVICES = SEARCH_SERVICES
cell = SearchCell(page=page, order=0)
assert not cell.is_visible()
cell._search_services = {'data': ['_text']}
assert cell.is_visible()
def test_search_contents():
page = Page(title='example page', slug='example-page')
page.save()
# private cells are indexed
cell = TextCell(page=page, text='foobar', public=False, order=0)
assert cell.render_for_search().strip() == 'foobar'
# no indexation of empty cells (is_relevant check)
cell = TextCell(page=page, text='', order=0)
assert cell.render_for_search() == ''
# indexation
cell = TextCell(page=page, text='<p>foobar</p>', order=0)
assert cell.render_for_search().strip() == 'foobar'
# no indexation of menu cells
cell = MenuCell(page=page, order=0)
assert cell.exclude_from_search is True
def test_search_contents_index():
page = Page(title='example page', slug='example-page')
page.public = True
page.save()
cell = TextCell(page=page, placeholder='content', text='<p>foobar</p>', order=0)
cell.save()
request = RequestFactory().get('/')
request.user = AnonymousUser()
hits = search_site(request, 'foobar')
assert len(hits) == 0
index_site()
hits = search_site(request, 'foobar')
assert len(hits) == 1
def test_search_contents_technical_placeholder():
page = Page(title='example page', slug='example-page')
page.save()
TextCell(page=page, text='<p>foobar</p>', order=0, placeholder='_off').save()
TextCell(page=page, text='<p>barfoo</p>', order=0, placeholder='content').save()
request = RequestFactory().get('/')
request.user = AnonymousUser()
index_site()
hits = search_site(request, 'foobar')
assert len(hits) == 0
hits = search_site(request, 'barfoo')
assert len(hits) == 1
def test_search_api(app):
page = Page.objects.create(title='example page', slug='example-page')
TextCell.objects.create(page=page, placeholder='content', text='<p>foobar baz</p>', order=0)
second_page = Page.objects.create(
title='second page', slug='second-page', description='Foo Bar Description'
)
TextCell.objects.create(page=second_page, placeholder='content', text='<p>other baz</p>', order=0)
index_site()
cell = SearchCell.objects.create(
page=page, placeholder='content', _search_services={'data': ['_text']}, order=0
)
resp = app.get('/ajax/search/%s/_text/?q=foobar' % cell.id, status=200)
assert resp.text.count('<li') == 1
assert 'example page' in resp.text
resp = app.get('/ajax/search/%s/_text/?q=other' % cell.id, status=200)
assert resp.text.count('<li') == 1
result = resp.text
result = result.replace(' ', '')
result = result.replace('\n', '')
assert '<li><a href="/second-page/">second page</a></li>' in result
# indexed cell without page
cell_type = ContentType.objects.get_for_model(cell)
IndexedCell.objects.create(
title='other', cell_type=cell_type, cell_pk=cell.pk, public_access=True, url='fake'
)
# enable description
cell._search_services['options'] = {'_text': {'with_description': True}}
cell.save()
resp = app.get('/ajax/search/%s/_text/?q=other' % cell.id, status=200)
assert resp.text.count('<li') == 2
result = resp.text
result = result.replace(' ', '')
result = result.replace('\n', '')
assert '<li><a href="fake">other</a></li>' in result
assert '<li><a href="/second-page/">second page</a><div>Foo Bar Description</div></li>' in result
resp = app.get('/ajax/search/%s/_text/?q=baz' % cell.id, status=200)
assert resp.text.count('<li') == 2
resp = app.get('/ajax/search/%s/_text/?q=quux' % cell.id, status=200)
assert resp.text.count('<li') == 0
def test_search_on_root_page_api(settings, app):
settings.KNOWN_SERVICES = {}
# not indexed: with sub_slug
page = Page.objects.create(title='example page', slug='example-page', sub_slug='foo')
TextCell.objects.create(page=page, placeholder='content', text='<p>foobar baz</p>', order=0)
second_page = Page.objects.create(title='second page', slug='second-page')
TextCell.objects.create(page=second_page, placeholder='content', text='<p>other baz</p>', order=0)
sub_second_page = Page.objects.create(parent=second_page, title='sub second page', slug='sub-second-page')
TextCell.objects.create(page=sub_second_page, placeholder='content', text='<p>other baz</p>', order=0)
# not indexed: with snapshot
third_page = Page.objects.create(title='second page', slug='third-page')
TextCell.objects.create(page=third_page, placeholder='content', text='<p>other baz again</p>', order=0)
third_page.snapshot = PageSnapshot.objects.create(page=third_page)
third_page.save()
index_site()
cell = SearchCell.objects.create(
page=page, placeholder='content', _search_services={'data': ['_text']}, order=1
)
resp = app.get('/ajax/search/%s/_text/?q=baz' % cell.pk, status=200)
assert resp.text.count('<li') == 2
cell._search_services = {'data': ['_text_page_second-page']}
cell.save()
resp = app.get('/ajax/search/%s/_text_page_second-page/?q=baz' % cell.pk, status=200)
assert resp.text.count('<li') == 2
cell._search_services = {'data': ['_text_page_sub-second-page']}
cell.save()
resp = app.get('/ajax/search/%s/_text_page_sub-second-page/?q=baz' % cell.pk, status=200)
assert resp.text.count('<li') == 1
# invalid page, search everywhere
# with sub_slug
cell._search_services = {'data': ['_text_page_example-page']}
cell.save()
resp = app.get('/ajax/search/%s/_text_page_example-page/?q=baz' % cell.pk, status=200)
assert resp.text.count('<li') == 2
# with snapshot
cell._search_services = {'data': ['_text_page_third-page']}
cell.save()
resp = app.get('/ajax/search/%s/_text_page_third-page/?q=baz' % cell.pk, status=200)
assert resp.text.count('<li') == 2
# page does not exists, search everywhere
cell._search_services = {'data': ['_text_page_foo']}
cell.save()
resp = app.get('/ajax/search/%s/_text_page_foo/?q=baz' % cell.pk, status=200)
assert resp.text.count('<li') == 2
# slug is not unique, search everywhere
page.slug = 'sub-second-page'
page.sub_slug = ''
page.save()
cell._search_services = {'data': ['_text_page_sub-second-page']}
cell.save()
resp = app.get('/ajax/search/%s/_text_page_sub-second-page/?q=baz' % cell.pk, status=200)
assert resp.text.count('<li') == 2
# search with custom title
cell._search_services = {
'data': ['_text', '_text_page_sub-second-page'],
'options': {'_text_page_sub-second-page': {'title': 'Custom Title'}},
}
cell.save()
resp = app.get('/ajax/search/%s/_text_page_sub-second-page/?q=baz' % cell.pk, status=200)
assert 'Custom Title' in resp.text
assert resp.text.count('<li') == 2
def test_search_external_links(app):
page = Page(title='example page', slug='example-page')
page.save()
cell = SearchCell(page=page, placeholder='content', _search_services={'data': ['_text']}, order=0)
cell.save()
index_site()
request = RequestFactory().get('/')
request.user = AnonymousUser()
hits = search_site(request, 'foobar')
assert len(hits) == 0
LinkCell(title='foobar', url='http://example.net', page=page, placeholder='content', order=0).save()
index_site()
hits = search_site(request, 'foobar')
assert len(hits) == 1
assert hits[0]['text'] == 'foobar'
assert hits[0]['url'] == 'http://example.net'
# second link with same target
LinkCell(title='baz', url='http://example.net', page=page, placeholder='content', order=0).save()
index_site()
# add a second link with the same target
hits = search_site(request, 'baz')
assert len(hits) == 1
assert hits[0]['text'] in ('foobar', 'baz')
assert hits[0]['url'] == 'http://example.net'
hits = search_site(request, 'foobar')
assert len(hits) == 1
assert hits[0]['text'] in ('foobar', 'baz')
assert hits[0]['url'] == 'http://example.net'
def test_manager_search_cell(settings, app, admin_user):
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = SearchCell.objects.create(page=page, placeholder='content', order=0)
app = login(app)
settings.KNOWN_SERVICES = {}
assert cell._search_services == {}
resp = app.get('/manage/pages/%s/' % page.pk)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text/add/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text/delete/' % (page.pk, cell.pk)
not in resp.text
)
settings.COMBO_SEARCH_SERVICES = SEARCH_SERVICES
resp = app.get('/manage/pages/%s/' % page.pk)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text/add/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search1/add/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_tmpl/add/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_alternate_key/add/'
% (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text/delete/' % (page.pk, cell.pk)
not in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search1/delete/' % (page.pk, cell.pk)
not in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_tmpl/delete/' % (page.pk, cell.pk)
not in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_alternate_key/delete/'
% (page.pk, cell.pk)
not in resp.text
)
# add engines
resp = resp.click(href='.*/search_searchcell-%s/engine/_text/add/' % cell.pk)
resp = resp.form.submit('submit')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click(href='.*/search_searchcell-%s/engine/search1/add/' % cell.pk)
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click(href='.*/search_searchcell-%s/engine/search_tmpl/add/' % cell.pk)
cell.refresh_from_db()
assert cell._search_services['data'] == ['_text', 'search1', 'search_tmpl']
resp = app.get('/manage/pages/%s/' % page.pk)
# '_text' is always available
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text/add/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search1/add/' % (page.pk, cell.pk)
not in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_tmpl/add/' % (page.pk, cell.pk)
not in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_alternate_key/add/'
% (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text/delete/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search1/delete/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_tmpl/delete/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_alternate_key/delete/'
% (page.pk, cell.pk)
not in resp.text
)
# delete engines
resp = resp.click(href='.*/search_searchcell-%s/engine/_text/delete/' % cell.pk)
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
cell.refresh_from_db()
assert cell._search_services['data'] == ['search1', 'search_tmpl']
settings.COMBO_SEARCH_SERVICES = {}
# check there's no crash if search engines are removed from config
resp = app.get('/manage/pages/%s/' % page.pk)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search1/add/' % (page.pk, cell.pk)
not in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_tmpl/add/' % (page.pk, cell.pk)
not in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_alternate_key/add/'
% (page.pk, cell.pk)
not in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search1/delete/' % (page.pk, cell.pk)
not in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_tmpl/delete/' % (page.pk, cell.pk)
not in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/search_alternate_key/delete/'
% (page.pk, cell.pk)
not in resp.text
)
# add engines on page and sub pages
resp = resp.click(href='.*/search_searchcell-%s/engine/_text/add/' % cell.pk)
assert list(resp.context['form']['selected_page'].field.queryset) == [page]
resp = resp.form.submit('submit')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
cell.refresh_from_db()
assert cell._search_services['data'] == ['search1', 'search_tmpl', '_text']
resp = app.get('/manage/pages/%s/' % page.pk)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text/add/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text/delete/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text_page_one/delete/' % (page.pk, cell.pk)
not in resp.text
)
resp = resp.click(href='.*/search_searchcell-%s/engine/_text/add/' % cell.pk)
resp.form['selected_page'] = page.pk
resp = resp.form.submit('submit')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
cell.refresh_from_db()
assert cell._search_services['data'] == ['search1', 'search_tmpl', '_text', '_text_page_one']
resp = app.get('/manage/pages/%s/' % page.pk)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text/add/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text/delete/' % (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/_text_page_one/delete/' % (page.pk, cell.pk)
in resp.text
)
# remove engine
resp = resp.click(href='.*/search_searchcell-%s/engine/_text_page_one/delete/' % cell.pk)
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
cell.refresh_from_db()
# add engine on page and sub pages, with a custom title and description
resp = app.get('/manage/pages/%s/' % page.pk)
resp = resp.click(href='.*/search_searchcell-%s/engine/_text/add/' % cell.pk)
resp.form['selected_page'] = page.pk
resp.form['title'] = 'Custom Title'
resp.form['with_description'] = True
resp = resp.form.submit('submit')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
cell.refresh_from_db()
assert cell._search_services['data'] == ['search1', 'search_tmpl', '_text', '_text_page_one']
assert cell._search_services['options']['_text_page_one'] == {
'title': 'Custom Title',
'with_description': True,
}
def test_manager_search_cell_order(settings, app, admin_user):
settings.COMBO_SEARCH_SERVICES = SEARCH_SERVICES
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = SearchCell.objects.create(
page=page,
placeholder='content',
order=0,
_search_services={'data': ['_text', 'search1', 'search_tmpl']},
)
params = []
new_order = [2, 3, 1]
for i, (service_slug, new_pos) in enumerate(zip(cell._search_services['data'], new_order)):
params.append(('pos_%s' % service_slug, str(new_pos)))
app = login(app)
resp = app.get(
'/manage/search/pages/%s/cell/%s/engine/order?%s' % (page.pk, cell.get_reference(), urlencode(params))
)
assert resp.status_code == 204
cell.refresh_from_db()
assert cell._search_services == {'data': ['search_tmpl', '_text', 'search1']}
def test_manager_waiting_index_message(app, admin_user):
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = SearchCell.objects.create(page=page, placeholder='content', order=0)
TextCell.objects.create(page=page, placeholder='content', text='<p>foobar</p>', order=0)
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
assert 'Content indexing has been scheduled' not in resp.text
resp = resp.click(href='.*/search_searchcell-%s/engine/_text/add/' % cell.pk)
resp = resp.form.submit('submit')
resp = app.get('/manage/pages/%s/' % page.pk)
assert 'Content indexing has been scheduled' in resp.text
index_site()
resp = app.get('/manage/pages/%s/' % page.pk)
assert 'Content indexing has been scheduled' not in resp.text
def test_wcs_search_engines(settings, app):
settings.KNOWN_SERVICES = {}
search_engines = engines.get_engines()
assert 'tracking-code' not in search_engines.keys()
assert len([x for x in search_engines.keys() if x.startswith('formdata:')]) == 0
assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 0
settings.KNOWN_SERVICES = {'wcs': {'default': {'title': 'test', 'url': 'http://127.0.0.1:8999/'}}}
settings.TEMPLATE_VARS['is_portal_agent'] = False
search_engines = engines.get_engines()
assert len([x for x in search_engines.keys() if x.startswith('formdata:')]) == 0
settings.TEMPLATE_VARS['is_portal_agent'] = True
search_engines = engines.get_engines()
assert len([x for x in search_engines.keys() if x.startswith('formdata:')]) == 1
for key, engine in search_engines.items():
if key.startswith('formdata:'):
assert '&include-anonymised=off' in engine['url']
# create a page with sub_slug to enable card engines
Page.objects.create(slug='foo', title='Foo', sub_slug='(?P<foo_id>[a-z0-9]+)')
with mock.patch('combo.apps.wcs.utils.get_wcs_json') as mock_wcs:
# no card model found
mock_wcs.return_value = {}
search_engines = engines.get_engines()
assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 0
assert mock_wcs.call_args_list[0][0][1] == 'api/cards/@list'
mock_wcs.return_value = {'data': []}
search_engines = engines.get_engines()
assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 0
# card model found, but related page does not exist
mock_wcs.return_value = {'data': [{'id': 'card-bar', 'text': 'Card Bar'}]}
search_engines = engines.get_engines()
assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 0
# related page exists
page = Page.objects.create(slug='bar', title='Bar')
for sub_slug in ['(?P<card-bar_id>[a-z0-9]+)', 'card-bar_id']:
page.sub_slug = sub_slug
page.save()
search_engines = engines.get_engines()
assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 1
assert 'cards:c21f969b:card-bar' in search_engines.keys()
card_engine = search_engines['cards:c21f969b:card-bar']
assert card_engine['url'] == (
'http://127.0.0.1:8999/api/cards/card-bar/list/'
'{% if search_service.selected_custom_view %}{{ search_service.selected_custom_view }}{% endif %}'
'?{% if not search_service.without_user %}NameID={{ user_nameid }}&{% endif %}q=%(q)s'
)
assert card_engine['hit_url_template'] == '/bar/{{ id }}'
def test_wcs_errors(settings, app):
settings.KNOWN_SERVICES = {'wcs': {'default': {'title': 'test', 'url': 'http://127.0.0.1:8999/'}}}
settings.TEMPLATE_VARS['is_portal_agent'] = True
Page.objects.create(slug='foo', title='Foo', sub_slug='(?P<foo_id>[a-z0-9]+)')
with mock.patch('combo.apps.wcs.utils.requests.get') as requests_get:
mock_resp = Response()
mock_resp.status_code = 500
requests_get.return_value = mock_resp
assert set(engines.get_engines().keys()) == set(['tracking-code', 'formdata:c21f969b', '_text'])
with mock.patch('combo.apps.wcs.utils.requests.get') as requests_get:
requests_get.side_effect = ConnectionError()
assert set(engines.get_engines().keys()) == set(['tracking-code', 'formdata:c21f969b', '_text'])
with mock.patch('combo.apps.wcs.utils.requests.get') as requests_get:
mock_resp = Response()
mock_resp.status_code = 404
requests_get.return_value = mock_resp
assert set(engines.get_engines().keys()) == set(['tracking-code', 'formdata:c21f969b', '_text'])
@mock.patch('combo.apps.wcs.utils.get_wcs_json')
def test_wcs_add_search_engines(mock_wcs, settings, app, admin_user):
settings.KNOWN_SERVICES = {'wcs': {'default': {'title': 'test', 'url': 'http://127.0.0.1:8999/'}}}
settings.TEMPLATE_VARS['is_portal_agent'] = True
Page.objects.create(slug='bar', title='Bar', sub_slug='card-bar_id')
mock_wcs.return_value = {'data': [{'id': 'card-bar', 'text': 'Card Bar'}]}
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = SearchCell.objects.create(page=page, placeholder='content', order=0)
app = login(app)
resp = app.get('/manage/pages/%s/' % page.pk)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/cards:c21f969b:card-bar/add/'
% (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/cards:c21f969b:card-bar/delete/'
% (page.pk, cell.pk)
not in resp.text
)
resp = resp.click(href='.*/search_searchcell-%s/engine/cards:c21f969b:card-bar/add/' % cell.pk)
assert resp.context['form'].fields['selected_view'].choices == [(None, '- All cards -')]
resp.form['title'] = 'Custom Title'
resp = resp.form.submit('submit')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
cell.refresh_from_db()
assert cell._search_services['data'] == ['cards:c21f969b:card-bar']
assert cell._search_services['options']['cards:c21f969b:card-bar'] == {'title': 'Custom Title'}
mock_wcs.return_value = {
'data': [
{
'id': 'card-bar',
'text': 'Card Bar',
'custom_views': [{'id': 'foo', 'text': 'Foo'}, {'id': 'baz', 'text': 'Baz'}],
}
]
}
resp = resp.follow()
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/cards:c21f969b:card-bar/add/'
% (page.pk, cell.pk)
in resp.text
)
assert (
'/manage/search/pages/%s/cell/search_searchcell-%s/engine/cards:c21f969b:card-bar/delete/'
% (page.pk, cell.pk)
in resp.text
)
resp = resp.click(href='.*/search_searchcell-%s/engine/cards:c21f969b:card-bar/add/' % cell.pk)
assert resp.context['form'].fields['selected_view'].choices == [
(None, '- All cards -'),
('foo', 'Foo'),
('baz', 'Baz'),
]
resp.form['selected_view'] = 'foo'
resp = resp.form.submit('submit')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
cell.refresh_from_db()
assert cell._search_services['data'] == ['cards:c21f969b:card-bar', 'cards:c21f969b:card-bar:foo']
# remove engine
resp = resp.follow()
resp = resp.click(href='.*/search_searchcell-%s/engine/cards:c21f969b:card-bar:foo/delete/' % cell.pk)
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
cell.refresh_from_db()
assert cell._search_services['data'] == ['cards:c21f969b:card-bar']
# without_user ?
resp = resp.follow()
resp = resp.click(href='.*/search_searchcell-%s/engine/cards:c21f969b:card-bar/add/' % cell.pk)
resp.form['without_user'] = True
resp = resp.form.submit('submit')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
cell.refresh_from_db()
assert cell._search_services['data'] == [
'cards:c21f969b:card-bar',
'cards:c21f969b:card-bar__without-user__',
]
resp = resp.follow()
resp = resp.click(href='.*/search_searchcell-%s/engine/cards:c21f969b:card-bar/add/' % cell.pk)
resp.form['selected_view'] = 'foo'
resp.form['without_user'] = True
resp = resp.form.submit('submit')
assert resp.status_int == 302
assert resp.location.endswith('/manage/pages/%s/#cell-%s' % (page.pk, cell.get_reference()))
cell.refresh_from_db()
assert cell._search_services['data'] == [
'cards:c21f969b:card-bar',
'cards:c21f969b:card-bar__without-user__',
'cards:c21f969b:card-bar__without-user__:foo',
]
@mock.patch('combo.apps.wcs.utils.get_wcs_json')
@mock.patch('combo.apps.search.models.requests.get')
def test_wcs_search_cell(requests_get, mock_wcs, settings, app):
settings.KNOWN_SERVICES = {'wcs': {'default': {'title': 'test', 'url': 'http://127.0.0.1:8999/'}}}
settings.TEMPLATE_VARS['is_portal_agent'] = True
Page.objects.create(slug='bar', title='Bar', sub_slug='(?P<card-bar_id>[a-z0-9]+)')
mock_wcs.return_value = {
'data': [{'id': 'card-bar', 'text': 'Card Bar', 'custom_views': [{'id': 'foo', 'text': 'Foo'}]}]
}
response = {'err': 0, 'data': []}
mock_json = mock.Mock()
mock_json.json.return_value = response
requests_get.return_value = mock_json
page = Page.objects.create(title='One', slug='one', template_name='standard')
cell = SearchCell.objects.create(page=page, placeholder='content', order=0)
cell._search_services = {
'data': [
'cards:c21f969b:card-bar',
'cards:c21f969b:card-bar:foo',
'cards:c21f969b:card-bar__without-user__',
'cards:c21f969b:card-bar__without-user__:foo',
]
}
cell.save()
assert cell.search_services[0]['custom_views'] == [{'id': 'foo', 'text': 'Foo'}]
assert cell.search_services[0]['label'] == 'Card Bar'
assert cell.search_services[0]['slug'] == 'cards:c21f969b:card-bar'
assert 'selected_custom_view' not in cell.search_services[0]
assert cell.search_services[1]['custom_views'] == [{'id': 'foo', 'text': 'Foo'}]
assert cell.search_services[1]['label'] == 'Card Bar - Foo'
assert cell.search_services[1]['slug'] == 'cards:c21f969b:card-bar:foo'
assert cell.search_services[1]['selected_custom_view'] == 'foo'
app.get('/ajax/search/%s/cards:c21f969b:card-bar/?q=foobar' % cell.pk)
assert (
requests_get.call_args_list[0][0][0]
== 'http://127.0.0.1:8999/api/cards/card-bar/list/?NameID=&q=foobar'
)
requests_get.reset_mock()
app.get('/ajax/search/%s/cards:c21f969b:card-bar:foo/?q=foobar' % cell.pk)
assert (
requests_get.call_args_list[0][0][0]
== 'http://127.0.0.1:8999/api/cards/card-bar/list/foo?NameID=&q=foobar'
)
requests_get.reset_mock()
app.get('/ajax/search/%s/cards:c21f969b:card-bar__without-user__/?q=foobar' % cell.pk)
assert requests_get.call_args_list[0][0][0] == 'http://127.0.0.1:8999/api/cards/card-bar/list/?q=foobar'
requests_get.reset_mock()
app.get('/ajax/search/%s/cards:c21f969b:card-bar__without-user__:foo/?q=foobar' % cell.pk)
assert (
requests_get.call_args_list[0][0][0] == 'http://127.0.0.1:8999/api/cards/card-bar/list/foo?q=foobar'
)
# test with unknown view selected
mock_wcs.return_value = {'data': [{'id': 'card-bar', 'text': 'Card Bar', 'custom_views': []}]}
del cell.search_services # clear cache
assert cell.search_services[0]['custom_views'] == []
assert cell.search_services[0]['label'] == 'Card Bar'
assert cell.search_services[0]['slug'] == 'cards:c21f969b:card-bar'
assert 'selected_custom_view' not in cell.search_services[0]
assert cell.search_services[1]['custom_views'] == []
assert cell.search_services[1]['label'] == 'Card Bar - None' # unknown view
assert cell.search_services[1]['slug'] == 'cards:c21f969b:card-bar:foo'
assert cell.search_services[1]['selected_custom_view'] == 'foo'
requests_get.reset_mock()
app.get('/ajax/search/%s/cards:c21f969b:card-bar:foo/?q=foobar' % cell.pk)
assert (
requests_get.call_args_list[0][0][0]
== 'http://127.0.0.1:8999/api/cards/card-bar/list/foo?NameID=&q=foobar'
)
# test with unknown card model selected
mock_wcs.return_value = {'data': []}
del cell.search_services # clear cache
assert cell.search_services == []
requests_get.reset_mock()
app.get('/ajax/search/%s/cards:c21f969b:card-bar/?q=foobar' % cell.pk)
assert requests_get.call_args_list == []
def test_profile_search_engines(settings, app):
settings.KNOWN_SERVICES = {}
search_engines = engines.get_engines()
assert 'users' not in search_engines.keys()
settings.KNOWN_SERVICES = {'authentic': {'default': {'title': 'authentic', 'url': 'https://authentic/'}}}
search_engines = engines.get_engines()
assert 'users' not in search_engines.keys()
page = Page.objects.create(slug='users', title='Users', sub_slug='(?P<name_id>[a-z0-9]+)')
search_engines = engines.get_engines()
assert 'users' in search_engines.keys()
page.sub_slug = 'name_id'
page.save()
search_engines = engines.get_engines()
assert 'users' in search_engines.keys()
def test_private_search(app):
page = Page(title='example page', slug='example-page')
page.save()
TextCell(page=page, placeholder='content', text='<p>foobar</p>', order=0, public=False).save()
TextCell(page=page, placeholder='content', text='<p>barfoo</p>', order=0, public=True).save()
request = RequestFactory().get('/')
request.user = AnonymousUser()
index_site()
hits = search_site(request, 'foobar')
assert len(hits) == 0
hits = search_site(request, 'barfoo')
assert len(hits) == 1
request.user = User.objects.create_user(username='normal-user')
hits = search_site(request, 'foobar')
assert len(hits) == 1
hits = search_site(request, 'barfoo')
assert len(hits) == 1
def test_restricted_search(app):
group = Group(name='plop')
group.save()
page = Page(title='example page', slug='example-page')
page.save()
cell = TextCell(page=page, placeholder='content', text='<p>foobar</p>', order=0, public=False)
cell.save()
cell.groups.set([group])
TextCell(page=page, placeholder='content', text='<p>barfoo</p>', order=0, public=False).save()
index_site()
# first cell is restricted, it's not found
request = RequestFactory().get('/')
request.user = User.objects.create_user(username='normal-user')
hits = search_site(request, 'foobar')
assert len(hits) == 0
hits = search_site(request, 'barfoo')
assert len(hits) == 1
page.groups.set([group])
index_site()
# page is restricted, no cell is found
hits = search_site(request, 'foobar')
assert len(hits) == 0
hits = search_site(request, 'barfoo')
assert len(hits) == 0
# user is in group, gets a result
request.user.groups.set([group])
hits = search_site(request, 'foobar')
assert len(hits) == 1
hits = search_site(request, 'barfoo')
assert len(hits) == 1
# cell is excluded from group view
cell.restricted_to_unlogged = True
cell.save()
index_site()
hits = search_site(request, 'foobar')
assert len(hits) == 0
hits = search_site(request, 'barfoo')
assert len(hits) == 1
def test_no_sub_slug_search(app):
page = Page(title='example page', slug='example-page')
page.save()
TextCell(page=page, placeholder='content', text='<p>foobar</p>', order=0, public=True).save()
page = Page(title='example page with sub_slug', slug='sub-slugged-page', sub_slug='(?P<foo>\d+)')
page.save()
TextCell(page=page, placeholder='content', text='<p>barfoo</p>', order=0, public=True).save()
request = RequestFactory().get('/')
request.user = AnonymousUser()
index_site()
hits = search_site(request, 'foobar')
assert len(hits) == 1
hits = search_site(request, 'barfoo')
assert len(hits) == 0
def test_index_site_inactive_placeholder(app):
page = Page.objects.create(title='page', slug='example-page')
cell = TextCell.objects.create(page=page, placeholder='content', text='<p>foobar</p>', order=0)
assert cell.is_placeholder_active() is True
index_site()
assert IndexedCell.objects.count() == 1
cell.placeholder = ''
cell.save()
assert cell.is_placeholder_active() is False
index_site()
assert IndexedCell.objects.count() == 0
def test_index_site_num_queries(settings, app):
group = Group.objects.create(name='plop')
for i in range(0, 10):
page = Page.objects.create(title='page %s' % i, slug='example-page-%s' % i)
page.groups.set([group])
for j in range(0, 5):
cell = TextCell.objects.create(
page=page, placeholder='content', text='<p>foobar %s</p>' % j, order=j, public=False
)
cell.groups.set([group])
index_site() # populate cache
assert IndexedCell.objects.count() == 50
with CaptureQueriesContext(connection) as ctx:
index_site()
assert len(ctx.captured_queries) == 224
SearchCell.objects.create(
page=page, placeholder='content', order=0, _search_services={'data': ['search1']}
)
# search cells are not indexed
index_site()
assert IndexedCell.objects.count() == 50
@mock.patch('combo.apps.search.engines.get_engines')
def test_index_site_search_engines_load(get_engines_mock, settings, app):
# be sure that get_engines is not called during page indexation
get_engines_mock.side_effect = Exception
settings.COMBO_SEARCH_SERVICES = {}
page = Page.objects.create(title='page', slug='example-page')
SearchCell.objects.create(
page=page, placeholder='content', order=0, _search_services={'data': ['search1']}
)
TextCell.objects.create(page=page, placeholder='content', text='<p>foobar</p>', order=0)
# no exception raised
index_site()
@pytest.mark.skipif(connection.vendor != 'postgresql', reason='only postgresql is supported')
def test_search_by_page_title(app):
query = 'nanoparticle electrochemistry'
# First page containing a cell whose text matches the query
page = Page(title='example page', slug='example-page')
page.save()
TextCell(
page=page,
placeholder='content',
text='<p>Some nanoparticle electrochemistry content here</p>',
order=0,
public=True,
).save()
# Second page whose title matches the search query
page_of_interest = Page(title='Nanoparticle electrochemistry', slug='page-of-interest')
page_of_interest.save()
TextCell(
page=page_of_interest,
placeholder='content',
text='<p>Some random text here</p>',
order=0,
public=True,
).save()
request = RequestFactory().get('/')
request.user = AnonymousUser()
index_site()
hits = search_site(request, query)
# Check that title matching gets precedence over content matching
assert len(hits) == 2
assert hits[0]['text'] == page_of_interest.title
assert hits[0]['url'] == '/{}/'.format(page_of_interest.slug)
assert hits[0]['rank'] > hits[1]['rank']