OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is govered by a BSD-style |
| 3 # license that can be found in the LICENSE file or at |
| 4 # https://developers.google.com/open-source/licenses/bsd |
| 5 |
| 6 """Tests for the fulltext_helpers module.""" |
| 7 |
| 8 import unittest |
| 9 |
| 10 import mox |
| 11 |
| 12 from google.appengine.api import search |
| 13 |
| 14 from proto import ast_pb2 |
| 15 from proto import tracker_pb2 |
| 16 from services import fulltext_helpers |
| 17 |
| 18 |
| 19 TEXT_HAS = ast_pb2.QueryOp.TEXT_HAS |
| 20 NOT_TEXT_HAS = ast_pb2.QueryOp.NOT_TEXT_HAS |
| 21 |
| 22 |
| 23 class MockResult(object): |
| 24 |
| 25 def __init__(self, doc_id): |
| 26 self.doc_id = doc_id |
| 27 |
| 28 |
| 29 class MockSearchResponse(object): |
| 30 """Mock object that can be iterated over in batches.""" |
| 31 |
| 32 def __init__(self, results, cursor): |
| 33 """Constructor. |
| 34 |
| 35 Args: |
| 36 results: list of strings for document IDs. |
| 37 cursor: search.Cursor object, if there are more results to |
| 38 retrieve in another round-trip. Or, None if there are not. |
| 39 """ |
| 40 self.results = [MockResult(r) for r in results] |
| 41 self.cursor = cursor |
| 42 |
| 43 def __iter__(self): |
| 44 """The response itself is an iterator over the results.""" |
| 45 return self.results.__iter__() |
| 46 |
| 47 |
| 48 class FulltextHelpersTest(unittest.TestCase): |
| 49 |
| 50 def setUp(self): |
| 51 self.mox = mox.Mox() |
| 52 self.any_field_fd = tracker_pb2.FieldDef( |
| 53 field_name='any_field', field_type=tracker_pb2.FieldTypes.STR_TYPE) |
| 54 self.summary_fd = tracker_pb2.FieldDef( |
| 55 field_name='summary', field_type=tracker_pb2.FieldTypes.STR_TYPE) |
| 56 self.milestone_fd = tracker_pb2.FieldDef( |
| 57 field_name='milestone', field_type=tracker_pb2.FieldTypes.STR_TYPE, |
| 58 field_id=123) |
| 59 self.fulltext_fields = ['summary'] |
| 60 |
| 61 self.mock_index = self.mox.CreateMockAnything() |
| 62 self.mox.StubOutWithMock(search, 'Index') |
| 63 self.query = None |
| 64 |
| 65 def tearDown(self): |
| 66 self.mox.UnsetStubs() |
| 67 self.mox.ResetAll() |
| 68 |
| 69 def RecordQuery(self, query): |
| 70 self.query = query |
| 71 |
| 72 def testBuildFTSQuery_EmptyQueryConjunction(self): |
| 73 query_ast_conj = ast_pb2.Conjunction() |
| 74 fulltext_query = fulltext_helpers.BuildFTSQuery( |
| 75 query_ast_conj, self.fulltext_fields) |
| 76 self.assertEqual(None, fulltext_query) |
| 77 |
| 78 def testBuildFTSQuery_NoFullTextConditions(self): |
| 79 estimated_hours_fd = tracker_pb2.FieldDef( |
| 80 field_name='estimate', field_type=tracker_pb2.FieldTypes.INT_TYPE, |
| 81 field_id=124) |
| 82 query_ast_conj = ast_pb2.Conjunction(conds=[ |
| 83 ast_pb2.MakeCond(TEXT_HAS, [estimated_hours_fd], [], [40])]) |
| 84 fulltext_query = fulltext_helpers.BuildFTSQuery( |
| 85 query_ast_conj, self.fulltext_fields) |
| 86 self.assertEqual(None, fulltext_query) |
| 87 |
| 88 def testBuildFTSQuery_Normal(self): |
| 89 query_ast_conj = ast_pb2.Conjunction(conds=[ |
| 90 ast_pb2.MakeCond(TEXT_HAS, [self.summary_fd], ['needle'], []), |
| 91 ast_pb2.MakeCond(TEXT_HAS, [self.milestone_fd], ['Q3', 'Q4'], [])]) |
| 92 fulltext_query = fulltext_helpers.BuildFTSQuery( |
| 93 query_ast_conj, self.fulltext_fields) |
| 94 self.assertEqual( |
| 95 '(summary:"needle") (custom_123:"Q3" OR custom_123:"Q4")', |
| 96 fulltext_query) |
| 97 |
| 98 def testBuildFTSQuery_WithQuotes(self): |
| 99 query_ast_conj = ast_pb2.Conjunction(conds=[ |
| 100 ast_pb2.MakeCond(TEXT_HAS, [self.summary_fd], ['"needle haystack"'], |
| 101 [])]) |
| 102 fulltext_query = fulltext_helpers.BuildFTSQuery( |
| 103 query_ast_conj, self.fulltext_fields) |
| 104 self.assertEqual('(summary:"needle haystack")', fulltext_query) |
| 105 |
| 106 def testBuildFTSQuery_IngoreColonInText(self): |
| 107 query_ast_conj = ast_pb2.Conjunction(conds=[ |
| 108 ast_pb2.MakeCond(TEXT_HAS, [self.summary_fd], ['"needle:haystack"'], |
| 109 [])]) |
| 110 fulltext_query = fulltext_helpers.BuildFTSQuery( |
| 111 query_ast_conj, self.fulltext_fields) |
| 112 self.assertEqual('(summary:"needle haystack")', fulltext_query) |
| 113 |
| 114 def testBuildFTSQuery_InvalidQuery(self): |
| 115 query_ast_conj = ast_pb2.Conjunction(conds=[ |
| 116 ast_pb2.MakeCond(TEXT_HAS, [self.summary_fd], ['haystack"needle'], []), |
| 117 ast_pb2.MakeCond(TEXT_HAS, [self.milestone_fd], ['Q3', 'Q4'], [])]) |
| 118 try: |
| 119 fulltext_helpers.BuildFTSQuery( |
| 120 query_ast_conj, self.fulltext_fields) |
| 121 raise Exception('Expected AssertionError') |
| 122 except AssertionError: |
| 123 pass |
| 124 |
| 125 def testBuildFTSQuery_SpecialPrefixQuery(self): |
| 126 special_prefix = fulltext_helpers.NON_OP_PREFIXES[0] |
| 127 |
| 128 # Test with summary field. |
| 129 query_ast_conj = ast_pb2.Conjunction(conds=[ |
| 130 ast_pb2.MakeCond(TEXT_HAS, [self.summary_fd], |
| 131 ['%s//google.com' % special_prefix], []), |
| 132 ast_pb2.MakeCond(TEXT_HAS, [self.milestone_fd], ['Q3', 'Q4'], [])]) |
| 133 fulltext_query = fulltext_helpers.BuildFTSQuery( |
| 134 query_ast_conj, self.fulltext_fields) |
| 135 self.assertEqual( |
| 136 '(summary:"%s//google.com") (custom_123:"Q3" OR custom_123:"Q4")' % ( |
| 137 special_prefix), |
| 138 fulltext_query) |
| 139 |
| 140 # Test with any field. |
| 141 any_fd = tracker_pb2.FieldDef( |
| 142 field_name=ast_pb2.ANY_FIELD, |
| 143 field_type=tracker_pb2.FieldTypes.STR_TYPE) |
| 144 query_ast_conj = ast_pb2.Conjunction(conds=[ |
| 145 ast_pb2.MakeCond( |
| 146 TEXT_HAS, [any_fd], ['%s//google.com' % special_prefix], []), |
| 147 ast_pb2.MakeCond(TEXT_HAS, [self.milestone_fd], ['Q3', 'Q4'], [])]) |
| 148 fulltext_query = fulltext_helpers.BuildFTSQuery( |
| 149 query_ast_conj, self.fulltext_fields) |
| 150 self.assertEqual( |
| 151 '("%s//google.com") (custom_123:"Q3" OR custom_123:"Q4")' % ( |
| 152 special_prefix), |
| 153 fulltext_query) |
| 154 |
| 155 def testBuildFTSCondition_BuiltinField(self): |
| 156 query_cond = ast_pb2.MakeCond( |
| 157 TEXT_HAS, [self.summary_fd], ['needle'], []) |
| 158 fulltext_query_clause = fulltext_helpers._BuildFTSCondition( |
| 159 query_cond, self.fulltext_fields) |
| 160 self.assertEqual('(summary:"needle")', fulltext_query_clause) |
| 161 |
| 162 def testBuildFTSCondition_Negatation(self): |
| 163 query_cond = ast_pb2.MakeCond( |
| 164 NOT_TEXT_HAS, [self.summary_fd], ['needle'], []) |
| 165 fulltext_query_clause = fulltext_helpers._BuildFTSCondition( |
| 166 query_cond, self.fulltext_fields) |
| 167 self.assertEqual('NOT (summary:"needle")', fulltext_query_clause) |
| 168 |
| 169 def testBuildFTSCondition_QuickOR(self): |
| 170 query_cond = ast_pb2.MakeCond( |
| 171 TEXT_HAS, [self.summary_fd], ['needle', 'pin'], []) |
| 172 fulltext_query_clause = fulltext_helpers._BuildFTSCondition( |
| 173 query_cond, self.fulltext_fields) |
| 174 self.assertEqual( |
| 175 '(summary:"needle" OR summary:"pin")', |
| 176 fulltext_query_clause) |
| 177 |
| 178 def testBuildFTSCondition_NegatedQuickOR(self): |
| 179 query_cond = ast_pb2.MakeCond( |
| 180 NOT_TEXT_HAS, [self.summary_fd], ['needle', 'pin'], []) |
| 181 fulltext_query_clause = fulltext_helpers._BuildFTSCondition( |
| 182 query_cond, self.fulltext_fields) |
| 183 self.assertEqual( |
| 184 'NOT (summary:"needle" OR summary:"pin")', |
| 185 fulltext_query_clause) |
| 186 |
| 187 def testBuildFTSCondition_AnyField(self): |
| 188 query_cond = ast_pb2.MakeCond( |
| 189 TEXT_HAS, [self.any_field_fd], ['needle'], []) |
| 190 fulltext_query_clause = fulltext_helpers._BuildFTSCondition( |
| 191 query_cond, self.fulltext_fields) |
| 192 self.assertEqual('("needle")', fulltext_query_clause) |
| 193 |
| 194 def testBuildFTSCondition_NegatedAnyField(self): |
| 195 query_cond = ast_pb2.MakeCond( |
| 196 NOT_TEXT_HAS, [self.any_field_fd], ['needle'], []) |
| 197 fulltext_query_clause = fulltext_helpers._BuildFTSCondition( |
| 198 query_cond, self.fulltext_fields) |
| 199 self.assertEqual('NOT ("needle")', fulltext_query_clause) |
| 200 |
| 201 def testBuildFTSCondition_CrossProjectWithMultipleFieldDescriptors(self): |
| 202 other_milestone_fd = tracker_pb2.FieldDef( |
| 203 field_name='milestone', field_type=tracker_pb2.FieldTypes.STR_TYPE, |
| 204 field_id=456) |
| 205 query_cond = ast_pb2.MakeCond( |
| 206 TEXT_HAS, [self.milestone_fd, other_milestone_fd], ['needle'], []) |
| 207 fulltext_query_clause = fulltext_helpers._BuildFTSCondition( |
| 208 query_cond, self.fulltext_fields) |
| 209 self.assertEqual( |
| 210 '(custom_123:"needle" OR custom_456:"needle")', fulltext_query_clause) |
| 211 |
| 212 def SetUpComprehensiveSearch(self): |
| 213 search.Index(name='search index name').AndReturn( |
| 214 self.mock_index) |
| 215 self.mock_index.search(mox.IgnoreArg()).WithSideEffects( |
| 216 self.RecordQuery).AndReturn( |
| 217 MockSearchResponse(['123', '234'], search.Cursor())) |
| 218 self.mock_index.search(mox.IgnoreArg()).WithSideEffects( |
| 219 self.RecordQuery).AndReturn(MockSearchResponse(['345'], None)) |
| 220 |
| 221 def testComprehensiveSearch(self): |
| 222 self.SetUpComprehensiveSearch() |
| 223 self.mox.ReplayAll() |
| 224 project_ids = fulltext_helpers.ComprehensiveSearch( |
| 225 'browser', 'search index name') |
| 226 self.mox.VerifyAll() |
| 227 self.assertItemsEqual([123, 234, 345], project_ids) |
| 228 |
| 229 |
| 230 if __name__ == '__main__': |
| 231 unittest.main() |
OLD | NEW |