blob: cd50b186b08adce9b616105abd7f494e281c0623 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Tests for the ast2ast module."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010010import six
Copybara854996b2021-09-07 19:36:02 +000011import unittest
12
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010013from mrproto import ast_pb2
14from mrproto import tracker_pb2
Copybara854996b2021-09-07 19:36:02 +000015from search import ast2ast
16from search import query2ast
17from services import service_manager
18from testing import fake
19from tracker import tracker_bizobj
20
21
22BUILTIN_ISSUE_FIELDS = query2ast.BUILTIN_ISSUE_FIELDS
23ANY_FIELD = query2ast.BUILTIN_ISSUE_FIELDS['any_field']
24OWNER_FIELD = query2ast.BUILTIN_ISSUE_FIELDS['owner']
25OWNER_ID_FIELD = query2ast.BUILTIN_ISSUE_FIELDS['owner_id']
26
27
28class AST2ASTTest(unittest.TestCase):
29
30 def setUp(self):
31 self.cnxn = 'fake cnxn'
32 self.config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
33 self.config.component_defs.append(
34 tracker_bizobj.MakeComponentDef(
35 101, 789, 'UI', 'doc', False, [], [], 0, 0))
36 self.config.component_defs.append(
37 tracker_bizobj.MakeComponentDef(
38 102, 789, 'UI>Search', 'doc', False, [], [], 0, 0))
39 self.config.component_defs.append(
40 tracker_bizobj.MakeComponentDef(
41 201, 789, 'DB', 'doc', False, [], [], 0, 0))
42 self.config.component_defs.append(
43 tracker_bizobj.MakeComponentDef(
44 301, 789, 'Search', 'doc', False, [], [], 0, 0))
45 self.services = service_manager.Services(
46 user=fake.UserService(),
47 project=fake.ProjectService(),
48 issue=fake.IssueService(),
49 config=fake.ConfigService(),
50 features=fake.FeaturesService())
51 self.services.user.TestAddUser('a@example.com', 111)
52 self.project = self.services.project.TestAddProject(
53 'proj', project_id=100)
54
55 def testPreprocessAST_EmptyAST(self):
56 ast = ast_pb2.QueryAST() # No conjunctions in it.
57 new_ast = ast2ast.PreprocessAST(
58 self.cnxn, ast, [789], self.services, self.config)
59 self.assertEqual(ast, new_ast)
60
61 def testPreprocessAST_Normal(self):
62 open_field = BUILTIN_ISSUE_FIELDS['open']
63 label_field = BUILTIN_ISSUE_FIELDS['label']
64 label_id_field = BUILTIN_ISSUE_FIELDS['label_id']
65 status_id_field = BUILTIN_ISSUE_FIELDS['status_id']
66 conds = [
67 ast_pb2.MakeCond(ast_pb2.QueryOp.EQ, [open_field], [], []),
68 ast_pb2.MakeCond(ast_pb2.QueryOp.EQ, [label_field], ['Hot'], [])]
69 self.services.config.TestAddLabelsDict({'Hot': 0})
70
71 ast = ast_pb2.QueryAST()
72 ast.conjunctions.append(ast_pb2.Conjunction(conds=conds))
73 new_ast = ast2ast.PreprocessAST(
74 self.cnxn, ast, [789], self.services, self.config)
75 self.assertEqual(2, len(new_ast.conjunctions[0].conds))
76 new_cond_1, new_cond_2 = new_ast.conjunctions[0].conds
77 self.assertEqual(ast_pb2.QueryOp.NE, new_cond_1.op)
78 self.assertEqual([status_id_field], new_cond_1.field_defs)
79 self.assertEqual([7, 8, 9], new_cond_1.int_values)
80 self.assertEqual([], new_cond_1.str_values)
81 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond_2.op)
82 self.assertEqual([label_id_field], new_cond_2.field_defs)
83 self.assertEqual([0], new_cond_2.int_values)
84 self.assertEqual([], new_cond_2.str_values)
85
86 def testPreprocessIsOpenCond(self):
87 open_field = BUILTIN_ISSUE_FIELDS['open']
88 status_id_field = BUILTIN_ISSUE_FIELDS['status_id']
89
90 # is:open -> status_id!=closed_status_ids
91 cond = ast_pb2.MakeCond(
92 ast_pb2.QueryOp.EQ, [open_field], [], [])
93 new_cond = ast2ast._PreprocessIsOpenCond(
94 self.cnxn, cond, [789], self.services, self.config, True)
95 self.assertEqual(ast_pb2.QueryOp.NE, new_cond.op)
96 self.assertEqual([status_id_field], new_cond.field_defs)
97 self.assertEqual([7, 8, 9], new_cond.int_values)
98 self.assertEqual([], new_cond.str_values)
99
100 # -is:open -> status_id=closed_status_ids
101 cond = ast_pb2.MakeCond(
102 ast_pb2.QueryOp.NE, [open_field], [], [])
103 new_cond = ast2ast._PreprocessIsOpenCond(
104 self.cnxn, cond, [789], self.services, self.config, True)
105 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
106 self.assertEqual([status_id_field], new_cond.field_defs)
107 self.assertEqual([7, 8, 9], new_cond.int_values)
108 self.assertEqual([], new_cond.str_values)
109
110 def testPreprocessBlockedOnCond_WithSingleProjectID(self):
111 blockedon_field = BUILTIN_ISSUE_FIELDS['blockedon']
112 blockedon_id_field = BUILTIN_ISSUE_FIELDS['blockedon_id']
113 self.services.project.TestAddProject('Project1', project_id=1)
114 issue1 = fake.MakeTestIssue(
115 project_id=1, local_id=1, summary='sum', status='new', owner_id=2,
116 issue_id=101)
117 issue2 = fake.MakeTestIssue(
118 project_id=1, local_id=2, summary='sum', status='new', owner_id=2,
119 issue_id=102)
120 self.services.issue.TestAddIssue(issue1)
121 self.services.issue.TestAddIssue(issue2)
122
123 for local_ids, expected in (
124 (['1'], [101]), # One existing issue.
125 (['Project1:1'], [101]), # One existing issue with project prefix.
126 (['1', '2'], [101, 102]), # Two existing issues.
127 (['3'], [])): # Non-existant issue.
128 cond = ast_pb2.MakeCond(
129 ast_pb2.QueryOp.TEXT_HAS, [blockedon_field], local_ids, [])
130 new_cond = ast2ast._PreprocessBlockedOnCond(
131 self.cnxn, cond, [1], self.services, None, True)
132 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
133 self.assertEqual([blockedon_id_field], new_cond.field_defs)
134 self.assertEqual(expected, new_cond.int_values)
135 self.assertEqual([], new_cond.str_values)
136
137 def testPreprocessBlockedOnCond_WithMultipleProjectIDs(self):
138 blockedon_field = BUILTIN_ISSUE_FIELDS['blockedon']
139 blockedon_id_field = BUILTIN_ISSUE_FIELDS['blockedon_id']
140 self.services.project.TestAddProject('Project1', project_id=1)
141 self.services.project.TestAddProject('Project2', project_id=2)
142 issue1 = fake.MakeTestIssue(
143 project_id=1, local_id=1, summary='sum', status='new', owner_id=2,
144 issue_id=101)
145 issue2 = fake.MakeTestIssue(
146 project_id=2, local_id=2, summary='sum', status='new', owner_id=2,
147 issue_id=102)
148 self.services.issue.TestAddIssue(issue1)
149 self.services.issue.TestAddIssue(issue2)
150
151 for local_ids, expected in (
152 (['Project1:1'], [101]),
153 (['Project1:1', 'Project2:2'], [101, 102])):
154 cond = ast_pb2.MakeCond(
155 ast_pb2.QueryOp.TEXT_HAS, [blockedon_field], local_ids, [])
156 new_cond = ast2ast._PreprocessBlockedOnCond(
157 self.cnxn, cond, [1, 2], self.services, None, True)
158 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
159 self.assertEqual([blockedon_id_field], new_cond.field_defs)
160 self.assertEqual(expected, new_cond.int_values)
161 self.assertEqual([], new_cond.str_values)
162
163 def testPreprocessBlockedOnCond_WithMultipleProjectIDs_NoPrefix(self):
164 blockedon_field = BUILTIN_ISSUE_FIELDS['blockedon']
165 self.services.project.TestAddProject('Project1', project_id=1)
166 self.services.project.TestAddProject('Project2', project_id=2)
167 issue1 = fake.MakeTestIssue(
168 project_id=1, local_id=1, summary='sum', status='new', owner_id=2,
169 issue_id=101)
170 issue2 = fake.MakeTestIssue(
171 project_id=2, local_id=2, summary='sum', status='new', owner_id=2,
172 issue_id=102)
173 self.services.issue.TestAddIssue(issue1)
174 self.services.issue.TestAddIssue(issue2)
175
176 for local_ids in (['1'], ['1', '2'], ['3']):
177 cond = ast_pb2.MakeCond(
178 ast_pb2.QueryOp.TEXT_HAS, [blockedon_field], local_ids, [])
179 with self.assertRaises(ValueError) as cm:
180 ast2ast._PreprocessBlockedOnCond(
181 self.cnxn, cond, [1, 2], self.services, None, True)
182 self.assertEqual(
183 'Searching for issues accross multiple/all projects without '
184 'project prefixes is ambiguous and is currently not supported.',
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100185 str(cm.exception))
Copybara854996b2021-09-07 19:36:02 +0000186
187 def testPreprocessBlockedOnCond_WithExternalIssues(self):
188 blockedon_field = BUILTIN_ISSUE_FIELDS['blockedon']
189 blockedon_id_field = BUILTIN_ISSUE_FIELDS['blockedon_id']
190 self.services.project.TestAddProject('Project1', project_id=1)
191 issue1 = fake.MakeTestIssue(
192 project_id=1, local_id=1, summary='sum', status='new', owner_id=2,
193 issue_id=101)
194 issue2 = fake.MakeTestIssue(
195 project_id=1, local_id=2, summary='sum', status='new', owner_id=2,
196 issue_id=102)
197 self.services.issue.TestAddIssue(issue1)
198 self.services.issue.TestAddIssue(issue2)
199
200 for local_ids, expected_issues, expected_ext_issues in (
201 (['b/1234'], [], ['b/1234']),
202 (['Project1:1', 'b/1234'], [101], ['b/1234']),
203 (['1', 'b/1234', 'b/1551', 'Project1:2'],
204 [101, 102], ['b/1234', 'b/1551'])):
205 cond = ast_pb2.MakeCond(
206 ast_pb2.QueryOp.TEXT_HAS, [blockedon_field], local_ids, [])
207 new_cond = ast2ast._PreprocessBlockedOnCond(
208 self.cnxn, cond, [1], self.services, None, True)
209 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
210 self.assertEqual([blockedon_id_field], new_cond.field_defs)
211 self.assertEqual(expected_issues, new_cond.int_values)
212 self.assertEqual(expected_ext_issues, new_cond.str_values)
213
214 def testPreprocessIsBlockedCond(self):
215 blocked_field = BUILTIN_ISSUE_FIELDS['blockedon_id']
216 for input_op, expected_op in (
217 (ast_pb2.QueryOp.EQ, ast_pb2.QueryOp.IS_DEFINED),
218 (ast_pb2.QueryOp.NE, ast_pb2.QueryOp.IS_NOT_DEFINED)):
219 cond = ast_pb2.MakeCond(
220 input_op, [blocked_field], [], [])
221 new_cond = ast2ast._PreprocessIsBlockedCond(
222 self.cnxn, cond, [100], self.services, None, True)
223 self.assertEqual(expected_op, new_cond.op)
224 self.assertEqual([blocked_field], new_cond.field_defs)
225 self.assertEqual([], new_cond.int_values)
226 self.assertEqual([], new_cond.str_values)
227
228 def testPreprocessHasBlockedOnCond(self):
229 blocked_field = BUILTIN_ISSUE_FIELDS['blockedon_id']
230 for op in (ast_pb2.QueryOp.IS_DEFINED, ast_pb2.QueryOp.IS_NOT_DEFINED):
231 cond = ast_pb2.MakeCond(op, [blocked_field], [], [])
232 new_cond = ast2ast._PreprocessBlockedOnCond(
233 self.cnxn, cond, [100], self.services, None, True)
234 self.assertEqual(op, op)
235 self.assertEqual([blocked_field], new_cond.field_defs)
236 self.assertEqual([], new_cond.int_values)
237 self.assertEqual([], new_cond.str_values)
238
239 def testPreprocessHasBlockingCond(self):
240 blocking_field = BUILTIN_ISSUE_FIELDS['blocking_id']
241 for op in (ast_pb2.QueryOp.IS_DEFINED, ast_pb2.QueryOp.IS_NOT_DEFINED):
242 cond = ast_pb2.MakeCond(op, [blocking_field], [], [])
243 new_cond = ast2ast._PreprocessBlockingCond(
244 self.cnxn, cond, [100], self.services, None, True)
245 self.assertEqual(op, op)
246 self.assertEqual([blocking_field], new_cond.field_defs)
247 self.assertEqual([], new_cond.int_values)
248 self.assertEqual([], new_cond.str_values)
249
250 def testPreprocessBlockingCond_WithSingleProjectID(self):
251 blocking_field = BUILTIN_ISSUE_FIELDS['blocking']
252 blocking_id_field = BUILTIN_ISSUE_FIELDS['blocking_id']
253 self.services.project.TestAddProject('Project1', project_id=1)
254 issue1 = fake.MakeTestIssue(
255 project_id=1, local_id=1, summary='sum', status='new', owner_id=2,
256 issue_id=101)
257 issue2 = fake.MakeTestIssue(
258 project_id=1, local_id=2, summary='sum', status='new', owner_id=2,
259 issue_id=102)
260 self.services.issue.TestAddIssue(issue1)
261 self.services.issue.TestAddIssue(issue2)
262
263 for local_ids, expected in (
264 (['1'], [101]), # One existing issue.
265 (['Project1:1'], [101]), # One existing issue with project prefix.
266 (['1', '2'], [101, 102]), # Two existing issues.
267 (['3'], [])): # Non-existant issue.
268 cond = ast_pb2.MakeCond(
269 ast_pb2.QueryOp.TEXT_HAS, [blocking_field], local_ids, [])
270 new_cond = ast2ast._PreprocessBlockingCond(
271 self.cnxn, cond, [1], self.services, None, True)
272 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
273 self.assertEqual([blocking_id_field], new_cond.field_defs)
274 self.assertEqual(expected, new_cond.int_values)
275 self.assertEqual([], new_cond.str_values)
276
277 def testPreprocessBlockingCond_WithMultipleProjectIDs(self):
278 blocking_field = BUILTIN_ISSUE_FIELDS['blocking']
279 blocking_id_field = BUILTIN_ISSUE_FIELDS['blocking_id']
280 self.services.project.TestAddProject('Project1', project_id=1)
281 self.services.project.TestAddProject('Project2', project_id=2)
282 issue1 = fake.MakeTestIssue(
283 project_id=1, local_id=1, summary='sum', status='new', owner_id=2,
284 issue_id=101)
285 issue2 = fake.MakeTestIssue(
286 project_id=2, local_id=2, summary='sum', status='new', owner_id=2,
287 issue_id=102)
288 self.services.issue.TestAddIssue(issue1)
289 self.services.issue.TestAddIssue(issue2)
290
291 for local_ids, expected in (
292 (['Project1:1'], [101]),
293 (['Project1:1', 'Project2:2'], [101, 102])):
294 cond = ast_pb2.MakeCond(
295 ast_pb2.QueryOp.TEXT_HAS, [blocking_field], local_ids, [])
296 new_cond = ast2ast._PreprocessBlockingCond(
297 self.cnxn, cond, [1, 2], self.services, None, True)
298 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
299 self.assertEqual([blocking_id_field], new_cond.field_defs)
300 self.assertEqual(expected, new_cond.int_values)
301 self.assertEqual([], new_cond.str_values)
302
303 def testPreprocessBlockingCond_WithMultipleProjectIDs_NoPrefix(self):
304 blocking_field = BUILTIN_ISSUE_FIELDS['blocking']
305 self.services.project.TestAddProject('Project1', project_id=1)
306 self.services.project.TestAddProject('Project2', project_id=2)
307 issue1 = fake.MakeTestIssue(
308 project_id=1, local_id=1, summary='sum', status='new', owner_id=2,
309 issue_id=101)
310 issue2 = fake.MakeTestIssue(
311 project_id=2, local_id=2, summary='sum', status='new', owner_id=2,
312 issue_id=102)
313 self.services.issue.TestAddIssue(issue1)
314 self.services.issue.TestAddIssue(issue2)
315
316 for local_ids in (['1'], ['1', '2'], ['3']):
317 cond = ast_pb2.MakeCond(
318 ast_pb2.QueryOp.TEXT_HAS, [blocking_field], local_ids, [])
319 with self.assertRaises(ValueError) as cm:
320 ast2ast._PreprocessBlockingCond(
321 self.cnxn, cond, [1, 2], self.services, None, True)
322 self.assertEqual(
323 'Searching for issues accross multiple/all projects without '
324 'project prefixes is ambiguous and is currently not supported.',
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100325 str(cm.exception))
Copybara854996b2021-09-07 19:36:02 +0000326
327 def testPreprocessBlockingCond_WithExternalIssues(self):
328 blocking_field = BUILTIN_ISSUE_FIELDS['blocking']
329 blocking_id_field = BUILTIN_ISSUE_FIELDS['blocking_id']
330 self.services.project.TestAddProject('Project1', project_id=1)
331 issue1 = fake.MakeTestIssue(
332 project_id=1, local_id=1, summary='sum', status='new', owner_id=2,
333 issue_id=101)
334 issue2 = fake.MakeTestIssue(
335 project_id=1, local_id=2, summary='sum', status='new', owner_id=2,
336 issue_id=102)
337 self.services.issue.TestAddIssue(issue1)
338 self.services.issue.TestAddIssue(issue2)
339
340 for local_ids, expected_issues, expected_ext_issues in (
341 (['b/1234'], [], ['b/1234']),
342 (['Project1:1', 'b/1234'], [101], ['b/1234']),
343 (['1', 'b/1234', 'b/1551', 'Project1:2'],
344 [101, 102], ['b/1234', 'b/1551'])):
345 cond = ast_pb2.MakeCond(
346 ast_pb2.QueryOp.TEXT_HAS, [blocking_field], local_ids, [])
347 new_cond = ast2ast._PreprocessBlockingCond(
348 self.cnxn, cond, [1], self.services, None, True)
349 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
350 self.assertEqual([blocking_id_field], new_cond.field_defs)
351 self.assertEqual(expected_issues, new_cond.int_values)
352 self.assertEqual(expected_ext_issues, new_cond.str_values)
353
354 def testPreprocessMergedIntoCond_WithSingleProjectID(self):
355 field = BUILTIN_ISSUE_FIELDS['mergedinto']
356 id_field = BUILTIN_ISSUE_FIELDS['mergedinto_id']
357 self.services.project.TestAddProject('Project1', project_id=1)
358 issue1 = fake.MakeTestIssue(
359 project_id=1, local_id=1, summary='sum', status='new', owner_id=2,
360 issue_id=101)
361 issue2 = fake.MakeTestIssue(
362 project_id=1, local_id=2, summary='sum', status='new', owner_id=2,
363 issue_id=102)
364 self.services.issue.TestAddIssue(issue1)
365 self.services.issue.TestAddIssue(issue2)
366
367 for local_ids, expected in (
368 (['1'], [101]), # One existing issue.
369 (['Project1:1'], [101]), # One existing issue with project prefix.
370 (['1', '2'], [101, 102]), # Two existing issues.
371 (['3'], [])): # Non-existant issue.
372 cond = ast_pb2.MakeCond(
373 ast_pb2.QueryOp.TEXT_HAS, [field], local_ids, [])
374 new_cond = ast2ast._PreprocessMergedIntoCond(
375 self.cnxn, cond, [1], self.services, None, True)
376 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
377 self.assertEqual([id_field], new_cond.field_defs)
378 self.assertEqual(expected, new_cond.int_values)
379 self.assertEqual([], new_cond.str_values)
380
381 def testPreprocessMergedIntoCond_WithExternalIssues(self):
382 blocking_field = BUILTIN_ISSUE_FIELDS['mergedinto']
383 blocking_id_field = BUILTIN_ISSUE_FIELDS['mergedinto_id']
384 self.services.project.TestAddProject('Project1', project_id=1)
385 issue1 = fake.MakeTestIssue(
386 project_id=1, local_id=1, summary='sum', status='new', owner_id=2,
387 issue_id=101)
388 issue2 = fake.MakeTestIssue(
389 project_id=1, local_id=2, summary='sum', status='new', owner_id=2,
390 issue_id=102)
391 self.services.issue.TestAddIssue(issue1)
392 self.services.issue.TestAddIssue(issue2)
393
394 for local_ids, expected_issues, expected_ext_issues in (
395 (['b/1234'], [], ['b/1234']),
396 (['Project1:1', 'b/1234'], [101], ['b/1234']),
397 (['1', 'b/1234', 'b/1551', 'Project1:2'],
398 [101, 102], ['b/1234', 'b/1551'])):
399 cond = ast_pb2.MakeCond(
400 ast_pb2.QueryOp.TEXT_HAS, [blocking_field], local_ids, [])
401 new_cond = ast2ast._PreprocessMergedIntoCond(
402 self.cnxn, cond, [1], self.services, None, True)
403 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
404 self.assertEqual([blocking_id_field], new_cond.field_defs)
405 self.assertEqual(expected_issues, new_cond.int_values)
406 self.assertEqual(expected_ext_issues, new_cond.str_values)
407
408 def testPreprocessIsSpamCond(self):
409 spam_field = BUILTIN_ISSUE_FIELDS['spam']
410 is_spam_field = BUILTIN_ISSUE_FIELDS['is_spam']
411 for input_op, int_values in (
412 (ast_pb2.QueryOp.EQ, [1]), (ast_pb2.QueryOp.NE, [0])):
413 cond = ast_pb2.MakeCond(
414 input_op, [spam_field], [], [])
415 new_cond = ast2ast._PreprocessIsSpamCond(
416 self.cnxn, cond, [789], self.services, None, True)
417 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
418 self.assertEqual([is_spam_field], new_cond.field_defs)
419 self.assertEqual(int_values, new_cond.int_values)
420 self.assertEqual([], new_cond.str_values)
421
422 def testPreprocessStatusCond(self):
423 status_field = BUILTIN_ISSUE_FIELDS['status']
424 status_id_field = BUILTIN_ISSUE_FIELDS['status_id']
425
426 cond = ast_pb2.MakeCond(
427 ast_pb2.QueryOp.IS_DEFINED, [status_field], [], [])
428 new_cond = ast2ast._PreprocessStatusCond(
429 self.cnxn, cond, [789], self.services, self.config, True)
430 self.assertEqual(ast_pb2.QueryOp.IS_DEFINED, new_cond.op)
431 self.assertEqual([status_id_field], new_cond.field_defs)
432 self.assertEqual([], new_cond.int_values)
433 self.assertEqual([], new_cond.str_values)
434
435 cond = ast_pb2.MakeCond(
436 ast_pb2.QueryOp.EQ, [status_field], ['New', 'Assigned'], [])
437 new_cond = ast2ast._PreprocessStatusCond(
438 self.cnxn, cond, [789], self.services, self.config, True)
439 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
440 self.assertEqual([status_id_field], new_cond.field_defs)
441 self.assertEqual([0, 1], new_cond.int_values)
442 self.assertEqual([], new_cond.str_values)
443
444 cond = ast_pb2.MakeCond(
445 ast_pb2.QueryOp.TEXT_HAS, [status_field], [], [])
446 new_cond = ast2ast._PreprocessStatusCond(
447 self.cnxn, cond, [789], self.services, self.config, True)
448 self.assertEqual([], new_cond.int_values)
449
450 def testPrefixRegex(self):
451 cond = ast_pb2.MakeCond(
452 ast_pb2.QueryOp.IS_DEFINED, [BUILTIN_ISSUE_FIELDS['label']],
453 ['Priority', 'Severity'], [])
454 regex = ast2ast._MakePrefixRegex(cond)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100455 self.assertRegex('Priority-1', regex)
456 self.assertRegex('Severity-3', regex)
457 self.assertNotRegex('My-Priority', regex)
Copybara854996b2021-09-07 19:36:02 +0000458
459 def testKeyValueRegex(self):
460 cond = ast_pb2.MakeCond(
461 ast_pb2.QueryOp.KEY_HAS, [BUILTIN_ISSUE_FIELDS['label']],
462 ['Type-Feature', 'Type-Security'], [])
463 regex = ast2ast._MakeKeyValueRegex(cond)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100464 self.assertRegex('Type-Feature', regex)
465 self.assertRegex('Type-Bug-Security', regex)
466 self.assertNotRegex('Type-Bug', regex)
467 self.assertNotRegex('Security-Feature', regex)
Copybara854996b2021-09-07 19:36:02 +0000468
469 def testKeyValueRegex_multipleKeys(self):
470 cond = ast_pb2.MakeCond(
471 ast_pb2.QueryOp.KEY_HAS, [BUILTIN_ISSUE_FIELDS['label']],
472 ['Type-Bug', 'Security-Bug'], [])
473 with self.assertRaises(ValueError):
474 ast2ast._MakeKeyValueRegex(cond)
475
476 def testWordBoundryRegex(self):
477 cond = ast_pb2.MakeCond(
478 ast_pb2.QueryOp.TEXT_HAS, [BUILTIN_ISSUE_FIELDS['label']],
479 ['Type-Bug'], [])
480 regex = ast2ast._MakeKeyValueRegex(cond)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100481 self.assertRegex('Type-Bug-Security', regex)
482 self.assertNotRegex('Type-BugSecurity', regex)
Copybara854996b2021-09-07 19:36:02 +0000483
484 def testPreprocessLabelCond(self):
485 label_field = BUILTIN_ISSUE_FIELDS['label']
486 label_id_field = BUILTIN_ISSUE_FIELDS['label_id']
487
488 cond = ast_pb2.MakeCond(
489 ast_pb2.QueryOp.IS_DEFINED, [label_field], ['Priority'], [])
490 new_cond = ast2ast._PreprocessLabelCond(
491 self.cnxn, cond, [789], self.services, self.config, True)
492 self.assertEqual(ast_pb2.QueryOp.IS_DEFINED, new_cond.op)
493 self.assertEqual([label_id_field], new_cond.field_defs)
494 self.assertEqual([1, 2, 3], new_cond.int_values)
495 self.assertEqual([], new_cond.str_values)
496
497 self.services.config.TestAddLabelsDict(
498 {
499 'Priority-Low': 0,
500 'Priority-High': 1
501 })
502 cond = ast_pb2.MakeCond(
503 ast_pb2.QueryOp.EQ, [label_field],
504 ['Priority-Low', 'Priority-High'], [])
505 self.services.config.TestAddLabelsDict(
506 {
507 'Priority-Low': 0,
508 'Priority-High': 1
509 })
510 new_cond = ast2ast._PreprocessLabelCond(
511 self.cnxn, cond, [789], self.services, self.config, True)
512 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
513 self.assertEqual([label_id_field], new_cond.field_defs)
514 self.assertEqual([0, 1], new_cond.int_values)
515 self.assertEqual([], new_cond.str_values)
516
517 cond = ast_pb2.MakeCond(
518 ast_pb2.QueryOp.KEY_HAS, [label_field],
519 ['Priority-Low', 'Priority-High'], [])
520 new_cond = ast2ast._PreprocessLabelCond(
521 self.cnxn, cond, [789], self.services, self.config, True)
522 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
523 self.assertEqual([label_id_field], new_cond.field_defs)
524 self.assertEqual([1, 2, 3], new_cond.int_values)
525 self.assertEqual([], new_cond.str_values)
526
527 def testPreprocessComponentCond_QuickOR(self):
528 component_field = BUILTIN_ISSUE_FIELDS['component']
529 component_id_field = BUILTIN_ISSUE_FIELDS['component_id']
530
531 cond = ast_pb2.MakeCond(
532 ast_pb2.QueryOp.IS_DEFINED, [component_field], ['UI', 'DB'], [])
533 new_cond = ast2ast._PreprocessComponentCond(
534 self.cnxn, cond, [789], self.services, self.config, True)
535 self.assertEqual(ast_pb2.QueryOp.IS_DEFINED, new_cond.op)
536 self.assertEqual([component_id_field], new_cond.field_defs)
537 self.assertEqual([101, 102, 201], new_cond.int_values)
538 self.assertEqual([], new_cond.str_values)
539
540 cond = ast_pb2.MakeCond(
541 ast_pb2.QueryOp.TEXT_HAS, [component_field], ['UI', 'DB'], [])
542 new_cond = ast2ast._PreprocessComponentCond(
543 self.cnxn, cond, [789], self.services, self.config, True)
544 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
545 self.assertEqual([component_id_field], new_cond.field_defs)
546 self.assertEqual([101, 102, 201], new_cond.int_values)
547 self.assertEqual([], new_cond.str_values)
548
549 cond = ast_pb2.MakeCond(
550 ast_pb2.QueryOp.TEXT_HAS, [component_field], [], [])
551 new_cond = ast2ast._PreprocessComponentCond(
552 self.cnxn, cond, [789], self.services, self.config, True)
553 self.assertEqual([], new_cond.int_values)
554
555 cond = ast_pb2.MakeCond(
556 ast_pb2.QueryOp.TEXT_HAS, [component_field], ['unknown@example.com'],
557 [])
558 new_cond = ast2ast._PreprocessComponentCond(
559 self.cnxn, cond, [789], self.services, self.config, True)
560 self.assertEqual([], new_cond.int_values)
561
562 def testPreprocessComponentCond_RootedAndNonRooted(self):
563 component_field = BUILTIN_ISSUE_FIELDS['component']
564 component_id_field = BUILTIN_ISSUE_FIELDS['component_id']
565
566 cond = ast_pb2.MakeCond(
567 ast_pb2.QueryOp.TEXT_HAS, [component_field], ['UI'], [])
568 new_cond = ast2ast._PreprocessComponentCond(
569 self.cnxn, cond, [789], self.services, self.config, True)
570 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
571 self.assertEqual([component_id_field], new_cond.field_defs)
572 self.assertEqual([101, 102], new_cond.int_values)
573 self.assertEqual([], new_cond.str_values)
574
575 cond = ast_pb2.MakeCond(
576 ast_pb2.QueryOp.EQ, [component_field], ['UI'], [])
577 new_cond = ast2ast._PreprocessComponentCond(
578 self.cnxn, cond, [789], self.services, self.config, True)
579 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
580 self.assertEqual([component_id_field], new_cond.field_defs)
581 self.assertEqual([101], new_cond.int_values)
582 self.assertEqual([], new_cond.str_values)
583
584 def testPreprocessExactUsers_IsDefined(self):
585 """Anyone can search for [has:owner]."""
586 cond = ast_pb2.MakeCond(
587 ast_pb2.QueryOp.IS_DEFINED, [OWNER_FIELD], ['a@example.com'], [])
588 new_cond = ast2ast._PreprocessExactUsers(
589 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], True)
590 self.assertEqual(ast_pb2.QueryOp.IS_DEFINED, new_cond.op)
591 self.assertEqual([OWNER_ID_FIELD], new_cond.field_defs)
592 self.assertEqual([], new_cond.int_values)
593 self.assertEqual([], new_cond.str_values)
594
595 # Non-members do not raise an exception.
596 ast2ast._PreprocessExactUsers(
597 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], False)
598
599
600 def testPreprocessExactUsers_UserFound(self):
601 """Anyone can search for a know user, [owner:user@example.com]."""
602 cond = ast_pb2.MakeCond(
603 ast_pb2.QueryOp.TEXT_HAS, [OWNER_FIELD], ['a@example.com'], [])
604 new_cond = ast2ast._PreprocessExactUsers(
605 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], True)
606 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
607 self.assertEqual([OWNER_ID_FIELD], new_cond.field_defs)
608 self.assertEqual([111], new_cond.int_values)
609 self.assertEqual([], new_cond.str_values)
610
611 # Non-members do not raise an exception.
612 ast2ast._PreprocessExactUsers(
613 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], False)
614
615 def testPreprocessExactUsers_UserSpecifiedByID(self):
616 """Anyone may search for users by ID, [owner:1234]."""
617 cond = ast_pb2.MakeCond(
618 ast_pb2.QueryOp.TEXT_HAS, [OWNER_FIELD], ['123'], [])
619 new_cond = ast2ast._PreprocessExactUsers(
620 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], True)
621 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
622 self.assertEqual([OWNER_ID_FIELD], new_cond.field_defs)
623 self.assertEqual([123], new_cond.int_values)
624 self.assertEqual([], new_cond.str_values)
625
626 # Non-members do not raise an exception.
627 ast2ast._PreprocessExactUsers(
628 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], False)
629
630 def testPreprocessExactUsers_NonEquality(self):
631 """Project members may search for [owner_id>111]."""
632 cond = ast_pb2.MakeCond(
633 ast_pb2.QueryOp.GE, [OWNER_ID_FIELD], ['111'], [])
634 new_cond = ast2ast._PreprocessExactUsers(
635 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], True)
636 self.assertEqual(cond, new_cond)
637
638 with self.assertRaises(ast2ast.MalformedQuery):
639 ast2ast._PreprocessExactUsers(
640 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], False)
641
642 def testPreprocessExactUsers_UserNotFound(self):
643 cond = ast_pb2.MakeCond(
644 ast_pb2.QueryOp.TEXT_HAS, [OWNER_FIELD], ['unknown@example.com'], [])
645 new_cond = ast2ast._PreprocessExactUsers(
646 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], True)
647 self.assertEqual(cond, new_cond)
648
649 with self.assertRaises(ast2ast.MalformedQuery):
650 ast2ast._PreprocessExactUsers(
651 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], False)
652
653 def testPreprocessExactUsers_KeywordMe(self):
654 cond = ast_pb2.MakeCond(
655 ast_pb2.QueryOp.TEXT_HAS, [OWNER_FIELD], ['me'], [])
656 new_cond = ast2ast._PreprocessExactUsers(
657 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], True)
658 self.assertEqual(cond, new_cond)
659
660 new_cond = ast2ast._PreprocessExactUsers(
661 self.cnxn, cond, self.services.user, [OWNER_ID_FIELD], False)
662 self.assertEqual(cond, new_cond)
663
664 def testPreprocessHotlistCond(self):
665 hotlist_field = BUILTIN_ISSUE_FIELDS['hotlist']
666 hotlist_id_field = BUILTIN_ISSUE_FIELDS['hotlist_id']
667
668 self.services.user.TestAddUser('gatsby@example.org', 111)
669 self.services.user.TestAddUser('daisy@example.com', 222)
670 self.services.user.TestAddUser('nick@example.org', 333)
671
672 # Setup hotlists
673 self.services.features.TestAddHotlist(
674 'Hotlist1', owner_ids=[111], hotlist_id=10)
675 self.services.features.TestAddHotlist(
676 'Hotlist2', owner_ids=[111], hotlist_id=20)
677 self.services.features.TestAddHotlist(
678 'Hotlist3', owner_ids=[222], hotlist_id=30)
679 self.services.features.TestAddHotlist(
680 'Hotlist4', owner_ids=[222], hotlist_id=40)
681 self.services.features.TestAddHotlist(
682 'Hotlist5', owner_ids=[333], hotlist_id=50)
683 self.services.features.TestAddHotlist(
684 'Hotlist6', owner_ids=[333], hotlist_id=60)
685
686 hotlist_query_vals = [
687 'gatsby@example.org:Hotlist1',
688 'nick@example.org:',
689 'daisy@example.com:Hotlist3', 'Hotlist4']
690 cond = ast_pb2.MakeCond(
691 ast_pb2.QueryOp.TEXT_HAS, [hotlist_field], hotlist_query_vals, [])
692 actual = ast2ast._PreprocessHotlistCond(
693 self.cnxn, cond, [1], self.services, None, True)
694 self.assertEqual(ast_pb2.QueryOp.EQ, actual.op)
695 self.assertEqual([hotlist_id_field], actual.field_defs)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100696 six.assertCountEqual(self, [10, 30, 40, 50, 60], actual.int_values)
Copybara854996b2021-09-07 19:36:02 +0000697
698 def testPreprocessHotlistCond_UserNotFound(self):
699 hotlist_field = BUILTIN_ISSUE_FIELDS['hotlist']
700 hotlist_query_vals = ['gatsby@chromium.org:Hotlist1', 'Hotlist3']
701 cond = ast_pb2.MakeCond(
702 ast_pb2.QueryOp.TEXT_HAS, [hotlist_field], hotlist_query_vals, [])
703 actual = ast2ast._PreprocessHotlistCond(
704 self.cnxn, cond, [1], self.services, None, True)
705 self.assertEqual(cond, actual)
706
707 def testPreprocessCustomCond_User(self):
708 fd = tracker_pb2.FieldDef(
709 field_id=1, field_name='TPM',
710 field_type=tracker_pb2.FieldTypes.USER_TYPE)
711 cond = ast_pb2.MakeCond(
712 ast_pb2.QueryOp.TEXT_HAS, [fd], ['a@example.com'], [])
713 new_cond = ast2ast._PreprocessCustomCond(
714 self.cnxn, cond, self.services, True)
715 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
716 self.assertEqual(cond.field_defs, new_cond.field_defs)
717 self.assertEqual([111], new_cond.int_values)
718 self.assertEqual([], new_cond.str_values)
719
720 cond = ast_pb2.MakeCond(
721 ast_pb2.QueryOp.TEXT_HAS, [fd], ['111'], [])
722 new_cond = ast2ast._PreprocessCustomCond(
723 self.cnxn, cond, self.services, True)
724 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
725 self.assertEqual(cond.field_defs, new_cond.field_defs)
726 self.assertEqual([111], new_cond.int_values)
727 self.assertEqual([], new_cond.str_values)
728
729 cond = ast_pb2.MakeCond(
730 ast_pb2.QueryOp.TEXT_HAS, [fd], ['unknown@example.com'], [])
731 new_cond = ast2ast._PreprocessCustomCond(
732 self.cnxn, cond, self.services, True)
733 self.assertEqual(cond, new_cond)
734
735 def testPreprocessCustomCond_NonUser(self):
736 fd = tracker_pb2.FieldDef(
737 field_id=1, field_name='TPM',
738 field_type=tracker_pb2.FieldTypes.INT_TYPE)
739 cond = ast_pb2.MakeCond(
740 ast_pb2.QueryOp.TEXT_HAS, [fd], ['foo'], [123])
741 new_cond = ast2ast._PreprocessCustomCond(
742 self.cnxn, cond, self.services, True)
743 self.assertEqual(cond, new_cond)
744
745 fd.field_type = tracker_pb2.FieldTypes.STR_TYPE
746 new_cond = ast2ast._PreprocessCustomCond(
747 self.cnxn, cond, self.services, True)
748 self.assertEqual(cond, new_cond)
749
750 def testPreprocessCustomCond_ApprovalUser(self):
751 fd = tracker_pb2.FieldDef(
752 field_id=1, field_name='UXReview',
753 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE)
754 cond = ast_pb2.MakeCond(
755 ast_pb2.QueryOp.TEXT_HAS, [fd], ['a@example.com'], [],
756 key_suffix=query2ast.APPROVER_SUFFIX)
757 new_cond = ast2ast._PreprocessCustomCond(
758 self.cnxn, cond, self.services, True)
759 self.assertEqual(ast_pb2.QueryOp.EQ, new_cond.op)
760 self.assertEqual(cond.field_defs, new_cond.field_defs)
761 self.assertEqual([111], new_cond.int_values)
762 self.assertEqual([], new_cond.str_values)
763 self.assertEqual(query2ast.APPROVER_SUFFIX, new_cond.key_suffix)
764
765 def testPreprocessCond_NoChange(self):
766 cond = ast_pb2.MakeCond(ast_pb2.QueryOp.TEXT_HAS, [ANY_FIELD], ['foo'], [])
767 self.assertEqual(
768 cond, ast2ast._PreprocessCond(self.cnxn, cond, [], None, None, True))
769
770 def testTextOpToIntOp(self):
771 self.assertEqual(ast_pb2.QueryOp.EQ,
772 ast2ast._TextOpToIntOp(ast_pb2.QueryOp.TEXT_HAS))
773 self.assertEqual(ast_pb2.QueryOp.EQ,
774 ast2ast._TextOpToIntOp(ast_pb2.QueryOp.KEY_HAS))
775 self.assertEqual(ast_pb2.QueryOp.NE,
776 ast2ast._TextOpToIntOp(ast_pb2.QueryOp.NOT_TEXT_HAS))
777
778 for enum_name, _enum_id in ast_pb2.QueryOp.to_dict().items():
779 no_change_op = ast_pb2.QueryOp(enum_name)
780 if no_change_op not in (
781 ast_pb2.QueryOp.TEXT_HAS,
782 ast_pb2.QueryOp.NOT_TEXT_HAS,
783 ast_pb2.QueryOp.KEY_HAS):
784 self.assertEqual(no_change_op,
785 ast2ast._TextOpToIntOp(no_change_op))