From 7981890eb5d4b07d6850a0c093d8386d4398cea9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Laur=C3=A9line=20Gu=C3=A9rin?= <zebuline@entrouvert.com>
Date: Tue, 5 May 2020 11:08:57 +0200
Subject: [PATCH] search: add card search engins (#41845)

---
 combo/apps/wcs/__init__.py | 44 +++++++++++++++++++++---
 tests/test_search.py       | 70 ++++++++++++++++++++++++++++++--------
 2 files changed, 94 insertions(+), 20 deletions(-)

diff --git a/combo/apps/wcs/__init__.py b/combo/apps/wcs/__init__.py
index 26e95f8..83e0542 100644
--- a/combo/apps/wcs/__init__.py
+++ b/combo/apps/wcs/__init__.py
@@ -33,12 +33,46 @@ class AppConfig(django.apps.AppConfig):
 
     def get_search_engines(self):
         from .utils import get_wcs_services
-        services = get_wcs_services()
-        if not services:
+        wcs_services = get_wcs_services()
+        if not wcs_services:
             return
 
+        engines = self.get_portal_agent_search_engines(wcs_services)
+        engines.update(self.get_card_search_engines(wcs_services))
+
+        return engines
+
+    def get_card_search_engines(self, wcs_services):
+        from combo.data.models import Page
+        from .utils import get_wcs_json
+        pages_with_sub_slug = Page.objects.exclude(sub_slug='')
+        if not pages_with_sub_slug:
+            return {}
+
+        engines = {}
+        for key, service in wcs_services.items():
+            card_models = get_wcs_json(service, 'api/cards/@list')
+            for card in card_models.get('data', []):
+                matching_pages = ([p for p in pages_with_sub_slug if '<%s_id>' % card['id'] in p.sub_slug])
+                if not matching_pages:
+                    continue
+                card_page = matching_pages[0]
+                card_page_base_url = card_page.get_online_url()
+                label = card['text']
+                if len(wcs_services.keys()) > 1:
+                    label = '%s (%s)' % (label, service['title'])
+                engines['cards:%s:%s' % (hashlib.md5(force_bytes(key)).hexdigest()[:8], card['id'])] = {
+                    'url': service['url'] + 'api/cards/' + card['id'] + '/list?NameID={{ user_nameid }}&q=%(q)s',
+                    'label': label,
+                    'signature': True,
+                    'hit_url_template': card_page_base_url + '{{ id }}',
+                    'hit_label_template': '{% firstof digest text %}',
+                }
+        return engines
+
+    def get_portal_agent_search_engines(self, wcs_services):
         if not settings.TEMPLATE_VARS.get('is_portal_agent'):
-            return
+            return {}
 
         engines = {
             'tracking-code': {
@@ -46,9 +80,9 @@ class AppConfig(django.apps.AppConfig):
                 'label': _('Tracking Code'),
             }
         }
-        for key, service in services.items():
+        for key, service in wcs_services.items():
             label = pgettext_lazy('user-forms', 'Forms')
-            if len(services.keys()) > 1:
+            if len(wcs_services.keys()) > 1:
                 label = pgettext_lazy('user-forms', 'Forms (%s)') % service['title']
             engines['formdata:%s' % hashlib.md5(force_bytes(key)).hexdigest()[:8]] = {
                 'url': service['url'] + 'api/forms/?NameID={{user_nameid}}&status=all&ignore-roles=on&include-anonymised=off&q=%(q)s',
diff --git a/tests/test_search.py b/tests/test_search.py
index 21175b2..4a8ef12 100644
--- a/tests/test_search.py
+++ b/tests/test_search.py
@@ -361,7 +361,9 @@ def test_search_api(app):
     assert resp.text.count('<li') == 0
 
 
-def test_search_on_root_page_api(app):
+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)
@@ -574,28 +576,66 @@ def test_manager_waiting_index_message(app, admin_user):
     assert 'Content indexing has been scheduled' not in resp.text
 
 
-def test_wcs_search_engines(app):
-    with override_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
+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/'}}}
 
-    for key, engine in engines.get_engines().items():
+    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
 
-def test_profile_search_engines(app):
-    search_engines = engines.get_engines()
-    assert 'users' not in search_engines.keys()
-    with override_settings(KNOWN_SERVICES={'authentic': {'default': {'title': 'authentic', 'url': 'https://authentic/'}}}):
+        # card model found, but related page does not exist
+        mock_wcs.return_value = {'data': [{'id': 'bar', 'text': 'Bar'}]}
         search_engines = engines.get_engines()
-        assert 'users' not in search_engines.keys()
+        assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 0
 
-        page = Page(slug='users', title='Users', sub_slug='(?P<name_id>[a-z0-9]+)')
-        page.save()
+        # related page exists
+        Page.objects.create(slug='bar', title='Bar', sub_slug='(?P<bar_id>[a-z0-9]+')
         search_engines = engines.get_engines()
-        assert 'users' in search_engines.keys()
+        assert len([x for x in search_engines.keys() if x.startswith('cards:')]) == 1
+        assert 'cards:c21f969b:bar' in search_engines.keys()
+        card_engine = search_engines['cards:c21f969b:bar']
+        assert card_engine['url'] == 'http://127.0.0.1:8999/api/cards/bar/list?NameID={{ user_nameid }}&q=%(q)s'
+        assert card_engine['hit_url_template'] == '/bar/{{ id }}'
+
+
+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(slug='users', title='Users', sub_slug='(?P<name_id>[a-z0-9]+)')
+    page.save()
+    search_engines = engines.get_engines()
+    assert 'users' in search_engines.keys()
 
 
 def test_private_search(app):
-- 
2.20.1

