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