blob: b6fe682ffe3e57b080b6dfac64d5b22e455c1fe3 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# -*- coding: utf-8 -*-
2# Copyright 2016 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 issue_svc module."""
8
9from __future__ import division
10from __future__ import print_function
11from __future__ import absolute_import
12
13import logging
14import time
15import unittest
16from mock import patch, Mock, ANY
17
18import mox
19
20from google.appengine.api import search
21from google.appengine.ext import testbed
22
23import settings
24from framework import exceptions
25from framework import framework_constants
26from framework import sql
27from proto import tracker_pb2
28from services import caches
29from services import chart_svc
30from services import issue_svc
31from services import service_manager
32from services import spam_svc
33from services import tracker_fulltext
34from testing import fake
35from testing import testing_helpers
36from tracker import tracker_bizobj
37
38
39class MockIndex(object):
40
41 def delete(self, string_list):
42 pass
43
44
45def MakeIssueService(project_service, config_service, cache_manager,
46 chart_service, my_mox):
47 issue_service = issue_svc.IssueService(
48 project_service, config_service, cache_manager, chart_service)
49 for table_var in [
50 'issue_tbl', 'issuesummary_tbl', 'issue2label_tbl',
51 'issue2component_tbl', 'issue2cc_tbl', 'issue2notify_tbl',
52 'issue2fieldvalue_tbl', 'issuerelation_tbl', 'danglingrelation_tbl',
53 'issueformerlocations_tbl', 'comment_tbl', 'commentcontent_tbl',
54 'issueupdate_tbl', 'attachment_tbl', 'reindexqueue_tbl',
55 'localidcounter_tbl', 'issuephasedef_tbl', 'issue2approvalvalue_tbl',
56 'issueapproval2approver_tbl', 'issueapproval2comment_tbl',
57 'commentimporter_tbl']:
58 setattr(issue_service, table_var, my_mox.CreateMock(sql.SQLTableManager))
59
60 return issue_service
61
62
63class TestableIssueTwoLevelCache(issue_svc.IssueTwoLevelCache):
64
65 def __init__(self, issue_list):
66 cache_manager = fake.CacheManager()
67 super(TestableIssueTwoLevelCache, self).__init__(
68 cache_manager, None, None, None)
69 self.cache = caches.RamCache(cache_manager, 'issue')
70 self.memcache_prefix = 'issue:'
71 self.pb_class = tracker_pb2.Issue
72
73 self.issue_dict = {
74 issue.issue_id: issue
75 for issue in issue_list}
76
77 def FetchItems(self, cnxn, issue_ids, shard_id=None):
78 return {
79 issue_id: self.issue_dict[issue_id]
80 for issue_id in issue_ids
81 if issue_id in self.issue_dict}
82
83
84class IssueIDTwoLevelCacheTest(unittest.TestCase):
85
86 def setUp(self):
87 self.mox = mox.Mox()
88 self.cnxn = 'fake connection'
89 self.project_service = fake.ProjectService()
90 self.config_service = fake.ConfigService()
91 self.cache_manager = fake.CacheManager()
92 self.chart_service = chart_svc.ChartService(self.config_service)
93 self.issue_service = MakeIssueService(
94 self.project_service, self.config_service, self.cache_manager,
95 self.chart_service, self.mox)
96 self.issue_id_2lc = self.issue_service.issue_id_2lc
97 self.spam_service = fake.SpamService()
98
99 def tearDown(self):
100 self.mox.UnsetStubs()
101 self.mox.ResetAll()
102
103 def testDeserializeIssueIDs_Empty(self):
104 issue_id_dict = self.issue_id_2lc._DeserializeIssueIDs([])
105 self.assertEqual({}, issue_id_dict)
106
107 def testDeserializeIssueIDs_Normal(self):
108 rows = [(789, 1, 78901), (789, 2, 78902), (789, 3, 78903)]
109 issue_id_dict = self.issue_id_2lc._DeserializeIssueIDs(rows)
110 expected = {
111 (789, 1): 78901,
112 (789, 2): 78902,
113 (789, 3): 78903,
114 }
115 self.assertEqual(expected, issue_id_dict)
116
117 def SetUpFetchItems(self):
118 where = [
119 ('(Issue.project_id = %s AND Issue.local_id IN (%s,%s,%s))',
120 [789, 1, 2, 3])]
121 rows = [(789, 1, 78901), (789, 2, 78902), (789, 3, 78903)]
122 self.issue_service.issue_tbl.Select(
123 self.cnxn, cols=['project_id', 'local_id', 'id'],
124 where=where, or_where_conds=True).AndReturn(rows)
125
126 def testFetchItems(self):
127 project_local_ids_list = [(789, 1), (789, 2), (789, 3)]
128 issue_ids = [78901, 78902, 78903]
129 self.SetUpFetchItems()
130 self.mox.ReplayAll()
131 issue_dict = self.issue_id_2lc.FetchItems(
132 self.cnxn, project_local_ids_list)
133 self.mox.VerifyAll()
134 self.assertItemsEqual(project_local_ids_list, list(issue_dict.keys()))
135 self.assertItemsEqual(issue_ids, list(issue_dict.values()))
136
137 def testKeyToStr(self):
138 self.assertEqual('789,1', self.issue_id_2lc._KeyToStr((789, 1)))
139
140 def testStrToKey(self):
141 self.assertEqual((789, 1), self.issue_id_2lc._StrToKey('789,1'))
142
143
144class IssueTwoLevelCacheTest(unittest.TestCase):
145
146 def setUp(self):
147 self.mox = mox.Mox()
148 self.cnxn = 'fake connection'
149 self.project_service = fake.ProjectService()
150 self.config_service = fake.ConfigService()
151 self.cache_manager = fake.CacheManager()
152 self.chart_service = chart_svc.ChartService(self.config_service)
153 self.issue_service = MakeIssueService(
154 self.project_service, self.config_service, self.cache_manager,
155 self.chart_service, self.mox)
156 self.issue_2lc = self.issue_service.issue_2lc
157
158 now = int(time.time())
159 self.project_service.TestAddProject('proj', project_id=789)
160 self.issue_rows = [
161 (78901, 789, 1, 1, 111, 222,
162 now, now, now, now, now, now,
163 0, 0, 0, 1, 0, False)]
164 self.summary_rows = [(78901, 'sum')]
165 self.label_rows = [(78901, 1, 0)]
166 self.component_rows = []
167 self.cc_rows = [(78901, 333, 0)]
168 self.notify_rows = []
169 self.fieldvalue_rows = []
170 self.blocked_on_rows = (
171 (78901, 78902, 'blockedon', 20), (78903, 78901, 'blockedon', 10))
172 self.blocking_rows = ()
173 self.merged_rows = ()
174 self.relation_rows = (
175 self.blocked_on_rows + self.blocking_rows + self.merged_rows)
176 self.dangling_relation_rows = [
177 (78901, 'codesite', 5001, None, 'blocking'),
178 (78901, 'codesite', 5002, None, 'blockedon'),
179 (78901, None, None, 'b/1234567', 'blockedon')]
180 self.phase_rows = [(1, 'Canary', 1), (2, 'Stable', 11)]
181 self.approvalvalue_rows = [(22, 78901, 2, 'not_set', None, None),
182 (21, 78901, 1, 'needs_review', None, None),
183 (23, 78901, 1, 'not_set', None, None)]
184 self.av_approver_rows = [
185 (21, 111, 78901), (21, 222, 78901), (21, 333, 78901)]
186
187 def tearDown(self):
188 self.mox.UnsetStubs()
189 self.mox.ResetAll()
190
191 def testUnpackApprovalValue(self):
192 row = next(
193 row for row in self.approvalvalue_rows if row[3] == 'needs_review')
194 av, issue_id = self.issue_2lc._UnpackApprovalValue(row)
195 self.assertEqual(av.status, tracker_pb2.ApprovalStatus.NEEDS_REVIEW)
196 self.assertIsNone(av.setter_id)
197 self.assertIsNone(av.set_on)
198 self.assertEqual(issue_id, 78901)
199 self.assertEqual(av.phase_id, 1)
200
201 def testUnpackApprovalValue_MissingStatus(self):
202 av, _issue_id = self.issue_2lc._UnpackApprovalValue(
203 (21, 78901, 1, '', None, None))
204 self.assertEqual(av.status, tracker_pb2.ApprovalStatus.NOT_SET)
205
206 def testUnpackPhase(self):
207 phase = self.issue_2lc._UnpackPhase(
208 self.phase_rows[0])
209 self.assertEqual(phase.name, 'Canary')
210 self.assertEqual(phase.phase_id, 1)
211 self.assertEqual(phase.rank, 1)
212
213 def testDeserializeIssues_Empty(self):
214 issue_dict = self.issue_2lc._DeserializeIssues(
215 self.cnxn, [], [], [], [], [], [], [], [], [], [], [], [])
216 self.assertEqual({}, issue_dict)
217
218 def testDeserializeIssues_Normal(self):
219 issue_dict = self.issue_2lc._DeserializeIssues(
220 self.cnxn, self.issue_rows, self.summary_rows, self.label_rows,
221 self.component_rows, self.cc_rows, self.notify_rows,
222 self.fieldvalue_rows, self.relation_rows, self.dangling_relation_rows,
223 self.phase_rows, self.approvalvalue_rows, self.av_approver_rows)
224 self.assertItemsEqual([78901], list(issue_dict.keys()))
225 issue = issue_dict[78901]
226 self.assertEqual(len(issue.phases), 2)
227 self.assertIsNotNone(tracker_bizobj.FindPhaseByID(1, issue.phases))
228 av_21 = tracker_bizobj.FindApprovalValueByID(
229 21, issue.approval_values)
230 self.assertEqual(av_21.phase_id, 1)
231 self.assertItemsEqual(av_21.approver_ids, [111, 222, 333])
232 self.assertIsNotNone(tracker_bizobj.FindPhaseByID(2, issue.phases))
233 self.assertEqual(issue.phases,
234 [tracker_pb2.Phase(rank=1, phase_id=1, name='Canary'),
235 tracker_pb2.Phase(rank=11, phase_id=2, name='Stable')])
236 av_22 = tracker_bizobj.FindApprovalValueByID(
237 22, issue.approval_values)
238 self.assertEqual(av_22.phase_id, 2)
239 self.assertEqual([
240 tracker_pb2.DanglingIssueRef(
241 project=row[1],
242 issue_id=row[2],
243 ext_issue_identifier=row[3])
244 for row in self.dangling_relation_rows
245 if row[4] == 'blockedon'
246 ], issue.dangling_blocked_on_refs)
247 self.assertEqual([
248 tracker_pb2.DanglingIssueRef(
249 project=row[1],
250 issue_id=row[2],
251 ext_issue_identifier=row[3])
252 for row in self.dangling_relation_rows
253 if row[4] == 'blocking'
254 ], issue.dangling_blocking_refs)
255
256 def testDeserializeIssues_UnexpectedLabel(self):
257 unexpected_label_rows = [
258 (78901, 999, 0)
259 ]
260 self.assertRaises(
261 AssertionError,
262 self.issue_2lc._DeserializeIssues,
263 self.cnxn, self.issue_rows, self.summary_rows, unexpected_label_rows,
264 self.component_rows, self.cc_rows, self.notify_rows,
265 self.fieldvalue_rows, self.relation_rows, self.dangling_relation_rows,
266 self.phase_rows, self.approvalvalue_rows, self.av_approver_rows)
267
268 def testDeserializeIssues_UnexpectedIssueRelation(self):
269 unexpected_relation_rows = [
270 (78990, 78999, 'blockedon', None)
271 ]
272 self.assertRaises(
273 AssertionError,
274 self.issue_2lc._DeserializeIssues,
275 self.cnxn, self.issue_rows, self.summary_rows, self.label_rows,
276 self.component_rows, self.cc_rows, self.notify_rows,
277 self.fieldvalue_rows, unexpected_relation_rows,
278 self.dangling_relation_rows, self.phase_rows, self.approvalvalue_rows,
279 self.av_approver_rows)
280
281 def testDeserializeIssues_ExternalMergedInto(self):
282 """_DeserializeIssues handles external mergedinto refs correctly."""
283 dangling_relation_rows = self.dangling_relation_rows + [
284 (78901, None, None, 'b/1234567', 'mergedinto')]
285 issue_dict = self.issue_2lc._DeserializeIssues(
286 self.cnxn, self.issue_rows, self.summary_rows, self.label_rows,
287 self.component_rows, self.cc_rows, self.notify_rows,
288 self.fieldvalue_rows, self.relation_rows, dangling_relation_rows,
289 self.phase_rows, self.approvalvalue_rows, self.av_approver_rows)
290 self.assertEqual('b/1234567', issue_dict[78901].merged_into_external)
291
292 def SetUpFetchItems(self, issue_ids, has_approvalvalues=True):
293 shard_id = None
294 self.issue_service.issue_tbl.Select(
295 self.cnxn, cols=issue_svc.ISSUE_COLS, id=issue_ids,
296 shard_id=shard_id).AndReturn(self.issue_rows)
297 self.issue_service.issuesummary_tbl.Select(
298 self.cnxn, cols=issue_svc.ISSUESUMMARY_COLS, shard_id=shard_id,
299 issue_id=issue_ids).AndReturn(self.summary_rows)
300 self.issue_service.issue2label_tbl.Select(
301 self.cnxn, cols=issue_svc.ISSUE2LABEL_COLS, shard_id=shard_id,
302 issue_id=issue_ids).AndReturn(self.label_rows)
303 self.issue_service.issue2component_tbl.Select(
304 self.cnxn, cols=issue_svc.ISSUE2COMPONENT_COLS, shard_id=shard_id,
305 issue_id=issue_ids).AndReturn(self.component_rows)
306 self.issue_service.issue2cc_tbl.Select(
307 self.cnxn, cols=issue_svc.ISSUE2CC_COLS, shard_id=shard_id,
308 issue_id=issue_ids).AndReturn(self.cc_rows)
309 self.issue_service.issue2notify_tbl.Select(
310 self.cnxn, cols=issue_svc.ISSUE2NOTIFY_COLS, shard_id=shard_id,
311 issue_id=issue_ids).AndReturn(self.notify_rows)
312 self.issue_service.issue2fieldvalue_tbl.Select(
313 self.cnxn, cols=issue_svc.ISSUE2FIELDVALUE_COLS, shard_id=shard_id,
314 issue_id=issue_ids).AndReturn(self.fieldvalue_rows)
315 if has_approvalvalues:
316 self.issue_service.issuephasedef_tbl.Select(
317 self.cnxn, cols=issue_svc.ISSUEPHASEDEF_COLS,
318 id=[1, 2]).AndReturn(self.phase_rows)
319 self.issue_service.issue2approvalvalue_tbl.Select(
320 self.cnxn,
321 cols=issue_svc.ISSUE2APPROVALVALUE_COLS,
322 issue_id=issue_ids).AndReturn(self.approvalvalue_rows)
323 else:
324 self.issue_service.issue2approvalvalue_tbl.Select(
325 self.cnxn,
326 cols=issue_svc.ISSUE2APPROVALVALUE_COLS,
327 issue_id=issue_ids).AndReturn([])
328 self.issue_service.issueapproval2approver_tbl.Select(
329 self.cnxn, cols=issue_svc.ISSUEAPPROVAL2APPROVER_COLS,
330 issue_id=issue_ids).AndReturn(self.av_approver_rows)
331 self.issue_service.issuerelation_tbl.Select(
332 self.cnxn, cols=issue_svc.ISSUERELATION_COLS,
333 issue_id=issue_ids, kind='blockedon',
334 order_by=[('issue_id', []), ('rank DESC', []),
335 ('dst_issue_id', [])]).AndReturn(self.blocked_on_rows)
336 self.issue_service.issuerelation_tbl.Select(
337 self.cnxn, cols=issue_svc.ISSUERELATION_COLS,
338 dst_issue_id=issue_ids, kind='blockedon',
339 order_by=[('issue_id', []), ('dst_issue_id', [])]
340 ).AndReturn(self.blocking_rows)
341 self.issue_service.issuerelation_tbl.Select(
342 self.cnxn, cols=issue_svc.ISSUERELATION_COLS,
343 where=[('(issue_id IN (%s) OR dst_issue_id IN (%s))',
344 issue_ids + issue_ids),
345 ('kind != %s', ['blockedon'])]).AndReturn(self.merged_rows)
346 self.issue_service.danglingrelation_tbl.Select(
347 self.cnxn, cols=issue_svc.DANGLINGRELATION_COLS, # Note: no shard
348 issue_id=issue_ids).AndReturn(self.dangling_relation_rows)
349
350 def testFetchItems(self):
351 issue_ids = [78901]
352 self.SetUpFetchItems(issue_ids)
353 self.mox.ReplayAll()
354 issue_dict = self.issue_2lc.FetchItems(self.cnxn, issue_ids)
355 self.mox.VerifyAll()
356 self.assertItemsEqual(issue_ids, list(issue_dict.keys()))
357 self.assertEqual(2, len(issue_dict[78901].phases))
358
359 def testFetchItemsNoApprovalValues(self):
360 issue_ids = [78901]
361 self.SetUpFetchItems(issue_ids, False)
362 self.mox.ReplayAll()
363 issue_dict = self.issue_2lc.FetchItems(self.cnxn, issue_ids)
364 self.mox.VerifyAll()
365 self.assertItemsEqual(issue_ids, list(issue_dict.keys()))
366 self.assertEqual([], issue_dict[78901].phases)
367
368
369class IssueServiceTest(unittest.TestCase):
370
371 def setUp(self):
372 self.testbed = testbed.Testbed()
373 self.testbed.activate()
374 self.testbed.init_memcache_stub()
375
376 self.mox = mox.Mox()
377 self.cnxn = self.mox.CreateMock(sql.MonorailConnection)
378 self.services = service_manager.Services()
379 self.services.user = fake.UserService()
380 self.reporter = self.services.user.TestAddUser('reporter@example.com', 111)
381 self.services.usergroup = fake.UserGroupService()
382 self.services.project = fake.ProjectService()
383 self.project = self.services.project.TestAddProject('proj', project_id=789)
384 self.services.config = fake.ConfigService()
385 self.services.features = fake.FeaturesService()
386 self.cache_manager = fake.CacheManager()
387 self.services.chart = chart_svc.ChartService(self.services.config)
388 self.services.issue = MakeIssueService(
389 self.services.project, self.services.config, self.cache_manager,
390 self.services.chart, self.mox)
391 self.services.spam = self.mox.CreateMock(spam_svc.SpamService)
392 self.now = int(time.time())
393 self.patcher = patch('services.tracker_fulltext.IndexIssues')
394 self.patcher.start()
395 self.mox.StubOutWithMock(self.services.chart, 'StoreIssueSnapshots')
396
397 def classifierResult(self, score, failed_open=False):
398 return {'confidence_is_spam': score,
399 'failed_open': failed_open}
400
401 def tearDown(self):
402 self.testbed.deactivate()
403 self.mox.UnsetStubs()
404 self.mox.ResetAll()
405 self.patcher.stop()
406
407 ### Issue ID lookups
408
409 def testLookupIssueIDsFollowMoves(self):
410 moved_issue_id = 78901
411 moved_pair = (789, 1)
412 missing_pair = (1, 1)
413 cached_issue_id = 78902
414 cached_pair = (789, 2)
415 uncached_issue_id = 78903
416 uncached_pair = (789, 3)
417 uncached_issue_id_2 = 78904
418 uncached_pair_2 = (789, 4)
419 self.services.issue.issue_id_2lc.CacheItem(cached_pair, cached_issue_id)
420
421 # Simulate rows returned in reverse order (to verify the method still
422 # returns them in the specified order).
423 uncached_rows = [
424 (uncached_pair_2[0], uncached_pair_2[1], uncached_issue_id_2),
425 (uncached_pair[0], uncached_pair[1], uncached_issue_id)
426 ]
427 self.services.issue.issue_tbl.Select(
428 self.cnxn,
429 cols=['project_id', 'local_id', 'id'],
430 or_where_conds=True,
431 where=mox.IgnoreArg()).AndReturn(uncached_rows)
432 # Moved issue is found.
433 self.services.issue.issueformerlocations_tbl.SelectValue(
434 self.cnxn,
435 'issue_id',
436 default=0,
437 project_id=moved_pair[0],
438 local_id=moved_pair[1]).AndReturn(moved_issue_id)
439
440 self.mox.ReplayAll()
441 found_ids, misses = self.services.issue.LookupIssueIDsFollowMoves(
442 self.cnxn,
443 [moved_pair, missing_pair, cached_pair, uncached_pair, uncached_pair_2])
444 self.mox.VerifyAll()
445
446 expected_found_ids = [
447 moved_issue_id, cached_issue_id, uncached_issue_id, uncached_issue_id_2
448 ]
449 self.assertListEqual(expected_found_ids, found_ids)
450 self.assertListEqual([missing_pair], misses)
451
452 def testLookupIssueIDs_Hit(self):
453 self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
454 self.services.issue.issue_id_2lc.CacheItem((789, 2), 78902)
455 actual, _misses = self.services.issue.LookupIssueIDs(
456 self.cnxn, [(789, 1), (789, 2)])
457 self.assertEqual([78901, 78902], actual)
458
459 def testLookupIssueID(self):
460 self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
461 actual = self.services.issue.LookupIssueID(self.cnxn, 789, 1)
462 self.assertEqual(78901, actual)
463
464 def testResolveIssueRefs(self):
465 self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
466 self.services.issue.issue_id_2lc.CacheItem((789, 2), 78902)
467 prefetched_projects = {'proj': fake.Project('proj', project_id=789)}
468 refs = [('proj', 1), (None, 2)]
469 actual, misses = self.services.issue.ResolveIssueRefs(
470 self.cnxn, prefetched_projects, 'proj', refs)
471 self.assertEqual(misses, [])
472 self.assertEqual([78901, 78902], actual)
473
474 def testLookupIssueRefs_Empty(self):
475 actual = self.services.issue.LookupIssueRefs(self.cnxn, [])
476 self.assertEqual({}, actual)
477
478 def testLookupIssueRefs_Normal(self):
479 issue_1 = fake.MakeTestIssue(
480 project_id=789, local_id=1, owner_id=111, summary='sum',
481 status='Live', issue_id=78901, project_name='proj')
482 self.services.issue.issue_2lc.CacheItem(78901, issue_1)
483 actual = self.services.issue.LookupIssueRefs(self.cnxn, [78901])
484 self.assertEqual(
485 {78901: ('proj', 1)},
486 actual)
487
488 ### Issue objects
489
490 def CheckCreateIssue(self, is_project_member):
491 settings.classifier_spam_thresh = 0.9
492 av_23 = tracker_pb2.ApprovalValue(
493 approval_id=23, phase_id=1, approver_ids=[111, 222],
494 status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW)
495 av_24 = tracker_pb2.ApprovalValue(
496 approval_id=24, phase_id=1, approver_ids=[111])
497 approval_values = [av_23, av_24]
498 av_rows = [(23, 78901, 1, 'needs_review', None, None),
499 (24, 78901, 1, 'not_set', None, None)]
500 approver_rows = [(23, 111, 78901), (23, 222, 78901), (24, 111, 78901)]
501 ad_23 = tracker_pb2.ApprovalDef(
502 approval_id=23, approver_ids=[111], survey='Question?')
503 ad_24 = tracker_pb2.ApprovalDef(
504 approval_id=24, approver_ids=[111], survey='Question?')
505 config = self.services.config.GetProjectConfig(
506 self.cnxn, 789)
507 config.approval_defs.extend([ad_23, ad_24])
508 self.services.config.StoreConfig(self.cnxn, config)
509
510 self.SetUpAllocateNextLocalID(789, None, None)
511 self.SetUpInsertIssue(av_rows=av_rows, approver_rows=approver_rows)
512 self.SetUpInsertComment(7890101, is_description=True)
513 self.SetUpInsertComment(7890101, is_description=True, approval_id=23,
514 content='<b>Question?</b>')
515 self.SetUpInsertComment(7890101, is_description=True, approval_id=24,
516 content='<b>Question?</b>')
517 self.services.spam.ClassifyIssue(mox.IgnoreArg(),
518 mox.IgnoreArg(), self.reporter, is_project_member).AndReturn(
519 self.classifierResult(0.0))
520 self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
521 mox.IsA(tracker_pb2.Issue), False, 1.0, False)
522 self.SetUpEnqueueIssuesForIndexing([78901])
523
524 self.mox.ReplayAll()
525 issue = fake.MakeTestIssue(
526 789,
527 1,
528 'sum',
529 'New',
530 111,
531 reporter_id=111,
532 labels=['Type-Defect'],
533 opened_timestamp=self.now,
534 modified_timestamp=self.now,
535 approval_values=approval_values)
536 created_issue, _ = self.services.issue.CreateIssue(
537 self.cnxn, self.services, issue, 'content')
538 self.mox.VerifyAll()
539 self.assertEqual(1, created_issue.local_id)
540
541 def testCreateIssue_NonmemberSpamCheck(self):
542 """A non-member must pass a non-member spam check."""
543 self.CheckCreateIssue(False)
544
545 def testCreateIssue_DirectMemberSpamCheck(self):
546 """A direct member of a project gets a member spam check."""
547 self.project.committer_ids.append(self.reporter.user_id)
548 self.CheckCreateIssue(True)
549
550 def testCreateIssue_ComputedUsergroupSpamCheck(self):
551 """A member of a computed group in project gets a member spam check."""
552 group_id = self.services.usergroup.CreateGroup(
553 self.cnxn, self.services, 'everyone@example.com', 'ANYONE',
554 ext_group_type='COMPUTED')
555 self.project.committer_ids.append(group_id)
556 self.CheckCreateIssue(True)
557
558 def testCreateIssue_EmptyStringLabels(self):
559 settings.classifier_spam_thresh = 0.9
560 self.SetUpAllocateNextLocalID(789, None, None)
561 self.SetUpInsertIssue(label_rows=[])
562 self.SetUpInsertComment(7890101, is_description=True)
563 self.services.spam.ClassifyIssue(mox.IgnoreArg(),
564 mox.IgnoreArg(), self.reporter, False).AndReturn(
565 self.classifierResult(0.0))
566 self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
567 mox.IsA(tracker_pb2.Issue), False, 1.0, False)
568 self.SetUpEnqueueIssuesForIndexing([78901])
569
570 self.mox.ReplayAll()
571 issue = fake.MakeTestIssue(
572 789,
573 1,
574 'sum',
575 'New',
576 111,
577 reporter_id=111,
578 opened_timestamp=self.now,
579 modified_timestamp=self.now)
580 created_issue, _ = self.services.issue.CreateIssue(
581 self.cnxn, self.services, issue, 'content')
582 self.mox.VerifyAll()
583 self.assertEqual(1, created_issue.local_id)
584
585 def SetUpUpdateIssuesModified(self, iids, modified_timestamp=None):
586 self.services.issue.issue_tbl.Update(
587 self.cnxn, {'modified': modified_timestamp or self.now},
588 id=iids, commit=False)
589
590 def testCreateIssue_SpamPredictionFailed(self):
591 settings.classifier_spam_thresh = 0.9
592 self.SetUpAllocateNextLocalID(789, None, None)
593 self.SetUpInsertSpamIssue()
594 self.SetUpInsertComment(7890101, is_description=True)
595
596 self.services.spam.ClassifyIssue(mox.IsA(tracker_pb2.Issue),
597 mox.IsA(tracker_pb2.IssueComment), self.reporter, False).AndReturn(
598 self.classifierResult(1.0, True))
599 self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
600 mox.IsA(tracker_pb2.Issue), True, 1.0, True)
601 self.SetUpUpdateIssuesApprovals([])
602 self.SetUpEnqueueIssuesForIndexing([78901])
603
604 self.mox.ReplayAll()
605 issue = fake.MakeTestIssue(
606 789,
607 1,
608 'sum',
609 'New',
610 111,
611 reporter_id=111,
612 labels=['Type-Defect'],
613 opened_timestamp=self.now,
614 modified_timestamp=self.now)
615 created_issue, _ = self.services.issue.CreateIssue(
616 self.cnxn, self.services, issue, 'content')
617 self.mox.VerifyAll()
618 self.assertEqual(1, created_issue.local_id)
619
620 def testCreateIssue_Spam(self):
621 settings.classifier_spam_thresh = 0.9
622 self.SetUpAllocateNextLocalID(789, None, None)
623 self.SetUpInsertSpamIssue()
624 self.SetUpInsertComment(7890101, is_description=True)
625
626 self.services.spam.ClassifyIssue(mox.IsA(tracker_pb2.Issue),
627 mox.IsA(tracker_pb2.IssueComment), self.reporter, False).AndReturn(
628 self.classifierResult(1.0))
629 self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
630 mox.IsA(tracker_pb2.Issue), True, 1.0, False)
631 self.SetUpUpdateIssuesApprovals([])
632 self.SetUpEnqueueIssuesForIndexing([78901])
633
634 self.mox.ReplayAll()
635 issue = fake.MakeTestIssue(
636 789,
637 1,
638 'sum',
639 'New',
640 111,
641 reporter_id=111,
642 labels=['Type-Defect'],
643 opened_timestamp=self.now,
644 modified_timestamp=self.now)
645 created_issue, _ = self.services.issue.CreateIssue(
646 self.cnxn, self.services, issue, 'content')
647 self.mox.VerifyAll()
648 self.assertEqual(1, created_issue.local_id)
649
650 def testCreateIssue_FederatedReferences(self):
651 self.SetUpAllocateNextLocalID(789, None, None)
652 self.SetUpInsertIssue(dangling_relation_rows=[
653 (78901, None, None, 'b/1234', 'blockedon'),
654 (78901, None, None, 'b/5678', 'blockedon'),
655 (78901, None, None, 'b/9876', 'blocking'),
656 (78901, None, None, 'b/5432', 'blocking')])
657 self.SetUpInsertComment(7890101, is_description=True)
658 self.services.spam.ClassifyIssue(mox.IsA(tracker_pb2.Issue),
659 mox.IsA(tracker_pb2.IssueComment), self.reporter, False).AndReturn(
660 self.classifierResult(0.0))
661 self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
662 mox.IsA(tracker_pb2.Issue), mox.IgnoreArg(), mox.IgnoreArg(),
663 mox.IgnoreArg())
664 self.SetUpEnqueueIssuesForIndexing([78901])
665
666 self.mox.ReplayAll()
667 issue = fake.MakeTestIssue(
668 789,
669 1,
670 'sum',
671 'New',
672 111,
673 reporter_id=111,
674 labels=['Type-Defect'],
675 opened_timestamp=self.now,
676 modified_timestamp=self.now)
677 issue.dangling_blocked_on_refs = [
678 tracker_pb2.DanglingIssueRef(ext_issue_identifier=shortlink)
679 for shortlink in ['b/1234', 'b/5678']
680 ]
681 issue.dangling_blocking_refs = [
682 tracker_pb2.DanglingIssueRef(ext_issue_identifier=shortlink)
683 for shortlink in ['b/9876', 'b/5432']
684 ]
685 self.services.issue.CreateIssue(self.cnxn, self.services, issue, 'content')
686 self.mox.VerifyAll()
687
688 def testCreateIssue_Imported(self):
689 settings.classifier_spam_thresh = 0.9
690 self.SetUpAllocateNextLocalID(789, None, None)
691 self.SetUpInsertIssue(label_rows=[])
692 self.SetUpInsertComment(7890101, is_description=True)
693 self.services.issue.commentimporter_tbl.InsertRow(
694 self.cnxn, comment_id=7890101, importer_id=222)
695 self.services.spam.ClassifyIssue(mox.IgnoreArg(),
696 mox.IgnoreArg(), self.reporter, False).AndReturn(
697 self.classifierResult(0.0))
698 self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
699 mox.IsA(tracker_pb2.Issue), False, 1.0, False)
700 self.SetUpEnqueueIssuesForIndexing([78901])
701 self.mox.ReplayAll()
702
703 issue = fake.MakeTestIssue(
704 789,
705 1,
706 'sum',
707 'New',
708 111,
709 reporter_id=111,
710 opened_timestamp=self.now,
711 modified_timestamp=self.now)
712 created_issue, comment = self.services.issue.CreateIssue(
713 self.cnxn, self.services, issue, 'content', importer_id=222)
714
715 self.mox.VerifyAll()
716 self.assertEqual(1, created_issue.local_id)
717 self.assertEqual(111, comment.user_id)
718 self.assertEqual(222, comment.importer_id)
719 self.assertEqual(self.now, comment.timestamp)
720
721 def testGetAllIssuesInProject_NoIssues(self):
722 self.SetUpGetHighestLocalID(789, None, None)
723 self.mox.ReplayAll()
724 issues = self.services.issue.GetAllIssuesInProject(self.cnxn, 789)
725 self.mox.VerifyAll()
726 self.assertEqual([], issues)
727
728 def testGetAnyOnHandIssue(self):
729 issue_ids = [78901, 78902, 78903]
730 self.SetUpGetIssues()
731 issue = self.services.issue.GetAnyOnHandIssue(issue_ids)
732 self.assertEqual(78901, issue.issue_id)
733
734 def SetUpGetIssues(self):
735 issue_1 = fake.MakeTestIssue(
736 project_id=789, local_id=1, owner_id=111, summary='sum',
737 status='Live', issue_id=78901)
738 issue_1.project_name = 'proj'
739 issue_2 = fake.MakeTestIssue(
740 project_id=789, local_id=2, owner_id=111, summary='sum',
741 status='Fixed', issue_id=78902)
742 issue_2.project_name = 'proj'
743 self.services.issue.issue_2lc.CacheItem(78901, issue_1)
744 self.services.issue.issue_2lc.CacheItem(78902, issue_2)
745 return issue_1, issue_2
746
747 def testGetIssuesDict(self):
748 issue_ids = [78901, 78902, 78903]
749 issue_1, issue_2 = self.SetUpGetIssues()
750 self.services.issue.issue_2lc = TestableIssueTwoLevelCache(
751 [issue_1, issue_2])
752 issues_dict, missed_iids = self.services.issue.GetIssuesDict(
753 self.cnxn, issue_ids)
754 self.assertEqual(
755 {78901: issue_1, 78902: issue_2},
756 issues_dict)
757 self.assertEqual([78903], missed_iids)
758
759 def testGetIssues(self):
760 issue_ids = [78901, 78902]
761 issue_1, issue_2 = self.SetUpGetIssues()
762 issues = self.services.issue.GetIssues(self.cnxn, issue_ids)
763 self.assertEqual([issue_1, issue_2], issues)
764
765 def testGetIssue(self):
766 issue_1, _issue_2 = self.SetUpGetIssues()
767 actual_issue = self.services.issue.GetIssue(self.cnxn, 78901)
768 self.assertEqual(issue_1, actual_issue)
769
770 def testGetIssuesByLocalIDs(self):
771 issue_1, issue_2 = self.SetUpGetIssues()
772 self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
773 self.services.issue.issue_id_2lc.CacheItem((789, 2), 78902)
774 actual_issues = self.services.issue.GetIssuesByLocalIDs(
775 self.cnxn, 789, [1, 2])
776 self.assertEqual([issue_1, issue_2], actual_issues)
777
778 def testGetIssueByLocalID(self):
779 issue_1, _issue_2 = self.SetUpGetIssues()
780 self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
781 actual_issues = self.services.issue.GetIssueByLocalID(self.cnxn, 789, 1)
782 self.assertEqual(issue_1, actual_issues)
783
784 def testGetOpenAndClosedIssues(self):
785 issue_1, issue_2 = self.SetUpGetIssues()
786 open_issues, closed_issues = self.services.issue.GetOpenAndClosedIssues(
787 self.cnxn, [78901, 78902])
788 self.assertEqual([issue_1], open_issues)
789 self.assertEqual([issue_2], closed_issues)
790
791 def SetUpGetCurrentLocationOfMovedIssue(self, project_id, local_id):
792 issue_id = project_id * 100 + local_id
793 self.services.issue.issueformerlocations_tbl.SelectValue(
794 self.cnxn, 'issue_id', default=0, project_id=project_id,
795 local_id=local_id).AndReturn(issue_id)
796 self.services.issue.issue_tbl.SelectRow(
797 self.cnxn, cols=['project_id', 'local_id'], id=issue_id).AndReturn(
798 (project_id + 1, local_id + 1))
799
800 def testGetCurrentLocationOfMovedIssue(self):
801 self.SetUpGetCurrentLocationOfMovedIssue(789, 1)
802 self.mox.ReplayAll()
803 new_project_id, new_local_id = (
804 self.services.issue.GetCurrentLocationOfMovedIssue(self.cnxn, 789, 1))
805 self.mox.VerifyAll()
806 self.assertEqual(789 + 1, new_project_id)
807 self.assertEqual(1 + 1, new_local_id)
808
809 def SetUpGetPreviousLocations(self, issue_id, location_rows):
810 self.services.issue.issueformerlocations_tbl.Select(
811 self.cnxn, cols=['project_id', 'local_id'],
812 issue_id=issue_id).AndReturn(location_rows)
813
814 def testGetPreviousLocations(self):
815 self.SetUpGetPreviousLocations(78901, [(781, 1), (782, 11), (789, 1)])
816 self.mox.ReplayAll()
817 issue = fake.MakeTestIssue(
818 project_id=789, local_id=1, owner_id=111, summary='sum',
819 status='Live', issue_id=78901)
820 locations = self.services.issue.GetPreviousLocations(self.cnxn, issue)
821 self.mox.VerifyAll()
822 self.assertEqual(locations, [(781, 1), (782, 11)])
823
824 def SetUpInsertIssue(
825 self, label_rows=None, av_rows=None, approver_rows=None,
826 dangling_relation_rows=None):
827 row = (789, 1, 1, 111, 111,
828 self.now, 0, self.now, self.now, self.now, self.now,
829 None, 0,
830 False, 0, 0, False)
831 self.services.issue.issue_tbl.InsertRows(
832 self.cnxn, issue_svc.ISSUE_COLS[1:], [row],
833 commit=False, return_generated_ids=True).AndReturn([78901])
834 self.cnxn.Commit()
835 self.services.issue.issue_tbl.Update(
836 self.cnxn, {'shard': 78901 % settings.num_logical_shards},
837 id=78901, commit=False)
838 self.SetUpUpdateIssuesSummary()
839 self.SetUpUpdateIssuesLabels(label_rows=label_rows)
840 self.SetUpUpdateIssuesFields()
841 self.SetUpUpdateIssuesComponents()
842 self.SetUpUpdateIssuesCc()
843 self.SetUpUpdateIssuesNotify()
844 self.SetUpUpdateIssuesRelation(
845 dangling_relation_rows=dangling_relation_rows)
846 self.SetUpUpdateIssuesApprovals(
847 av_rows=av_rows, approver_rows=approver_rows)
848 self.services.chart.StoreIssueSnapshots(self.cnxn, mox.IgnoreArg(),
849 commit=False)
850
851 def SetUpInsertSpamIssue(self):
852 row = (789, 1, 1, 111, 111,
853 self.now, 0, self.now, self.now, self.now, self.now,
854 None, 0, False, 0, 0, True)
855 self.services.issue.issue_tbl.InsertRows(
856 self.cnxn, issue_svc.ISSUE_COLS[1:], [row],
857 commit=False, return_generated_ids=True).AndReturn([78901])
858 self.cnxn.Commit()
859 self.services.issue.issue_tbl.Update(
860 self.cnxn, {'shard': 78901 % settings.num_logical_shards},
861 id=78901, commit=False)
862 self.SetUpUpdateIssuesSummary()
863 self.SetUpUpdateIssuesLabels()
864 self.SetUpUpdateIssuesFields()
865 self.SetUpUpdateIssuesComponents()
866 self.SetUpUpdateIssuesCc()
867 self.SetUpUpdateIssuesNotify()
868 self.SetUpUpdateIssuesRelation()
869 self.services.chart.StoreIssueSnapshots(self.cnxn, mox.IgnoreArg(),
870 commit=False)
871
872 def SetUpUpdateIssuesSummary(self):
873 self.services.issue.issuesummary_tbl.InsertRows(
874 self.cnxn, ['issue_id', 'summary'],
875 [(78901, 'sum')], replace=True, commit=False)
876
877 def SetUpUpdateIssuesLabels(self, label_rows=None):
878 if label_rows is None:
879 label_rows = [(78901, 1, False, 1)]
880 self.services.issue.issue2label_tbl.Delete(
881 self.cnxn, issue_id=[78901], commit=False)
882 self.services.issue.issue2label_tbl.InsertRows(
883 self.cnxn, ['issue_id', 'label_id', 'derived', 'issue_shard'],
884 label_rows, ignore=True, commit=False)
885
886 def SetUpUpdateIssuesFields(self, issue2fieldvalue_rows=None):
887 issue2fieldvalue_rows = issue2fieldvalue_rows or []
888 self.services.issue.issue2fieldvalue_tbl.Delete(
889 self.cnxn, issue_id=[78901], commit=False)
890 self.services.issue.issue2fieldvalue_tbl.InsertRows(
891 self.cnxn, issue_svc.ISSUE2FIELDVALUE_COLS + ['issue_shard'],
892 issue2fieldvalue_rows, commit=False)
893
894 def SetUpUpdateIssuesComponents(self, issue2component_rows=None):
895 issue2component_rows = issue2component_rows or []
896 self.services.issue.issue2component_tbl.Delete(
897 self.cnxn, issue_id=[78901], commit=False)
898 self.services.issue.issue2component_tbl.InsertRows(
899 self.cnxn, ['issue_id', 'component_id', 'derived', 'issue_shard'],
900 issue2component_rows, ignore=True, commit=False)
901
902 def SetUpUpdateIssuesCc(self, issue2cc_rows=None):
903 issue2cc_rows = issue2cc_rows or []
904 self.services.issue.issue2cc_tbl.Delete(
905 self.cnxn, issue_id=[78901], commit=False)
906 self.services.issue.issue2cc_tbl.InsertRows(
907 self.cnxn, ['issue_id', 'cc_id', 'derived', 'issue_shard'],
908 issue2cc_rows, ignore=True, commit=False)
909
910 def SetUpUpdateIssuesNotify(self, notify_rows=None):
911 notify_rows = notify_rows or []
912 self.services.issue.issue2notify_tbl.Delete(
913 self.cnxn, issue_id=[78901], commit=False)
914 self.services.issue.issue2notify_tbl.InsertRows(
915 self.cnxn, issue_svc.ISSUE2NOTIFY_COLS,
916 notify_rows, ignore=True, commit=False)
917
918 def SetUpUpdateIssuesRelation(
919 self, relation_rows=None, dangling_relation_rows=None):
920 relation_rows = relation_rows or []
921 dangling_relation_rows = dangling_relation_rows or []
922 self.services.issue.issuerelation_tbl.Select(
923 self.cnxn, cols=issue_svc.ISSUERELATION_COLS[:-1],
924 dst_issue_id=[78901], kind='blockedon').AndReturn([])
925 self.services.issue.issuerelation_tbl.Delete(
926 self.cnxn, issue_id=[78901], commit=False)
927 self.services.issue.issuerelation_tbl.InsertRows(
928 self.cnxn, issue_svc.ISSUERELATION_COLS, relation_rows,
929 ignore=True, commit=False)
930 self.services.issue.danglingrelation_tbl.Delete(
931 self.cnxn, issue_id=[78901], commit=False)
932 self.services.issue.danglingrelation_tbl.InsertRows(
933 self.cnxn, issue_svc.DANGLINGRELATION_COLS, dangling_relation_rows,
934 ignore=True, commit=False)
935
936 def SetUpUpdateIssuesApprovals(self, av_rows=None, approver_rows=None):
937 av_rows = av_rows or []
938 approver_rows = approver_rows or []
939 self.services.issue.issue2approvalvalue_tbl.Delete(
940 self.cnxn, issue_id=78901, commit=False)
941 self.services.issue.issue2approvalvalue_tbl.InsertRows(
942 self.cnxn, issue_svc.ISSUE2APPROVALVALUE_COLS, av_rows, commit=False)
943 self.services.issue.issueapproval2approver_tbl.Delete(
944 self.cnxn, issue_id=78901, commit=False)
945 self.services.issue.issueapproval2approver_tbl.InsertRows(
946 self.cnxn, issue_svc.ISSUEAPPROVAL2APPROVER_COLS, approver_rows,
947 commit=False)
948
949 def testInsertIssue(self):
950 self.SetUpInsertIssue()
951 self.mox.ReplayAll()
952 issue = fake.MakeTestIssue(
953 project_id=789, local_id=1, owner_id=111, reporter_id=111,
954 summary='sum', status='New', labels=['Type-Defect'], issue_id=78901,
955 opened_timestamp=self.now, modified_timestamp=self.now)
956 actual_issue_id = self.services.issue.InsertIssue(self.cnxn, issue)
957 self.mox.VerifyAll()
958 self.assertEqual(78901, actual_issue_id)
959
960 def SetUpUpdateIssues(self, given_delta=None):
961 delta = given_delta or {
962 'project_id': 789,
963 'local_id': 1,
964 'owner_id': 111,
965 'status_id': 1,
966 'opened': 123456789,
967 'closed': 0,
968 'modified': 123456789,
969 'owner_modified': 123456789,
970 'status_modified': 123456789,
971 'component_modified': 123456789,
972 'derived_owner_id': None,
973 'derived_status_id': None,
974 'deleted': False,
975 'star_count': 12,
976 'attachment_count': 0,
977 'is_spam': False,
978 }
979 self.services.issue.issue_tbl.Update(
980 self.cnxn, delta, id=78901, commit=False)
981 if not given_delta:
982 self.SetUpUpdateIssuesLabels()
983 self.SetUpUpdateIssuesCc()
984 self.SetUpUpdateIssuesFields()
985 self.SetUpUpdateIssuesComponents()
986 self.SetUpUpdateIssuesNotify()
987 self.SetUpUpdateIssuesSummary()
988 self.SetUpUpdateIssuesRelation()
989 self.services.chart.StoreIssueSnapshots(self.cnxn, mox.IgnoreArg(),
990 commit=False)
991
992 if given_delta:
993 self.services.chart.StoreIssueSnapshots(self.cnxn, mox.IgnoreArg(),
994 commit=False)
995
996 self.cnxn.Commit()
997
998 def testUpdateIssues_Empty(self):
999 # Note: no setup because DB should not be called.
1000 self.mox.ReplayAll()
1001 self.services.issue.UpdateIssues(self.cnxn, [])
1002 self.mox.VerifyAll()
1003
1004 def testUpdateIssues_Normal(self):
1005 issue = fake.MakeTestIssue(
1006 project_id=789, local_id=1, owner_id=111, summary='sum',
1007 status='Live', labels=['Type-Defect'], issue_id=78901,
1008 opened_timestamp=123456789, modified_timestamp=123456789,
1009 star_count=12)
1010 issue.assume_stale = False
1011 self.SetUpUpdateIssues()
1012 self.mox.ReplayAll()
1013 self.services.issue.UpdateIssues(self.cnxn, [issue])
1014 self.mox.VerifyAll()
1015
1016 def testUpdateIssue_Normal(self):
1017 issue = fake.MakeTestIssue(
1018 project_id=789, local_id=1, owner_id=111, summary='sum',
1019 status='Live', labels=['Type-Defect'], issue_id=78901,
1020 opened_timestamp=123456789, modified_timestamp=123456789,
1021 star_count=12)
1022 issue.assume_stale = False
1023 self.SetUpUpdateIssues()
1024 self.mox.ReplayAll()
1025 self.services.issue.UpdateIssue(self.cnxn, issue)
1026 self.mox.VerifyAll()
1027
1028 def testUpdateIssue_Stale(self):
1029 issue = fake.MakeTestIssue(
1030 project_id=789, local_id=1, owner_id=111, summary='sum',
1031 status='Live', labels=['Type-Defect'], issue_id=78901,
1032 opened_timestamp=123456789, modified_timestamp=123456789,
1033 star_count=12)
1034 # Do not set issue.assume_stale = False
1035 # Do not call self.SetUpUpdateIssues() because nothing should be updated.
1036 self.mox.ReplayAll()
1037 self.assertRaises(
1038 AssertionError, self.services.issue.UpdateIssue, self.cnxn, issue)
1039 self.mox.VerifyAll()
1040
1041 def testUpdateIssuesSummary(self):
1042 issue = fake.MakeTestIssue(
1043 local_id=1, issue_id=78901, owner_id=111, summary='sum', status='New',
1044 project_id=789)
1045 issue.assume_stale = False
1046 self.SetUpUpdateIssuesSummary()
1047 self.mox.ReplayAll()
1048 self.services.issue._UpdateIssuesSummary(self.cnxn, [issue], commit=False)
1049 self.mox.VerifyAll()
1050
1051 def testUpdateIssuesLabels(self):
1052 issue = fake.MakeTestIssue(
1053 local_id=1, issue_id=78901, owner_id=111, summary='sum', status='New',
1054 labels=['Type-Defect'], project_id=789)
1055 self.SetUpUpdateIssuesLabels()
1056 self.mox.ReplayAll()
1057 self.services.issue._UpdateIssuesLabels(
1058 self.cnxn, [issue], commit=False)
1059 self.mox.VerifyAll()
1060
1061 def testUpdateIssuesFields_Empty(self):
1062 issue = fake.MakeTestIssue(
1063 local_id=1, issue_id=78901, owner_id=111, summary='sum', status='New',
1064 project_id=789)
1065 self.SetUpUpdateIssuesFields()
1066 self.mox.ReplayAll()
1067 self.services.issue._UpdateIssuesFields(self.cnxn, [issue], commit=False)
1068 self.mox.VerifyAll()
1069
1070 def testUpdateIssuesFields_Some(self):
1071 issue = fake.MakeTestIssue(
1072 local_id=1, issue_id=78901, owner_id=111, summary='sum', status='New',
1073 project_id=789)
1074 issue_shard = issue.issue_id % settings.num_logical_shards
1075 fv1 = tracker_bizobj.MakeFieldValue(345, 679, '', 0, None, None, False)
1076 issue.field_values.append(fv1)
1077 fv2 = tracker_bizobj.MakeFieldValue(346, 0, 'Blue', 0, None, None, True)
1078 issue.field_values.append(fv2)
1079 fv3 = tracker_bizobj.MakeFieldValue(347, 0, '', 0, 1234567890, None, True)
1080 issue.field_values.append(fv3)
1081 fv4 = tracker_bizobj.MakeFieldValue(
1082 348, 0, '', 0, None, 'www.google.com', True, phase_id=14)
1083 issue.field_values.append(fv4)
1084 self.SetUpUpdateIssuesFields(issue2fieldvalue_rows=[
1085 (issue.issue_id, fv1.field_id, fv1.int_value, fv1.str_value,
1086 None, fv1.date_value, fv1.url_value, fv1.derived, None,
1087 issue_shard),
1088 (issue.issue_id, fv2.field_id, fv2.int_value, fv2.str_value,
1089 None, fv2.date_value, fv2.url_value, fv2.derived, None,
1090 issue_shard),
1091 (issue.issue_id, fv3.field_id, fv3.int_value, fv3.str_value,
1092 None, fv3.date_value, fv3.url_value, fv3.derived, None,
1093 issue_shard),
1094 (issue.issue_id, fv4.field_id, fv4.int_value, fv4.str_value,
1095 None, fv4.date_value, fv4.url_value, fv4.derived, 14,
1096 issue_shard),
1097 ])
1098 self.mox.ReplayAll()
1099 self.services.issue._UpdateIssuesFields(self.cnxn, [issue], commit=False)
1100 self.mox.VerifyAll()
1101
1102 def testUpdateIssuesComponents_Empty(self):
1103 issue = fake.MakeTestIssue(
1104 project_id=789, local_id=1, owner_id=111, summary='sum',
1105 status='Live', issue_id=78901)
1106 self.SetUpUpdateIssuesComponents()
1107 self.mox.ReplayAll()
1108 self.services.issue._UpdateIssuesComponents(
1109 self.cnxn, [issue], commit=False)
1110 self.mox.VerifyAll()
1111
1112 def testUpdateIssuesCc_Empty(self):
1113 issue = fake.MakeTestIssue(
1114 project_id=789, local_id=1, owner_id=111, summary='sum',
1115 status='Live', issue_id=78901)
1116 self.SetUpUpdateIssuesCc()
1117 self.mox.ReplayAll()
1118 self.services.issue._UpdateIssuesCc(self.cnxn, [issue], commit=False)
1119 self.mox.VerifyAll()
1120
1121 def testUpdateIssuesCc_Some(self):
1122 issue = fake.MakeTestIssue(
1123 project_id=789, local_id=1, owner_id=111, summary='sum',
1124 status='Live', issue_id=78901)
1125 issue.cc_ids = [222, 333]
1126 issue.derived_cc_ids = [888]
1127 issue_shard = issue.issue_id % settings.num_logical_shards
1128 self.SetUpUpdateIssuesCc(issue2cc_rows=[
1129 (issue.issue_id, 222, False, issue_shard),
1130 (issue.issue_id, 333, False, issue_shard),
1131 (issue.issue_id, 888, True, issue_shard),
1132 ])
1133 self.mox.ReplayAll()
1134 self.services.issue._UpdateIssuesCc(self.cnxn, [issue], commit=False)
1135 self.mox.VerifyAll()
1136
1137 def testUpdateIssuesNotify_Empty(self):
1138 issue = fake.MakeTestIssue(
1139 project_id=789, local_id=1, owner_id=111, summary='sum',
1140 status='Live', issue_id=78901)
1141 self.SetUpUpdateIssuesNotify()
1142 self.mox.ReplayAll()
1143 self.services.issue._UpdateIssuesNotify(self.cnxn, [issue], commit=False)
1144 self.mox.VerifyAll()
1145
1146 def testUpdateIssuesRelation_Empty(self):
1147 issue = fake.MakeTestIssue(
1148 project_id=789, local_id=1, owner_id=111, summary='sum',
1149 status='Live', issue_id=78901)
1150 self.SetUpUpdateIssuesRelation()
1151 self.mox.ReplayAll()
1152 self.services.issue._UpdateIssuesRelation(self.cnxn, [issue], commit=False)
1153 self.mox.VerifyAll()
1154
1155 def testUpdateIssuesRelation_MergedIntoExternal(self):
1156 self.services.issue.issuerelation_tbl.Select = Mock(return_value=[])
1157 self.services.issue.issuerelation_tbl.Delete = Mock()
1158 self.services.issue.issuerelation_tbl.InsertRows = Mock()
1159 self.services.issue.danglingrelation_tbl.Delete = Mock()
1160 self.services.issue.danglingrelation_tbl.InsertRows = Mock()
1161
1162 issue = fake.MakeTestIssue(
1163 project_id=789, local_id=1, owner_id=111, summary='sum',
1164 status='Live', issue_id=78901, merged_into_external='b/5678')
1165
1166 self.services.issue._UpdateIssuesRelation(self.cnxn, [issue])
1167
1168 self.services.issue.danglingrelation_tbl.Delete.assert_called_once_with(
1169 self.cnxn, commit=False, issue_id=[78901])
1170 self.services.issue.danglingrelation_tbl.InsertRows\
1171 .assert_called_once_with(
1172 self.cnxn, ['issue_id', 'dst_issue_project', 'dst_issue_local_id',
1173 'ext_issue_identifier', 'kind'],
1174 [(78901, None, None, 'b/5678', 'mergedinto')],
1175 ignore=True, commit=True)
1176
1177 @patch('time.time')
1178 def testUpdateIssueStructure(self, mockTime):
1179 mockTime.return_value = self.now
1180 reporter_id = 111
1181 comment_content = 'This issue is being converted'
1182 # Set up config
1183 config = self.services.config.GetProjectConfig(
1184 self.cnxn, 789)
1185 config.approval_defs = [
1186 tracker_pb2.ApprovalDef(
1187 approval_id=3, survey='Question3', approver_ids=[222]),
1188 tracker_pb2.ApprovalDef(
1189 approval_id=4, survey='Question4', approver_ids=[444]),
1190 tracker_pb2.ApprovalDef(
1191 approval_id=7, survey='Question7', approver_ids=[222]),
1192 ]
1193 config.field_defs = [
1194 tracker_pb2.FieldDef(
1195 field_id=3, project_id=789, field_name='Cow'),
1196 tracker_pb2.FieldDef(
1197 field_id=4, project_id=789, field_name='Chicken'),
1198 tracker_pb2.FieldDef(
1199 field_id=6, project_id=789, field_name='Llama'),
1200 tracker_pb2.FieldDef(
1201 field_id=7, project_id=789, field_name='Roo'),
1202 tracker_pb2.FieldDef(
1203 field_id=8, project_id=789, field_name='Salmon'),
1204 tracker_pb2.FieldDef(
1205 field_id=9, project_id=789, field_name='Tuna', is_phase_field=True),
1206 tracker_pb2.FieldDef(
1207 field_id=10, project_id=789, field_name='Clown', is_phase_field=True),
1208 tracker_pb2.FieldDef(
1209 field_id=11, project_id=789, field_name='Dory', is_phase_field=True),
1210 ]
1211
1212 # Set up issue
1213 issue = fake.MakeTestIssue(
1214 project_id=789, local_id=1, owner_id=111, summary='sum', status='Open',
1215 issue_id=78901, project_name='proj')
1216 issue.approval_values = [
1217 tracker_pb2.ApprovalValue(
1218 approval_id=3,
1219 phase_id=4,
1220 status=tracker_pb2.ApprovalStatus.APPROVED,
1221 approver_ids=[111], # trumps approval_def approver_ids
1222 ),
1223 tracker_pb2.ApprovalValue(
1224 approval_id=4,
1225 phase_id=5,
1226 approver_ids=[111]), # trumps approval_def approver_ids
1227 tracker_pb2.ApprovalValue(approval_id=6)]
1228 issue.phases = [
1229 tracker_pb2.Phase(name='Expired', phase_id=4),
1230 tracker_pb2.Phase(name='canarY', phase_id=3),
1231 tracker_pb2.Phase(name='Stable', phase_id=2)]
1232 issue.field_values = [
1233 tracker_bizobj.MakeFieldValue(8, None, 'Pink', None, None, None, False),
1234 tracker_bizobj.MakeFieldValue(
1235 9, None, 'Silver', None, None, None, False, phase_id=3),
1236 tracker_bizobj.MakeFieldValue(
1237 10, None, 'Orange', None, None, None, False, phase_id=4),
1238 tracker_bizobj.MakeFieldValue(
1239 11, None, 'Flat', None, None, None, False, phase_id=2),
1240 ]
1241
1242 # Set up template
1243 template = testing_helpers.DefaultTemplates()[0]
1244 template.approval_values = [
1245 tracker_pb2.ApprovalValue(
1246 approval_id=3,
1247 phase_id=6), # Different phase. Nothing else affected.
1248 # No phase. Nothing else affected.
1249 tracker_pb2.ApprovalValue(approval_id=4),
1250 # New approval not already found in issue.
1251 tracker_pb2.ApprovalValue(
1252 approval_id=7,
1253 phase_id=5),
1254 ] # No approval 6
1255 # TODO(jojwang): monorail:4693, rename 'Stable-Full' after all
1256 # 'stable-full' gates have been renamed to 'stable'.
1257 template.phases = [tracker_pb2.Phase(name='Canary', phase_id=5),
1258 tracker_pb2.Phase(name='Stable-Full', phase_id=6)]
1259
1260 self.SetUpInsertComment(
1261 7890101, is_description=True, approval_id=3,
1262 content=config.approval_defs[0].survey, commit=False)
1263 self.SetUpInsertComment(
1264 7890101, is_description=True, approval_id=4,
1265 content=config.approval_defs[1].survey, commit=False)
1266 self.SetUpInsertComment(
1267 7890101, is_description=True, approval_id=7,
1268 content=config.approval_defs[2].survey, commit=False)
1269 amendment_row = (
1270 78901, 7890101, 'custom', None, '-Llama Roo', None, None, 'Approvals')
1271 self.SetUpInsertComment(
1272 7890101, content=comment_content, amendment_rows=[amendment_row],
1273 commit=False)
1274 av_rows = [
1275 (3, 78901, 6, 'approved', None, None),
1276 (4, 78901, None, 'not_set', None, None),
1277 (7, 78901, 5, 'not_set', None, None),
1278 ]
1279 approver_rows = [(3, 111, 78901), (4, 111, 78901), (7, 222, 78901)]
1280 self.SetUpUpdateIssuesApprovals(
1281 av_rows=av_rows, approver_rows=approver_rows)
1282 issue_shard = issue.issue_id % settings.num_logical_shards
1283 issue2fieldvalue_rows = [
1284 (78901, 8, None, 'Pink', None, None, None, False, None, issue_shard),
1285 (78901, 9, None, 'Silver', None, None, None, False, 5, issue_shard),
1286 (78901, 11, None, 'Flat', None, None, None, False, 6, issue_shard),
1287 ]
1288 self.SetUpUpdateIssuesFields(issue2fieldvalue_rows=issue2fieldvalue_rows)
1289
1290 self.mox.ReplayAll()
1291 comment = self.services.issue.UpdateIssueStructure(
1292 self.cnxn, config, issue, template, reporter_id,
1293 comment_content=comment_content, commit=False, invalidate=False)
1294 self.mox.VerifyAll()
1295
1296 expected_avs = [
1297 tracker_pb2.ApprovalValue(
1298 approval_id=3,
1299 phase_id=6,
1300 status=tracker_pb2.ApprovalStatus.APPROVED,
1301 approver_ids=[111],
1302 ),
1303 tracker_pb2.ApprovalValue(
1304 approval_id=4,
1305 status=tracker_pb2.ApprovalStatus.NOT_SET,
1306 approver_ids=[111]),
1307 tracker_pb2.ApprovalValue(
1308 approval_id=7,
1309 status=tracker_pb2.ApprovalStatus.NOT_SET,
1310 phase_id=5,
1311 approver_ids=[222]),
1312 ]
1313 self.assertEqual(issue.approval_values, expected_avs)
1314 self.assertEqual(issue.phases, template.phases)
1315 amendment = tracker_bizobj.MakeApprovalStructureAmendment(
1316 ['Roo', 'Cow', 'Chicken'], ['Cow', 'Chicken', 'Llama'])
1317 expected_comment = self.services.issue._MakeIssueComment(
1318 789, reporter_id, content=comment_content, amendments=[amendment])
1319 expected_comment.issue_id = 78901
1320 expected_comment.id = 7890101
1321 self.assertEqual(expected_comment, comment)
1322
1323 def testDeltaUpdateIssue(self):
1324 pass # TODO(jrobbins): write more tests
1325
1326 def testDeltaUpdateIssue_NoOp(self):
1327 """If the user didn't provide any content, we don't make an IssueComment."""
1328 commenter_id = 222
1329 issue = fake.MakeTestIssue(
1330 project_id=789, local_id=1, owner_id=111, summary='sum',
1331 status='Live', issue_id=78901, project_name='proj')
1332 config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
1333 delta = tracker_pb2.IssueDelta()
1334
1335 amendments, comment_pb = self.services.issue.DeltaUpdateIssue(
1336 self.cnxn, self.services, commenter_id, issue.project_id, config,
1337 issue, delta, comment='', index_now=False, timestamp=self.now)
1338 self.assertEqual([], amendments)
1339 self.assertIsNone(comment_pb)
1340
1341 def testDeltaUpdateIssue_MergedInto(self):
1342 commenter_id = 222
1343 issue = fake.MakeTestIssue(
1344 project_id=789, local_id=1, owner_id=111, summary='sum',
1345 status='Live', issue_id=78901, project_name='proj')
1346 target_issue = fake.MakeTestIssue(
1347 project_id=789, local_id=2, owner_id=111, summary='sum sum',
1348 status='Live', issue_id=78902, project_name='proj')
1349 config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
1350
1351 self.mox.StubOutWithMock(self.services.issue, 'GetIssue')
1352 self.mox.StubOutWithMock(self.services.issue, 'UpdateIssue')
1353 self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
1354 self.mox.StubOutWithMock(self.services.issue, '_UpdateIssuesModified')
1355
1356 self.services.issue.GetIssue(
1357 self.cnxn, 0).AndRaise(exceptions.NoSuchIssueException)
1358 self.services.issue.GetIssue(
1359 self.cnxn, target_issue.issue_id).AndReturn(target_issue)
1360 self.services.issue.UpdateIssue(
1361 self.cnxn, issue, commit=False, invalidate=False)
1362 amendments = [
1363 tracker_bizobj.MakeMergedIntoAmendment(
1364 [('proj', 2)], [None], default_project_name='proj')]
1365 self.services.issue.CreateIssueComment(
1366 self.cnxn, issue, commenter_id, 'comment text', attachments=None,
1367 amendments=amendments, commit=False, is_description=False,
1368 kept_attachments=None, importer_id=None, timestamp=ANY,
1369 inbound_message=None)
1370 self.services.issue._UpdateIssuesModified(
1371 self.cnxn, {issue.issue_id, target_issue.issue_id},
1372 modified_timestamp=self.now, invalidate=True)
1373 self.SetUpEnqueueIssuesForIndexing([78901])
1374
1375 self.mox.ReplayAll()
1376 delta = tracker_pb2.IssueDelta(merged_into=target_issue.issue_id)
1377 self.services.issue.DeltaUpdateIssue(
1378 self.cnxn, self.services, commenter_id, issue.project_id, config,
1379 issue, delta, comment='comment text',
1380 index_now=False, timestamp=self.now)
1381 self.mox.VerifyAll()
1382
1383 def testDeltaUpdateIssue_BlockedOn(self):
1384 commenter_id = 222
1385 issue = fake.MakeTestIssue(
1386 project_id=789, local_id=1, owner_id=111, summary='sum',
1387 status='Live', issue_id=78901, project_name='proj')
1388 blockedon_issue = fake.MakeTestIssue(
1389 project_id=789, local_id=2, owner_id=111, summary='sum sum',
1390 status='Live', issue_id=78902, project_name='proj')
1391 config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
1392
1393 self.mox.StubOutWithMock(self.services.issue, 'GetIssue')
1394 self.mox.StubOutWithMock(self.services.issue, 'GetIssues')
1395 self.mox.StubOutWithMock(self.services.issue, 'LookupIssueRefs')
1396 self.mox.StubOutWithMock(self.services.issue, 'UpdateIssue')
1397 self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
1398 self.mox.StubOutWithMock(self.services.issue, '_UpdateIssuesModified')
1399 self.mox.StubOutWithMock(self.services.issue, "SortBlockedOn")
1400
1401 # Calls in ApplyIssueDelta
1402 # Call to find added blockedon issues.
1403 issue_refs = {blockedon_issue.issue_id: (
1404 blockedon_issue.project_name, blockedon_issue.local_id)}
1405 self.services.issue.LookupIssueRefs(
1406 self.cnxn, [blockedon_issue.issue_id]).AndReturn(issue_refs)
1407
1408 # Call to find removed blockedon issues.
1409 self.services.issue.LookupIssueRefs(self.cnxn, []).AndReturn({})
1410 # Call to sort blockedon issues.
1411 self.services.issue.SortBlockedOn(
1412 self.cnxn, issue, [blockedon_issue.issue_id]).AndReturn(([78902], [0]))
1413
1414 self.services.issue.UpdateIssue(
1415 self.cnxn, issue, commit=False, invalidate=False)
1416 amendments = [
1417 tracker_bizobj.MakeBlockedOnAmendment(
1418 [('proj', 2)], [], default_project_name='proj')]
1419 self.services.issue.CreateIssueComment(
1420 self.cnxn, issue, commenter_id, 'comment text', attachments=None,
1421 amendments=amendments, commit=False, is_description=False,
1422 kept_attachments=None, importer_id=None, timestamp=ANY,
1423 inbound_message=None)
1424 # Call to find added blockedon issues.
1425 self.services.issue.GetIssues(
1426 self.cnxn, [blockedon_issue.issue_id]).AndReturn([blockedon_issue])
1427 self.services.issue.CreateIssueComment(
1428 self.cnxn, blockedon_issue, commenter_id, content='',
1429 amendments=[tracker_bizobj.MakeBlockingAmendment(
1430 [(issue.project_name, issue.local_id)], [],
1431 default_project_name='proj')],
1432 importer_id=None, timestamp=ANY)
1433 # Call to find removed blockedon issues.
1434 self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
1435 # Call to find added blocking issues.
1436 self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
1437 # Call to find removed blocking issues.
1438 self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
1439
1440 self.services.issue._UpdateIssuesModified(
1441 self.cnxn, {issue.issue_id, blockedon_issue.issue_id},
1442 modified_timestamp=self.now, invalidate=True)
1443 self.SetUpEnqueueIssuesForIndexing([78901])
1444
1445 self.mox.ReplayAll()
1446 delta = tracker_pb2.IssueDelta(blocked_on_add=[blockedon_issue.issue_id])
1447 self.services.issue.DeltaUpdateIssue(
1448 self.cnxn, self.services, commenter_id, issue.project_id, config,
1449 issue, delta, comment='comment text',
1450 index_now=False, timestamp=self.now)
1451 self.mox.VerifyAll()
1452
1453 def testDeltaUpdateIssue_Blocking(self):
1454 commenter_id = 222
1455 issue = fake.MakeTestIssue(
1456 project_id=789, local_id=1, owner_id=111, summary='sum',
1457 status='Live', issue_id=78901, project_name='proj')
1458 blocking_issue = fake.MakeTestIssue(
1459 project_id=789, local_id=2, owner_id=111, summary='sum sum',
1460 status='Live', issue_id=78902, project_name='proj')
1461 config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
1462
1463 self.mox.StubOutWithMock(self.services.issue, 'GetIssue')
1464 self.mox.StubOutWithMock(self.services.issue, 'GetIssues')
1465 self.mox.StubOutWithMock(self.services.issue, 'LookupIssueRefs')
1466 self.mox.StubOutWithMock(self.services.issue, 'UpdateIssue')
1467 self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
1468 self.mox.StubOutWithMock(self.services.issue, '_UpdateIssuesModified')
1469 self.mox.StubOutWithMock(self.services.issue, "SortBlockedOn")
1470
1471 # Calls in ApplyIssueDelta
1472 # Call to find added blocking issues.
1473 issue_refs = {blocking_issue: (
1474 blocking_issue.project_name, blocking_issue.local_id)}
1475 self.services.issue.LookupIssueRefs(
1476 self.cnxn, [blocking_issue.issue_id]).AndReturn(issue_refs)
1477 # Call to find removed blocking issues.
1478 self.services.issue.LookupIssueRefs(self.cnxn, []).AndReturn({})
1479
1480 self.services.issue.UpdateIssue(
1481 self.cnxn, issue, commit=False, invalidate=False)
1482 amendments = [
1483 tracker_bizobj.MakeBlockingAmendment(
1484 [('proj', 2)], [], default_project_name='proj')]
1485 self.services.issue.CreateIssueComment(
1486 self.cnxn, issue, commenter_id, 'comment text', attachments=None,
1487 amendments=amendments, commit=False, is_description=False,
1488 kept_attachments=None, importer_id=None, timestamp=ANY,
1489 inbound_message=None)
1490 # Call to find added blockedon issues.
1491 self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
1492 # Call to find removed blockedon issues.
1493 self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
1494 # Call to find added blocking issues.
1495 self.services.issue.GetIssues(
1496 self.cnxn, [blocking_issue.issue_id]).AndReturn([blocking_issue])
1497 self.services.issue.CreateIssueComment(
1498 self.cnxn, blocking_issue, commenter_id, content='',
1499 amendments=[tracker_bizobj.MakeBlockedOnAmendment(
1500 [(issue.project_name, issue.local_id)], [],
1501 default_project_name='proj')],
1502 importer_id=None, timestamp=ANY)
1503 # Call to find removed blocking issues.
1504 self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
1505 self.services.issue._UpdateIssuesModified(
1506 self.cnxn, {issue.issue_id, blocking_issue.issue_id},
1507 modified_timestamp=self.now, invalidate=True)
1508 self.SetUpEnqueueIssuesForIndexing([78901])
1509
1510 self.mox.ReplayAll()
1511 delta = tracker_pb2.IssueDelta(blocking_add=[blocking_issue.issue_id])
1512 self.services.issue.DeltaUpdateIssue(
1513 self.cnxn, self.services, commenter_id, issue.project_id, config,
1514 issue, delta, comment='comment text',
1515 index_now=False, timestamp=self.now)
1516 self.mox.VerifyAll()
1517
1518 def testDeltaUpdateIssue_Imported(self):
1519 """If importer_id is specified, store it."""
1520 commenter_id = 222
1521 issue = fake.MakeTestIssue(
1522 project_id=789, local_id=1, owner_id=111, summary='sum',
1523 status='Live', issue_id=78901, project_name='proj')
1524 issue.assume_stale = False
1525 config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
1526 delta = tracker_pb2.IssueDelta()
1527
1528 self.mox.StubOutWithMock(self.services.issue, 'GetIssue')
1529 self.mox.StubOutWithMock(self.services.issue, 'GetIssues')
1530 self.mox.StubOutWithMock(self.services.issue, 'UpdateIssue')
1531 self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
1532 self.mox.StubOutWithMock(self.services.issue, '_UpdateIssuesModified')
1533 self.mox.StubOutWithMock(self.services.issue, "SortBlockedOn")
1534 self.services.issue.UpdateIssue(
1535 self.cnxn, issue, commit=False, invalidate=False)
1536 # Call to find added blockedon issues.
1537 self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
1538 # Call to find removed blockedon issues.
1539 self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
1540 self.services.issue.CreateIssueComment(
1541 self.cnxn, issue, commenter_id, 'a comment', attachments=None,
1542 amendments=[], commit=False, is_description=False,
1543 kept_attachments=None, importer_id=333, timestamp=ANY,
1544 inbound_message=None).AndReturn(
1545 tracker_pb2.IssueComment(content='a comment', importer_id=333))
1546 self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
1547 self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
1548 self.services.issue._UpdateIssuesModified(
1549 self.cnxn, {issue.issue_id},
1550 modified_timestamp=self.now, invalidate=True)
1551 self.SetUpEnqueueIssuesForIndexing([78901])
1552 self.mox.ReplayAll()
1553
1554 amendments, comment_pb = self.services.issue.DeltaUpdateIssue(
1555 self.cnxn, self.services, commenter_id, issue.project_id, config,
1556 issue, delta, comment='a comment', index_now=False, timestamp=self.now,
1557 importer_id=333)
1558
1559 self.mox.VerifyAll()
1560 self.assertEqual([], amendments)
1561 self.assertEqual('a comment', comment_pb.content)
1562 self.assertEqual(333, comment_pb.importer_id)
1563
1564 def SetUpMoveIssues_NewProject(self):
1565 self.services.issue.issueformerlocations_tbl.Select(
1566 self.cnxn, cols=issue_svc.ISSUEFORMERLOCATIONS_COLS, project_id=789,
1567 issue_id=[78901]).AndReturn([])
1568 self.SetUpAllocateNextLocalID(789, None, None)
1569 self.SetUpUpdateIssues()
1570 self.services.issue.comment_tbl.Update(
1571 self.cnxn, {'project_id': 789}, issue_id=[78901], commit=False)
1572
1573 old_location_rows = [(78901, 711, 2)]
1574 self.services.issue.issueformerlocations_tbl.InsertRows(
1575 self.cnxn, issue_svc.ISSUEFORMERLOCATIONS_COLS, old_location_rows,
1576 ignore=True, commit=False)
1577 self.cnxn.Commit()
1578
1579 def testMoveIssues_NewProject(self):
1580 """Move project 711 issue 2 to become project 789 issue 1."""
1581 dest_project = fake.Project(project_id=789)
1582 issue = fake.MakeTestIssue(
1583 project_id=711, local_id=2, owner_id=111, summary='sum',
1584 status='Live', labels=['Type-Defect'], issue_id=78901,
1585 opened_timestamp=123456789, modified_timestamp=123456789,
1586 star_count=12)
1587 issue.assume_stale = False
1588 self.SetUpMoveIssues_NewProject()
1589 self.mox.ReplayAll()
1590 self.services.issue.MoveIssues(
1591 self.cnxn, dest_project, [issue], self.services.user)
1592 self.mox.VerifyAll()
1593
1594 # TODO(jrobbins): case where issue is moved back into former project
1595
1596 def testExpungeFormerLocations(self):
1597 self.services.issue.issueformerlocations_tbl.Delete(
1598 self.cnxn, project_id=789)
1599
1600 self.mox.ReplayAll()
1601 self.services.issue.ExpungeFormerLocations(self.cnxn, 789)
1602 self.mox.VerifyAll()
1603
1604 def testExpungeIssues(self):
1605 issue_ids = [1, 2]
1606
1607 self.mox.StubOutWithMock(search, 'Index')
1608 search.Index(name=settings.search_index_name_format % 1).AndReturn(
1609 MockIndex())
1610 search.Index(name=settings.search_index_name_format % 2).AndReturn(
1611 MockIndex())
1612
1613 self.services.issue.issuesummary_tbl.Delete(self.cnxn, issue_id=[1, 2])
1614 self.services.issue.issue2label_tbl.Delete(self.cnxn, issue_id=[1, 2])
1615 self.services.issue.issue2component_tbl.Delete(self.cnxn, issue_id=[1, 2])
1616 self.services.issue.issue2cc_tbl.Delete(self.cnxn, issue_id=[1, 2])
1617 self.services.issue.issue2notify_tbl.Delete(self.cnxn, issue_id=[1, 2])
1618 self.services.issue.issueupdate_tbl.Delete(self.cnxn, issue_id=[1, 2])
1619 self.services.issue.attachment_tbl.Delete(self.cnxn, issue_id=[1, 2])
1620 self.services.issue.comment_tbl.Delete(self.cnxn, issue_id=[1, 2])
1621 self.services.issue.issuerelation_tbl.Delete(self.cnxn, issue_id=[1, 2])
1622 self.services.issue.issuerelation_tbl.Delete(self.cnxn, dst_issue_id=[1, 2])
1623 self.services.issue.danglingrelation_tbl.Delete(self.cnxn, issue_id=[1, 2])
1624 self.services.issue.issueformerlocations_tbl.Delete(
1625 self.cnxn, issue_id=[1, 2])
1626 self.services.issue.reindexqueue_tbl.Delete(self.cnxn, issue_id=[1, 2])
1627 self.services.issue.issue_tbl.Delete(self.cnxn, id=[1, 2])
1628
1629 self.mox.ReplayAll()
1630 self.services.issue.ExpungeIssues(self.cnxn, issue_ids)
1631 self.mox.VerifyAll()
1632
1633 def testSoftDeleteIssue(self):
1634 project = fake.Project(project_id=789)
1635 issue_1, issue_2 = self.SetUpGetIssues()
1636 self.services.issue.issue_2lc = TestableIssueTwoLevelCache(
1637 [issue_1, issue_2])
1638 self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
1639 delta = {'deleted': True}
1640 self.services.issue.issue_tbl.Update(
1641 self.cnxn, delta, id=78901, commit=False)
1642
1643 self.services.chart.StoreIssueSnapshots(self.cnxn, mox.IgnoreArg(),
1644 commit=False)
1645
1646 self.cnxn.Commit()
1647 self.mox.ReplayAll()
1648 self.services.issue.SoftDeleteIssue(
1649 self.cnxn, project.project_id, 1, True, self.services.user)
1650 self.mox.VerifyAll()
1651 self.assertTrue(issue_1.deleted)
1652
1653 def SetUpDeleteComponentReferences(self, component_id):
1654 self.services.issue.issue2component_tbl.Delete(
1655 self.cnxn, component_id=component_id)
1656
1657 def testDeleteComponentReferences(self):
1658 self.SetUpDeleteComponentReferences(123)
1659 self.mox.ReplayAll()
1660 self.services.issue.DeleteComponentReferences(self.cnxn, 123)
1661 self.mox.VerifyAll()
1662
1663 ### Local ID generation
1664
1665 def SetUpInitializeLocalID(self, project_id):
1666 self.services.issue.localidcounter_tbl.InsertRow(
1667 self.cnxn, project_id=project_id, used_local_id=0, used_spam_id=0)
1668
1669 def testInitializeLocalID(self):
1670 self.SetUpInitializeLocalID(789)
1671 self.mox.ReplayAll()
1672 self.services.issue.InitializeLocalID(self.cnxn, 789)
1673 self.mox.VerifyAll()
1674
1675 def SetUpAllocateNextLocalID(
1676 self, project_id, highest_in_use, highest_former):
1677 highest_either = max(highest_in_use or 0, highest_former or 0)
1678 self.services.issue.localidcounter_tbl.IncrementCounterValue(
1679 self.cnxn, 'used_local_id', project_id=project_id).AndReturn(
1680 highest_either + 1)
1681
1682 def testAllocateNextLocalID_NewProject(self):
1683 self.SetUpAllocateNextLocalID(789, None, None)
1684 self.mox.ReplayAll()
1685 next_local_id = self.services.issue.AllocateNextLocalID(self.cnxn, 789)
1686 self.mox.VerifyAll()
1687 self.assertEqual(1, next_local_id)
1688
1689 def testAllocateNextLocalID_HighestInUse(self):
1690 self.SetUpAllocateNextLocalID(789, 14, None)
1691 self.mox.ReplayAll()
1692 next_local_id = self.services.issue.AllocateNextLocalID(self.cnxn, 789)
1693 self.mox.VerifyAll()
1694 self.assertEqual(15, next_local_id)
1695
1696 def testAllocateNextLocalID_HighestWasMoved(self):
1697 self.SetUpAllocateNextLocalID(789, 23, 66)
1698 self.mox.ReplayAll()
1699 next_local_id = self.services.issue.AllocateNextLocalID(self.cnxn, 789)
1700 self.mox.VerifyAll()
1701 self.assertEqual(67, next_local_id)
1702
1703 def SetUpGetHighestLocalID(self, project_id, highest_in_use, highest_former):
1704 self.services.issue.issue_tbl.SelectValue(
1705 self.cnxn, 'MAX(local_id)', project_id=project_id).AndReturn(
1706 highest_in_use)
1707 self.services.issue.issueformerlocations_tbl.SelectValue(
1708 self.cnxn, 'MAX(local_id)', project_id=project_id).AndReturn(
1709 highest_former)
1710
1711 def testGetHighestLocalID_OnlyActiveLocalIDs(self):
1712 self.SetUpGetHighestLocalID(789, 14, None)
1713 self.mox.ReplayAll()
1714 highest_id = self.services.issue.GetHighestLocalID(self.cnxn, 789)
1715 self.mox.VerifyAll()
1716 self.assertEqual(14, highest_id)
1717
1718 def testGetHighestLocalID_OnlyFormerIDs(self):
1719 self.SetUpGetHighestLocalID(789, None, 97)
1720 self.mox.ReplayAll()
1721 highest_id = self.services.issue.GetHighestLocalID(self.cnxn, 789)
1722 self.mox.VerifyAll()
1723 self.assertEqual(97, highest_id)
1724
1725 def testGetHighestLocalID_BothActiveAndFormer(self):
1726 self.SetUpGetHighestLocalID(789, 345, 97)
1727 self.mox.ReplayAll()
1728 highest_id = self.services.issue.GetHighestLocalID(self.cnxn, 789)
1729 self.mox.VerifyAll()
1730 self.assertEqual(345, highest_id)
1731
1732 def testGetAllLocalIDsInProject(self):
1733 self.SetUpGetHighestLocalID(789, 14, None)
1734 self.mox.ReplayAll()
1735 local_id_range = self.services.issue.GetAllLocalIDsInProject(self.cnxn, 789)
1736 self.mox.VerifyAll()
1737 self.assertEqual(list(range(1, 15)), local_id_range)
1738
1739 ### Comments
1740
1741 def testConsolidateAmendments_Empty(self):
1742 amendments = []
1743 actual = self.services.issue._ConsolidateAmendments(amendments)
1744 self.assertEqual([], actual)
1745
1746 def testConsolidateAmendments_NoOp(self):
1747 amendments = [
1748 tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
1749 oldvalue='old sum', newvalue='new sum'),
1750 tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
1751 oldvalue='New', newvalue='Accepted')]
1752 actual = self.services.issue._ConsolidateAmendments(amendments)
1753 self.assertEqual(amendments, actual)
1754
1755 def testConsolidateAmendments_StandardFields(self):
1756 amendments = [
1757 tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
1758 oldvalue='New'),
1759 tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
1760 newvalue='Accepted'),
1761 tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
1762 oldvalue='old sum'),
1763 tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
1764 newvalue='new sum')]
1765 actual = self.services.issue._ConsolidateAmendments(amendments)
1766
1767 expected = [
1768 tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
1769 oldvalue='old sum', newvalue='new sum'),
1770 tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
1771 oldvalue='New', newvalue='Accepted')]
1772 self.assertEqual(expected, actual)
1773
1774 def testConsolidateAmendments_BlockerRelations(self):
1775 amendments = [
1776 tracker_pb2.Amendment(
1777 field=tracker_pb2.FieldID('BLOCKEDON'), newvalue='78901'),
1778 tracker_pb2.Amendment(
1779 field=tracker_pb2.FieldID('BLOCKEDON'), newvalue='-b/3 b/1 b/2'),
1780 tracker_pb2.Amendment(
1781 field=tracker_pb2.FieldID('BLOCKING'), newvalue='78902'),
1782 tracker_pb2.Amendment(
1783 field=tracker_pb2.FieldID('BLOCKING'), newvalue='-b/33 b/11 b/22')
1784 ]
1785
1786 actual = self.services.issue._ConsolidateAmendments(amendments)
1787
1788 expected = [
1789 tracker_pb2.Amendment(
1790 field=tracker_pb2.FieldID('BLOCKEDON'),
1791 newvalue='78901 -b/3 b/1 b/2'),
1792 tracker_pb2.Amendment(
1793 field=tracker_pb2.FieldID('BLOCKING'),
1794 newvalue='78902 -b/33 b/11 b/22')
1795 ]
1796 self.assertEqual(expected, actual)
1797
1798 def testConsolidateAmendments_CustomFields(self):
1799 amendments = [
1800 tracker_pb2.Amendment(field=tracker_pb2.FieldID('CUSTOM'),
1801 custom_field_name='a', oldvalue='old a'),
1802 tracker_pb2.Amendment(field=tracker_pb2.FieldID('CUSTOM'),
1803 custom_field_name='b', oldvalue='old b')]
1804 actual = self.services.issue._ConsolidateAmendments(amendments)
1805 self.assertEqual(amendments, actual)
1806
1807 def testConsolidateAmendments_SortAmmendments(self):
1808 amendments = [
1809 tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
1810 oldvalue='New', newvalue='Accepted'),
1811 tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
1812 oldvalue='old sum', newvalue='new sum'),
1813 tracker_pb2.Amendment(field=tracker_pb2.FieldID('LABELS'),
1814 oldvalue='Type-Defect', newvalue='-Type-Defect Type-Enhancement'),
1815 tracker_pb2.Amendment(field=tracker_pb2.FieldID('CC'),
1816 oldvalue='a@google.com', newvalue='b@google.com')]
1817 expected = [
1818 tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
1819 oldvalue='old sum', newvalue='new sum'),
1820 tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
1821 oldvalue='New', newvalue='Accepted'),
1822 tracker_pb2.Amendment(field=tracker_pb2.FieldID('CC'),
1823 oldvalue='a@google.com', newvalue='b@google.com'),
1824 tracker_pb2.Amendment(field=tracker_pb2.FieldID('LABELS'),
1825 oldvalue='Type-Defect', newvalue='-Type-Defect Type-Enhancement')]
1826 actual = self.services.issue._ConsolidateAmendments(amendments)
1827 self.assertEqual(expected, actual)
1828
1829 def testDeserializeComments_Empty(self):
1830 comments = self.services.issue._DeserializeComments([], [], [], [], [], [])
1831 self.assertEqual([], comments)
1832
1833 def SetUpCommentRows(self):
1834 comment_rows = [
1835 (7890101, 78901, self.now, 789, 111,
1836 None, False, False, 'unused_commentcontent_id'),
1837 (7890102, 78901, self.now, 789, 111,
1838 None, False, False, 'unused_commentcontent_id')]
1839 commentcontent_rows = [(7890101, 'content', 'msg'),
1840 (7890102, 'content2', 'msg')]
1841 amendment_rows = [
1842 (1, 78901, 7890101, 'cc', 'old', 'new val', 222, None, None)]
1843 attachment_rows = []
1844 approval_rows = [(23, 7890102)]
1845 importer_rows = []
1846 return (comment_rows, commentcontent_rows, amendment_rows,
1847 attachment_rows, approval_rows, importer_rows)
1848
1849 def testDeserializeComments_Normal(self):
1850 (comment_rows, commentcontent_rows, amendment_rows,
1851 attachment_rows, approval_rows, importer_rows) = self.SetUpCommentRows()
1852 commentcontent_rows = [(7890101, 'content', 'msg')]
1853 comments = self.services.issue._DeserializeComments(
1854 comment_rows, commentcontent_rows, amendment_rows, attachment_rows,
1855 approval_rows, importer_rows)
1856 self.assertEqual(2, len(comments))
1857
1858 def testDeserializeComments_Imported(self):
1859 (comment_rows, commentcontent_rows, amendment_rows,
1860 attachment_rows, approval_rows, _) = self.SetUpCommentRows()
1861 importer_rows = [(7890101, 222)]
1862 commentcontent_rows = [(7890101, 'content', 'msg')]
1863 comments = self.services.issue._DeserializeComments(
1864 comment_rows, commentcontent_rows, amendment_rows, attachment_rows,
1865 approval_rows, importer_rows)
1866 self.assertEqual(2, len(comments))
1867 self.assertEqual(222, comments[0].importer_id)
1868
1869 def MockTheRestOfGetCommentsByID(self, comment_ids):
1870 self.services.issue.commentcontent_tbl.Select = Mock(
1871 return_value=[
1872 (cid + 5000, 'content', None) for cid in comment_ids])
1873 self.services.issue.issueupdate_tbl.Select = Mock(
1874 return_value=[])
1875 self.services.issue.attachment_tbl.Select = Mock(
1876 return_value=[])
1877 self.services.issue.issueapproval2comment_tbl.Select = Mock(
1878 return_value=[])
1879 self.services.issue.commentimporter_tbl.Select = Mock(
1880 return_value=[])
1881
1882 def testGetCommentsByID_Normal(self):
1883 """We can load comments by comment_ids."""
1884 comment_ids = [101001, 101002, 101003]
1885 self.services.issue.comment_tbl.Select = Mock(
1886 return_value=[
1887 (cid, cid - cid % 100, self.now, 789, 111,
1888 None, False, False, cid + 5000)
1889 for cid in comment_ids])
1890 self.MockTheRestOfGetCommentsByID(comment_ids)
1891
1892 comments = self.services.issue.GetCommentsByID(
1893 self.cnxn, comment_ids, [0, 1, 2])
1894
1895 self.services.issue.comment_tbl.Select.assert_called_with(
1896 self.cnxn, cols=issue_svc.COMMENT_COLS,
1897 id=comment_ids, shard_id=ANY)
1898
1899 self.assertEqual(3, len(comments))
1900
1901 def testGetCommentsByID_CacheReplicationLag(self):
1902 self._testGetCommentsByID_ReplicationLag(True)
1903
1904 def testGetCommentsByID_NoCacheReplicationLag(self):
1905 self._testGetCommentsByID_ReplicationLag(False)
1906
1907 def _testGetCommentsByID_ReplicationLag(self, use_cache):
1908 """If not all comments are on the replica, we try the primary DB."""
1909 comment_ids = [101001, 101002, 101003]
1910 replica_comment_ids = comment_ids[:-1]
1911
1912 return_value_1 = [
1913 (cid, cid - cid % 100, self.now, 789, 111,
1914 None, False, False, cid + 5000)
1915 for cid in replica_comment_ids]
1916 return_value_2 = [
1917 (cid, cid - cid % 100, self.now, 789, 111,
1918 None, False, False, cid + 5000)
1919 for cid in comment_ids]
1920 return_values = [return_value_1, return_value_2]
1921 self.services.issue.comment_tbl.Select = Mock(
1922 side_effect=lambda *_args, **_kwargs: return_values.pop(0))
1923
1924 self.MockTheRestOfGetCommentsByID(comment_ids)
1925
1926 comments = self.services.issue.GetCommentsByID(
1927 self.cnxn, comment_ids, [0, 1, 2], use_cache=use_cache)
1928
1929 self.services.issue.comment_tbl.Select.assert_called_with(
1930 self.cnxn, cols=issue_svc.COMMENT_COLS,
1931 id=comment_ids, shard_id=ANY)
1932 self.services.issue.comment_tbl.Select.assert_called_with(
1933 self.cnxn, cols=issue_svc.COMMENT_COLS,
1934 id=comment_ids, shard_id=ANY)
1935 self.assertEqual(3, len(comments))
1936
1937 def SetUpGetComments(self, issue_ids):
1938 # Assumes one comment per issue.
1939 cids = [issue_id + 1000 for issue_id in issue_ids]
1940 self.services.issue.comment_tbl.Select(
1941 self.cnxn, cols=issue_svc.COMMENT_COLS,
1942 where=None, issue_id=issue_ids, order_by=[('created', [])],
1943 shard_id=mox.IsA(int)).AndReturn([
1944 (issue_id + 1000, issue_id, self.now, 789, 111,
1945 None, False, False, issue_id + 5000)
1946 for issue_id in issue_ids])
1947 self.services.issue.commentcontent_tbl.Select(
1948 self.cnxn, cols=issue_svc.COMMENTCONTENT_COLS,
1949 id=[issue_id + 5000 for issue_id in issue_ids],
1950 shard_id=mox.IsA(int)).AndReturn([
1951 (issue_id + 5000, 'content', None) for issue_id in issue_ids])
1952 self.services.issue.issueapproval2comment_tbl.Select(
1953 self.cnxn, cols=issue_svc.ISSUEAPPROVAL2COMMENT_COLS,
1954 comment_id=cids).AndReturn([
1955 (23, cid) for cid in cids])
1956
1957 # Assume no amendments or attachment for now.
1958 self.services.issue.issueupdate_tbl.Select(
1959 self.cnxn, cols=issue_svc.ISSUEUPDATE_COLS,
1960 comment_id=cids, shard_id=mox.IsA(int)).AndReturn([])
1961 attachment_rows = []
1962 if issue_ids:
1963 attachment_rows = [
1964 (1234, issue_ids[0], cids[0], 'a_filename', 1024, 'text/plain',
1965 False, None)]
1966
1967 self.services.issue.attachment_tbl.Select(
1968 self.cnxn, cols=issue_svc.ATTACHMENT_COLS,
1969 comment_id=cids, shard_id=mox.IsA(int)).AndReturn(attachment_rows)
1970
1971 self.services.issue.commentimporter_tbl.Select(
1972 self.cnxn, cols=issue_svc.COMMENTIMPORTER_COLS,
1973 comment_id=cids, shard_id=mox.IsA(int)).AndReturn([])
1974
1975 def testGetComments_Empty(self):
1976 self.SetUpGetComments([])
1977 self.mox.ReplayAll()
1978 comments = self.services.issue.GetComments(
1979 self.cnxn, issue_id=[])
1980 self.mox.VerifyAll()
1981 self.assertEqual(0, len(comments))
1982
1983 def testGetComments_Normal(self):
1984 self.SetUpGetComments([100001, 100002])
1985 self.mox.ReplayAll()
1986 comments = self.services.issue.GetComments(
1987 self.cnxn, issue_id=[100001, 100002])
1988 self.mox.VerifyAll()
1989 self.assertEqual(2, len(comments))
1990 self.assertEqual('content', comments[0].content)
1991 self.assertEqual('content', comments[1].content)
1992 self.assertEqual(23, comments[0].approval_id)
1993 self.assertEqual(23, comments[1].approval_id)
1994
1995 def SetUpGetComment_Found(self, comment_id):
1996 # Assumes one comment per issue.
1997 commentcontent_id = comment_id * 10
1998 self.services.issue.comment_tbl.Select(
1999 self.cnxn, cols=issue_svc.COMMENT_COLS,
2000 where=None, id=comment_id, order_by=[('created', [])],
2001 shard_id=mox.IsA(int)).AndReturn([
2002 (comment_id, int(comment_id // 100), self.now, 789, 111,
2003 None, False, True, commentcontent_id)])
2004 self.services.issue.commentcontent_tbl.Select(
2005 self.cnxn, cols=issue_svc.COMMENTCONTENT_COLS,
2006 id=[commentcontent_id], shard_id=mox.IsA(int)).AndReturn([
2007 (commentcontent_id, 'content', None)])
2008 self.services.issue.issueapproval2comment_tbl.Select(
2009 self.cnxn, cols=issue_svc.ISSUEAPPROVAL2COMMENT_COLS,
2010 comment_id=[comment_id]).AndReturn([(23, comment_id)])
2011 # Assume no amendments or attachment for now.
2012 self.services.issue.issueupdate_tbl.Select(
2013 self.cnxn, cols=issue_svc.ISSUEUPDATE_COLS,
2014 comment_id=[comment_id], shard_id=mox.IsA(int)).AndReturn([])
2015 self.services.issue.attachment_tbl.Select(
2016 self.cnxn, cols=issue_svc.ATTACHMENT_COLS,
2017 comment_id=[comment_id], shard_id=mox.IsA(int)).AndReturn([])
2018 self.services.issue.commentimporter_tbl.Select(
2019 self.cnxn, cols=issue_svc.COMMENTIMPORTER_COLS,
2020 comment_id=[comment_id], shard_id=mox.IsA(int)).AndReturn([])
2021
2022 def testGetComment_Found(self):
2023 self.SetUpGetComment_Found(7890101)
2024 self.mox.ReplayAll()
2025 comment = self.services.issue.GetComment(self.cnxn, 7890101)
2026 self.mox.VerifyAll()
2027 self.assertEqual('content', comment.content)
2028 self.assertEqual(23, comment.approval_id)
2029
2030 def SetUpGetComment_Missing(self, comment_id):
2031 # Assumes one comment per issue.
2032 self.services.issue.comment_tbl.Select(
2033 self.cnxn, cols=issue_svc.COMMENT_COLS,
2034 where=None, id=comment_id, order_by=[('created', [])],
2035 shard_id=mox.IsA(int)).AndReturn([])
2036 self.services.issue.commentcontent_tbl.Select(
2037 self.cnxn, cols=issue_svc.COMMENTCONTENT_COLS,
2038 id=[], shard_id=mox.IsA(int)).AndReturn([])
2039 self.services.issue.issueapproval2comment_tbl.Select(
2040 self.cnxn, cols=issue_svc.ISSUEAPPROVAL2COMMENT_COLS,
2041 comment_id=[]).AndReturn([])
2042 # Assume no amendments or attachment for now.
2043 self.services.issue.issueupdate_tbl.Select(
2044 self.cnxn, cols=issue_svc.ISSUEUPDATE_COLS,
2045 comment_id=[], shard_id=mox.IsA(int)).AndReturn([])
2046 self.services.issue.attachment_tbl.Select(
2047 self.cnxn, cols=issue_svc.ATTACHMENT_COLS, comment_id=[],
2048 shard_id=mox.IsA(int)).AndReturn([])
2049 self.services.issue.commentimporter_tbl.Select(
2050 self.cnxn, cols=issue_svc.COMMENTIMPORTER_COLS,
2051 comment_id=[], shard_id=mox.IsA(int)).AndReturn([])
2052
2053 def testGetComment_Missing(self):
2054 self.SetUpGetComment_Missing(7890101)
2055 self.mox.ReplayAll()
2056 self.assertRaises(
2057 exceptions.NoSuchCommentException,
2058 self.services.issue.GetComment, self.cnxn, 7890101)
2059 self.mox.VerifyAll()
2060
2061 def testGetCommentsForIssue(self):
2062 issue = fake.MakeTestIssue(789, 1, 'Summary', 'New', 111)
2063 self.SetUpGetComments([issue.issue_id])
2064 self.mox.ReplayAll()
2065 self.services.issue.GetCommentsForIssue(self.cnxn, issue.issue_id)
2066 self.mox.VerifyAll()
2067
2068 def testGetCommentsForIssues(self):
2069 self.SetUpGetComments([100001, 100002])
2070 self.mox.ReplayAll()
2071 self.services.issue.GetCommentsForIssues(
2072 self.cnxn, issue_ids=[100001, 100002])
2073 self.mox.VerifyAll()
2074
2075 def SetUpInsertComment(
2076 self, comment_id, is_spam=False, is_description=False, approval_id=None,
2077 content=None, amendment_rows=None, commit=True):
2078 content = content or 'content'
2079 commentcontent_id = comment_id * 10
2080 self.services.issue.commentcontent_tbl.InsertRow(
2081 self.cnxn, content=content,
2082 inbound_message=None, commit=False).AndReturn(commentcontent_id)
2083 self.services.issue.comment_tbl.InsertRow(
2084 self.cnxn, issue_id=78901, created=self.now, project_id=789,
2085 commenter_id=111, deleted_by=None, is_spam=is_spam,
2086 is_description=is_description, commentcontent_id=commentcontent_id,
2087 commit=False).AndReturn(comment_id)
2088
2089 amendment_rows = amendment_rows or []
2090 self.services.issue.issueupdate_tbl.InsertRows(
2091 self.cnxn, issue_svc.ISSUEUPDATE_COLS[1:], amendment_rows,
2092 commit=False)
2093
2094 attachment_rows = []
2095 self.services.issue.attachment_tbl.InsertRows(
2096 self.cnxn, issue_svc.ATTACHMENT_COLS[1:], attachment_rows,
2097 commit=False)
2098
2099 if approval_id:
2100 self.services.issue.issueapproval2comment_tbl.InsertRows(
2101 self.cnxn, issue_svc.ISSUEAPPROVAL2COMMENT_COLS,
2102 [(approval_id, comment_id)], commit=False)
2103
2104 if commit:
2105 self.cnxn.Commit()
2106
2107 def testInsertComment(self):
2108 self.SetUpInsertComment(7890101, approval_id=23)
2109 self.mox.ReplayAll()
2110 comment = tracker_pb2.IssueComment(
2111 issue_id=78901, timestamp=self.now, project_id=789, user_id=111,
2112 content='content', approval_id=23)
2113 self.services.issue.InsertComment(self.cnxn, comment, commit=True)
2114 self.mox.VerifyAll()
2115 self.assertEqual(7890101, comment.id)
2116
2117 def SetUpUpdateComment(self, comment_id, delta=None):
2118 delta = delta or {
2119 'commenter_id': 111,
2120 'deleted_by': 222,
2121 'is_spam': False,
2122 }
2123 self.services.issue.comment_tbl.Update(
2124 self.cnxn, delta, id=comment_id)
2125
2126 def testUpdateComment(self):
2127 self.SetUpUpdateComment(7890101)
2128 self.mox.ReplayAll()
2129 comment = tracker_pb2.IssueComment(
2130 id=7890101, issue_id=78901, timestamp=self.now, project_id=789,
2131 user_id=111, content='new content', deleted_by=222,
2132 is_spam=False)
2133 self.services.issue._UpdateComment(self.cnxn, comment)
2134 self.mox.VerifyAll()
2135
2136 def testMakeIssueComment(self):
2137 comment = self.services.issue._MakeIssueComment(
2138 789, 111, 'content', timestamp=self.now, approval_id=23,
2139 importer_id=222)
2140 self.assertEqual('content', comment.content)
2141 self.assertEqual([], comment.amendments)
2142 self.assertEqual([], comment.attachments)
2143 self.assertEqual(comment.approval_id, 23)
2144 self.assertEqual(222, comment.importer_id)
2145
2146 def testMakeIssueComment_NonAscii(self):
2147 _ = self.services.issue._MakeIssueComment(
2148 789, 111, 'content', timestamp=self.now,
2149 inbound_message=u'sent by написа')
2150
2151 def testCreateIssueComment_Normal(self):
2152 issue_1, _issue_2 = self.SetUpGetIssues()
2153 self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
2154 self.SetUpInsertComment(7890101, approval_id=24)
2155 self.mox.ReplayAll()
2156 comment = self.services.issue.CreateIssueComment(
2157 self.cnxn, issue_1, 111, 'content', timestamp=self.now, approval_id=24)
2158 self.mox.VerifyAll()
2159 self.assertEqual('content', comment.content)
2160
2161 def testCreateIssueComment_EditDescription(self):
2162 issue_1, _issue_2 = self.SetUpGetIssues()
2163 self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
2164 self.services.issue.attachment_tbl.Select(
2165 self.cnxn, cols=issue_svc.ATTACHMENT_COLS, id=[123])
2166 self.SetUpInsertComment(7890101, is_description=True)
2167 self.mox.ReplayAll()
2168
2169 comment = self.services.issue.CreateIssueComment(
2170 self.cnxn, issue_1, 111, 'content', is_description=True,
2171 kept_attachments=[123], timestamp=self.now)
2172 self.mox.VerifyAll()
2173 self.assertEqual('content', comment.content)
2174
2175 def testCreateIssueComment_Spam(self):
2176 issue_1, _issue_2 = self.SetUpGetIssues()
2177 self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
2178 self.SetUpInsertComment(7890101, is_spam=True)
2179 self.mox.ReplayAll()
2180 comment = self.services.issue.CreateIssueComment(
2181 self.cnxn, issue_1, 111, 'content', timestamp=self.now, is_spam=True)
2182 self.mox.VerifyAll()
2183 self.assertEqual('content', comment.content)
2184 self.assertTrue(comment.is_spam)
2185
2186 def testSoftDeleteComment(self):
2187 """Deleting a comment with an attachment marks it and updates count."""
2188 issue_1, issue_2 = self.SetUpGetIssues()
2189 self.services.issue.issue_2lc = TestableIssueTwoLevelCache(
2190 [issue_1, issue_2])
2191 issue_1.attachment_count = 1
2192 issue_1.assume_stale = False
2193 comment = tracker_pb2.IssueComment(id=7890101)
2194 comment.attachments = [tracker_pb2.Attachment()]
2195 self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
2196 self.SetUpUpdateComment(
2197 comment.id, delta={'deleted_by': 222, 'is_spam': False})
2198 self.SetUpUpdateIssues(given_delta={'attachment_count': 0})
2199 self.SetUpEnqueueIssuesForIndexing([78901])
2200 self.mox.ReplayAll()
2201 self.services.issue.SoftDeleteComment(
2202 self.cnxn, issue_1, comment, 222, self.services.user)
2203 self.mox.VerifyAll()
2204
2205 ### Approvals
2206
2207 def testGetIssueApproval(self):
2208 av_24 = tracker_pb2.ApprovalValue(approval_id=24)
2209 av_25 = tracker_pb2.ApprovalValue(approval_id=25)
2210 issue_1 = fake.MakeTestIssue(
2211 project_id=789, local_id=1, owner_id=111, summary='sum',
2212 status='Live', issue_id=78901, approval_values=[av_24, av_25])
2213 issue_1.project_name = 'proj'
2214 self.services.issue.issue_2lc.CacheItem(78901, issue_1)
2215
2216 issue, actual_approval_value = self.services.issue.GetIssueApproval(
2217 self.cnxn, issue_1.issue_id, av_24.approval_id)
2218
2219 self.assertEqual(av_24, actual_approval_value)
2220 self.assertEqual(issue, issue_1)
2221
2222 def testGetIssueApproval_NoSuchApproval(self):
2223 issue_1 = fake.MakeTestIssue(
2224 project_id=789, local_id=1, owner_id=111, summary='sum',
2225 status='Live', issue_id=78901)
2226 issue_1.project_name = 'proj'
2227 self.services.issue.issue_2lc.CacheItem(78901, issue_1)
2228 self.assertRaises(
2229 exceptions.NoSuchIssueApprovalException,
2230 self.services.issue.GetIssueApproval,
2231 self.cnxn, issue_1.issue_id, 24)
2232
2233 def testDeltaUpdateIssueApproval(self):
2234 config = self.services.config.GetProjectConfig(
2235 self.cnxn, 789)
2236 config.field_defs = [
2237 tracker_pb2.FieldDef(
2238 field_id=1, project_id=789, field_name='EstDays',
2239 field_type=tracker_pb2.FieldTypes.INT_TYPE,
2240 applicable_type=''),
2241 tracker_pb2.FieldDef(
2242 field_id=2, project_id=789, field_name='Tag',
2243 field_type=tracker_pb2.FieldTypes.STR_TYPE,
2244 applicable_type=''),
2245 ]
2246 self.services.config.StoreConfig(self.cnxn, config)
2247
2248 issue = fake.MakeTestIssue(
2249 project_id=789, local_id=1, summary='summary', status='New',
2250 owner_id=999, issue_id=78901, labels=['noodle-puppies'])
2251 av = tracker_pb2.ApprovalValue(approval_id=23)
2252 final_av = tracker_pb2.ApprovalValue(
2253 approval_id=23, setter_id=111, set_on=1234,
2254 status=tracker_pb2.ApprovalStatus.REVIEW_REQUESTED,
2255 approver_ids=[222, 444])
2256 labels_add = ['snakes-are']
2257 label_id = 1001
2258 labels_remove = ['noodle-puppies']
2259 amendments = [
2260 tracker_bizobj.MakeApprovalStatusAmendment(
2261 tracker_pb2.ApprovalStatus.REVIEW_REQUESTED),
2262 tracker_bizobj.MakeApprovalApproversAmendment([222, 444], []),
2263 tracker_bizobj.MakeFieldAmendment(1, config, [4], []),
2264 tracker_bizobj.MakeFieldClearedAmendment(2, config),
2265 tracker_bizobj.MakeLabelsAmendment(labels_add, labels_remove)
2266 ]
2267 approval_delta = tracker_pb2.ApprovalDelta(
2268 status=tracker_pb2.ApprovalStatus.REVIEW_REQUESTED,
2269 approver_ids_add=[222, 444], set_on=1234,
2270 subfield_vals_add=[
2271 tracker_bizobj.MakeFieldValue(1, 4, None, None, None, None, False)
2272 ],
2273 labels_add=labels_add,
2274 labels_remove=labels_remove,
2275 subfields_clear=[2]
2276 )
2277
2278 self.services.issue.issue2approvalvalue_tbl.Update = Mock()
2279 self.services.issue.issueapproval2approver_tbl.Delete = Mock()
2280 self.services.issue.issueapproval2approver_tbl.InsertRows = Mock()
2281 self.services.issue.issue2fieldvalue_tbl.Delete = Mock()
2282 self.services.issue.issue2fieldvalue_tbl.InsertRows = Mock()
2283 self.services.issue.issue2label_tbl.Delete = Mock()
2284 self.services.issue.issue2label_tbl.InsertRows = Mock()
2285 self.services.issue.CreateIssueComment = Mock()
2286 self.services.config.LookupLabelID = Mock(return_value=label_id)
2287 shard = issue.issue_id % settings.num_logical_shards
2288 fv_rows = [(78901, 1, 4, None, None, None, None, False, None, shard)]
2289 label_rows = [(78901, label_id, False, shard)]
2290
2291 self.services.issue.DeltaUpdateIssueApproval(
2292 self.cnxn, 111, config, issue, av, approval_delta, 'some comment',
2293 attachments=[], commit=False, kept_attachments=[1, 2, 3])
2294
2295 self.assertEqual(av, final_av)
2296
2297 self.services.issue.issue2approvalvalue_tbl.Update.assert_called_once_with(
2298 self.cnxn,
2299 {'status': 'review_requested', 'setter_id': 111, 'set_on': 1234},
2300 approval_id=23, issue_id=78901, commit=False)
2301 self.services.issue.issueapproval2approver_tbl.\
2302 Delete.assert_called_once_with(
2303 self.cnxn, issue_id=78901, approval_id=23, commit=False)
2304 self.services.issue.issueapproval2approver_tbl.\
2305 InsertRows.assert_called_once_with(
2306 self.cnxn, issue_svc.ISSUEAPPROVAL2APPROVER_COLS,
2307 [(23, 222, 78901), (23, 444, 78901)], commit=False)
2308 self.services.issue.issue2fieldvalue_tbl.\
2309 Delete.assert_called_once_with(
2310 self.cnxn, issue_id=[78901], commit=False)
2311 self.services.issue.issue2fieldvalue_tbl.\
2312 InsertRows.assert_called_once_with(
2313 self.cnxn, issue_svc.ISSUE2FIELDVALUE_COLS + ['issue_shard'],
2314 fv_rows, commit=False)
2315 self.services.issue.issue2label_tbl.\
2316 Delete.assert_called_once_with(
2317 self.cnxn, issue_id=[78901], commit=False)
2318 self.services.issue.issue2label_tbl.\
2319 InsertRows.assert_called_once_with(
2320 self.cnxn, issue_svc.ISSUE2LABEL_COLS + ['issue_shard'],
2321 label_rows, ignore=True, commit=False)
2322 self.services.issue.CreateIssueComment.assert_called_once_with(
2323 self.cnxn, issue, 111, 'some comment', amendments=amendments,
2324 approval_id=23, is_description=False, attachments=[], commit=False,
2325 kept_attachments=[1, 2, 3])
2326
2327 def testDeltaUpdateIssueApproval_IsDescription(self):
2328 config = self.services.config.GetProjectConfig(
2329 self.cnxn, 789)
2330 issue = fake.MakeTestIssue(
2331 project_id=789, local_id=1, summary='summary', status='New',
2332 owner_id=999, issue_id=78901)
2333 av = tracker_pb2.ApprovalValue(approval_id=23)
2334 approval_delta = tracker_pb2.ApprovalDelta()
2335
2336 self.services.issue.CreateIssueComment = Mock()
2337
2338 self.services.issue.DeltaUpdateIssueApproval(
2339 self.cnxn, 111, config, issue, av, approval_delta, 'better response',
2340 is_description=True, commit=False)
2341
2342 self.services.issue.CreateIssueComment.assert_called_once_with(
2343 self.cnxn, issue, 111, 'better response', amendments=[],
2344 approval_id=23, is_description=True, attachments=None, commit=False,
2345 kept_attachments=None)
2346
2347 def testUpdateIssueApprovalStatus(self):
2348 av = tracker_pb2.ApprovalValue(approval_id=23, setter_id=111, set_on=1234)
2349
2350 self.services.issue.issue2approvalvalue_tbl.Update(
2351 self.cnxn, {'status': 'not_set', 'setter_id': 111, 'set_on': 1234},
2352 approval_id=23, issue_id=78901, commit=False)
2353
2354 self.mox.ReplayAll()
2355 self.services.issue._UpdateIssueApprovalStatus(
2356 self.cnxn, 78901, av.approval_id, av.status,
2357 av.setter_id, av.set_on)
2358 self.mox.VerifyAll()
2359
2360 def testUpdateIssueApprovalApprovers(self):
2361 self.services.issue.issueapproval2approver_tbl.Delete(
2362 self.cnxn, issue_id=78901, approval_id=23, commit=False)
2363 self.services.issue.issueapproval2approver_tbl.InsertRows(
2364 self.cnxn, issue_svc.ISSUEAPPROVAL2APPROVER_COLS,
2365 [(23, 111, 78901), (23, 222, 78901), (23, 444, 78901)], commit=False)
2366
2367 self.mox.ReplayAll()
2368 self.services.issue._UpdateIssueApprovalApprovers(
2369 self.cnxn, 78901, 23, [111, 222, 444])
2370 self.mox.VerifyAll()
2371
2372 ### Attachments
2373
2374 def testGetAttachmentAndContext(self):
2375 # TODO(jrobbins): re-implemnent to use Google Cloud Storage.
2376 pass
2377
2378 def SetUpUpdateAttachment(self, comment_id, attachment_id, delta):
2379 self.services.issue.attachment_tbl.Update(
2380 self.cnxn, delta, id=attachment_id)
2381 self.services.issue.comment_2lc.InvalidateKeys(
2382 self.cnxn, [comment_id])
2383
2384
2385 def testUpdateAttachment(self):
2386 delta = {
2387 'filename': 'a_filename',
2388 'filesize': 1024,
2389 'mimetype': 'text/plain',
2390 'deleted': False,
2391 }
2392 self.SetUpUpdateAttachment(5678, 1234, delta)
2393 self.mox.ReplayAll()
2394 attach = tracker_pb2.Attachment(
2395 attachment_id=1234, filename='a_filename', filesize=1024,
2396 mimetype='text/plain')
2397 comment = tracker_pb2.IssueComment(id=5678)
2398 self.services.issue._UpdateAttachment(self.cnxn, comment, attach)
2399 self.mox.VerifyAll()
2400
2401 def testStoreAttachmentBlob(self):
2402 # TODO(jrobbins): re-implemnent to use Google Cloud Storage.
2403 pass
2404
2405 def testSoftDeleteAttachment(self):
2406 issue = fake.MakeTestIssue(789, 1, 'sum', 'New', 111, issue_id=78901)
2407 issue.assume_stale = False
2408 issue.attachment_count = 1
2409
2410 comment = tracker_pb2.IssueComment(
2411 project_id=789, content='soon to be deleted', user_id=111,
2412 issue_id=issue.issue_id)
2413 attachment = tracker_pb2.Attachment(
2414 attachment_id=1234)
2415 comment.attachments.append(attachment)
2416
2417 self.SetUpUpdateAttachment(179901, 1234, {'deleted': True})
2418 self.SetUpUpdateIssues(given_delta={'attachment_count': 0})
2419 self.SetUpEnqueueIssuesForIndexing([78901])
2420
2421 self.mox.ReplayAll()
2422 self.services.issue.SoftDeleteAttachment(
2423 self.cnxn, issue, comment, 1234, self.services.user)
2424 self.mox.VerifyAll()
2425
2426 ### Reindex queue
2427
2428 def SetUpEnqueueIssuesForIndexing(self, issue_ids):
2429 reindex_rows = [(issue_id,) for issue_id in issue_ids]
2430 self.services.issue.reindexqueue_tbl.InsertRows(
2431 self.cnxn, ['issue_id'], reindex_rows, ignore=True, commit=True)
2432
2433 def testEnqueueIssuesForIndexing(self):
2434 self.SetUpEnqueueIssuesForIndexing([78901])
2435 self.mox.ReplayAll()
2436 self.services.issue.EnqueueIssuesForIndexing(self.cnxn, [78901])
2437 self.mox.VerifyAll()
2438
2439 def SetUpReindexIssues(self, issue_ids):
2440 self.services.issue.reindexqueue_tbl.Select(
2441 self.cnxn, order_by=[('created', [])],
2442 limit=50).AndReturn([(issue_id,) for issue_id in issue_ids])
2443
2444 if issue_ids:
2445 _issue_1, _issue_2 = self.SetUpGetIssues()
2446 self.services.issue.reindexqueue_tbl.Delete(
2447 self.cnxn, issue_id=issue_ids)
2448
2449 def testReindexIssues_QueueEmpty(self):
2450 self.SetUpReindexIssues([])
2451 self.mox.ReplayAll()
2452 self.services.issue.ReindexIssues(self.cnxn, 50, self.services.user)
2453 self.mox.VerifyAll()
2454
2455 def testReindexIssues_QueueHasTwoIssues(self):
2456 self.SetUpReindexIssues([78901, 78902])
2457 self.mox.ReplayAll()
2458 self.services.issue.ReindexIssues(self.cnxn, 50, self.services.user)
2459 self.mox.VerifyAll()
2460
2461 ### Search functions
2462
2463 def SetUpRunIssueQuery(
2464 self, rows, limit=settings.search_limit_per_shard):
2465 self.services.issue.issue_tbl.Select(
2466 self.cnxn, shard_id=1, distinct=True, cols=['Issue.id'],
2467 left_joins=[], where=[('Issue.deleted = %s', [False])], order_by=[],
2468 limit=limit).AndReturn(rows)
2469
2470 def testRunIssueQuery_NoResults(self):
2471 self.SetUpRunIssueQuery([])
2472 self.mox.ReplayAll()
2473 result_iids, capped = self.services.issue.RunIssueQuery(
2474 self.cnxn, [], [], [], shard_id=1)
2475 self.mox.VerifyAll()
2476 self.assertEqual([], result_iids)
2477 self.assertFalse(capped)
2478
2479 def testRunIssueQuery_Normal(self):
2480 self.SetUpRunIssueQuery([(1,), (11,), (21,)])
2481 self.mox.ReplayAll()
2482 result_iids, capped = self.services.issue.RunIssueQuery(
2483 self.cnxn, [], [], [], shard_id=1)
2484 self.mox.VerifyAll()
2485 self.assertEqual([1, 11, 21], result_iids)
2486 self.assertFalse(capped)
2487
2488 def testRunIssueQuery_Capped(self):
2489 try:
2490 orig = settings.search_limit_per_shard
2491 settings.search_limit_per_shard = 3
2492 self.SetUpRunIssueQuery([(1,), (11,), (21,)], limit=3)
2493 self.mox.ReplayAll()
2494 result_iids, capped = self.services.issue.RunIssueQuery(
2495 self.cnxn, [], [], [], shard_id=1)
2496 self.mox.VerifyAll()
2497 self.assertEqual([1, 11, 21], result_iids)
2498 self.assertTrue(capped)
2499 finally:
2500 settings.search_limit_per_shard = orig
2501
2502 def SetUpGetIIDsByLabelIDs(self):
2503 self.services.issue.issue_tbl.Select(
2504 self.cnxn, shard_id=1, cols=['id'],
2505 left_joins=[('Issue2Label ON Issue.id = Issue2Label.issue_id', [])],
2506 label_id=[123, 456], project_id=789,
2507 where=[('shard = %s', [1])]
2508 ).AndReturn([(1,), (2,), (3,)])
2509
2510 def testGetIIDsByLabelIDs(self):
2511 self.SetUpGetIIDsByLabelIDs()
2512 self.mox.ReplayAll()
2513 iids = self.services.issue.GetIIDsByLabelIDs(self.cnxn, [123, 456], 789, 1)
2514 self.mox.VerifyAll()
2515 self.assertEqual([1, 2, 3], iids)
2516
2517 def testGetIIDsByLabelIDsWithEmptyLabelIds(self):
2518 self.mox.ReplayAll()
2519 iids = self.services.issue.GetIIDsByLabelIDs(self.cnxn, [], 789, 1)
2520 self.mox.VerifyAll()
2521 self.assertEqual([], iids)
2522
2523 def SetUpGetIIDsByParticipant(self):
2524 self.services.issue.issue_tbl.Select(
2525 self.cnxn, shard_id=1, cols=['id'],
2526 reporter_id=[111, 888],
2527 where=[('shard = %s', [1]), ('Issue.project_id IN (%s)', [789])]
2528 ).AndReturn([(1,)])
2529 self.services.issue.issue_tbl.Select(
2530 self.cnxn, shard_id=1, cols=['id'],
2531 owner_id=[111, 888],
2532 where=[('shard = %s', [1]), ('Issue.project_id IN (%s)', [789])]
2533 ).AndReturn([(2,)])
2534 self.services.issue.issue_tbl.Select(
2535 self.cnxn, shard_id=1, cols=['id'],
2536 derived_owner_id=[111, 888],
2537 where=[('shard = %s', [1]), ('Issue.project_id IN (%s)', [789])]
2538 ).AndReturn([(3,)])
2539 self.services.issue.issue_tbl.Select(
2540 self.cnxn, shard_id=1, cols=['id'],
2541 left_joins=[('Issue2Cc ON Issue2Cc.issue_id = Issue.id', [])],
2542 cc_id=[111, 888],
2543 where=[('shard = %s', [1]), ('Issue.project_id IN (%s)', [789]),
2544 ('cc_id IS NOT NULL', [])]
2545 ).AndReturn([(4,)])
2546 self.services.issue.issue_tbl.Select(
2547 self.cnxn, shard_id=1, cols=['Issue.id'],
2548 left_joins=[
2549 ('Issue2FieldValue ON Issue.id = Issue2FieldValue.issue_id', []),
2550 ('FieldDef ON Issue2FieldValue.field_id = FieldDef.id', [])],
2551 user_id=[111, 888], grants_perm='View',
2552 where=[('shard = %s', [1]), ('Issue.project_id IN (%s)', [789]),
2553 ('user_id IS NOT NULL', [])]
2554 ).AndReturn([(5,)])
2555
2556 def testGetIIDsByParticipant(self):
2557 self.SetUpGetIIDsByParticipant()
2558 self.mox.ReplayAll()
2559 iids = self.services.issue.GetIIDsByParticipant(
2560 self.cnxn, [111, 888], [789], 1)
2561 self.mox.VerifyAll()
2562 self.assertEqual([1, 2, 3, 4, 5], iids)
2563
2564 ### Issue Dependency reranking
2565
2566 def testSortBlockedOn(self):
2567 issue = self.SetUpSortBlockedOn()
2568 self.mox.ReplayAll()
2569 ret = self.services.issue.SortBlockedOn(
2570 self.cnxn, issue, issue.blocked_on_iids)
2571 self.mox.VerifyAll()
2572 self.assertEqual(ret, ([78902, 78903], [20, 10]))
2573
2574 def SetUpSortBlockedOn(self):
2575 issue = fake.MakeTestIssue(
2576 project_id=789, local_id=1, owner_id=111, summary='sum',
2577 status='Live', issue_id=78901)
2578 issue.project_name = 'proj'
2579 issue.blocked_on_iids = [78902, 78903]
2580 issue.blocked_on_ranks = [20, 10]
2581 self.services.issue.issue_2lc.CacheItem(78901, issue)
2582 blocked_on_rows = (
2583 (78901, 78902, 'blockedon', 20), (78901, 78903, 'blockedon', 10))
2584 self.services.issue.issuerelation_tbl.Select(
2585 self.cnxn, cols=issue_svc.ISSUERELATION_COLS,
2586 issue_id=issue.issue_id, dst_issue_id=issue.blocked_on_iids,
2587 kind='blockedon',
2588 order_by=[('rank DESC', []), ('dst_issue_id', [])]).AndReturn(
2589 blocked_on_rows)
2590 return issue
2591
2592 def testApplyIssueRerank(self):
2593 blocker_ids = [78902, 78903]
2594 relations_to_change = list(zip(blocker_ids, [20, 10]))
2595 self.services.issue.issuerelation_tbl.Delete(
2596 self.cnxn, issue_id=78901, dst_issue_id=blocker_ids, commit=False)
2597 insert_rows = [(78901, blocker_id, 'blockedon', rank)
2598 for blocker_id, rank in relations_to_change]
2599 self.services.issue.issuerelation_tbl.InsertRows(
2600 self.cnxn, cols=issue_svc.ISSUERELATION_COLS, row_values=insert_rows,
2601 commit=True)
2602
2603 self.mox.StubOutWithMock(self.services.issue, "InvalidateIIDs")
2604
2605 self.services.issue.InvalidateIIDs(self.cnxn, [78901])
2606 self.mox.ReplayAll()
2607 self.services.issue.ApplyIssueRerank(self.cnxn, 78901, relations_to_change)
2608 self.mox.VerifyAll()
2609
2610 def testExpungeUsersInIssues(self):
2611 comment_id_rows = [(12, 78901, 112), (13, 78902, 113)]
2612 comment_ids = [12, 13]
2613 content_ids = [112, 113]
2614 self.services.issue.comment_tbl.Select = Mock(
2615 return_value=comment_id_rows)
2616 self.services.issue.commentcontent_tbl.Update = Mock()
2617 self.services.issue.comment_tbl.Update = Mock()
2618
2619 fv_issue_id_rows = [(78902,), (78903,), (78904,)]
2620 self.services.issue.issue2fieldvalue_tbl.Select = Mock(
2621 return_value=fv_issue_id_rows)
2622 self.services.issue.issue2fieldvalue_tbl.Delete = Mock()
2623 self.services.issue.issueapproval2approver_tbl.Delete = Mock()
2624 self.services.issue.issue2approvalvalue_tbl.Update = Mock()
2625
2626 self.services.issue.issueupdate_tbl.Update = Mock()
2627
2628 self.services.issue.issue2notify_tbl.Delete = Mock()
2629
2630 cc_issue_id_rows = [(78904,), (78905,), (78906,)]
2631 self.services.issue.issue2cc_tbl.Select = Mock(
2632 return_value=cc_issue_id_rows)
2633 self.services.issue.issue2cc_tbl.Delete = Mock()
2634 owner_issue_id_rows = [(78907,), (78908,), (78909,)]
2635 derived_owner_issue_id_rows = [(78910,), (78911,), (78912,)]
2636 reporter_issue_id_rows = [(78912,), (78913,)]
2637 self.services.issue.issue_tbl.Select = Mock(
2638 side_effect=[owner_issue_id_rows, derived_owner_issue_id_rows,
2639 reporter_issue_id_rows])
2640 self.services.issue.issue_tbl.Update = Mock()
2641
2642 self.services.issue.issuesnapshot_tbl.Update = Mock()
2643 self.services.issue.issuesnapshot2cc_tbl.Delete = Mock()
2644
2645 emails = ['cow@farm.com', 'pig@farm.com', 'chicken@farm.com']
2646 user_ids = [222, 888, 444]
2647 user_ids_by_email = {
2648 email: user_id for user_id, email in zip(user_ids, emails)}
2649 commit = False
2650 limit = 50
2651
2652 affected_user_ids = self.services.issue.ExpungeUsersInIssues(
2653 self.cnxn, user_ids_by_email, limit=limit)
2654 self.assertItemsEqual(
2655 affected_user_ids,
2656 [78901, 78902, 78903, 78904, 78905, 78906, 78907, 78908, 78909,
2657 78910, 78911, 78912, 78913])
2658
2659 self.services.issue.comment_tbl.Select.assert_called_once()
2660 _cnxn, kwargs = self.services.issue.comment_tbl.Select.call_args
2661 self.assertEqual(
2662 kwargs['cols'], ['Comment.id', 'Comment.issue_id', 'commentcontent_id'])
2663 self.assertItemsEqual(kwargs['commenter_id'], user_ids)
2664 self.assertEqual(kwargs['limit'], limit)
2665
2666 # since user_ids are passed to ExpungeUsersInIssues via a dictionary,
2667 # we cannot know the order of the user_ids list that the method
2668 # ends up using. To be able to use assert_called_with()
2669 # rather than extract call_args, we are saving the order of user_ids
2670 # used by the method after confirming that it has the correct items.
2671 user_ids = kwargs['commenter_id']
2672
2673 self.services.issue.commentcontent_tbl.Update.assert_called_once_with(
2674 self.cnxn, {'inbound_message': None}, id=content_ids, commit=commit)
2675 self.assertEqual(
2676 len(self.services.issue.comment_tbl.Update.call_args_list), 2)
2677 self.services.issue.comment_tbl.Update.assert_any_call(
2678 self.cnxn, {'commenter_id': framework_constants.DELETED_USER_ID},
2679 id=comment_ids, commit=False)
2680 self.services.issue.comment_tbl.Update.assert_any_call(
2681 self.cnxn, {'deleted_by': framework_constants.DELETED_USER_ID},
2682 deleted_by=user_ids, commit=False, limit=limit)
2683
2684 # field values
2685 self.services.issue.issue2fieldvalue_tbl.Select.assert_called_once_with(
2686 self.cnxn, cols=['issue_id'], user_id=user_ids, limit=limit)
2687 self.services.issue.issue2fieldvalue_tbl.Delete.assert_called_once_with(
2688 self.cnxn, user_id=user_ids, limit=limit, commit=commit)
2689
2690 # approval values
2691 self.services.issue.issueapproval2approver_tbl.\
2692Delete.assert_called_once_with(
2693 self.cnxn, approver_id=user_ids, commit=commit, limit=limit)
2694 self.services.issue.issue2approvalvalue_tbl.Update.assert_called_once_with(
2695 self.cnxn, {'setter_id': framework_constants.DELETED_USER_ID},
2696 setter_id=user_ids, commit=commit, limit=limit)
2697
2698 # issue ccs
2699 self.services.issue.issue2cc_tbl.Select.assert_called_once_with(
2700 self.cnxn, cols=['issue_id'], cc_id=user_ids, limit=limit)
2701 self.services.issue.issue2cc_tbl.Delete.assert_called_once_with(
2702 self.cnxn, cc_id=user_ids, limit=limit, commit=commit)
2703
2704 # issue owners
2705 self.services.issue.issue_tbl.Select.assert_any_call(
2706 self.cnxn, cols=['id'], owner_id=user_ids, limit=limit)
2707 self.services.issue.issue_tbl.Update.assert_any_call(
2708 self.cnxn, {'owner_id': None},
2709 id=[row[0] for row in owner_issue_id_rows], commit=commit)
2710 self.services.issue.issue_tbl.Select.assert_any_call(
2711 self.cnxn, cols=['id'], derived_owner_id=user_ids, limit=limit)
2712 self.services.issue.issue_tbl.Update.assert_any_call(
2713 self.cnxn, {'derived_owner_id': None},
2714 id=[row[0] for row in derived_owner_issue_id_rows], commit=commit)
2715
2716 # issue reporter
2717 self.services.issue.issue_tbl.Select.assert_any_call(
2718 self.cnxn, cols=['id'], reporter_id=user_ids, limit=limit)
2719 self.services.issue.issue_tbl.Update.assert_any_call(
2720 self.cnxn, {'reporter_id': framework_constants.DELETED_USER_ID},
2721 id=[row[0] for row in reporter_issue_id_rows], commit=commit)
2722
2723 self.assertEqual(
2724 3, len(self.services.issue.issue_tbl.Update.call_args_list))
2725
2726 # issue updates
2727 self.services.issue.issueupdate_tbl.Update.assert_any_call(
2728 self.cnxn, {'added_user_id': framework_constants.DELETED_USER_ID},
2729 added_user_id=user_ids, commit=commit)
2730 self.services.issue.issueupdate_tbl.Update.assert_any_call(
2731 self.cnxn, {'removed_user_id': framework_constants.DELETED_USER_ID},
2732 removed_user_id=user_ids, commit=commit)
2733 self.assertEqual(
2734 2, len(self.services.issue.issueupdate_tbl.Update.call_args_list))
2735
2736 # issue notify
2737 call_args_list = self.services.issue.issue2notify_tbl.Delete.call_args_list
2738 self.assertEqual(1, len(call_args_list))
2739 _cnxn, kwargs = call_args_list[0]
2740 self.assertItemsEqual(kwargs['email'], emails)
2741 self.assertEqual(kwargs['commit'], commit)
2742
2743 # issue snapshots
2744 self.services.issue.issuesnapshot_tbl.Update.assert_any_call(
2745 self.cnxn, {'owner_id': framework_constants.DELETED_USER_ID},
2746 owner_id=user_ids, commit=commit, limit=limit)
2747 self.services.issue.issuesnapshot_tbl.Update.assert_any_call(
2748 self.cnxn, {'reporter_id': framework_constants.DELETED_USER_ID},
2749 reporter_id=user_ids, commit=commit, limit=limit)
2750 self.assertEqual(
2751 2, len(self.services.issue.issuesnapshot_tbl.Update.call_args_list))
2752
2753 self.services.issue.issuesnapshot2cc_tbl.Delete.assert_called_once_with(
2754 self.cnxn, cc_id=user_ids, commit=commit, limit=limit)