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