blob: fbd87df4f7d00efd6b116075ff095baac5a0e96f [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# -*- coding: utf-8 -*-
2# Copyright 2018 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style
4# license that can be found in the LICENSE file or at
5# https://developers.google.com/open-source/licenses/bsd
6
7"""Unit tests for chart_svc module."""
8from __future__ import print_function
9from __future__ import division
10from __future__ import absolute_import
11
12import datetime
13import mox
14import re
15import settings
16import unittest
17
18from google.appengine.ext import testbed
19
20from services import chart_svc
21from services import config_svc
22from services import service_manager
23from framework import permissions
24from framework import sql
25from proto import ast_pb2
26from proto import tracker_pb2
27from search import ast2select
28from search import search_helpers
29from testing import fake
30from tracker import tracker_bizobj
31
32
33def MakeChartService(my_mox, config):
34 chart_service = chart_svc.ChartService(config)
35 for table_var in ['issuesnapshot_tbl', 'issuesnapshot2label_tbl',
36 'issuesnapshot2component_tbl', 'issuesnapshot2cctbl', 'labeldef_tbl']:
37 setattr(chart_service, table_var, my_mox.CreateMock(sql.SQLTableManager))
38 return chart_service
39
40
41class ChartServiceTest(unittest.TestCase):
42
43 def setUp(self):
44 self.testbed = testbed.Testbed()
45 self.testbed.activate()
46 self.testbed.init_memcache_stub()
47
48 self.mox = mox.Mox()
49 self.cnxn = self.mox.CreateMock(sql.MonorailConnection)
50 self.services = service_manager.Services()
51 self.config_service = fake.ConfigService()
52 self.services.config = self.config_service
53 self.services.chart = MakeChartService(self.mox, self.config_service)
54 self.services.issue = fake.IssueService()
55 self.mox.StubOutWithMock(self.services.chart, '_QueryToWhere')
56 self.mox.StubOutWithMock(search_helpers, 'GetPersonalAtRiskLabelIDs')
57 self.mox.StubOutWithMock(settings, 'num_logical_shards')
58 settings.num_logical_shards = 1
59 self.mox.StubOutWithMock(self.services.chart, '_currentTime')
60
61 self.defaultLeftJoins = [
62 ('Issue ON IssueSnapshot.issue_id = Issue.id', []),
63 ('Issue2Label AS Forbidden_label'
64 ' ON Issue.id = Forbidden_label.issue_id'
65 ' AND Forbidden_label.label_id IN (%s,%s)', [91, 81]),
66 ('Issue2Cc AS I2cc'
67 ' ON Issue.id = I2cc.issue_id'
68 ' AND I2cc.cc_id IN (%s,%s)', [10, 20]),
69 ]
70 self.defaultWheres = [
71 ('IssueSnapshot.period_start <= %s', [1514764800]),
72 ('IssueSnapshot.period_end > %s', [1514764800]),
73 ('Issue.is_spam = %s', [False]),
74 ('Issue.deleted = %s', [False]),
75 ('IssueSnapshot.project_id IN (%s)', [789]),
76 ('(Issue.reporter_id IN (%s,%s)'
77 ' OR Issue.owner_id IN (%s,%s)'
78 ' OR I2cc.cc_id IS NOT NULL'
79 ' OR Forbidden_label.label_id IS NULL)',
80 [10, 20, 10, 20]
81 ),
82 ]
83
84 def tearDown(self):
85 self.testbed.deactivate()
86 self.mox.UnsetStubs()
87 self.mox.ResetAll()
88
89 def _verifySQL(self, cols, left_joins, where, group_by=None):
90 for col in cols:
91 self.assertTrue(sql._IsValidColumnName(col))
92 for join_str, _ in left_joins:
93 self.assertTrue(sql._IsValidJoin(join_str))
94 for where_str, _ in where:
95 self.assertTrue(sql._IsValidWhereCond(where_str))
96 if group_by:
97 for groupby_str in group_by:
98 self.assertTrue(sql._IsValidGroupByTerm(groupby_str))
99
100 def testQueryIssueSnapshots_InvalidGroupBy(self):
101 """Make sure the `group_by` argument is checked."""
102 project = fake.Project(project_id=789)
103 perms = permissions.USER_PERMISSIONSET
104 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
105 self.config_service, [10, 20], project,
106 perms).AndReturn([91, 81])
107 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
108 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
109 mox.IgnoreArg()).AndReturn(([], [], []))
110
111 self.mox.ReplayAll()
112 with self.assertRaises(ValueError):
113 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
114 unixtime=1514764800, effective_ids=[10, 20], project=project,
115 perms=perms, group_by='rutabaga', label_prefix='rutabaga')
116 self.mox.VerifyAll()
117
118 def testQueryIssueSnapshots_NoLabelPrefix(self):
119 """Make sure the `label_prefix` argument is required."""
120 project = fake.Project(project_id=789)
121 perms = permissions.USER_PERMISSIONSET
122 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
123 self.config_service, [10, 20], project,
124 perms).AndReturn([91, 81])
125 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
126 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
127 mox.IgnoreArg()).AndReturn(([], [], []))
128
129 self.mox.ReplayAll()
130 with self.assertRaises(ValueError):
131 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
132 unixtime=1514764800, effective_ids=[10, 20], project=project,
133 perms=perms, group_by='label')
134 self.mox.VerifyAll()
135
136 def testQueryIssueSnapshots_Impossible(self):
137 """We give an error message when a query could never have results."""
138 project = fake.Project(project_id=789)
139 perms = permissions.USER_PERMISSIONSET
140 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
141 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
142 mox.IgnoreArg()).AndRaise(ast2select.NoPossibleResults())
143 self.mox.ReplayAll()
144 total, errors, limit_reached = self.services.chart.QueryIssueSnapshots(
145 self.cnxn, self.services,
146 unixtime=1514764800, effective_ids=[10, 20], project=project,
147 perms=perms, query='prefix=')
148 self.mox.VerifyAll()
149 self.assertEqual({}, total)
150 self.assertEqual(['Invalid query.'], errors)
151 self.assertFalse(limit_reached)
152
153 def testQueryIssueSnapshots_Components(self):
154 """Test a burndown query from a regular user grouping by component."""
155 project = fake.Project(project_id=789)
156 perms = permissions.PermissionSet(['BarPerm'])
157 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
158 self.config_service, [10, 20], project,
159 perms).AndReturn([91, 81])
160
161 cols = [
162 'Comp.path',
163 'COUNT(IssueSnapshot.issue_id)'
164 ]
165 left_joins = self.defaultLeftJoins + [
166 ('IssueSnapshot2Component AS Is2c'
167 ' ON Is2c.issuesnapshot_id = IssueSnapshot.id', []),
168 ('ComponentDef AS Comp ON Comp.id = Is2c.component_id', [])
169 ]
170 where = self.defaultWheres
171 group_by = ['Comp.path']
172 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols, where,
173 left_joins, group_by, shard_id=0)
174
175 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
176 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
177 mox.IgnoreArg()).AndReturn(([], [], []))
178 self.cnxn.Execute(stmt, stmt_args, shard_id=0).AndReturn([])
179
180 self._verifySQL(cols, left_joins, where, group_by)
181
182 self.mox.ReplayAll()
183 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
184 unixtime=1514764800, effective_ids=[10, 20], project=project,
185 perms=perms, group_by='component')
186 self.mox.VerifyAll()
187
188 def testQueryIssueSnapshots_Labels(self):
189 """Test a burndown query from a regular user grouping by label."""
190 project = fake.Project(project_id=789)
191 perms = permissions.PermissionSet(['BarPerm'])
192 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
193 self.config_service, [10, 20], project,
194 perms).AndReturn([91, 81])
195
196 cols = [
197 'Lab.label',
198 'COUNT(IssueSnapshot.issue_id)',
199 ]
200 left_joins = self.defaultLeftJoins + [
201 ('IssueSnapshot2Label AS Is2l'
202 ' ON Is2l.issuesnapshot_id = IssueSnapshot.id', []),
203 ('LabelDef AS Lab ON Lab.id = Is2l.label_id', [])
204 ]
205 where = self.defaultWheres + [
206 ('LOWER(Lab.label) LIKE %s', ['foo-%']),
207 ]
208 group_by = ['Lab.label']
209 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols, where,
210 left_joins, group_by, shard_id=0)
211
212 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
213 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
214 mox.IgnoreArg()).AndReturn(([], [], []))
215 self.cnxn.Execute(stmt, stmt_args, shard_id=0).AndReturn([])
216
217 self._verifySQL(cols, left_joins, where, group_by)
218
219 self.mox.ReplayAll()
220 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
221 unixtime=1514764800, effective_ids=[10, 20], project=project,
222 perms=perms, group_by='label', label_prefix='Foo')
223 self.mox.VerifyAll()
224
225 def testQueryIssueSnapshots_Open(self):
226 """Test a burndown query from a regular user grouping
227 by status is open or closed."""
228 project = fake.Project(project_id=789)
229 perms = permissions.PermissionSet(['BarPerm'])
230 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
231 self.config_service, [10, 20], project,
232 perms).AndReturn([91, 81])
233
234 cols = [
235 'IssueSnapshot.is_open',
236 'COUNT(IssueSnapshot.issue_id) AS issue_count',
237 ]
238
239 left_joins = self.defaultLeftJoins
240 where = self.defaultWheres
241 group_by = ['IssueSnapshot.is_open']
242 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols, where,
243 left_joins, group_by, shard_id=0)
244
245 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
246 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
247 mox.IgnoreArg()).AndReturn(([], [], []))
248 self.cnxn.Execute(stmt, stmt_args, shard_id=0).AndReturn([])
249
250 self._verifySQL(cols, left_joins, where, group_by)
251
252 self.mox.ReplayAll()
253 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
254 unixtime=1514764800, effective_ids=[10, 20], project=project,
255 perms=perms, group_by='open')
256 self.mox.VerifyAll()
257
258 def testQueryIssueSnapshots_Status(self):
259 """Test a burndown query from a regular user grouping by open status."""
260 project = fake.Project(project_id=789)
261 perms = permissions.PermissionSet(['BarPerm'])
262 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
263 self.config_service, [10, 20], project,
264 perms).AndReturn([91, 81])
265
266 cols = [
267 'Stats.status',
268 'COUNT(IssueSnapshot.issue_id)',
269 ]
270 left_joins = self.defaultLeftJoins + [
271 ('StatusDef AS Stats ON ' \
272 'Stats.id = IssueSnapshot.status_id', [])
273 ]
274 where = self.defaultWheres
275 group_by = ['Stats.status']
276 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols, where,
277 left_joins, group_by, shard_id=0)
278
279 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
280 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
281 mox.IgnoreArg()).AndReturn(([], [], []))
282 self.cnxn.Execute(stmt, stmt_args, shard_id=0).AndReturn([])
283
284 self._verifySQL(cols, left_joins, where, group_by)
285
286 self.mox.ReplayAll()
287 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
288 unixtime=1514764800, effective_ids=[10, 20], project=project,
289 perms=perms, group_by='status')
290 self.mox.VerifyAll()
291
292 def testQueryIssueSnapshots_Hotlist(self):
293 """Test a QueryIssueSnapshots when a hotlist is passed."""
294 hotlist = fake.Hotlist('hotlist_rutabaga', 19191)
295 project = fake.Project(project_id=789)
296 perms = permissions.PermissionSet(['BarPerm'])
297 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
298 self.config_service, [10, 20], project,
299 perms).AndReturn([91, 81])
300
301 cols = [
302 'IssueSnapshot.issue_id',
303 ]
304 left_joins = self.defaultLeftJoins + [
305 (('IssueSnapshot2Hotlist AS Is2h'
306 ' ON Is2h.issuesnapshot_id = IssueSnapshot.id'
307 ' AND Is2h.hotlist_id = %s'), [hotlist.hotlist_id]),
308 ]
309 where = self.defaultWheres + [
310 ('Is2h.hotlist_id = %s', [hotlist.hotlist_id]),
311 ]
312 group_by = []
313 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols, where,
314 left_joins, group_by, shard_id=0)
315
316 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
317 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
318 mox.IgnoreArg()).AndReturn(([], [], []))
319 self.cnxn.Execute(stmt, stmt_args, shard_id=0).AndReturn([])
320
321 self._verifySQL(cols, left_joins, where, group_by)
322
323 self.mox.ReplayAll()
324 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
325 unixtime=1514764800, effective_ids=[10, 20], project=project,
326 perms=perms, hotlist=hotlist)
327 self.mox.VerifyAll()
328
329 def testQueryIssueSnapshots_Owner(self):
330 """Test a burndown query from a regular user grouping by owner."""
331 project = fake.Project(project_id=789)
332 perms = permissions.PermissionSet(['BarPerm'])
333 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
334 self.config_service, [10, 20], project,
335 perms).AndReturn([91, 81])
336 cols = [
337 'IssueSnapshot.owner_id',
338 'COUNT(IssueSnapshot.issue_id)',
339 ]
340 left_joins = self.defaultLeftJoins
341 where = self.defaultWheres
342 group_by = ['IssueSnapshot.owner_id']
343 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols, where,
344 left_joins, group_by, shard_id=0)
345
346 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
347 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
348 mox.IgnoreArg()).AndReturn(([], [], []))
349 self.cnxn.Execute(stmt, stmt_args, shard_id=0).AndReturn([])
350
351 self._verifySQL(cols, left_joins, where, group_by)
352
353 self.mox.ReplayAll()
354 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
355 unixtime=1514764800, effective_ids=[10, 20], project=project,
356 perms=perms, group_by='owner')
357 self.mox.VerifyAll()
358
359 def testQueryIssueSnapshots_NoGroupBy(self):
360 """Test a burndown query from a regular user with no grouping."""
361 project = fake.Project(project_id=789)
362 perms = permissions.PermissionSet(['BarPerm'])
363 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
364 self.config_service, [10, 20], project,
365 perms).AndReturn([91, 81])
366
367 cols = [
368 'IssueSnapshot.issue_id',
369 ]
370 left_joins = self.defaultLeftJoins
371 where = self.defaultWheres
372 group_by = None
373 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols, where,
374 left_joins, group_by, shard_id=0)
375
376 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
377 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
378 mox.IgnoreArg()).AndReturn(([], [], []))
379 self.cnxn.Execute(stmt, stmt_args, shard_id=0).AndReturn([])
380
381 self._verifySQL(cols, left_joins, where)
382
383 self.mox.ReplayAll()
384 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
385 unixtime=1514764800, effective_ids=[10, 20], project=project,
386 perms=perms, group_by=None, label_prefix='Foo')
387 self.mox.VerifyAll()
388
389 def testQueryIssueSnapshots_LabelsNotLoggedInUser(self):
390 """Tests fetching burndown snapshot counts grouped by labels
391 for a user who is not logged in. Also no restricted labels are
392 present.
393 """
394 project = fake.Project(project_id=789)
395 perms = permissions.READ_ONLY_PERMISSIONSET
396 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
397 self.config_service, set([]), project,
398 perms).AndReturn([91, 81])
399
400 cols = [
401 'Lab.label',
402 'COUNT(IssueSnapshot.issue_id)',
403 ]
404 left_joins = [
405 ('Issue ON IssueSnapshot.issue_id = Issue.id', []),
406 ('Issue2Label AS Forbidden_label'
407 ' ON Issue.id = Forbidden_label.issue_id'
408 ' AND Forbidden_label.label_id IN (%s,%s)', [91, 81]),
409 ('IssueSnapshot2Label AS Is2l'
410 ' ON Is2l.issuesnapshot_id = IssueSnapshot.id', []),
411 ('LabelDef AS Lab ON Lab.id = Is2l.label_id', []),
412 ]
413 where = [
414 ('IssueSnapshot.period_start <= %s', [1514764800]),
415 ('IssueSnapshot.period_end > %s', [1514764800]),
416 ('Issue.is_spam = %s', [False]),
417 ('Issue.deleted = %s', [False]),
418 ('IssueSnapshot.project_id IN (%s)', [789]),
419 ('Forbidden_label.label_id IS NULL', []),
420 ('LOWER(Lab.label) LIKE %s', ['foo-%']),
421 ]
422 group_by = ['Lab.label']
423 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols, where,
424 left_joins, group_by, shard_id=0)
425
426 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
427 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
428 mox.IgnoreArg()).AndReturn(([], [], []))
429 self.cnxn.Execute(stmt, stmt_args, shard_id=0).AndReturn([])
430
431 self._verifySQL(cols, left_joins, where, group_by)
432
433 self.mox.ReplayAll()
434 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
435 unixtime=1514764800, effective_ids=set([]), project=project,
436 perms=perms, group_by='label', label_prefix='Foo')
437 self.mox.VerifyAll()
438
439 def testQueryIssueSnapshots_NoRestrictedLabels(self):
440 """Test a label burndown query when the project has no restricted labels."""
441 project = fake.Project(project_id=789)
442 perms = permissions.USER_PERMISSIONSET
443 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
444 self.config_service, [10, 20], project,
445 perms).AndReturn([])
446
447 cols = [
448 'Lab.label',
449 'COUNT(IssueSnapshot.issue_id)',
450 ]
451 left_joins = [
452 ('Issue ON IssueSnapshot.issue_id = Issue.id', []),
453 ('Issue2Cc AS I2cc'
454 ' ON Issue.id = I2cc.issue_id'
455 ' AND I2cc.cc_id IN (%s,%s)', [10, 20]),
456 ('IssueSnapshot2Label AS Is2l'
457 ' ON Is2l.issuesnapshot_id = IssueSnapshot.id', []),
458 ('LabelDef AS Lab ON Lab.id = Is2l.label_id', []),
459 ]
460 where = [
461 ('IssueSnapshot.period_start <= %s', [1514764800]),
462 ('IssueSnapshot.period_end > %s', [1514764800]),
463 ('Issue.is_spam = %s', [False]),
464 ('Issue.deleted = %s', [False]),
465 ('IssueSnapshot.project_id IN (%s)', [789]),
466 ('(Issue.reporter_id IN (%s,%s)'
467 ' OR Issue.owner_id IN (%s,%s)'
468 ' OR I2cc.cc_id IS NOT NULL)',
469 [10, 20, 10, 20]
470 ),
471 ('LOWER(Lab.label) LIKE %s', ['foo-%']),
472 ]
473 group_by = ['Lab.label']
474 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols, where,
475 left_joins, group_by, shard_id=0)
476
477 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
478 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
479 mox.IgnoreArg()).AndReturn(([], [], []))
480 self.cnxn.Execute(stmt, stmt_args, shard_id=0).AndReturn([])
481
482 self._verifySQL(cols, left_joins, where, group_by)
483
484 self.mox.ReplayAll()
485 self.services.chart.QueryIssueSnapshots(self.cnxn, self.services,
486 unixtime=1514764800, effective_ids=[10, 20], project=project,
487 perms=perms, group_by='label', label_prefix='Foo')
488 self.mox.VerifyAll()
489
490 def SetUpStoreIssueSnapshots(self, replace_now=None,
491 project_id=789, owner_id=111,
492 component_ids=None, cc_rows=None):
493 """Set up all calls to mocks that StoreIssueSnapshots will call."""
494 now = self.services.chart._currentTime().AndReturn(replace_now or 12345678)
495
496 self.services.chart.issuesnapshot_tbl.Update(self.cnxn,
497 delta={'period_end': now},
498 where=[('IssueSnapshot.issue_id = %s', [78901]),
499 ('IssueSnapshot.period_end = %s',
500 [settings.maximum_snapshot_period_end])],
501 commit=False)
502
503 # Shard is 0 because len(shards) = 1 and 1 % 1 = 0.
504 shard = 0
505 self.services.chart.issuesnapshot_tbl.InsertRows(self.cnxn,
506 chart_svc.ISSUESNAPSHOT_COLS[1:],
507 [(78901, shard, project_id, 1, 111, owner_id, 1,
508 now, 4294967295, True)],
509 replace=True, commit=False, return_generated_ids=True).AndReturn([5678])
510
511 label_rows = [(5678, 1)]
512
513 self.services.chart.issuesnapshot2label_tbl.InsertRows(self.cnxn,
514 chart_svc.ISSUESNAPSHOT2LABEL_COLS,
515 label_rows,
516 replace=True, commit=False)
517
518 self.services.chart.issuesnapshot2cc_tbl.InsertRows(
519 self.cnxn, chart_svc.ISSUESNAPSHOT2CC_COLS,
520 [(5678, row[1]) for row in cc_rows],
521 replace=True, commit=False)
522
523 component_rows = [(5678, component_id) for component_id in component_ids]
524 self.services.chart.issuesnapshot2component_tbl.InsertRows(
525 self.cnxn, chart_svc.ISSUESNAPSHOT2COMPONENT_COLS,
526 component_rows,
527 replace=True, commit=False)
528
529 # Spacing of string must match.
530 self.cnxn.Execute((
531 '\n INSERT INTO IssueSnapshot2Hotlist '
532 '(issuesnapshot_id, hotlist_id)\n '
533 'SELECT %s, hotlist_id FROM Hotlist2Issue '
534 'WHERE issue_id = %s\n '
535 ), [5678, 78901])
536
537 def testStoreIssueSnapshots_NoChange(self):
538 """Test that StoreIssueSnapshots inserts and updates previous
539 issue snapshots correctly."""
540
541 now_1 = 1517599888
542 now_2 = 1517599999
543
544 issue = fake.MakeTestIssue(issue_id=78901,
545 project_id=789, local_id=1, reporter_id=111, owner_id=111,
546 summary='sum', status='Status1',
547 labels=['Type-Defect'],
548 component_ids=[11], assume_stale=False,
549 opened_timestamp=123456789, modified_timestamp=123456789,
550 star_count=12, cc_ids=[222, 333], derived_cc_ids=[888])
551
552 # Snapshot #1
553 cc_rows = [(5678, 222), (5678, 333), (5678, 888)]
554 self.SetUpStoreIssueSnapshots(replace_now=now_1,
555 component_ids=[11], cc_rows=cc_rows)
556
557 # Snapshot #2
558 self.SetUpStoreIssueSnapshots(replace_now=now_2,
559 component_ids=[11], cc_rows=cc_rows)
560
561 self.mox.ReplayAll()
562 self.services.chart.StoreIssueSnapshots(self.cnxn, [issue], commit=False)
563 self.services.chart.StoreIssueSnapshots(self.cnxn, [issue], commit=False)
564 self.mox.VerifyAll()
565
566 def testStoreIssueSnapshots_AllFieldsChanged(self):
567 """Test that StoreIssueSnapshots inserts and updates previous
568 issue snapshots correctly. This tests that all relations (labels,
569 CCs, and components) are updated."""
570
571 now_1 = 1517599888
572 now_2 = 1517599999
573
574 issue_1 = fake.MakeTestIssue(issue_id=78901,
575 project_id=789, local_id=1, reporter_id=111, owner_id=111,
576 summary='sum', status='Status1',
577 labels=['Type-Defect'],
578 component_ids=[11, 12], assume_stale=False,
579 opened_timestamp=123456789, modified_timestamp=123456789,
580 star_count=12, cc_ids=[222, 333], derived_cc_ids=[888])
581
582 issue_2 = fake.MakeTestIssue(issue_id=78901,
583 project_id=123, local_id=1, reporter_id=111, owner_id=222,
584 summary='sum', status='Status2',
585 labels=['Type-Enhancement'],
586 component_ids=[13], assume_stale=False,
587 opened_timestamp=123456789, modified_timestamp=123456789,
588 star_count=12, cc_ids=[222, 444], derived_cc_ids=[888, 999])
589
590 # Snapshot #1
591 cc_rows_1 = [(5678, 222), (5678, 333), (5678, 888)]
592 self.SetUpStoreIssueSnapshots(replace_now=now_1,
593 component_ids=[11, 12], cc_rows=cc_rows_1)
594
595 # Snapshot #2
596 cc_rows_2 = [(5678, 222), (5678, 444), (5678, 888), (5678, 999)]
597 self.SetUpStoreIssueSnapshots(replace_now=now_2,
598 project_id=123, owner_id=222, component_ids=[13],
599 cc_rows=cc_rows_2)
600
601 self.mox.ReplayAll()
602 self.services.chart.StoreIssueSnapshots(self.cnxn, [issue_1], commit=False)
603 self.services.chart.StoreIssueSnapshots(self.cnxn, [issue_2], commit=False)
604 self.mox.VerifyAll()
605
606 def testQueryIssueSnapshots_WithQueryStringAndCannedQuery(self):
607 """Test the query param is parsed and used."""
608 project = fake.Project(project_id=789)
609 perms = permissions.USER_PERMISSIONSET
610 search_helpers.GetPersonalAtRiskLabelIDs(self.cnxn, None,
611 self.config_service, [10, 20], project, perms).AndReturn([])
612
613 cols = [
614 'Lab.label',
615 'COUNT(IssueSnapshot.issue_id)',
616 ]
617 left_joins = [
618 ('Issue ON IssueSnapshot.issue_id = Issue.id', []),
619 ('Issue2Cc AS I2cc'
620 ' ON Issue.id = I2cc.issue_id'
621 ' AND I2cc.cc_id IN (%s,%s)', [10, 20]),
622 ('IssueSnapshot2Label AS Is2l'
623 ' ON Is2l.issuesnapshot_id = IssueSnapshot.id', []),
624 ('LabelDef AS Lab ON Lab.id = Is2l.label_id', []),
625 ('IssueSnapshot2Label AS Cond0 '
626 'ON IssueSnapshot.id = Cond0.issuesnapshot_id '
627 'AND Cond0.label_id = %s', [15]),
628 ]
629 where = [
630 ('IssueSnapshot.period_start <= %s', [1514764800]),
631 ('IssueSnapshot.period_end > %s', [1514764800]),
632 ('Issue.is_spam = %s', [False]),
633 ('Issue.deleted = %s', [False]),
634 ('IssueSnapshot.project_id IN (%s)', [789]),
635 ('(Issue.reporter_id IN (%s,%s)'
636 ' OR Issue.owner_id IN (%s,%s)'
637 ' OR I2cc.cc_id IS NOT NULL)',
638 [10, 20, 10, 20]
639 ),
640 ('LOWER(Lab.label) LIKE %s', ['foo-%']),
641 ('Cond0.label_id IS NULL', []),
642 ('IssueSnapshot.is_open = %s', [True]),
643 ]
644 group_by = ['Lab.label']
645
646 query_left_joins = [(
647 'IssueSnapshot2Label AS Cond0 '
648 'ON IssueSnapshot.id = Cond0.issuesnapshot_id '
649 'AND Cond0.label_id = %s', [15])]
650 query_where = [
651 ('Cond0.label_id IS NULL', []),
652 ('IssueSnapshot.is_open = %s', [True]),
653 ]
654
655 unsupported_field_names = ['ownerbouncing']
656
657 unsupported_conds = [
658 ast_pb2.Condition(op=ast_pb2.QueryOp(1), field_defs=[
659 tracker_pb2.FieldDef(field_name='ownerbouncing',
660 field_type=tracker_pb2.FieldTypes.BOOL_TYPE),
661 ])
662 ]
663
664 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols, where,
665 left_joins, group_by, shard_id=0)
666
667 self.services.chart._QueryToWhere(mox.IgnoreArg(), mox.IgnoreArg(),
668 mox.IgnoreArg(), mox.IgnoreArg(), mox.IgnoreArg(),
669 mox.IgnoreArg()).AndReturn((query_left_joins, query_where,
670 unsupported_conds))
671 self.cnxn.Execute(stmt, stmt_args, shard_id=0).AndReturn([])
672
673 self._verifySQL(cols, left_joins, where, group_by)
674
675 self.mox.ReplayAll()
676 _, unsupported, limit_reached = self.services.chart.QueryIssueSnapshots(
677 self.cnxn, self.services, unixtime=1514764800,
678 effective_ids=[10, 20], project=project, perms=perms,
679 group_by='label', label_prefix='Foo',
680 query='-label:Performance%20is:ownerbouncing', canned_query='is:open')
681 self.mox.VerifyAll()
682
683 self.assertEqual(unsupported_field_names, unsupported)
684 self.assertFalse(limit_reached)
685
686 def testQueryToWhere_AddsShardId(self):
687 """Test that shards are handled correctly."""
688 cols = []
689 where = []
690 joins = []
691 group_by = []
692 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols=cols,
693 where=where, joins=joins, group_by=group_by, shard_id=9)
694
695 self.assertEqual(stmt, ('SELECT COUNT(results.issue_id) '
696 'FROM (SELECT DISTINCT FROM IssueSnapshot\n'
697 'WHERE IssueSnapshot.shard = %s\nLIMIT 10000) AS results'))
698 self.assertEqual(stmt_args, [9])
699
700 # Test that shard_id is still correct on second invocation.
701 stmt, stmt_args = self.services.chart._BuildSnapshotQuery(cols=cols,
702 where=where, joins=joins, group_by=group_by, shard_id=8)
703
704 self.assertEqual(stmt, ('SELECT COUNT(results.issue_id) '
705 'FROM (SELECT DISTINCT FROM IssueSnapshot\n'
706 'WHERE IssueSnapshot.shard = %s\nLIMIT 10000) AS results'))
707 self.assertEqual(stmt_args, [8])
708
709 # Test no parameters were modified.
710 self.assertEqual(cols, [])
711 self.assertEqual(where, [])
712 self.assertEqual(joins, [])
713 self.assertEqual(group_by, [])