cubicweb/web/test/unittest_magicsearch.py
changeset 11057 0b59724cb3f2
parent 10657 92c96bf05528
child 11240 1694e6e9ff94
equal deleted inserted replaced
11052:058bb3dc685f 11057:0b59724cb3f2
       
     1 # -*- coding: utf-8 -*-
       
     2 # copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
       
     3 # contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
       
     4 #
       
     5 # This file is part of CubicWeb.
       
     6 #
       
     7 # CubicWeb is free software: you can redistribute it and/or modify it under the
       
     8 # terms of the GNU Lesser General Public License as published by the Free
       
     9 # Software Foundation, either version 2.1 of the License, or (at your option)
       
    10 # any later version.
       
    11 #
       
    12 # CubicWeb is distributed in the hope that it will be useful, but WITHOUT
       
    13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
       
    14 # FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
       
    15 # details.
       
    16 #
       
    17 # You should have received a copy of the GNU Lesser General Public License along
       
    18 # with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
       
    19 """Unit tests for cw.web.views.magicsearch"""
       
    20 
       
    21 import sys
       
    22 from contextlib import contextmanager
       
    23 
       
    24 from six.moves import range
       
    25 
       
    26 from logilab.common.testlib import TestCase, unittest_main
       
    27 
       
    28 from rql import BadRQLQuery, RQLSyntaxError
       
    29 
       
    30 from cubicweb.devtools.testlib import CubicWebTC
       
    31 
       
    32 
       
    33 translations = {
       
    34     u'CWUser' : u"Utilisateur",
       
    35     u'EmailAddress' : u"Adresse",
       
    36     u'name' : u"nom",
       
    37     u'alias' : u"nom",
       
    38     u'surname' : u"nom",
       
    39     u'firstname' : u"prénom",
       
    40     u'state' : u"état",
       
    41     u'address' : u"adresse",
       
    42     u'use_email' : u"adel",
       
    43     }
       
    44 
       
    45 def _translate(msgid):
       
    46     return translations.get(msgid, msgid)
       
    47 
       
    48 def _ctxtranslate(ctx, msgid):
       
    49     return _translate(msgid)
       
    50 
       
    51 from cubicweb.web.views.magicsearch import translate_rql_tree, QSPreProcessor, QueryTranslator
       
    52 
       
    53 class QueryTranslatorTC(CubicWebTC):
       
    54     """test suite for QueryTranslatorTC"""
       
    55 
       
    56     @contextmanager
       
    57     def proc(self):
       
    58         with self.admin_access.web_request() as req:
       
    59             self.vreg.config.translations = {'en': (_translate, _ctxtranslate)}
       
    60             proc = self.vreg['components'].select('magicsearch', req)
       
    61             proc = [p for p in proc.processors if isinstance(p, QueryTranslator)][0]
       
    62             yield proc
       
    63 
       
    64     def test_basic_translations(self):
       
    65         """tests basic translations (no ambiguities)"""
       
    66         with self.proc() as proc:
       
    67             rql = u"Any C WHERE C is Adresse, P adel C, C adresse 'Logilab'"
       
    68             rql, = proc.preprocess_query(rql)
       
    69             self.assertEqual(rql, 'Any C WHERE C is EmailAddress, P use_email C, C address "Logilab"')
       
    70 
       
    71     def test_ambiguous_translations(self):
       
    72         """tests possibly ambiguous translations"""
       
    73         with self.proc() as proc:
       
    74             rql = u"Any P WHERE P adel C, C is EmailAddress, C nom 'Logilab'"
       
    75             rql, = proc.preprocess_query(rql)
       
    76             self.assertEqual(rql, 'Any P WHERE P use_email C, C is EmailAddress, C alias "Logilab"')
       
    77             rql = u"Any P WHERE P is Utilisateur, P adel C, P nom 'Smith'"
       
    78             rql, = proc.preprocess_query(rql)
       
    79             self.assertEqual(rql, 'Any P WHERE P is CWUser, P use_email C, P surname "Smith"')
       
    80 
       
    81 
       
    82 class QSPreProcessorTC(CubicWebTC):
       
    83     """test suite for QSPreProcessor"""
       
    84 
       
    85     @contextmanager
       
    86     def proc(self):
       
    87         self.vreg.config.translations = {'en': (_translate, _ctxtranslate)}
       
    88         with self.admin_access.web_request() as req:
       
    89             proc = self.vreg['components'].select('magicsearch', req)
       
    90             proc = [p for p in proc.processors if isinstance(p, QSPreProcessor)][0]
       
    91             proc._cw = req
       
    92             yield proc
       
    93 
       
    94     def test_entity_translation(self):
       
    95         """tests QSPreProcessor._get_entity_name()"""
       
    96         with self.proc() as proc:
       
    97             translate = proc._get_entity_type
       
    98             self.assertEqual(translate(u'EmailAddress'), "EmailAddress")
       
    99             self.assertEqual(translate(u'emailaddress'), "EmailAddress")
       
   100             self.assertEqual(translate(u'Adresse'), "EmailAddress")
       
   101             self.assertEqual(translate(u'adresse'), "EmailAddress")
       
   102             self.assertRaises(BadRQLQuery, translate, 'whatever')
       
   103 
       
   104     def test_attribute_translation(self):
       
   105         """tests QSPreProcessor._get_attribute_name"""
       
   106         with self.proc() as proc:
       
   107             translate = proc._get_attribute_name
       
   108             eschema = self.schema.eschema('CWUser')
       
   109             self.assertEqual(translate(u'prénom', eschema), "firstname")
       
   110             self.assertEqual(translate(u'nom', eschema), 'surname')
       
   111             eschema = self.schema.eschema('EmailAddress')
       
   112             self.assertEqual(translate(u'adresse', eschema), "address")
       
   113             self.assertEqual(translate(u'nom', eschema), 'alias')
       
   114             # should fail if the name is not an attribute for the given entity schema
       
   115             self.assertRaises(BadRQLQuery, translate, 'whatever', eschema)
       
   116             self.assertRaises(BadRQLQuery, translate, 'prénom', eschema)
       
   117 
       
   118     def test_one_word_query(self):
       
   119         """tests the 'one word shortcut queries'"""
       
   120         with self.proc() as proc:
       
   121             transform = proc._one_word_query
       
   122             self.assertEqual(transform('123'),
       
   123                               ('Any X WHERE X eid %(x)s', {'x': 123}, 'x'))
       
   124             self.assertEqual(transform('CWUser'),
       
   125                               ('CWUser C',))
       
   126             self.assertEqual(transform('Utilisateur'),
       
   127                               ('CWUser C',))
       
   128             self.assertEqual(transform('Adresse'),
       
   129                               ('EmailAddress E',))
       
   130             self.assertEqual(transform('adresse'),
       
   131                               ('EmailAddress E',))
       
   132             self.assertRaises(BadRQLQuery, transform, 'Workcases')
       
   133 
       
   134     def test_two_words_query(self):
       
   135         """tests the 'two words shortcut queries'"""
       
   136         with self.proc() as proc:
       
   137             transform = proc._two_words_query
       
   138             self.assertEqual(transform('CWUser', 'E'),
       
   139                               ("CWUser E",))
       
   140             self.assertEqual(transform('CWUser', 'Smith'),
       
   141                               ('CWUser C ORDERBY FTIRANK(C) DESC WHERE C has_text %(text)s', {'text': 'Smith'}))
       
   142             self.assertEqual(transform('utilisateur', 'Smith'),
       
   143                               ('CWUser C ORDERBY FTIRANK(C) DESC WHERE C has_text %(text)s', {'text': 'Smith'}))
       
   144             self.assertEqual(transform(u'adresse', 'Logilab'),
       
   145                               ('EmailAddress E ORDERBY FTIRANK(E) DESC WHERE E has_text %(text)s', {'text': 'Logilab'}))
       
   146             self.assertEqual(transform(u'adresse', 'Logi%'),
       
   147                               ('EmailAddress E WHERE E alias LIKE %(text)s', {'text': 'Logi%'}))
       
   148             self.assertRaises(BadRQLQuery, transform, "pers", "taratata")
       
   149 
       
   150     def test_three_words_query(self):
       
   151         """tests the 'three words shortcut queries'"""
       
   152         with self.proc() as proc:
       
   153             transform = proc._three_words_query
       
   154             self.assertEqual(transform('utilisateur', u'prénom', 'cubicweb'),
       
   155                               ('CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'}))
       
   156             self.assertEqual(transform('utilisateur', 'nom', 'cubicweb'),
       
   157                               ('CWUser C WHERE C surname %(text)s', {'text': 'cubicweb'}))
       
   158             self.assertEqual(transform(u'adresse', 'nom', 'cubicweb'),
       
   159                               ('EmailAddress E WHERE E alias %(text)s', {'text': 'cubicweb'}))
       
   160             self.assertEqual(transform('EmailAddress', 'nom', 'cubicweb'),
       
   161                               ('EmailAddress E WHERE E alias %(text)s', {'text': 'cubicweb'}))
       
   162             self.assertEqual(transform('utilisateur', u'prénom', 'cubicweb%'),
       
   163                               ('CWUser C WHERE C firstname LIKE %(text)s', {'text': 'cubicweb%'}))
       
   164             # expanded shortcuts
       
   165             self.assertEqual(transform('CWUser', 'use_email', 'Logilab'),
       
   166                               ('CWUser C ORDERBY FTIRANK(C1) DESC WHERE C use_email C1, C1 has_text %(text)s', {'text': 'Logilab'}))
       
   167             self.assertEqual(transform('CWUser', 'use_email', '%Logilab'),
       
   168                               ('CWUser C WHERE C use_email C1, C1 alias LIKE %(text)s', {'text': '%Logilab'}))
       
   169             self.assertRaises(BadRQLQuery, transform, 'word1', 'word2', 'word3')
       
   170 
       
   171     def test_quoted_queries(self):
       
   172         """tests how quoted queries are handled"""
       
   173         queries = [
       
   174             (u'Adresse "My own EmailAddress"', ('EmailAddress E ORDERBY FTIRANK(E) DESC WHERE E has_text %(text)s', {'text': u'My own EmailAddress'})),
       
   175             (u'Utilisateur prénom "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})),
       
   176             (u'Utilisateur firstname "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})),
       
   177             (u'CWUser firstname "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})),
       
   178             ]
       
   179         with self.proc() as proc:
       
   180             transform = proc._quoted_words_query
       
   181             for query, expected in queries:
       
   182                 self.assertEqual(transform(query), expected)
       
   183             self.assertRaises(BadRQLQuery, transform, "unquoted rql")
       
   184             self.assertRaises(BadRQLQuery, transform, 'pers "Jean Paul"')
       
   185             self.assertRaises(BadRQLQuery, transform, 'CWUser firstname other "Jean Paul"')
       
   186 
       
   187     def test_process_query(self):
       
   188         """tests how queries are processed"""
       
   189         queries = [
       
   190             (u'Utilisateur', (u"CWUser C",)),
       
   191             (u'Utilisateur P', (u"CWUser P",)),
       
   192             (u'Utilisateur cubicweb', (u'CWUser C ORDERBY FTIRANK(C) DESC WHERE C has_text %(text)s', {'text': u'cubicweb'})),
       
   193             (u'CWUser prénom cubicweb', (u'CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'},)),
       
   194             ]
       
   195         with self.proc() as proc:
       
   196             for query, expected in queries:
       
   197                 self.assertEqual(proc.preprocess_query(query), expected)
       
   198             self.assertRaises(BadRQLQuery,
       
   199                               proc.preprocess_query, 'Any X WHERE X is Something')
       
   200 
       
   201 
       
   202 
       
   203 ## Processor Chains tests ############################################
       
   204 
       
   205 class ProcessorChainTC(CubicWebTC):
       
   206     """test suite for magic_search's processor chains"""
       
   207 
       
   208     @contextmanager
       
   209     def proc(self):
       
   210         self.vreg.config.translations = {'en': (_translate, _ctxtranslate)}
       
   211         with self.admin_access.web_request() as req:
       
   212             proc = self.vreg['components'].select('magicsearch', req)
       
   213             yield proc
       
   214 
       
   215     def test_main_preprocessor_chain(self):
       
   216         """tests QUERY_PROCESSOR"""
       
   217         queries = [
       
   218             (u'foo',
       
   219              ("Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s", {'text': u'foo'})),
       
   220             # XXX this sounds like a language translator test...
       
   221             # and it fails
       
   222             (u'Utilisateur Smith',
       
   223              ('CWUser C ORDERBY FTIRANK(C) DESC WHERE C has_text %(text)s', {'text': u'Smith'})),
       
   224             (u'utilisateur nom Smith',
       
   225              ('CWUser C WHERE C surname %(text)s', {'text': u'Smith'})),
       
   226             (u'Any P WHERE P is Utilisateur, P nom "Smith"',
       
   227              ('Any P WHERE P is CWUser, P surname "Smith"', None)),
       
   228             ]
       
   229         with self.proc() as proc:
       
   230             for query, expected in queries:
       
   231                 rset = proc.process_query(query)
       
   232                 self.assertEqual((rset.rql, rset.args), expected)
       
   233 
       
   234     def test_accentuated_fulltext(self):
       
   235         """we must be able to type accentuated characters in the search field"""
       
   236         with self.proc() as proc:
       
   237             rset = proc.process_query(u'écrire')
       
   238             self.assertEqual(rset.rql, "Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s")
       
   239             self.assertEqual(rset.args, {'text': u'écrire'})
       
   240 
       
   241     def test_explicit_component(self):
       
   242         with self.proc() as proc:
       
   243             self.assertRaises(RQLSyntaxError,
       
   244                               proc.process_query, u'rql: CWUser E WHERE E noattr "Smith",')
       
   245             self.assertRaises(BadRQLQuery,
       
   246                               proc.process_query, u'rql: CWUser E WHERE E noattr "Smith"')
       
   247             rset = proc.process_query(u'text: utilisateur Smith')
       
   248             self.assertEqual(rset.rql, 'Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s')
       
   249             self.assertEqual(rset.args, {'text': u'utilisateur Smith'})
       
   250 
       
   251 
       
   252 class RQLSuggestionsBuilderTC(CubicWebTC):
       
   253     def suggestions(self, rql):
       
   254         with self.admin_access.web_request() as req:
       
   255             rbs = self.vreg['components'].select('rql.suggestions', req)
       
   256             return rbs.build_suggestions(rql)
       
   257 
       
   258     def test_no_restrictions_rql(self):
       
   259         self.assertListEqual([], self.suggestions(''))
       
   260         self.assertListEqual([], self.suggestions('An'))
       
   261         self.assertListEqual([], self.suggestions('Any X'))
       
   262         self.assertListEqual([], self.suggestions('Any X, Y'))
       
   263 
       
   264     def test_invalid_rql(self):
       
   265         self.assertListEqual([], self.suggestions('blabla'))
       
   266         self.assertListEqual([], self.suggestions('Any X WHERE foo, bar'))
       
   267 
       
   268     def test_is_rql(self):
       
   269         self.assertListEqual(['Any X WHERE X is %s' % eschema
       
   270                               for eschema in sorted(self.vreg.schema.entities())
       
   271                               if not eschema.final],
       
   272                              self.suggestions('Any X WHERE X is'))
       
   273 
       
   274         self.assertListEqual(['Any X WHERE X is Personne', 'Any X WHERE X is Project'],
       
   275                              self.suggestions('Any X WHERE X is P'))
       
   276 
       
   277         self.assertListEqual(['Any X WHERE X is Personne, Y is Personne',
       
   278                               'Any X WHERE X is Personne, Y is Project'],
       
   279                              self.suggestions('Any X WHERE X is Personne, Y is P'))
       
   280 
       
   281 
       
   282     def test_relations_rql(self):
       
   283         self.assertListEqual(['Any X WHERE X is Personne, X ass A',
       
   284                               'Any X WHERE X is Personne, X datenaiss A',
       
   285                               'Any X WHERE X is Personne, X description A',
       
   286                               'Any X WHERE X is Personne, X fax A',
       
   287                               'Any X WHERE X is Personne, X nom A',
       
   288                               'Any X WHERE X is Personne, X prenom A',
       
   289                               'Any X WHERE X is Personne, X promo A',
       
   290                               'Any X WHERE X is Personne, X salary A',
       
   291                               'Any X WHERE X is Personne, X sexe A',
       
   292                               'Any X WHERE X is Personne, X tel A',
       
   293                               'Any X WHERE X is Personne, X test A',
       
   294                               'Any X WHERE X is Personne, X titre A',
       
   295                               'Any X WHERE X is Personne, X travaille A',
       
   296                               'Any X WHERE X is Personne, X web A',
       
   297                               ],
       
   298                              self.suggestions('Any X WHERE X is Personne, X '))
       
   299         self.assertListEqual(['Any X WHERE X is Personne, X tel A',
       
   300                               'Any X WHERE X is Personne, X test A',
       
   301                               'Any X WHERE X is Personne, X titre A',
       
   302                               'Any X WHERE X is Personne, X travaille A',
       
   303                               ],
       
   304                              self.suggestions('Any X WHERE X is Personne, X t'))
       
   305         # try completion on selected
       
   306         self.assertListEqual(['Any X WHERE X is Personne, Y is Societe, X tel A',
       
   307                               'Any X WHERE X is Personne, Y is Societe, X test A',
       
   308                               'Any X WHERE X is Personne, Y is Societe, X titre A',
       
   309                               'Any X WHERE X is Personne, Y is Societe, X travaille Y',
       
   310                               ],
       
   311                              self.suggestions('Any X WHERE X is Personne, Y is Societe, X t'))
       
   312         # invalid relation should not break
       
   313         self.assertListEqual([],
       
   314                              self.suggestions('Any X WHERE X is Personne, X asdasd'))
       
   315 
       
   316     def test_attribute_vocabulary_rql(self):
       
   317         self.assertListEqual(['Any X WHERE X is Personne, X promo "bon"',
       
   318                               'Any X WHERE X is Personne, X promo "pasbon"',
       
   319                               ],
       
   320                              self.suggestions('Any X WHERE X is Personne, X promo "'))
       
   321         self.assertListEqual(['Any X WHERE X is Personne, X promo "pasbon"',
       
   322                               ],
       
   323                              self.suggestions('Any X WHERE X is Personne, X promo "p'))
       
   324         # "bon" should be considered complete, hence no suggestion
       
   325         self.assertListEqual([],
       
   326                              self.suggestions('Any X WHERE X is Personne, X promo "bon"'))
       
   327         # no valid vocabulary starts with "po"
       
   328         self.assertListEqual([],
       
   329                              self.suggestions('Any X WHERE X is Personne, X promo "po'))
       
   330 
       
   331     def test_attribute_value_rql(self):
       
   332         # suggestions should contain any possible value for
       
   333         # a given attribute (limited to 10)
       
   334         with self.admin_access.web_request() as req:
       
   335             for i in range(15):
       
   336                 req.create_entity('Personne', nom=u'n%s' % i, prenom=u'p%s' % i)
       
   337             req.cnx.commit()
       
   338         self.assertListEqual(['Any X WHERE X is Personne, X nom "n0"',
       
   339                               'Any X WHERE X is Personne, X nom "n1"',
       
   340                               'Any X WHERE X is Personne, X nom "n10"',
       
   341                               'Any X WHERE X is Personne, X nom "n11"',
       
   342                               'Any X WHERE X is Personne, X nom "n12"',
       
   343                               'Any X WHERE X is Personne, X nom "n13"',
       
   344                               'Any X WHERE X is Personne, X nom "n14"',
       
   345                               'Any X WHERE X is Personne, X nom "n2"',
       
   346                               'Any X WHERE X is Personne, X nom "n3"',
       
   347                               'Any X WHERE X is Personne, X nom "n4"',
       
   348                               'Any X WHERE X is Personne, X nom "n5"',
       
   349                               'Any X WHERE X is Personne, X nom "n6"',
       
   350                               'Any X WHERE X is Personne, X nom "n7"',
       
   351                               'Any X WHERE X is Personne, X nom "n8"',
       
   352                               'Any X WHERE X is Personne, X nom "n9"',
       
   353                               ],
       
   354                              self.suggestions('Any X WHERE X is Personne, X nom "'))
       
   355         self.assertListEqual(['Any X WHERE X is Personne, X nom "n1"',
       
   356                               'Any X WHERE X is Personne, X nom "n10"',
       
   357                               'Any X WHERE X is Personne, X nom "n11"',
       
   358                               'Any X WHERE X is Personne, X nom "n12"',
       
   359                               'Any X WHERE X is Personne, X nom "n13"',
       
   360                               'Any X WHERE X is Personne, X nom "n14"',
       
   361                               ],
       
   362                              self.suggestions('Any X WHERE X is Personne, X nom "n1'))
       
   363 
       
   364 
       
   365 if __name__ == '__main__':
       
   366     unittest_main()