blob: db8a7a74d8d7a1a33b6478347e9a2f9bbf2a3e36 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2016 The Chromium Authors. All rights reserved.
2# Use of this source code is governed 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"""Unit tests for tracker_fulltext module."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import unittest
12
13import mox
14
15from google.appengine.api import search
16
17import settings
18from framework import framework_views
19from proto import ast_pb2
20from proto import tracker_pb2
21from services import fulltext_helpers
22from services import tracker_fulltext
23from testing import fake
24from tracker import tracker_bizobj
25
26
27class TrackerFulltextTest(unittest.TestCase):
28
29 def setUp(self):
30 self.mox = mox.Mox()
31 self.mock_index = self.mox.CreateMockAnything()
32 self.mox.StubOutWithMock(search, 'Index')
33 self.docs = None
34 self.cnxn = 'fake connection'
35 self.user_service = fake.UserService()
36 self.user_service.TestAddUser('test@example.com', 111)
37 self.issue_service = fake.IssueService()
38 self.config_service = fake.ConfigService()
39
40 self.issue = fake.MakeTestIssue(
41 123, 1, 'test summary', 'New', 111)
42 self.issue_service.TestAddIssue(self.issue)
43 self.comment = tracker_pb2.IssueComment(
44 project_id=789, issue_id=self.issue.issue_id, user_id=111,
45 content='comment content',
46 attachments=[
47 tracker_pb2.Attachment(filename='hello.c'),
48 tracker_pb2.Attachment(filename='hello.h')])
49 self.issue_service.TestAddComment(self.comment, 1)
50 self.users_by_id = framework_views.MakeAllUserViews(
51 self.cnxn, self.user_service, [111])
52
53 def tearDown(self):
54 self.mox.UnsetStubs()
55 self.mox.ResetAll()
56
57 def RecordDocs(self, docs):
58 self.docs = docs
59
60 def SetUpIndexIssues(self):
61 search.Index(name=settings.search_index_name_format % 1).AndReturn(
62 self.mock_index)
63 self.mock_index.put(mox.IgnoreArg()).WithSideEffects(self.RecordDocs)
64
65 def testIndexIssues(self):
66 self.SetUpIndexIssues()
67 self.mox.ReplayAll()
68 tracker_fulltext.IndexIssues(
69 self.cnxn, [self.issue], self.user_service, self.issue_service,
70 self.config_service)
71 self.mox.VerifyAll()
72 self.assertEqual(1, len(self.docs))
73 issue_doc = self.docs[0]
74 self.assertEqual(123, issue_doc.fields[0].value)
75 self.assertEqual('test summary', issue_doc.fields[1].value)
76
77 def SetUpCreateIssueSearchDocuments(self):
78 self.mox.StubOutWithMock(tracker_fulltext, '_IndexDocsInShard')
79 tracker_fulltext._IndexDocsInShard(1, mox.IgnoreArg()).WithSideEffects(
80 lambda shard_id, docs: self.RecordDocs(docs))
81
82 def testCreateIssueSearchDocuments_Normal(self):
83 self.SetUpCreateIssueSearchDocuments()
84 self.mox.ReplayAll()
85 config_dict = {123: tracker_bizobj.MakeDefaultProjectIssueConfig(123)}
86 tracker_fulltext._CreateIssueSearchDocuments(
87 [self.issue], {self.issue.issue_id: [self.comment]}, self.users_by_id,
88 config_dict)
89 self.mox.VerifyAll()
90 self.assertEqual(1, len(self.docs))
91 issue_doc = self.docs[0]
92 self.assertEqual(5, len(issue_doc.fields))
93 self.assertEqual(123, issue_doc.fields[0].value)
94 self.assertEqual('test summary', issue_doc.fields[1].value)
95 self.assertEqual('test@example.com comment content hello.c hello.h',
96 issue_doc.fields[3].value)
97 self.assertEqual('', issue_doc.fields[4].value)
98
99 def testCreateIssueSearchDocuments_NoIndexableComments(self):
100 """Sometimes all comments on a issue are spam or deleted."""
101 self.SetUpCreateIssueSearchDocuments()
102 self.mox.ReplayAll()
103 config_dict = {123: tracker_bizobj.MakeDefaultProjectIssueConfig(123)}
104 self.comment.deleted_by = 111
105 tracker_fulltext._CreateIssueSearchDocuments(
106 [self.issue], {self.issue.issue_id: [self.comment]}, self.users_by_id,
107 config_dict)
108 self.mox.VerifyAll()
109 self.assertEqual(1, len(self.docs))
110 issue_doc = self.docs[0]
111 self.assertEqual(5, len(issue_doc.fields))
112 self.assertEqual(123, issue_doc.fields[0].value)
113 self.assertEqual('test summary', issue_doc.fields[1].value)
114 self.assertEqual('', issue_doc.fields[3].value)
115 self.assertEqual('', issue_doc.fields[4].value)
116
117 def testCreateIssueSearchDocuments_CustomFields(self):
118 self.SetUpCreateIssueSearchDocuments()
119 self.mox.ReplayAll()
120 config = tracker_bizobj.MakeDefaultProjectIssueConfig(123)
121 config_dict = {123: tracker_bizobj.MakeDefaultProjectIssueConfig(123)}
122 int_field = tracker_bizobj.MakeFieldDef(
123 1, 123, 'CustomInt', tracker_pb2.FieldTypes.INT_TYPE, None, False,
124 False, False, None, None, None, None, False, None, None, None,
125 'no_action', 'A custom int field', False)
126 int_field_value = tracker_bizobj.MakeFieldValue(
127 1, 42, None, None, False, None, None)
128 str_field = tracker_bizobj.MakeFieldDef(
129 2, 123, 'CustomStr', tracker_pb2.FieldTypes.STR_TYPE, None, False,
130 False, False, None, None, None, None, False, None, None, None,
131 'no_action', 'A custom string field', False)
132 str_field_value = tracker_bizobj.MakeFieldValue(
133 2, None, u'\xf0\x9f\x92\x96\xef\xb8\x8f', None, None, None, False)
134 # TODO(jrobbins): user-type field 3
135 date_field = tracker_bizobj.MakeFieldDef(
136 4, 123, 'CustomDate', tracker_pb2.FieldTypes.DATE_TYPE, None, False,
137 False, False, None, None, None, None, False, None, None, None,
138 'no_action', 'A custom date field', False)
139 date_field_value = tracker_bizobj.MakeFieldValue(
140 4, None, None, None, 1234567890, None, False)
141 config.field_defs.extend([int_field, str_field, date_field])
142 self.issue.field_values.extend([
143 int_field_value, str_field_value, date_field_value])
144
145 tracker_fulltext._CreateIssueSearchDocuments(
146 [self.issue], {self.issue.issue_id: [self.comment]}, self.users_by_id,
147 config_dict)
148 self.mox.VerifyAll()
149 self.assertEqual(1, len(self.docs))
150 issue_doc = self.docs[0]
151 metadata = issue_doc.fields[2]
152 self.assertEqual(
153 u'New test@example.com [] 42 \xf0\x9f\x92\x96\xef\xb8\x8f 2009-02-13 ',
154 metadata.value)
155
156 def testExtractCommentText(self):
157 extracted_text = tracker_fulltext._ExtractCommentText(
158 self.comment, self.users_by_id)
159 self.assertEqual(
160 'test@example.com comment content hello.c hello.h',
161 extracted_text)
162
163 def testIndexableComments_NumberOfComments(self):
164 """We consider at most 100 initial comments and 500 most recent comments."""
165 comments = [self.comment]
166 indexable = tracker_fulltext._IndexableComments(comments, self.users_by_id)
167 self.assertEqual(1, len(indexable))
168
169 comments = [self.comment] * 100
170 indexable = tracker_fulltext._IndexableComments(comments, self.users_by_id)
171 self.assertEqual(100, len(indexable))
172
173 comments = [self.comment] * 101
174 indexable = tracker_fulltext._IndexableComments(comments, self.users_by_id)
175 self.assertEqual(101, len(indexable))
176
177 comments = [self.comment] * 600
178 indexable = tracker_fulltext._IndexableComments(comments, self.users_by_id)
179 self.assertEqual(600, len(indexable))
180
181 comments = [self.comment] * 601
182 indexable = tracker_fulltext._IndexableComments(comments, self.users_by_id)
183 self.assertEqual(600, len(indexable))
184 self.assertNotIn(100, indexable)
185
186 def testIndexableComments_NumberOfChars(self):
187 """We consider comments that can fit into the search index document."""
188 self.comment.content = 'x' * 1000
189 comments = [self.comment] * 100
190
191 indexable = tracker_fulltext._IndexableComments(
192 comments, self.users_by_id, remaining_chars=100000)
193 self.assertEqual(100, len(indexable))
194
195 indexable = tracker_fulltext._IndexableComments(
196 comments, self.users_by_id, remaining_chars=50000)
197 self.assertEqual(50, len(indexable))
198 indexable = tracker_fulltext._IndexableComments(
199 comments, self.users_by_id, remaining_chars=50999)
200 self.assertEqual(50, len(indexable))
201
202 indexable = tracker_fulltext._IndexableComments(
203 comments, self.users_by_id, remaining_chars=999)
204 self.assertEqual(0, len(indexable))
205
206 indexable = tracker_fulltext._IndexableComments(
207 comments, self.users_by_id, remaining_chars=0)
208 self.assertEqual(0, len(indexable))
209
210 indexable = tracker_fulltext._IndexableComments(
211 comments, self.users_by_id, remaining_chars=-1)
212 self.assertEqual(0, len(indexable))
213
214 def SetUpUnindexIssues(self):
215 search.Index(name=settings.search_index_name_format % 1).AndReturn(
216 self.mock_index)
217 self.mock_index.delete(['1'])
218
219 def testUnindexIssues(self):
220 self.SetUpUnindexIssues()
221 self.mox.ReplayAll()
222 tracker_fulltext.UnindexIssues([1])
223 self.mox.VerifyAll()
224
225 def SetUpSearchIssueFullText(self):
226 self.mox.StubOutWithMock(fulltext_helpers, 'ComprehensiveSearch')
227 fulltext_helpers.ComprehensiveSearch(
228 '(project_id:789) (summary:"test")',
229 settings.search_index_name_format % 1).AndReturn([123, 234])
230
231 def testSearchIssueFullText_Normal(self):
232 self.SetUpSearchIssueFullText()
233 self.mox.ReplayAll()
234 summary_fd = tracker_pb2.FieldDef(
235 field_name='summary', field_type=tracker_pb2.FieldTypes.STR_TYPE)
236 query_ast_conj = ast_pb2.Conjunction(conds=[
237 ast_pb2.Condition(
238 op=ast_pb2.QueryOp.TEXT_HAS, field_defs=[summary_fd],
239 str_values=['test'])])
240 issue_ids, capped = tracker_fulltext.SearchIssueFullText(
241 [789], query_ast_conj, 1)
242 self.mox.VerifyAll()
243 self.assertItemsEqual([123, 234], issue_ids)
244 self.assertFalse(capped)
245
246 def testSearchIssueFullText_CrossProject(self):
247 self.mox.StubOutWithMock(fulltext_helpers, 'ComprehensiveSearch')
248 fulltext_helpers.ComprehensiveSearch(
249 '(project_id:789 OR project_id:678) (summary:"test")',
250 settings.search_index_name_format % 1).AndReturn([123, 234])
251 self.mox.ReplayAll()
252
253 summary_fd = tracker_pb2.FieldDef(
254 field_name='summary', field_type=tracker_pb2.FieldTypes.STR_TYPE)
255 query_ast_conj = ast_pb2.Conjunction(conds=[
256 ast_pb2.Condition(
257 op=ast_pb2.QueryOp.TEXT_HAS, field_defs=[summary_fd],
258 str_values=['test'])])
259 issue_ids, capped = tracker_fulltext.SearchIssueFullText(
260 [789, 678], query_ast_conj, 1)
261 self.mox.VerifyAll()
262 self.assertItemsEqual([123, 234], issue_ids)
263 self.assertFalse(capped)
264
265 def testSearchIssueFullText_Capped(self):
266 try:
267 orig = settings.fulltext_limit_per_shard
268 settings.fulltext_limit_per_shard = 1
269 self.SetUpSearchIssueFullText()
270 self.mox.ReplayAll()
271 summary_fd = tracker_pb2.FieldDef(
272 field_name='summary', field_type=tracker_pb2.FieldTypes.STR_TYPE)
273 query_ast_conj = ast_pb2.Conjunction(conds=[
274 ast_pb2.Condition(
275 op=ast_pb2.QueryOp.TEXT_HAS, field_defs=[summary_fd],
276 str_values=['test'])])
277 issue_ids, capped = tracker_fulltext.SearchIssueFullText(
278 [789], query_ast_conj, 1)
279 self.mox.VerifyAll()
280 self.assertItemsEqual([123, 234], issue_ids)
281 self.assertTrue(capped)
282 finally:
283 settings.fulltext_limit_per_shard = orig