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