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