unittest_magicsearch.py 17.4 KB
Newer Older
Adrien Di Mascio's avatar
Adrien Di Mascio committed
1
# -*- coding: utf-8 -*-
2
3
4
5
6
7
8
9
10
11
# copyright 2003-2010 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
# contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This file is part of CubicWeb.
#
# CubicWeb is free software: you can redistribute it and/or modify it under the
# terms of the GNU Lesser General Public License as published by the Free
# Software Foundation, either version 2.1 of the License, or (at your option)
# any later version.
#
12
# CubicWeb is distributed in the hope that it will be useful, but WITHOUT
13
14
15
16
17
18
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
# details.
#
# You should have received a copy of the GNU Lesser General Public License along
# with CubicWeb.  If not, see <http://www.gnu.org/licenses/>.
19
"""Unit tests for cw.web.views.magicsearch"""
Adrien Di Mascio's avatar
Adrien Di Mascio committed
20
21
22
23
24
25
26

import sys

from logilab.common.testlib import TestCase, unittest_main

from rql import BadRQLQuery, RQLSyntaxError

27
from cubicweb.devtools.testlib import CubicWebTC
Adrien Di Mascio's avatar
Adrien Di Mascio committed
28
29
30


translations = {
31
    u'CWUser' : u"Utilisateur",
Adrien Di Mascio's avatar
Adrien Di Mascio committed
32
33
34
35
36
37
38
39
40
    u'EmailAddress' : u"Adresse",
    u'name' : u"nom",
    u'alias' : u"nom",
    u'surname' : u"nom",
    u'firstname' : u"prénom",
    u'state' : u"état",
    u'address' : u"adresse",
    u'use_email' : u"adel",
    }
41

Adrien Di Mascio's avatar
Adrien Di Mascio committed
42
43
44
def _translate(msgid):
    return translations.get(msgid, msgid)

45
46
def _ctxtranslate(ctx, msgid):
    return _translate(msgid)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
47
48
49

from cubicweb.web.views.magicsearch import translate_rql_tree, QSPreProcessor, QueryTranslator

50
class QueryTranslatorTC(CubicWebTC):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
51
    """test suite for QueryTranslatorTC"""
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
52

Adrien Di Mascio's avatar
Adrien Di Mascio committed
53
54
    def setUp(self):
        super(QueryTranslatorTC, self).setUp()
55
        self.req = self.request()
56
        self.vreg.config.translations = {'en': (_translate, _ctxtranslate)}
57
        proc = self.vreg['components'].select('magicsearch', self.req)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
58
59
60
61
62
        self.proc = [p for p in proc.processors if isinstance(p, QueryTranslator)][0]

    def test_basic_translations(self):
        """tests basic translations (no ambiguities)"""
        rql = "Any C WHERE C is Adresse, P adel C, C adresse 'Logilab'"
63
        rql, = self.proc.preprocess_query(rql)
64
        self.assertEqual(rql, "Any C WHERE C is EmailAddress, P use_email C, C address 'Logilab'")
Adrien Di Mascio's avatar
Adrien Di Mascio committed
65
66
67
68

    def test_ambiguous_translations(self):
        """tests possibly ambiguous translations"""
        rql = "Any P WHERE P adel C, C is EmailAddress, C nom 'Logilab'"
69
        rql, = self.proc.preprocess_query(rql)
70
        self.assertEqual(rql, "Any P WHERE P use_email C, C is EmailAddress, C alias 'Logilab'")
Adrien Di Mascio's avatar
Adrien Di Mascio committed
71
        rql = "Any P WHERE P is Utilisateur, P adel C, P nom 'Smith'"
72
        rql, = self.proc.preprocess_query(rql)
73
        self.assertEqual(rql, "Any P WHERE P is CWUser, P use_email C, P surname 'Smith'")
Adrien Di Mascio's avatar
Adrien Di Mascio committed
74
75


76
class QSPreProcessorTC(CubicWebTC):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
77
78
79
    """test suite for QSPreProcessor"""
    def setUp(self):
        super(QSPreProcessorTC, self).setUp()
80
        self.vreg.config.translations = {'en': (_translate, _ctxtranslate)}
Adrien Di Mascio's avatar
Adrien Di Mascio committed
81
        self.req = self.request()
82
        proc = self.vreg['components'].select('magicsearch', self.req)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
83
        self.proc = [p for p in proc.processors if isinstance(p, QSPreProcessor)][0]
84
        self.proc._cw = self.req
Adrien Di Mascio's avatar
Adrien Di Mascio committed
85
86
87
88

    def test_entity_translation(self):
        """tests QSPreProcessor._get_entity_name()"""
        translate = self.proc._get_entity_type
89
90
91
92
        self.assertEqual(translate(u'EmailAddress'), "EmailAddress")
        self.assertEqual(translate(u'emailaddress'), "EmailAddress")
        self.assertEqual(translate(u'Adresse'), "EmailAddress")
        self.assertEqual(translate(u'adresse'), "EmailAddress")
Adrien Di Mascio's avatar
Adrien Di Mascio committed
93
94
95
96
97
        self.assertRaises(BadRQLQuery, translate, 'whatever')

    def test_attribute_translation(self):
        """tests QSPreProcessor._get_attribute_name"""
        translate = self.proc._get_attribute_name
98
        eschema = self.schema.eschema('CWUser')
99
100
        self.assertEqual(translate(u'prénom', eschema), "firstname")
        self.assertEqual(translate(u'nom', eschema), 'surname')
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
101
        eschema = self.schema.eschema('EmailAddress')
102
103
        self.assertEqual(translate(u'adresse', eschema), "address")
        self.assertEqual(translate(u'nom', eschema), 'alias')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
104
105
106
107
108
109
110
        # should fail if the name is not an attribute for the given entity schema
        self.assertRaises(BadRQLQuery, translate, 'whatever', eschema)
        self.assertRaises(BadRQLQuery, translate, 'prénom', eschema)

    def test_one_word_query(self):
        """tests the 'one word shortcut queries'"""
        transform = self.proc._one_word_query
111
        self.assertEqual(transform('123'),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
112
                          ('Any X WHERE X eid %(x)s', {'x': 123}, 'x'))
113
        self.assertEqual(transform('CWUser'),
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
114
                          ('CWUser C',))
115
        self.assertEqual(transform('Utilisateur'),
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
116
                          ('CWUser C',))
117
        self.assertEqual(transform('Adresse'),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
118
                          ('EmailAddress E',))
119
        self.assertEqual(transform('adresse'),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
120
121
122
123
124
125
                          ('EmailAddress E',))
        self.assertRaises(BadRQLQuery, transform, 'Workcases')

    def test_two_words_query(self):
        """tests the 'two words shortcut queries'"""
        transform = self.proc._two_words_query
126
        self.assertEqual(transform('CWUser', 'E'),
127
                          ("CWUser E",))
128
        self.assertEqual(transform('CWUser', 'Smith'),
129
                          ('CWUser C ORDERBY FTIRANK(C) DESC WHERE C has_text %(text)s', {'text': 'Smith'}))
130
        self.assertEqual(transform('utilisateur', 'Smith'),
131
                          ('CWUser C ORDERBY FTIRANK(C) DESC WHERE C has_text %(text)s', {'text': 'Smith'}))
132
        self.assertEqual(transform(u'adresse', 'Logilab'),
133
                          ('EmailAddress E ORDERBY FTIRANK(E) DESC WHERE E has_text %(text)s', {'text': 'Logilab'}))
134
        self.assertEqual(transform(u'adresse', 'Logi%'),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
135
136
137
138
139
140
                          ('EmailAddress E WHERE E alias LIKE %(text)s', {'text': 'Logi%'}))
        self.assertRaises(BadRQLQuery, transform, "pers", "taratata")

    def test_three_words_query(self):
        """tests the 'three words shortcut queries'"""
        transform = self.proc._three_words_query
141
        self.assertEqual(transform('utilisateur', u'prénom', 'cubicweb'),
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
142
                          ('CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'}))
143
        self.assertEqual(transform('utilisateur', 'nom', 'cubicweb'),
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
144
                          ('CWUser C WHERE C surname %(text)s', {'text': 'cubicweb'}))
145
        self.assertEqual(transform(u'adresse', 'nom', 'cubicweb'),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
146
                          ('EmailAddress E WHERE E alias %(text)s', {'text': 'cubicweb'}))
147
        self.assertEqual(transform('EmailAddress', 'nom', 'cubicweb'),
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
148
                          ('EmailAddress E WHERE E alias %(text)s', {'text': 'cubicweb'}))
149
        self.assertEqual(transform('utilisateur', u'prénom', 'cubicweb%'),
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
150
                          ('CWUser C WHERE C firstname LIKE %(text)s', {'text': 'cubicweb%'}))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
151
        # expanded shortcuts
152
        self.assertEqual(transform('CWUser', 'use_email', 'Logilab'),
153
                          ('CWUser C ORDERBY FTIRANK(C1) DESC WHERE C use_email C1, C1 has_text %(text)s', {'text': 'Logilab'}))
154
        self.assertEqual(transform('CWUser', 'use_email', '%Logilab'),
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
155
                          ('CWUser C WHERE C use_email C1, C1 alias LIKE %(text)s', {'text': '%Logilab'}))
Adrien Di Mascio's avatar
Adrien Di Mascio committed
156
        self.assertRaises(BadRQLQuery, transform, 'word1', 'word2', 'word3')
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
157

Adrien Di Mascio's avatar
Adrien Di Mascio committed
158
159
160
    def test_quoted_queries(self):
        """tests how quoted queries are handled"""
        queries = [
161
            (u'Adresse "My own EmailAddress"', ('EmailAddress E ORDERBY FTIRANK(E) DESC WHERE E has_text %(text)s', {'text': u'My own EmailAddress'})),
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
162
163
164
            (u'Utilisateur prénom "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})),
            (u'Utilisateur firstname "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})),
            (u'CWUser firstname "Jean Paul"', ('CWUser C WHERE C firstname %(text)s', {'text': 'Jean Paul'})),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
165
166
167
            ]
        transform = self.proc._quoted_words_query
        for query, expected in queries:
168
            self.assertEqual(transform(query), expected)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
169
170
        self.assertRaises(BadRQLQuery, transform, "unquoted rql")
        self.assertRaises(BadRQLQuery, transform, 'pers "Jean Paul"')
171
        self.assertRaises(BadRQLQuery, transform, 'CWUser firstname other "Jean Paul"')
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
172

Adrien Di Mascio's avatar
Adrien Di Mascio committed
173
174
175
    def test_process_query(self):
        """tests how queries are processed"""
        queries = [
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
176
            (u'Utilisateur', (u"CWUser C",)),
177
            (u'Utilisateur P', (u"CWUser P",)),
178
            (u'Utilisateur cubicweb', (u'CWUser C ORDERBY FTIRANK(C) DESC WHERE C has_text %(text)s', {'text': u'cubicweb'})),
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
179
            (u'CWUser prénom cubicweb', (u'CWUser C WHERE C firstname %(text)s', {'text': 'cubicweb'},)),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
180
181
            ]
        for query, expected in queries:
182
            self.assertEqual(self.proc.preprocess_query(query), expected)
Sylvain Thénault's avatar
Sylvain Thénault committed
183
        self.assertRaises(BadRQLQuery,
184
                          self.proc.preprocess_query, 'Any X WHERE X is Something')
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
185

Adrien Di Mascio's avatar
Adrien Di Mascio committed
186
187
188


## Processor Chains tests ############################################
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
189

Adrien Di Mascio's avatar
Adrien Di Mascio committed
190

191
class ProcessorChainTC(CubicWebTC):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
192
193
194
195
    """test suite for magic_search's processor chains"""

    def setUp(self):
        super(ProcessorChainTC, self).setUp()
196
        self.vreg.config.translations = {'en': (_translate, _ctxtranslate)}
Adrien Di Mascio's avatar
Adrien Di Mascio committed
197
        self.req = self.request()
198
        self.proc = self.vreg['components'].select('magicsearch', self.req)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
199
200
201
202
203

    def test_main_preprocessor_chain(self):
        """tests QUERY_PROCESSOR"""
        queries = [
            (u'foo',
204
             ("Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s", {'text': u'foo'})),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
205
            # XXX this sounds like a language translator test...
206
            # and it fails
Adrien Di Mascio's avatar
Adrien Di Mascio committed
207
            (u'Utilisateur Smith',
208
             ('CWUser C ORDERBY FTIRANK(C) DESC WHERE C has_text %(text)s', {'text': u'Smith'})),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
209
            (u'utilisateur nom Smith',
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
210
             ('CWUser C WHERE C surname %(text)s', {'text': u'Smith'})),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
211
            (u'Any P WHERE P is Utilisateur, P nom "Smith"',
212
             ('Any P WHERE P is CWUser, P surname "Smith"', None)),
Adrien Di Mascio's avatar
Adrien Di Mascio committed
213
214
            ]
        for query, expected in queries:
215
            rset = self.proc.process_query(query)
216
            self.assertEqual((rset.rql, rset.args), expected)
Adrien Di Mascio's avatar
Adrien Di Mascio committed
217

218
    def test_accentuated_fulltext(self):
Adrien Di Mascio's avatar
Adrien Di Mascio committed
219
        """we must be able to type accentuated characters in the search field"""
220
        rset = self.proc.process_query(u'écrire')
221
222
        self.assertEqual(rset.rql, "Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s")
        self.assertEqual(rset.args, {'text': u'écrire'})
Adrien Di Mascio's avatar
Adrien Di Mascio committed
223
224
225

    def test_explicit_component(self):
        self.assertRaises(RQLSyntaxError,
226
                          self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith",')
Adrien Di Mascio's avatar
Adrien Di Mascio committed
227
        self.assertRaises(BadRQLQuery,
228
229
                          self.proc.process_query, u'rql: CWUser E WHERE E noattr "Smith"')
        rset = self.proc.process_query(u'text: utilisateur Smith')
230
231
        self.assertEqual(rset.rql, 'Any X ORDERBY FTIRANK(X) DESC WHERE X has_text %(text)s')
        self.assertEqual(rset.args, {'text': u'utilisateur Smith'})
sylvain.thenault@logilab.fr's avatar
sylvain.thenault@logilab.fr committed
232

233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320

class RQLSuggestionsBuilderTC(CubicWebTC):
    def suggestions(self, rql):
        req = self.request()
        rbs = self.vreg['components'].select('rql.suggestions', req)
        return rbs.build_suggestions(rql)

    def test_no_restrictions_rql(self):
        self.assertListEqual([], self.suggestions(''))
        self.assertListEqual([], self.suggestions('An'))
        self.assertListEqual([], self.suggestions('Any X'))
        self.assertListEqual([], self.suggestions('Any X, Y'))

    def test_invalid_rql(self):
        self.assertListEqual([], self.suggestions('blabla'))
        self.assertListEqual([], self.suggestions('Any X WHERE foo, bar'))

    def test_is_rql(self):
        self.assertListEqual(['Any X WHERE X is %s' % eschema
                              for eschema in sorted(self.vreg.schema.entities())
                              if not eschema.final],
                             self.suggestions('Any X WHERE X is'))

        self.assertListEqual(['Any X WHERE X is Personne', 'Any X WHERE X is Project'],
                             self.suggestions('Any X WHERE X is P'))

        self.assertListEqual(['Any X WHERE X is Personne, Y is Personne',
                              'Any X WHERE X is Personne, Y is Project'],
                             self.suggestions('Any X WHERE X is Personne, Y is P'))


    def test_relations_rql(self):
        self.assertListEqual(['Any X WHERE X is Personne, X ass A',
                              'Any X WHERE X is Personne, X datenaiss A',
                              'Any X WHERE X is Personne, X description A',
                              'Any X WHERE X is Personne, X fax A',
                              'Any X WHERE X is Personne, X nom A',
                              'Any X WHERE X is Personne, X prenom A',
                              'Any X WHERE X is Personne, X promo A',
                              'Any X WHERE X is Personne, X salary A',
                              'Any X WHERE X is Personne, X sexe A',
                              'Any X WHERE X is Personne, X tel A',
                              'Any X WHERE X is Personne, X test A',
                              'Any X WHERE X is Personne, X titre A',
                              'Any X WHERE X is Personne, X travaille A',
                              'Any X WHERE X is Personne, X web A',
                              ],
                             self.suggestions('Any X WHERE X is Personne, X '))
        self.assertListEqual(['Any X WHERE X is Personne, X tel A',
                              'Any X WHERE X is Personne, X test A',
                              'Any X WHERE X is Personne, X titre A',
                              'Any X WHERE X is Personne, X travaille A',
                              ],
                             self.suggestions('Any X WHERE X is Personne, X t'))
        # try completion on selected
        self.assertListEqual(['Any X WHERE X is Personne, Y is Societe, X tel A',
                              'Any X WHERE X is Personne, Y is Societe, X test A',
                              'Any X WHERE X is Personne, Y is Societe, X titre A',
                              'Any X WHERE X is Personne, Y is Societe, X travaille Y',
                              ],
                             self.suggestions('Any X WHERE X is Personne, Y is Societe, X t'))
        # invalid relation should not break
        self.assertListEqual([],
                             self.suggestions('Any X WHERE X is Personne, X asdasd'))

    def test_attribute_vocabulary_rql(self):
        self.assertListEqual(['Any X WHERE X is Personne, X promo "bon"',
                              'Any X WHERE X is Personne, X promo "pasbon"',
                              ],
                             self.suggestions('Any X WHERE X is Personne, X promo "'))
        self.assertListEqual(['Any X WHERE X is Personne, X promo "pasbon"',
                              ],
                             self.suggestions('Any X WHERE X is Personne, X promo "p'))
        # "bon" should be considered complete, hence no suggestion
        self.assertListEqual([],
                             self.suggestions('Any X WHERE X is Personne, X promo "bon"'))
        # no valid vocabulary starts with "po"
        self.assertListEqual([],
                             self.suggestions('Any X WHERE X is Personne, X promo "po'))

    def test_attribute_value_rql(self):
        # suggestions should contain any possible value for
        # a given attribute (limited to 10)
        req = self.request()
        for i in xrange(15):
            req.create_entity('Personne', nom=u'n%s' % i, prenom=u'p%s' % i)
        self.assertListEqual(['Any X WHERE X is Personne, X nom "n0"',
                              'Any X WHERE X is Personne, X nom "n1"',
321
322
323
324
325
                              'Any X WHERE X is Personne, X nom "n10"',
                              'Any X WHERE X is Personne, X nom "n11"',
                              'Any X WHERE X is Personne, X nom "n12"',
                              'Any X WHERE X is Personne, X nom "n13"',
                              'Any X WHERE X is Personne, X nom "n14"',
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
                              'Any X WHERE X is Personne, X nom "n2"',
                              'Any X WHERE X is Personne, X nom "n3"',
                              'Any X WHERE X is Personne, X nom "n4"',
                              'Any X WHERE X is Personne, X nom "n5"',
                              'Any X WHERE X is Personne, X nom "n6"',
                              'Any X WHERE X is Personne, X nom "n7"',
                              'Any X WHERE X is Personne, X nom "n8"',
                              'Any X WHERE X is Personne, X nom "n9"',
                              ],
                             self.suggestions('Any X WHERE X is Personne, X nom "'))
        self.assertListEqual(['Any X WHERE X is Personne, X nom "n1"',
                              'Any X WHERE X is Personne, X nom "n10"',
                              'Any X WHERE X is Personne, X nom "n11"',
                              'Any X WHERE X is Personne, X nom "n12"',
                              'Any X WHERE X is Personne, X nom "n13"',
                              'Any X WHERE X is Personne, X nom "n14"',
                              ],
                             self.suggestions('Any X WHERE X is Personne, X nom "n1'))


Adrien Di Mascio's avatar
Adrien Di Mascio committed
346
347
if __name__ == '__main__':
    unittest_main()