Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/services/test/issue_svc_test.py b/services/test/issue_svc_test.py
new file mode 100644
index 0000000..b6fe682
--- /dev/null
+++ b/services/test/issue_svc_test.py
@@ -0,0 +1,2754 @@
+# -*- coding: utf-8 -*-
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Unit tests for issue_svc module."""
+
+from __future__ import division
+from __future__ import print_function
+from __future__ import absolute_import
+
+import logging
+import time
+import unittest
+from mock import patch, Mock, ANY
+
+import mox
+
+from google.appengine.api import search
+from google.appengine.ext import testbed
+
+import settings
+from framework import exceptions
+from framework import framework_constants
+from framework import sql
+from proto import tracker_pb2
+from services import caches
+from services import chart_svc
+from services import issue_svc
+from services import service_manager
+from services import spam_svc
+from services import tracker_fulltext
+from testing import fake
+from testing import testing_helpers
+from tracker import tracker_bizobj
+
+
+class MockIndex(object):
+
+ def delete(self, string_list):
+ pass
+
+
+def MakeIssueService(project_service, config_service, cache_manager,
+ chart_service, my_mox):
+ issue_service = issue_svc.IssueService(
+ project_service, config_service, cache_manager, chart_service)
+ for table_var in [
+ 'issue_tbl', 'issuesummary_tbl', 'issue2label_tbl',
+ 'issue2component_tbl', 'issue2cc_tbl', 'issue2notify_tbl',
+ 'issue2fieldvalue_tbl', 'issuerelation_tbl', 'danglingrelation_tbl',
+ 'issueformerlocations_tbl', 'comment_tbl', 'commentcontent_tbl',
+ 'issueupdate_tbl', 'attachment_tbl', 'reindexqueue_tbl',
+ 'localidcounter_tbl', 'issuephasedef_tbl', 'issue2approvalvalue_tbl',
+ 'issueapproval2approver_tbl', 'issueapproval2comment_tbl',
+ 'commentimporter_tbl']:
+ setattr(issue_service, table_var, my_mox.CreateMock(sql.SQLTableManager))
+
+ return issue_service
+
+
+class TestableIssueTwoLevelCache(issue_svc.IssueTwoLevelCache):
+
+ def __init__(self, issue_list):
+ cache_manager = fake.CacheManager()
+ super(TestableIssueTwoLevelCache, self).__init__(
+ cache_manager, None, None, None)
+ self.cache = caches.RamCache(cache_manager, 'issue')
+ self.memcache_prefix = 'issue:'
+ self.pb_class = tracker_pb2.Issue
+
+ self.issue_dict = {
+ issue.issue_id: issue
+ for issue in issue_list}
+
+ def FetchItems(self, cnxn, issue_ids, shard_id=None):
+ return {
+ issue_id: self.issue_dict[issue_id]
+ for issue_id in issue_ids
+ if issue_id in self.issue_dict}
+
+
+class IssueIDTwoLevelCacheTest(unittest.TestCase):
+
+ def setUp(self):
+ self.mox = mox.Mox()
+ self.cnxn = 'fake connection'
+ self.project_service = fake.ProjectService()
+ self.config_service = fake.ConfigService()
+ self.cache_manager = fake.CacheManager()
+ self.chart_service = chart_svc.ChartService(self.config_service)
+ self.issue_service = MakeIssueService(
+ self.project_service, self.config_service, self.cache_manager,
+ self.chart_service, self.mox)
+ self.issue_id_2lc = self.issue_service.issue_id_2lc
+ self.spam_service = fake.SpamService()
+
+ def tearDown(self):
+ self.mox.UnsetStubs()
+ self.mox.ResetAll()
+
+ def testDeserializeIssueIDs_Empty(self):
+ issue_id_dict = self.issue_id_2lc._DeserializeIssueIDs([])
+ self.assertEqual({}, issue_id_dict)
+
+ def testDeserializeIssueIDs_Normal(self):
+ rows = [(789, 1, 78901), (789, 2, 78902), (789, 3, 78903)]
+ issue_id_dict = self.issue_id_2lc._DeserializeIssueIDs(rows)
+ expected = {
+ (789, 1): 78901,
+ (789, 2): 78902,
+ (789, 3): 78903,
+ }
+ self.assertEqual(expected, issue_id_dict)
+
+ def SetUpFetchItems(self):
+ where = [
+ ('(Issue.project_id = %s AND Issue.local_id IN (%s,%s,%s))',
+ [789, 1, 2, 3])]
+ rows = [(789, 1, 78901), (789, 2, 78902), (789, 3, 78903)]
+ self.issue_service.issue_tbl.Select(
+ self.cnxn, cols=['project_id', 'local_id', 'id'],
+ where=where, or_where_conds=True).AndReturn(rows)
+
+ def testFetchItems(self):
+ project_local_ids_list = [(789, 1), (789, 2), (789, 3)]
+ issue_ids = [78901, 78902, 78903]
+ self.SetUpFetchItems()
+ self.mox.ReplayAll()
+ issue_dict = self.issue_id_2lc.FetchItems(
+ self.cnxn, project_local_ids_list)
+ self.mox.VerifyAll()
+ self.assertItemsEqual(project_local_ids_list, list(issue_dict.keys()))
+ self.assertItemsEqual(issue_ids, list(issue_dict.values()))
+
+ def testKeyToStr(self):
+ self.assertEqual('789,1', self.issue_id_2lc._KeyToStr((789, 1)))
+
+ def testStrToKey(self):
+ self.assertEqual((789, 1), self.issue_id_2lc._StrToKey('789,1'))
+
+
+class IssueTwoLevelCacheTest(unittest.TestCase):
+
+ def setUp(self):
+ self.mox = mox.Mox()
+ self.cnxn = 'fake connection'
+ self.project_service = fake.ProjectService()
+ self.config_service = fake.ConfigService()
+ self.cache_manager = fake.CacheManager()
+ self.chart_service = chart_svc.ChartService(self.config_service)
+ self.issue_service = MakeIssueService(
+ self.project_service, self.config_service, self.cache_manager,
+ self.chart_service, self.mox)
+ self.issue_2lc = self.issue_service.issue_2lc
+
+ now = int(time.time())
+ self.project_service.TestAddProject('proj', project_id=789)
+ self.issue_rows = [
+ (78901, 789, 1, 1, 111, 222,
+ now, now, now, now, now, now,
+ 0, 0, 0, 1, 0, False)]
+ self.summary_rows = [(78901, 'sum')]
+ self.label_rows = [(78901, 1, 0)]
+ self.component_rows = []
+ self.cc_rows = [(78901, 333, 0)]
+ self.notify_rows = []
+ self.fieldvalue_rows = []
+ self.blocked_on_rows = (
+ (78901, 78902, 'blockedon', 20), (78903, 78901, 'blockedon', 10))
+ self.blocking_rows = ()
+ self.merged_rows = ()
+ self.relation_rows = (
+ self.blocked_on_rows + self.blocking_rows + self.merged_rows)
+ self.dangling_relation_rows = [
+ (78901, 'codesite', 5001, None, 'blocking'),
+ (78901, 'codesite', 5002, None, 'blockedon'),
+ (78901, None, None, 'b/1234567', 'blockedon')]
+ self.phase_rows = [(1, 'Canary', 1), (2, 'Stable', 11)]
+ self.approvalvalue_rows = [(22, 78901, 2, 'not_set', None, None),
+ (21, 78901, 1, 'needs_review', None, None),
+ (23, 78901, 1, 'not_set', None, None)]
+ self.av_approver_rows = [
+ (21, 111, 78901), (21, 222, 78901), (21, 333, 78901)]
+
+ def tearDown(self):
+ self.mox.UnsetStubs()
+ self.mox.ResetAll()
+
+ def testUnpackApprovalValue(self):
+ row = next(
+ row for row in self.approvalvalue_rows if row[3] == 'needs_review')
+ av, issue_id = self.issue_2lc._UnpackApprovalValue(row)
+ self.assertEqual(av.status, tracker_pb2.ApprovalStatus.NEEDS_REVIEW)
+ self.assertIsNone(av.setter_id)
+ self.assertIsNone(av.set_on)
+ self.assertEqual(issue_id, 78901)
+ self.assertEqual(av.phase_id, 1)
+
+ def testUnpackApprovalValue_MissingStatus(self):
+ av, _issue_id = self.issue_2lc._UnpackApprovalValue(
+ (21, 78901, 1, '', None, None))
+ self.assertEqual(av.status, tracker_pb2.ApprovalStatus.NOT_SET)
+
+ def testUnpackPhase(self):
+ phase = self.issue_2lc._UnpackPhase(
+ self.phase_rows[0])
+ self.assertEqual(phase.name, 'Canary')
+ self.assertEqual(phase.phase_id, 1)
+ self.assertEqual(phase.rank, 1)
+
+ def testDeserializeIssues_Empty(self):
+ issue_dict = self.issue_2lc._DeserializeIssues(
+ self.cnxn, [], [], [], [], [], [], [], [], [], [], [], [])
+ self.assertEqual({}, issue_dict)
+
+ def testDeserializeIssues_Normal(self):
+ issue_dict = self.issue_2lc._DeserializeIssues(
+ self.cnxn, self.issue_rows, self.summary_rows, self.label_rows,
+ self.component_rows, self.cc_rows, self.notify_rows,
+ self.fieldvalue_rows, self.relation_rows, self.dangling_relation_rows,
+ self.phase_rows, self.approvalvalue_rows, self.av_approver_rows)
+ self.assertItemsEqual([78901], list(issue_dict.keys()))
+ issue = issue_dict[78901]
+ self.assertEqual(len(issue.phases), 2)
+ self.assertIsNotNone(tracker_bizobj.FindPhaseByID(1, issue.phases))
+ av_21 = tracker_bizobj.FindApprovalValueByID(
+ 21, issue.approval_values)
+ self.assertEqual(av_21.phase_id, 1)
+ self.assertItemsEqual(av_21.approver_ids, [111, 222, 333])
+ self.assertIsNotNone(tracker_bizobj.FindPhaseByID(2, issue.phases))
+ self.assertEqual(issue.phases,
+ [tracker_pb2.Phase(rank=1, phase_id=1, name='Canary'),
+ tracker_pb2.Phase(rank=11, phase_id=2, name='Stable')])
+ av_22 = tracker_bizobj.FindApprovalValueByID(
+ 22, issue.approval_values)
+ self.assertEqual(av_22.phase_id, 2)
+ self.assertEqual([
+ tracker_pb2.DanglingIssueRef(
+ project=row[1],
+ issue_id=row[2],
+ ext_issue_identifier=row[3])
+ for row in self.dangling_relation_rows
+ if row[4] == 'blockedon'
+ ], issue.dangling_blocked_on_refs)
+ self.assertEqual([
+ tracker_pb2.DanglingIssueRef(
+ project=row[1],
+ issue_id=row[2],
+ ext_issue_identifier=row[3])
+ for row in self.dangling_relation_rows
+ if row[4] == 'blocking'
+ ], issue.dangling_blocking_refs)
+
+ def testDeserializeIssues_UnexpectedLabel(self):
+ unexpected_label_rows = [
+ (78901, 999, 0)
+ ]
+ self.assertRaises(
+ AssertionError,
+ self.issue_2lc._DeserializeIssues,
+ self.cnxn, self.issue_rows, self.summary_rows, unexpected_label_rows,
+ self.component_rows, self.cc_rows, self.notify_rows,
+ self.fieldvalue_rows, self.relation_rows, self.dangling_relation_rows,
+ self.phase_rows, self.approvalvalue_rows, self.av_approver_rows)
+
+ def testDeserializeIssues_UnexpectedIssueRelation(self):
+ unexpected_relation_rows = [
+ (78990, 78999, 'blockedon', None)
+ ]
+ self.assertRaises(
+ AssertionError,
+ self.issue_2lc._DeserializeIssues,
+ self.cnxn, self.issue_rows, self.summary_rows, self.label_rows,
+ self.component_rows, self.cc_rows, self.notify_rows,
+ self.fieldvalue_rows, unexpected_relation_rows,
+ self.dangling_relation_rows, self.phase_rows, self.approvalvalue_rows,
+ self.av_approver_rows)
+
+ def testDeserializeIssues_ExternalMergedInto(self):
+ """_DeserializeIssues handles external mergedinto refs correctly."""
+ dangling_relation_rows = self.dangling_relation_rows + [
+ (78901, None, None, 'b/1234567', 'mergedinto')]
+ issue_dict = self.issue_2lc._DeserializeIssues(
+ self.cnxn, self.issue_rows, self.summary_rows, self.label_rows,
+ self.component_rows, self.cc_rows, self.notify_rows,
+ self.fieldvalue_rows, self.relation_rows, dangling_relation_rows,
+ self.phase_rows, self.approvalvalue_rows, self.av_approver_rows)
+ self.assertEqual('b/1234567', issue_dict[78901].merged_into_external)
+
+ def SetUpFetchItems(self, issue_ids, has_approvalvalues=True):
+ shard_id = None
+ self.issue_service.issue_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUE_COLS, id=issue_ids,
+ shard_id=shard_id).AndReturn(self.issue_rows)
+ self.issue_service.issuesummary_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUESUMMARY_COLS, shard_id=shard_id,
+ issue_id=issue_ids).AndReturn(self.summary_rows)
+ self.issue_service.issue2label_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUE2LABEL_COLS, shard_id=shard_id,
+ issue_id=issue_ids).AndReturn(self.label_rows)
+ self.issue_service.issue2component_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUE2COMPONENT_COLS, shard_id=shard_id,
+ issue_id=issue_ids).AndReturn(self.component_rows)
+ self.issue_service.issue2cc_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUE2CC_COLS, shard_id=shard_id,
+ issue_id=issue_ids).AndReturn(self.cc_rows)
+ self.issue_service.issue2notify_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUE2NOTIFY_COLS, shard_id=shard_id,
+ issue_id=issue_ids).AndReturn(self.notify_rows)
+ self.issue_service.issue2fieldvalue_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUE2FIELDVALUE_COLS, shard_id=shard_id,
+ issue_id=issue_ids).AndReturn(self.fieldvalue_rows)
+ if has_approvalvalues:
+ self.issue_service.issuephasedef_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUEPHASEDEF_COLS,
+ id=[1, 2]).AndReturn(self.phase_rows)
+ self.issue_service.issue2approvalvalue_tbl.Select(
+ self.cnxn,
+ cols=issue_svc.ISSUE2APPROVALVALUE_COLS,
+ issue_id=issue_ids).AndReturn(self.approvalvalue_rows)
+ else:
+ self.issue_service.issue2approvalvalue_tbl.Select(
+ self.cnxn,
+ cols=issue_svc.ISSUE2APPROVALVALUE_COLS,
+ issue_id=issue_ids).AndReturn([])
+ self.issue_service.issueapproval2approver_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUEAPPROVAL2APPROVER_COLS,
+ issue_id=issue_ids).AndReturn(self.av_approver_rows)
+ self.issue_service.issuerelation_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUERELATION_COLS,
+ issue_id=issue_ids, kind='blockedon',
+ order_by=[('issue_id', []), ('rank DESC', []),
+ ('dst_issue_id', [])]).AndReturn(self.blocked_on_rows)
+ self.issue_service.issuerelation_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUERELATION_COLS,
+ dst_issue_id=issue_ids, kind='blockedon',
+ order_by=[('issue_id', []), ('dst_issue_id', [])]
+ ).AndReturn(self.blocking_rows)
+ self.issue_service.issuerelation_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUERELATION_COLS,
+ where=[('(issue_id IN (%s) OR dst_issue_id IN (%s))',
+ issue_ids + issue_ids),
+ ('kind != %s', ['blockedon'])]).AndReturn(self.merged_rows)
+ self.issue_service.danglingrelation_tbl.Select(
+ self.cnxn, cols=issue_svc.DANGLINGRELATION_COLS, # Note: no shard
+ issue_id=issue_ids).AndReturn(self.dangling_relation_rows)
+
+ def testFetchItems(self):
+ issue_ids = [78901]
+ self.SetUpFetchItems(issue_ids)
+ self.mox.ReplayAll()
+ issue_dict = self.issue_2lc.FetchItems(self.cnxn, issue_ids)
+ self.mox.VerifyAll()
+ self.assertItemsEqual(issue_ids, list(issue_dict.keys()))
+ self.assertEqual(2, len(issue_dict[78901].phases))
+
+ def testFetchItemsNoApprovalValues(self):
+ issue_ids = [78901]
+ self.SetUpFetchItems(issue_ids, False)
+ self.mox.ReplayAll()
+ issue_dict = self.issue_2lc.FetchItems(self.cnxn, issue_ids)
+ self.mox.VerifyAll()
+ self.assertItemsEqual(issue_ids, list(issue_dict.keys()))
+ self.assertEqual([], issue_dict[78901].phases)
+
+
+class IssueServiceTest(unittest.TestCase):
+
+ def setUp(self):
+ self.testbed = testbed.Testbed()
+ self.testbed.activate()
+ self.testbed.init_memcache_stub()
+
+ self.mox = mox.Mox()
+ self.cnxn = self.mox.CreateMock(sql.MonorailConnection)
+ self.services = service_manager.Services()
+ self.services.user = fake.UserService()
+ self.reporter = self.services.user.TestAddUser('reporter@example.com', 111)
+ self.services.usergroup = fake.UserGroupService()
+ self.services.project = fake.ProjectService()
+ self.project = self.services.project.TestAddProject('proj', project_id=789)
+ self.services.config = fake.ConfigService()
+ self.services.features = fake.FeaturesService()
+ self.cache_manager = fake.CacheManager()
+ self.services.chart = chart_svc.ChartService(self.services.config)
+ self.services.issue = MakeIssueService(
+ self.services.project, self.services.config, self.cache_manager,
+ self.services.chart, self.mox)
+ self.services.spam = self.mox.CreateMock(spam_svc.SpamService)
+ self.now = int(time.time())
+ self.patcher = patch('services.tracker_fulltext.IndexIssues')
+ self.patcher.start()
+ self.mox.StubOutWithMock(self.services.chart, 'StoreIssueSnapshots')
+
+ def classifierResult(self, score, failed_open=False):
+ return {'confidence_is_spam': score,
+ 'failed_open': failed_open}
+
+ def tearDown(self):
+ self.testbed.deactivate()
+ self.mox.UnsetStubs()
+ self.mox.ResetAll()
+ self.patcher.stop()
+
+ ### Issue ID lookups
+
+ def testLookupIssueIDsFollowMoves(self):
+ moved_issue_id = 78901
+ moved_pair = (789, 1)
+ missing_pair = (1, 1)
+ cached_issue_id = 78902
+ cached_pair = (789, 2)
+ uncached_issue_id = 78903
+ uncached_pair = (789, 3)
+ uncached_issue_id_2 = 78904
+ uncached_pair_2 = (789, 4)
+ self.services.issue.issue_id_2lc.CacheItem(cached_pair, cached_issue_id)
+
+ # Simulate rows returned in reverse order (to verify the method still
+ # returns them in the specified order).
+ uncached_rows = [
+ (uncached_pair_2[0], uncached_pair_2[1], uncached_issue_id_2),
+ (uncached_pair[0], uncached_pair[1], uncached_issue_id)
+ ]
+ self.services.issue.issue_tbl.Select(
+ self.cnxn,
+ cols=['project_id', 'local_id', 'id'],
+ or_where_conds=True,
+ where=mox.IgnoreArg()).AndReturn(uncached_rows)
+ # Moved issue is found.
+ self.services.issue.issueformerlocations_tbl.SelectValue(
+ self.cnxn,
+ 'issue_id',
+ default=0,
+ project_id=moved_pair[0],
+ local_id=moved_pair[1]).AndReturn(moved_issue_id)
+
+ self.mox.ReplayAll()
+ found_ids, misses = self.services.issue.LookupIssueIDsFollowMoves(
+ self.cnxn,
+ [moved_pair, missing_pair, cached_pair, uncached_pair, uncached_pair_2])
+ self.mox.VerifyAll()
+
+ expected_found_ids = [
+ moved_issue_id, cached_issue_id, uncached_issue_id, uncached_issue_id_2
+ ]
+ self.assertListEqual(expected_found_ids, found_ids)
+ self.assertListEqual([missing_pair], misses)
+
+ def testLookupIssueIDs_Hit(self):
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
+ self.services.issue.issue_id_2lc.CacheItem((789, 2), 78902)
+ actual, _misses = self.services.issue.LookupIssueIDs(
+ self.cnxn, [(789, 1), (789, 2)])
+ self.assertEqual([78901, 78902], actual)
+
+ def testLookupIssueID(self):
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
+ actual = self.services.issue.LookupIssueID(self.cnxn, 789, 1)
+ self.assertEqual(78901, actual)
+
+ def testResolveIssueRefs(self):
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
+ self.services.issue.issue_id_2lc.CacheItem((789, 2), 78902)
+ prefetched_projects = {'proj': fake.Project('proj', project_id=789)}
+ refs = [('proj', 1), (None, 2)]
+ actual, misses = self.services.issue.ResolveIssueRefs(
+ self.cnxn, prefetched_projects, 'proj', refs)
+ self.assertEqual(misses, [])
+ self.assertEqual([78901, 78902], actual)
+
+ def testLookupIssueRefs_Empty(self):
+ actual = self.services.issue.LookupIssueRefs(self.cnxn, [])
+ self.assertEqual({}, actual)
+
+ def testLookupIssueRefs_Normal(self):
+ issue_1 = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901, project_name='proj')
+ self.services.issue.issue_2lc.CacheItem(78901, issue_1)
+ actual = self.services.issue.LookupIssueRefs(self.cnxn, [78901])
+ self.assertEqual(
+ {78901: ('proj', 1)},
+ actual)
+
+ ### Issue objects
+
+ def CheckCreateIssue(self, is_project_member):
+ settings.classifier_spam_thresh = 0.9
+ av_23 = tracker_pb2.ApprovalValue(
+ approval_id=23, phase_id=1, approver_ids=[111, 222],
+ status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW)
+ av_24 = tracker_pb2.ApprovalValue(
+ approval_id=24, phase_id=1, approver_ids=[111])
+ approval_values = [av_23, av_24]
+ av_rows = [(23, 78901, 1, 'needs_review', None, None),
+ (24, 78901, 1, 'not_set', None, None)]
+ approver_rows = [(23, 111, 78901), (23, 222, 78901), (24, 111, 78901)]
+ ad_23 = tracker_pb2.ApprovalDef(
+ approval_id=23, approver_ids=[111], survey='Question?')
+ ad_24 = tracker_pb2.ApprovalDef(
+ approval_id=24, approver_ids=[111], survey='Question?')
+ config = self.services.config.GetProjectConfig(
+ self.cnxn, 789)
+ config.approval_defs.extend([ad_23, ad_24])
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ self.SetUpAllocateNextLocalID(789, None, None)
+ self.SetUpInsertIssue(av_rows=av_rows, approver_rows=approver_rows)
+ self.SetUpInsertComment(7890101, is_description=True)
+ self.SetUpInsertComment(7890101, is_description=True, approval_id=23,
+ content='<b>Question?</b>')
+ self.SetUpInsertComment(7890101, is_description=True, approval_id=24,
+ content='<b>Question?</b>')
+ self.services.spam.ClassifyIssue(mox.IgnoreArg(),
+ mox.IgnoreArg(), self.reporter, is_project_member).AndReturn(
+ self.classifierResult(0.0))
+ self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
+ mox.IsA(tracker_pb2.Issue), False, 1.0, False)
+ self.SetUpEnqueueIssuesForIndexing([78901])
+
+ self.mox.ReplayAll()
+ issue = fake.MakeTestIssue(
+ 789,
+ 1,
+ 'sum',
+ 'New',
+ 111,
+ reporter_id=111,
+ labels=['Type-Defect'],
+ opened_timestamp=self.now,
+ modified_timestamp=self.now,
+ approval_values=approval_values)
+ created_issue, _ = self.services.issue.CreateIssue(
+ self.cnxn, self.services, issue, 'content')
+ self.mox.VerifyAll()
+ self.assertEqual(1, created_issue.local_id)
+
+ def testCreateIssue_NonmemberSpamCheck(self):
+ """A non-member must pass a non-member spam check."""
+ self.CheckCreateIssue(False)
+
+ def testCreateIssue_DirectMemberSpamCheck(self):
+ """A direct member of a project gets a member spam check."""
+ self.project.committer_ids.append(self.reporter.user_id)
+ self.CheckCreateIssue(True)
+
+ def testCreateIssue_ComputedUsergroupSpamCheck(self):
+ """A member of a computed group in project gets a member spam check."""
+ group_id = self.services.usergroup.CreateGroup(
+ self.cnxn, self.services, 'everyone@example.com', 'ANYONE',
+ ext_group_type='COMPUTED')
+ self.project.committer_ids.append(group_id)
+ self.CheckCreateIssue(True)
+
+ def testCreateIssue_EmptyStringLabels(self):
+ settings.classifier_spam_thresh = 0.9
+ self.SetUpAllocateNextLocalID(789, None, None)
+ self.SetUpInsertIssue(label_rows=[])
+ self.SetUpInsertComment(7890101, is_description=True)
+ self.services.spam.ClassifyIssue(mox.IgnoreArg(),
+ mox.IgnoreArg(), self.reporter, False).AndReturn(
+ self.classifierResult(0.0))
+ self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
+ mox.IsA(tracker_pb2.Issue), False, 1.0, False)
+ self.SetUpEnqueueIssuesForIndexing([78901])
+
+ self.mox.ReplayAll()
+ issue = fake.MakeTestIssue(
+ 789,
+ 1,
+ 'sum',
+ 'New',
+ 111,
+ reporter_id=111,
+ opened_timestamp=self.now,
+ modified_timestamp=self.now)
+ created_issue, _ = self.services.issue.CreateIssue(
+ self.cnxn, self.services, issue, 'content')
+ self.mox.VerifyAll()
+ self.assertEqual(1, created_issue.local_id)
+
+ def SetUpUpdateIssuesModified(self, iids, modified_timestamp=None):
+ self.services.issue.issue_tbl.Update(
+ self.cnxn, {'modified': modified_timestamp or self.now},
+ id=iids, commit=False)
+
+ def testCreateIssue_SpamPredictionFailed(self):
+ settings.classifier_spam_thresh = 0.9
+ self.SetUpAllocateNextLocalID(789, None, None)
+ self.SetUpInsertSpamIssue()
+ self.SetUpInsertComment(7890101, is_description=True)
+
+ self.services.spam.ClassifyIssue(mox.IsA(tracker_pb2.Issue),
+ mox.IsA(tracker_pb2.IssueComment), self.reporter, False).AndReturn(
+ self.classifierResult(1.0, True))
+ self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
+ mox.IsA(tracker_pb2.Issue), True, 1.0, True)
+ self.SetUpUpdateIssuesApprovals([])
+ self.SetUpEnqueueIssuesForIndexing([78901])
+
+ self.mox.ReplayAll()
+ issue = fake.MakeTestIssue(
+ 789,
+ 1,
+ 'sum',
+ 'New',
+ 111,
+ reporter_id=111,
+ labels=['Type-Defect'],
+ opened_timestamp=self.now,
+ modified_timestamp=self.now)
+ created_issue, _ = self.services.issue.CreateIssue(
+ self.cnxn, self.services, issue, 'content')
+ self.mox.VerifyAll()
+ self.assertEqual(1, created_issue.local_id)
+
+ def testCreateIssue_Spam(self):
+ settings.classifier_spam_thresh = 0.9
+ self.SetUpAllocateNextLocalID(789, None, None)
+ self.SetUpInsertSpamIssue()
+ self.SetUpInsertComment(7890101, is_description=True)
+
+ self.services.spam.ClassifyIssue(mox.IsA(tracker_pb2.Issue),
+ mox.IsA(tracker_pb2.IssueComment), self.reporter, False).AndReturn(
+ self.classifierResult(1.0))
+ self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
+ mox.IsA(tracker_pb2.Issue), True, 1.0, False)
+ self.SetUpUpdateIssuesApprovals([])
+ self.SetUpEnqueueIssuesForIndexing([78901])
+
+ self.mox.ReplayAll()
+ issue = fake.MakeTestIssue(
+ 789,
+ 1,
+ 'sum',
+ 'New',
+ 111,
+ reporter_id=111,
+ labels=['Type-Defect'],
+ opened_timestamp=self.now,
+ modified_timestamp=self.now)
+ created_issue, _ = self.services.issue.CreateIssue(
+ self.cnxn, self.services, issue, 'content')
+ self.mox.VerifyAll()
+ self.assertEqual(1, created_issue.local_id)
+
+ def testCreateIssue_FederatedReferences(self):
+ self.SetUpAllocateNextLocalID(789, None, None)
+ self.SetUpInsertIssue(dangling_relation_rows=[
+ (78901, None, None, 'b/1234', 'blockedon'),
+ (78901, None, None, 'b/5678', 'blockedon'),
+ (78901, None, None, 'b/9876', 'blocking'),
+ (78901, None, None, 'b/5432', 'blocking')])
+ self.SetUpInsertComment(7890101, is_description=True)
+ self.services.spam.ClassifyIssue(mox.IsA(tracker_pb2.Issue),
+ mox.IsA(tracker_pb2.IssueComment), self.reporter, False).AndReturn(
+ self.classifierResult(0.0))
+ self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
+ mox.IsA(tracker_pb2.Issue), mox.IgnoreArg(), mox.IgnoreArg(),
+ mox.IgnoreArg())
+ self.SetUpEnqueueIssuesForIndexing([78901])
+
+ self.mox.ReplayAll()
+ issue = fake.MakeTestIssue(
+ 789,
+ 1,
+ 'sum',
+ 'New',
+ 111,
+ reporter_id=111,
+ labels=['Type-Defect'],
+ opened_timestamp=self.now,
+ modified_timestamp=self.now)
+ issue.dangling_blocked_on_refs = [
+ tracker_pb2.DanglingIssueRef(ext_issue_identifier=shortlink)
+ for shortlink in ['b/1234', 'b/5678']
+ ]
+ issue.dangling_blocking_refs = [
+ tracker_pb2.DanglingIssueRef(ext_issue_identifier=shortlink)
+ for shortlink in ['b/9876', 'b/5432']
+ ]
+ self.services.issue.CreateIssue(self.cnxn, self.services, issue, 'content')
+ self.mox.VerifyAll()
+
+ def testCreateIssue_Imported(self):
+ settings.classifier_spam_thresh = 0.9
+ self.SetUpAllocateNextLocalID(789, None, None)
+ self.SetUpInsertIssue(label_rows=[])
+ self.SetUpInsertComment(7890101, is_description=True)
+ self.services.issue.commentimporter_tbl.InsertRow(
+ self.cnxn, comment_id=7890101, importer_id=222)
+ self.services.spam.ClassifyIssue(mox.IgnoreArg(),
+ mox.IgnoreArg(), self.reporter, False).AndReturn(
+ self.classifierResult(0.0))
+ self.services.spam.RecordClassifierIssueVerdict(self.cnxn,
+ mox.IsA(tracker_pb2.Issue), False, 1.0, False)
+ self.SetUpEnqueueIssuesForIndexing([78901])
+ self.mox.ReplayAll()
+
+ issue = fake.MakeTestIssue(
+ 789,
+ 1,
+ 'sum',
+ 'New',
+ 111,
+ reporter_id=111,
+ opened_timestamp=self.now,
+ modified_timestamp=self.now)
+ created_issue, comment = self.services.issue.CreateIssue(
+ self.cnxn, self.services, issue, 'content', importer_id=222)
+
+ self.mox.VerifyAll()
+ self.assertEqual(1, created_issue.local_id)
+ self.assertEqual(111, comment.user_id)
+ self.assertEqual(222, comment.importer_id)
+ self.assertEqual(self.now, comment.timestamp)
+
+ def testGetAllIssuesInProject_NoIssues(self):
+ self.SetUpGetHighestLocalID(789, None, None)
+ self.mox.ReplayAll()
+ issues = self.services.issue.GetAllIssuesInProject(self.cnxn, 789)
+ self.mox.VerifyAll()
+ self.assertEqual([], issues)
+
+ def testGetAnyOnHandIssue(self):
+ issue_ids = [78901, 78902, 78903]
+ self.SetUpGetIssues()
+ issue = self.services.issue.GetAnyOnHandIssue(issue_ids)
+ self.assertEqual(78901, issue.issue_id)
+
+ def SetUpGetIssues(self):
+ issue_1 = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901)
+ issue_1.project_name = 'proj'
+ issue_2 = fake.MakeTestIssue(
+ project_id=789, local_id=2, owner_id=111, summary='sum',
+ status='Fixed', issue_id=78902)
+ issue_2.project_name = 'proj'
+ self.services.issue.issue_2lc.CacheItem(78901, issue_1)
+ self.services.issue.issue_2lc.CacheItem(78902, issue_2)
+ return issue_1, issue_2
+
+ def testGetIssuesDict(self):
+ issue_ids = [78901, 78902, 78903]
+ issue_1, issue_2 = self.SetUpGetIssues()
+ self.services.issue.issue_2lc = TestableIssueTwoLevelCache(
+ [issue_1, issue_2])
+ issues_dict, missed_iids = self.services.issue.GetIssuesDict(
+ self.cnxn, issue_ids)
+ self.assertEqual(
+ {78901: issue_1, 78902: issue_2},
+ issues_dict)
+ self.assertEqual([78903], missed_iids)
+
+ def testGetIssues(self):
+ issue_ids = [78901, 78902]
+ issue_1, issue_2 = self.SetUpGetIssues()
+ issues = self.services.issue.GetIssues(self.cnxn, issue_ids)
+ self.assertEqual([issue_1, issue_2], issues)
+
+ def testGetIssue(self):
+ issue_1, _issue_2 = self.SetUpGetIssues()
+ actual_issue = self.services.issue.GetIssue(self.cnxn, 78901)
+ self.assertEqual(issue_1, actual_issue)
+
+ def testGetIssuesByLocalIDs(self):
+ issue_1, issue_2 = self.SetUpGetIssues()
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
+ self.services.issue.issue_id_2lc.CacheItem((789, 2), 78902)
+ actual_issues = self.services.issue.GetIssuesByLocalIDs(
+ self.cnxn, 789, [1, 2])
+ self.assertEqual([issue_1, issue_2], actual_issues)
+
+ def testGetIssueByLocalID(self):
+ issue_1, _issue_2 = self.SetUpGetIssues()
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
+ actual_issues = self.services.issue.GetIssueByLocalID(self.cnxn, 789, 1)
+ self.assertEqual(issue_1, actual_issues)
+
+ def testGetOpenAndClosedIssues(self):
+ issue_1, issue_2 = self.SetUpGetIssues()
+ open_issues, closed_issues = self.services.issue.GetOpenAndClosedIssues(
+ self.cnxn, [78901, 78902])
+ self.assertEqual([issue_1], open_issues)
+ self.assertEqual([issue_2], closed_issues)
+
+ def SetUpGetCurrentLocationOfMovedIssue(self, project_id, local_id):
+ issue_id = project_id * 100 + local_id
+ self.services.issue.issueformerlocations_tbl.SelectValue(
+ self.cnxn, 'issue_id', default=0, project_id=project_id,
+ local_id=local_id).AndReturn(issue_id)
+ self.services.issue.issue_tbl.SelectRow(
+ self.cnxn, cols=['project_id', 'local_id'], id=issue_id).AndReturn(
+ (project_id + 1, local_id + 1))
+
+ def testGetCurrentLocationOfMovedIssue(self):
+ self.SetUpGetCurrentLocationOfMovedIssue(789, 1)
+ self.mox.ReplayAll()
+ new_project_id, new_local_id = (
+ self.services.issue.GetCurrentLocationOfMovedIssue(self.cnxn, 789, 1))
+ self.mox.VerifyAll()
+ self.assertEqual(789 + 1, new_project_id)
+ self.assertEqual(1 + 1, new_local_id)
+
+ def SetUpGetPreviousLocations(self, issue_id, location_rows):
+ self.services.issue.issueformerlocations_tbl.Select(
+ self.cnxn, cols=['project_id', 'local_id'],
+ issue_id=issue_id).AndReturn(location_rows)
+
+ def testGetPreviousLocations(self):
+ self.SetUpGetPreviousLocations(78901, [(781, 1), (782, 11), (789, 1)])
+ self.mox.ReplayAll()
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901)
+ locations = self.services.issue.GetPreviousLocations(self.cnxn, issue)
+ self.mox.VerifyAll()
+ self.assertEqual(locations, [(781, 1), (782, 11)])
+
+ def SetUpInsertIssue(
+ self, label_rows=None, av_rows=None, approver_rows=None,
+ dangling_relation_rows=None):
+ row = (789, 1, 1, 111, 111,
+ self.now, 0, self.now, self.now, self.now, self.now,
+ None, 0,
+ False, 0, 0, False)
+ self.services.issue.issue_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUE_COLS[1:], [row],
+ commit=False, return_generated_ids=True).AndReturn([78901])
+ self.cnxn.Commit()
+ self.services.issue.issue_tbl.Update(
+ self.cnxn, {'shard': 78901 % settings.num_logical_shards},
+ id=78901, commit=False)
+ self.SetUpUpdateIssuesSummary()
+ self.SetUpUpdateIssuesLabels(label_rows=label_rows)
+ self.SetUpUpdateIssuesFields()
+ self.SetUpUpdateIssuesComponents()
+ self.SetUpUpdateIssuesCc()
+ self.SetUpUpdateIssuesNotify()
+ self.SetUpUpdateIssuesRelation(
+ dangling_relation_rows=dangling_relation_rows)
+ self.SetUpUpdateIssuesApprovals(
+ av_rows=av_rows, approver_rows=approver_rows)
+ self.services.chart.StoreIssueSnapshots(self.cnxn, mox.IgnoreArg(),
+ commit=False)
+
+ def SetUpInsertSpamIssue(self):
+ row = (789, 1, 1, 111, 111,
+ self.now, 0, self.now, self.now, self.now, self.now,
+ None, 0, False, 0, 0, True)
+ self.services.issue.issue_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUE_COLS[1:], [row],
+ commit=False, return_generated_ids=True).AndReturn([78901])
+ self.cnxn.Commit()
+ self.services.issue.issue_tbl.Update(
+ self.cnxn, {'shard': 78901 % settings.num_logical_shards},
+ id=78901, commit=False)
+ self.SetUpUpdateIssuesSummary()
+ self.SetUpUpdateIssuesLabels()
+ self.SetUpUpdateIssuesFields()
+ self.SetUpUpdateIssuesComponents()
+ self.SetUpUpdateIssuesCc()
+ self.SetUpUpdateIssuesNotify()
+ self.SetUpUpdateIssuesRelation()
+ self.services.chart.StoreIssueSnapshots(self.cnxn, mox.IgnoreArg(),
+ commit=False)
+
+ def SetUpUpdateIssuesSummary(self):
+ self.services.issue.issuesummary_tbl.InsertRows(
+ self.cnxn, ['issue_id', 'summary'],
+ [(78901, 'sum')], replace=True, commit=False)
+
+ def SetUpUpdateIssuesLabels(self, label_rows=None):
+ if label_rows is None:
+ label_rows = [(78901, 1, False, 1)]
+ self.services.issue.issue2label_tbl.Delete(
+ self.cnxn, issue_id=[78901], commit=False)
+ self.services.issue.issue2label_tbl.InsertRows(
+ self.cnxn, ['issue_id', 'label_id', 'derived', 'issue_shard'],
+ label_rows, ignore=True, commit=False)
+
+ def SetUpUpdateIssuesFields(self, issue2fieldvalue_rows=None):
+ issue2fieldvalue_rows = issue2fieldvalue_rows or []
+ self.services.issue.issue2fieldvalue_tbl.Delete(
+ self.cnxn, issue_id=[78901], commit=False)
+ self.services.issue.issue2fieldvalue_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUE2FIELDVALUE_COLS + ['issue_shard'],
+ issue2fieldvalue_rows, commit=False)
+
+ def SetUpUpdateIssuesComponents(self, issue2component_rows=None):
+ issue2component_rows = issue2component_rows or []
+ self.services.issue.issue2component_tbl.Delete(
+ self.cnxn, issue_id=[78901], commit=False)
+ self.services.issue.issue2component_tbl.InsertRows(
+ self.cnxn, ['issue_id', 'component_id', 'derived', 'issue_shard'],
+ issue2component_rows, ignore=True, commit=False)
+
+ def SetUpUpdateIssuesCc(self, issue2cc_rows=None):
+ issue2cc_rows = issue2cc_rows or []
+ self.services.issue.issue2cc_tbl.Delete(
+ self.cnxn, issue_id=[78901], commit=False)
+ self.services.issue.issue2cc_tbl.InsertRows(
+ self.cnxn, ['issue_id', 'cc_id', 'derived', 'issue_shard'],
+ issue2cc_rows, ignore=True, commit=False)
+
+ def SetUpUpdateIssuesNotify(self, notify_rows=None):
+ notify_rows = notify_rows or []
+ self.services.issue.issue2notify_tbl.Delete(
+ self.cnxn, issue_id=[78901], commit=False)
+ self.services.issue.issue2notify_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUE2NOTIFY_COLS,
+ notify_rows, ignore=True, commit=False)
+
+ def SetUpUpdateIssuesRelation(
+ self, relation_rows=None, dangling_relation_rows=None):
+ relation_rows = relation_rows or []
+ dangling_relation_rows = dangling_relation_rows or []
+ self.services.issue.issuerelation_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUERELATION_COLS[:-1],
+ dst_issue_id=[78901], kind='blockedon').AndReturn([])
+ self.services.issue.issuerelation_tbl.Delete(
+ self.cnxn, issue_id=[78901], commit=False)
+ self.services.issue.issuerelation_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUERELATION_COLS, relation_rows,
+ ignore=True, commit=False)
+ self.services.issue.danglingrelation_tbl.Delete(
+ self.cnxn, issue_id=[78901], commit=False)
+ self.services.issue.danglingrelation_tbl.InsertRows(
+ self.cnxn, issue_svc.DANGLINGRELATION_COLS, dangling_relation_rows,
+ ignore=True, commit=False)
+
+ def SetUpUpdateIssuesApprovals(self, av_rows=None, approver_rows=None):
+ av_rows = av_rows or []
+ approver_rows = approver_rows or []
+ self.services.issue.issue2approvalvalue_tbl.Delete(
+ self.cnxn, issue_id=78901, commit=False)
+ self.services.issue.issue2approvalvalue_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUE2APPROVALVALUE_COLS, av_rows, commit=False)
+ self.services.issue.issueapproval2approver_tbl.Delete(
+ self.cnxn, issue_id=78901, commit=False)
+ self.services.issue.issueapproval2approver_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUEAPPROVAL2APPROVER_COLS, approver_rows,
+ commit=False)
+
+ def testInsertIssue(self):
+ self.SetUpInsertIssue()
+ self.mox.ReplayAll()
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, reporter_id=111,
+ summary='sum', status='New', labels=['Type-Defect'], issue_id=78901,
+ opened_timestamp=self.now, modified_timestamp=self.now)
+ actual_issue_id = self.services.issue.InsertIssue(self.cnxn, issue)
+ self.mox.VerifyAll()
+ self.assertEqual(78901, actual_issue_id)
+
+ def SetUpUpdateIssues(self, given_delta=None):
+ delta = given_delta or {
+ 'project_id': 789,
+ 'local_id': 1,
+ 'owner_id': 111,
+ 'status_id': 1,
+ 'opened': 123456789,
+ 'closed': 0,
+ 'modified': 123456789,
+ 'owner_modified': 123456789,
+ 'status_modified': 123456789,
+ 'component_modified': 123456789,
+ 'derived_owner_id': None,
+ 'derived_status_id': None,
+ 'deleted': False,
+ 'star_count': 12,
+ 'attachment_count': 0,
+ 'is_spam': False,
+ }
+ self.services.issue.issue_tbl.Update(
+ self.cnxn, delta, id=78901, commit=False)
+ if not given_delta:
+ self.SetUpUpdateIssuesLabels()
+ self.SetUpUpdateIssuesCc()
+ self.SetUpUpdateIssuesFields()
+ self.SetUpUpdateIssuesComponents()
+ self.SetUpUpdateIssuesNotify()
+ self.SetUpUpdateIssuesSummary()
+ self.SetUpUpdateIssuesRelation()
+ self.services.chart.StoreIssueSnapshots(self.cnxn, mox.IgnoreArg(),
+ commit=False)
+
+ if given_delta:
+ self.services.chart.StoreIssueSnapshots(self.cnxn, mox.IgnoreArg(),
+ commit=False)
+
+ self.cnxn.Commit()
+
+ def testUpdateIssues_Empty(self):
+ # Note: no setup because DB should not be called.
+ self.mox.ReplayAll()
+ self.services.issue.UpdateIssues(self.cnxn, [])
+ self.mox.VerifyAll()
+
+ def testUpdateIssues_Normal(self):
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', labels=['Type-Defect'], issue_id=78901,
+ opened_timestamp=123456789, modified_timestamp=123456789,
+ star_count=12)
+ issue.assume_stale = False
+ self.SetUpUpdateIssues()
+ self.mox.ReplayAll()
+ self.services.issue.UpdateIssues(self.cnxn, [issue])
+ self.mox.VerifyAll()
+
+ def testUpdateIssue_Normal(self):
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', labels=['Type-Defect'], issue_id=78901,
+ opened_timestamp=123456789, modified_timestamp=123456789,
+ star_count=12)
+ issue.assume_stale = False
+ self.SetUpUpdateIssues()
+ self.mox.ReplayAll()
+ self.services.issue.UpdateIssue(self.cnxn, issue)
+ self.mox.VerifyAll()
+
+ def testUpdateIssue_Stale(self):
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', labels=['Type-Defect'], issue_id=78901,
+ opened_timestamp=123456789, modified_timestamp=123456789,
+ star_count=12)
+ # Do not set issue.assume_stale = False
+ # Do not call self.SetUpUpdateIssues() because nothing should be updated.
+ self.mox.ReplayAll()
+ self.assertRaises(
+ AssertionError, self.services.issue.UpdateIssue, self.cnxn, issue)
+ self.mox.VerifyAll()
+
+ def testUpdateIssuesSummary(self):
+ issue = fake.MakeTestIssue(
+ local_id=1, issue_id=78901, owner_id=111, summary='sum', status='New',
+ project_id=789)
+ issue.assume_stale = False
+ self.SetUpUpdateIssuesSummary()
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssuesSummary(self.cnxn, [issue], commit=False)
+ self.mox.VerifyAll()
+
+ def testUpdateIssuesLabels(self):
+ issue = fake.MakeTestIssue(
+ local_id=1, issue_id=78901, owner_id=111, summary='sum', status='New',
+ labels=['Type-Defect'], project_id=789)
+ self.SetUpUpdateIssuesLabels()
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssuesLabels(
+ self.cnxn, [issue], commit=False)
+ self.mox.VerifyAll()
+
+ def testUpdateIssuesFields_Empty(self):
+ issue = fake.MakeTestIssue(
+ local_id=1, issue_id=78901, owner_id=111, summary='sum', status='New',
+ project_id=789)
+ self.SetUpUpdateIssuesFields()
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssuesFields(self.cnxn, [issue], commit=False)
+ self.mox.VerifyAll()
+
+ def testUpdateIssuesFields_Some(self):
+ issue = fake.MakeTestIssue(
+ local_id=1, issue_id=78901, owner_id=111, summary='sum', status='New',
+ project_id=789)
+ issue_shard = issue.issue_id % settings.num_logical_shards
+ fv1 = tracker_bizobj.MakeFieldValue(345, 679, '', 0, None, None, False)
+ issue.field_values.append(fv1)
+ fv2 = tracker_bizobj.MakeFieldValue(346, 0, 'Blue', 0, None, None, True)
+ issue.field_values.append(fv2)
+ fv3 = tracker_bizobj.MakeFieldValue(347, 0, '', 0, 1234567890, None, True)
+ issue.field_values.append(fv3)
+ fv4 = tracker_bizobj.MakeFieldValue(
+ 348, 0, '', 0, None, 'www.google.com', True, phase_id=14)
+ issue.field_values.append(fv4)
+ self.SetUpUpdateIssuesFields(issue2fieldvalue_rows=[
+ (issue.issue_id, fv1.field_id, fv1.int_value, fv1.str_value,
+ None, fv1.date_value, fv1.url_value, fv1.derived, None,
+ issue_shard),
+ (issue.issue_id, fv2.field_id, fv2.int_value, fv2.str_value,
+ None, fv2.date_value, fv2.url_value, fv2.derived, None,
+ issue_shard),
+ (issue.issue_id, fv3.field_id, fv3.int_value, fv3.str_value,
+ None, fv3.date_value, fv3.url_value, fv3.derived, None,
+ issue_shard),
+ (issue.issue_id, fv4.field_id, fv4.int_value, fv4.str_value,
+ None, fv4.date_value, fv4.url_value, fv4.derived, 14,
+ issue_shard),
+ ])
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssuesFields(self.cnxn, [issue], commit=False)
+ self.mox.VerifyAll()
+
+ def testUpdateIssuesComponents_Empty(self):
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901)
+ self.SetUpUpdateIssuesComponents()
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssuesComponents(
+ self.cnxn, [issue], commit=False)
+ self.mox.VerifyAll()
+
+ def testUpdateIssuesCc_Empty(self):
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901)
+ self.SetUpUpdateIssuesCc()
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssuesCc(self.cnxn, [issue], commit=False)
+ self.mox.VerifyAll()
+
+ def testUpdateIssuesCc_Some(self):
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901)
+ issue.cc_ids = [222, 333]
+ issue.derived_cc_ids = [888]
+ issue_shard = issue.issue_id % settings.num_logical_shards
+ self.SetUpUpdateIssuesCc(issue2cc_rows=[
+ (issue.issue_id, 222, False, issue_shard),
+ (issue.issue_id, 333, False, issue_shard),
+ (issue.issue_id, 888, True, issue_shard),
+ ])
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssuesCc(self.cnxn, [issue], commit=False)
+ self.mox.VerifyAll()
+
+ def testUpdateIssuesNotify_Empty(self):
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901)
+ self.SetUpUpdateIssuesNotify()
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssuesNotify(self.cnxn, [issue], commit=False)
+ self.mox.VerifyAll()
+
+ def testUpdateIssuesRelation_Empty(self):
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901)
+ self.SetUpUpdateIssuesRelation()
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssuesRelation(self.cnxn, [issue], commit=False)
+ self.mox.VerifyAll()
+
+ def testUpdateIssuesRelation_MergedIntoExternal(self):
+ self.services.issue.issuerelation_tbl.Select = Mock(return_value=[])
+ self.services.issue.issuerelation_tbl.Delete = Mock()
+ self.services.issue.issuerelation_tbl.InsertRows = Mock()
+ self.services.issue.danglingrelation_tbl.Delete = Mock()
+ self.services.issue.danglingrelation_tbl.InsertRows = Mock()
+
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901, merged_into_external='b/5678')
+
+ self.services.issue._UpdateIssuesRelation(self.cnxn, [issue])
+
+ self.services.issue.danglingrelation_tbl.Delete.assert_called_once_with(
+ self.cnxn, commit=False, issue_id=[78901])
+ self.services.issue.danglingrelation_tbl.InsertRows\
+ .assert_called_once_with(
+ self.cnxn, ['issue_id', 'dst_issue_project', 'dst_issue_local_id',
+ 'ext_issue_identifier', 'kind'],
+ [(78901, None, None, 'b/5678', 'mergedinto')],
+ ignore=True, commit=True)
+
+ @patch('time.time')
+ def testUpdateIssueStructure(self, mockTime):
+ mockTime.return_value = self.now
+ reporter_id = 111
+ comment_content = 'This issue is being converted'
+ # Set up config
+ config = self.services.config.GetProjectConfig(
+ self.cnxn, 789)
+ config.approval_defs = [
+ tracker_pb2.ApprovalDef(
+ approval_id=3, survey='Question3', approver_ids=[222]),
+ tracker_pb2.ApprovalDef(
+ approval_id=4, survey='Question4', approver_ids=[444]),
+ tracker_pb2.ApprovalDef(
+ approval_id=7, survey='Question7', approver_ids=[222]),
+ ]
+ config.field_defs = [
+ tracker_pb2.FieldDef(
+ field_id=3, project_id=789, field_name='Cow'),
+ tracker_pb2.FieldDef(
+ field_id=4, project_id=789, field_name='Chicken'),
+ tracker_pb2.FieldDef(
+ field_id=6, project_id=789, field_name='Llama'),
+ tracker_pb2.FieldDef(
+ field_id=7, project_id=789, field_name='Roo'),
+ tracker_pb2.FieldDef(
+ field_id=8, project_id=789, field_name='Salmon'),
+ tracker_pb2.FieldDef(
+ field_id=9, project_id=789, field_name='Tuna', is_phase_field=True),
+ tracker_pb2.FieldDef(
+ field_id=10, project_id=789, field_name='Clown', is_phase_field=True),
+ tracker_pb2.FieldDef(
+ field_id=11, project_id=789, field_name='Dory', is_phase_field=True),
+ ]
+
+ # Set up issue
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum', status='Open',
+ issue_id=78901, project_name='proj')
+ issue.approval_values = [
+ tracker_pb2.ApprovalValue(
+ approval_id=3,
+ phase_id=4,
+ status=tracker_pb2.ApprovalStatus.APPROVED,
+ approver_ids=[111], # trumps approval_def approver_ids
+ ),
+ tracker_pb2.ApprovalValue(
+ approval_id=4,
+ phase_id=5,
+ approver_ids=[111]), # trumps approval_def approver_ids
+ tracker_pb2.ApprovalValue(approval_id=6)]
+ issue.phases = [
+ tracker_pb2.Phase(name='Expired', phase_id=4),
+ tracker_pb2.Phase(name='canarY', phase_id=3),
+ tracker_pb2.Phase(name='Stable', phase_id=2)]
+ issue.field_values = [
+ tracker_bizobj.MakeFieldValue(8, None, 'Pink', None, None, None, False),
+ tracker_bizobj.MakeFieldValue(
+ 9, None, 'Silver', None, None, None, False, phase_id=3),
+ tracker_bizobj.MakeFieldValue(
+ 10, None, 'Orange', None, None, None, False, phase_id=4),
+ tracker_bizobj.MakeFieldValue(
+ 11, None, 'Flat', None, None, None, False, phase_id=2),
+ ]
+
+ # Set up template
+ template = testing_helpers.DefaultTemplates()[0]
+ template.approval_values = [
+ tracker_pb2.ApprovalValue(
+ approval_id=3,
+ phase_id=6), # Different phase. Nothing else affected.
+ # No phase. Nothing else affected.
+ tracker_pb2.ApprovalValue(approval_id=4),
+ # New approval not already found in issue.
+ tracker_pb2.ApprovalValue(
+ approval_id=7,
+ phase_id=5),
+ ] # No approval 6
+ # TODO(jojwang): monorail:4693, rename 'Stable-Full' after all
+ # 'stable-full' gates have been renamed to 'stable'.
+ template.phases = [tracker_pb2.Phase(name='Canary', phase_id=5),
+ tracker_pb2.Phase(name='Stable-Full', phase_id=6)]
+
+ self.SetUpInsertComment(
+ 7890101, is_description=True, approval_id=3,
+ content=config.approval_defs[0].survey, commit=False)
+ self.SetUpInsertComment(
+ 7890101, is_description=True, approval_id=4,
+ content=config.approval_defs[1].survey, commit=False)
+ self.SetUpInsertComment(
+ 7890101, is_description=True, approval_id=7,
+ content=config.approval_defs[2].survey, commit=False)
+ amendment_row = (
+ 78901, 7890101, 'custom', None, '-Llama Roo', None, None, 'Approvals')
+ self.SetUpInsertComment(
+ 7890101, content=comment_content, amendment_rows=[amendment_row],
+ commit=False)
+ av_rows = [
+ (3, 78901, 6, 'approved', None, None),
+ (4, 78901, None, 'not_set', None, None),
+ (7, 78901, 5, 'not_set', None, None),
+ ]
+ approver_rows = [(3, 111, 78901), (4, 111, 78901), (7, 222, 78901)]
+ self.SetUpUpdateIssuesApprovals(
+ av_rows=av_rows, approver_rows=approver_rows)
+ issue_shard = issue.issue_id % settings.num_logical_shards
+ issue2fieldvalue_rows = [
+ (78901, 8, None, 'Pink', None, None, None, False, None, issue_shard),
+ (78901, 9, None, 'Silver', None, None, None, False, 5, issue_shard),
+ (78901, 11, None, 'Flat', None, None, None, False, 6, issue_shard),
+ ]
+ self.SetUpUpdateIssuesFields(issue2fieldvalue_rows=issue2fieldvalue_rows)
+
+ self.mox.ReplayAll()
+ comment = self.services.issue.UpdateIssueStructure(
+ self.cnxn, config, issue, template, reporter_id,
+ comment_content=comment_content, commit=False, invalidate=False)
+ self.mox.VerifyAll()
+
+ expected_avs = [
+ tracker_pb2.ApprovalValue(
+ approval_id=3,
+ phase_id=6,
+ status=tracker_pb2.ApprovalStatus.APPROVED,
+ approver_ids=[111],
+ ),
+ tracker_pb2.ApprovalValue(
+ approval_id=4,
+ status=tracker_pb2.ApprovalStatus.NOT_SET,
+ approver_ids=[111]),
+ tracker_pb2.ApprovalValue(
+ approval_id=7,
+ status=tracker_pb2.ApprovalStatus.NOT_SET,
+ phase_id=5,
+ approver_ids=[222]),
+ ]
+ self.assertEqual(issue.approval_values, expected_avs)
+ self.assertEqual(issue.phases, template.phases)
+ amendment = tracker_bizobj.MakeApprovalStructureAmendment(
+ ['Roo', 'Cow', 'Chicken'], ['Cow', 'Chicken', 'Llama'])
+ expected_comment = self.services.issue._MakeIssueComment(
+ 789, reporter_id, content=comment_content, amendments=[amendment])
+ expected_comment.issue_id = 78901
+ expected_comment.id = 7890101
+ self.assertEqual(expected_comment, comment)
+
+ def testDeltaUpdateIssue(self):
+ pass # TODO(jrobbins): write more tests
+
+ def testDeltaUpdateIssue_NoOp(self):
+ """If the user didn't provide any content, we don't make an IssueComment."""
+ commenter_id = 222
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901, project_name='proj')
+ config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
+ delta = tracker_pb2.IssueDelta()
+
+ amendments, comment_pb = self.services.issue.DeltaUpdateIssue(
+ self.cnxn, self.services, commenter_id, issue.project_id, config,
+ issue, delta, comment='', index_now=False, timestamp=self.now)
+ self.assertEqual([], amendments)
+ self.assertIsNone(comment_pb)
+
+ def testDeltaUpdateIssue_MergedInto(self):
+ commenter_id = 222
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901, project_name='proj')
+ target_issue = fake.MakeTestIssue(
+ project_id=789, local_id=2, owner_id=111, summary='sum sum',
+ status='Live', issue_id=78902, project_name='proj')
+ config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
+
+ self.mox.StubOutWithMock(self.services.issue, 'GetIssue')
+ self.mox.StubOutWithMock(self.services.issue, 'UpdateIssue')
+ self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
+ self.mox.StubOutWithMock(self.services.issue, '_UpdateIssuesModified')
+
+ self.services.issue.GetIssue(
+ self.cnxn, 0).AndRaise(exceptions.NoSuchIssueException)
+ self.services.issue.GetIssue(
+ self.cnxn, target_issue.issue_id).AndReturn(target_issue)
+ self.services.issue.UpdateIssue(
+ self.cnxn, issue, commit=False, invalidate=False)
+ amendments = [
+ tracker_bizobj.MakeMergedIntoAmendment(
+ [('proj', 2)], [None], default_project_name='proj')]
+ self.services.issue.CreateIssueComment(
+ self.cnxn, issue, commenter_id, 'comment text', attachments=None,
+ amendments=amendments, commit=False, is_description=False,
+ kept_attachments=None, importer_id=None, timestamp=ANY,
+ inbound_message=None)
+ self.services.issue._UpdateIssuesModified(
+ self.cnxn, {issue.issue_id, target_issue.issue_id},
+ modified_timestamp=self.now, invalidate=True)
+ self.SetUpEnqueueIssuesForIndexing([78901])
+
+ self.mox.ReplayAll()
+ delta = tracker_pb2.IssueDelta(merged_into=target_issue.issue_id)
+ self.services.issue.DeltaUpdateIssue(
+ self.cnxn, self.services, commenter_id, issue.project_id, config,
+ issue, delta, comment='comment text',
+ index_now=False, timestamp=self.now)
+ self.mox.VerifyAll()
+
+ def testDeltaUpdateIssue_BlockedOn(self):
+ commenter_id = 222
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901, project_name='proj')
+ blockedon_issue = fake.MakeTestIssue(
+ project_id=789, local_id=2, owner_id=111, summary='sum sum',
+ status='Live', issue_id=78902, project_name='proj')
+ config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
+
+ self.mox.StubOutWithMock(self.services.issue, 'GetIssue')
+ self.mox.StubOutWithMock(self.services.issue, 'GetIssues')
+ self.mox.StubOutWithMock(self.services.issue, 'LookupIssueRefs')
+ self.mox.StubOutWithMock(self.services.issue, 'UpdateIssue')
+ self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
+ self.mox.StubOutWithMock(self.services.issue, '_UpdateIssuesModified')
+ self.mox.StubOutWithMock(self.services.issue, "SortBlockedOn")
+
+ # Calls in ApplyIssueDelta
+ # Call to find added blockedon issues.
+ issue_refs = {blockedon_issue.issue_id: (
+ blockedon_issue.project_name, blockedon_issue.local_id)}
+ self.services.issue.LookupIssueRefs(
+ self.cnxn, [blockedon_issue.issue_id]).AndReturn(issue_refs)
+
+ # Call to find removed blockedon issues.
+ self.services.issue.LookupIssueRefs(self.cnxn, []).AndReturn({})
+ # Call to sort blockedon issues.
+ self.services.issue.SortBlockedOn(
+ self.cnxn, issue, [blockedon_issue.issue_id]).AndReturn(([78902], [0]))
+
+ self.services.issue.UpdateIssue(
+ self.cnxn, issue, commit=False, invalidate=False)
+ amendments = [
+ tracker_bizobj.MakeBlockedOnAmendment(
+ [('proj', 2)], [], default_project_name='proj')]
+ self.services.issue.CreateIssueComment(
+ self.cnxn, issue, commenter_id, 'comment text', attachments=None,
+ amendments=amendments, commit=False, is_description=False,
+ kept_attachments=None, importer_id=None, timestamp=ANY,
+ inbound_message=None)
+ # Call to find added blockedon issues.
+ self.services.issue.GetIssues(
+ self.cnxn, [blockedon_issue.issue_id]).AndReturn([blockedon_issue])
+ self.services.issue.CreateIssueComment(
+ self.cnxn, blockedon_issue, commenter_id, content='',
+ amendments=[tracker_bizobj.MakeBlockingAmendment(
+ [(issue.project_name, issue.local_id)], [],
+ default_project_name='proj')],
+ importer_id=None, timestamp=ANY)
+ # Call to find removed blockedon issues.
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
+ # Call to find added blocking issues.
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
+ # Call to find removed blocking issues.
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
+
+ self.services.issue._UpdateIssuesModified(
+ self.cnxn, {issue.issue_id, blockedon_issue.issue_id},
+ modified_timestamp=self.now, invalidate=True)
+ self.SetUpEnqueueIssuesForIndexing([78901])
+
+ self.mox.ReplayAll()
+ delta = tracker_pb2.IssueDelta(blocked_on_add=[blockedon_issue.issue_id])
+ self.services.issue.DeltaUpdateIssue(
+ self.cnxn, self.services, commenter_id, issue.project_id, config,
+ issue, delta, comment='comment text',
+ index_now=False, timestamp=self.now)
+ self.mox.VerifyAll()
+
+ def testDeltaUpdateIssue_Blocking(self):
+ commenter_id = 222
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901, project_name='proj')
+ blocking_issue = fake.MakeTestIssue(
+ project_id=789, local_id=2, owner_id=111, summary='sum sum',
+ status='Live', issue_id=78902, project_name='proj')
+ config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
+
+ self.mox.StubOutWithMock(self.services.issue, 'GetIssue')
+ self.mox.StubOutWithMock(self.services.issue, 'GetIssues')
+ self.mox.StubOutWithMock(self.services.issue, 'LookupIssueRefs')
+ self.mox.StubOutWithMock(self.services.issue, 'UpdateIssue')
+ self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
+ self.mox.StubOutWithMock(self.services.issue, '_UpdateIssuesModified')
+ self.mox.StubOutWithMock(self.services.issue, "SortBlockedOn")
+
+ # Calls in ApplyIssueDelta
+ # Call to find added blocking issues.
+ issue_refs = {blocking_issue: (
+ blocking_issue.project_name, blocking_issue.local_id)}
+ self.services.issue.LookupIssueRefs(
+ self.cnxn, [blocking_issue.issue_id]).AndReturn(issue_refs)
+ # Call to find removed blocking issues.
+ self.services.issue.LookupIssueRefs(self.cnxn, []).AndReturn({})
+
+ self.services.issue.UpdateIssue(
+ self.cnxn, issue, commit=False, invalidate=False)
+ amendments = [
+ tracker_bizobj.MakeBlockingAmendment(
+ [('proj', 2)], [], default_project_name='proj')]
+ self.services.issue.CreateIssueComment(
+ self.cnxn, issue, commenter_id, 'comment text', attachments=None,
+ amendments=amendments, commit=False, is_description=False,
+ kept_attachments=None, importer_id=None, timestamp=ANY,
+ inbound_message=None)
+ # Call to find added blockedon issues.
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
+ # Call to find removed blockedon issues.
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
+ # Call to find added blocking issues.
+ self.services.issue.GetIssues(
+ self.cnxn, [blocking_issue.issue_id]).AndReturn([blocking_issue])
+ self.services.issue.CreateIssueComment(
+ self.cnxn, blocking_issue, commenter_id, content='',
+ amendments=[tracker_bizobj.MakeBlockedOnAmendment(
+ [(issue.project_name, issue.local_id)], [],
+ default_project_name='proj')],
+ importer_id=None, timestamp=ANY)
+ # Call to find removed blocking issues.
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
+ self.services.issue._UpdateIssuesModified(
+ self.cnxn, {issue.issue_id, blocking_issue.issue_id},
+ modified_timestamp=self.now, invalidate=True)
+ self.SetUpEnqueueIssuesForIndexing([78901])
+
+ self.mox.ReplayAll()
+ delta = tracker_pb2.IssueDelta(blocking_add=[blocking_issue.issue_id])
+ self.services.issue.DeltaUpdateIssue(
+ self.cnxn, self.services, commenter_id, issue.project_id, config,
+ issue, delta, comment='comment text',
+ index_now=False, timestamp=self.now)
+ self.mox.VerifyAll()
+
+ def testDeltaUpdateIssue_Imported(self):
+ """If importer_id is specified, store it."""
+ commenter_id = 222
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901, project_name='proj')
+ issue.assume_stale = False
+ config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
+ delta = tracker_pb2.IssueDelta()
+
+ self.mox.StubOutWithMock(self.services.issue, 'GetIssue')
+ self.mox.StubOutWithMock(self.services.issue, 'GetIssues')
+ self.mox.StubOutWithMock(self.services.issue, 'UpdateIssue')
+ self.mox.StubOutWithMock(self.services.issue, 'CreateIssueComment')
+ self.mox.StubOutWithMock(self.services.issue, '_UpdateIssuesModified')
+ self.mox.StubOutWithMock(self.services.issue, "SortBlockedOn")
+ self.services.issue.UpdateIssue(
+ self.cnxn, issue, commit=False, invalidate=False)
+ # Call to find added blockedon issues.
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
+ # Call to find removed blockedon issues.
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
+ self.services.issue.CreateIssueComment(
+ self.cnxn, issue, commenter_id, 'a comment', attachments=None,
+ amendments=[], commit=False, is_description=False,
+ kept_attachments=None, importer_id=333, timestamp=ANY,
+ inbound_message=None).AndReturn(
+ tracker_pb2.IssueComment(content='a comment', importer_id=333))
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([])
+ self.services.issue._UpdateIssuesModified(
+ self.cnxn, {issue.issue_id},
+ modified_timestamp=self.now, invalidate=True)
+ self.SetUpEnqueueIssuesForIndexing([78901])
+ self.mox.ReplayAll()
+
+ amendments, comment_pb = self.services.issue.DeltaUpdateIssue(
+ self.cnxn, self.services, commenter_id, issue.project_id, config,
+ issue, delta, comment='a comment', index_now=False, timestamp=self.now,
+ importer_id=333)
+
+ self.mox.VerifyAll()
+ self.assertEqual([], amendments)
+ self.assertEqual('a comment', comment_pb.content)
+ self.assertEqual(333, comment_pb.importer_id)
+
+ def SetUpMoveIssues_NewProject(self):
+ self.services.issue.issueformerlocations_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUEFORMERLOCATIONS_COLS, project_id=789,
+ issue_id=[78901]).AndReturn([])
+ self.SetUpAllocateNextLocalID(789, None, None)
+ self.SetUpUpdateIssues()
+ self.services.issue.comment_tbl.Update(
+ self.cnxn, {'project_id': 789}, issue_id=[78901], commit=False)
+
+ old_location_rows = [(78901, 711, 2)]
+ self.services.issue.issueformerlocations_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUEFORMERLOCATIONS_COLS, old_location_rows,
+ ignore=True, commit=False)
+ self.cnxn.Commit()
+
+ def testMoveIssues_NewProject(self):
+ """Move project 711 issue 2 to become project 789 issue 1."""
+ dest_project = fake.Project(project_id=789)
+ issue = fake.MakeTestIssue(
+ project_id=711, local_id=2, owner_id=111, summary='sum',
+ status='Live', labels=['Type-Defect'], issue_id=78901,
+ opened_timestamp=123456789, modified_timestamp=123456789,
+ star_count=12)
+ issue.assume_stale = False
+ self.SetUpMoveIssues_NewProject()
+ self.mox.ReplayAll()
+ self.services.issue.MoveIssues(
+ self.cnxn, dest_project, [issue], self.services.user)
+ self.mox.VerifyAll()
+
+ # TODO(jrobbins): case where issue is moved back into former project
+
+ def testExpungeFormerLocations(self):
+ self.services.issue.issueformerlocations_tbl.Delete(
+ self.cnxn, project_id=789)
+
+ self.mox.ReplayAll()
+ self.services.issue.ExpungeFormerLocations(self.cnxn, 789)
+ self.mox.VerifyAll()
+
+ def testExpungeIssues(self):
+ issue_ids = [1, 2]
+
+ self.mox.StubOutWithMock(search, 'Index')
+ search.Index(name=settings.search_index_name_format % 1).AndReturn(
+ MockIndex())
+ search.Index(name=settings.search_index_name_format % 2).AndReturn(
+ MockIndex())
+
+ self.services.issue.issuesummary_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.issue2label_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.issue2component_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.issue2cc_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.issue2notify_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.issueupdate_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.attachment_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.comment_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.issuerelation_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.issuerelation_tbl.Delete(self.cnxn, dst_issue_id=[1, 2])
+ self.services.issue.danglingrelation_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.issueformerlocations_tbl.Delete(
+ self.cnxn, issue_id=[1, 2])
+ self.services.issue.reindexqueue_tbl.Delete(self.cnxn, issue_id=[1, 2])
+ self.services.issue.issue_tbl.Delete(self.cnxn, id=[1, 2])
+
+ self.mox.ReplayAll()
+ self.services.issue.ExpungeIssues(self.cnxn, issue_ids)
+ self.mox.VerifyAll()
+
+ def testSoftDeleteIssue(self):
+ project = fake.Project(project_id=789)
+ issue_1, issue_2 = self.SetUpGetIssues()
+ self.services.issue.issue_2lc = TestableIssueTwoLevelCache(
+ [issue_1, issue_2])
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
+ delta = {'deleted': True}
+ self.services.issue.issue_tbl.Update(
+ self.cnxn, delta, id=78901, commit=False)
+
+ self.services.chart.StoreIssueSnapshots(self.cnxn, mox.IgnoreArg(),
+ commit=False)
+
+ self.cnxn.Commit()
+ self.mox.ReplayAll()
+ self.services.issue.SoftDeleteIssue(
+ self.cnxn, project.project_id, 1, True, self.services.user)
+ self.mox.VerifyAll()
+ self.assertTrue(issue_1.deleted)
+
+ def SetUpDeleteComponentReferences(self, component_id):
+ self.services.issue.issue2component_tbl.Delete(
+ self.cnxn, component_id=component_id)
+
+ def testDeleteComponentReferences(self):
+ self.SetUpDeleteComponentReferences(123)
+ self.mox.ReplayAll()
+ self.services.issue.DeleteComponentReferences(self.cnxn, 123)
+ self.mox.VerifyAll()
+
+ ### Local ID generation
+
+ def SetUpInitializeLocalID(self, project_id):
+ self.services.issue.localidcounter_tbl.InsertRow(
+ self.cnxn, project_id=project_id, used_local_id=0, used_spam_id=0)
+
+ def testInitializeLocalID(self):
+ self.SetUpInitializeLocalID(789)
+ self.mox.ReplayAll()
+ self.services.issue.InitializeLocalID(self.cnxn, 789)
+ self.mox.VerifyAll()
+
+ def SetUpAllocateNextLocalID(
+ self, project_id, highest_in_use, highest_former):
+ highest_either = max(highest_in_use or 0, highest_former or 0)
+ self.services.issue.localidcounter_tbl.IncrementCounterValue(
+ self.cnxn, 'used_local_id', project_id=project_id).AndReturn(
+ highest_either + 1)
+
+ def testAllocateNextLocalID_NewProject(self):
+ self.SetUpAllocateNextLocalID(789, None, None)
+ self.mox.ReplayAll()
+ next_local_id = self.services.issue.AllocateNextLocalID(self.cnxn, 789)
+ self.mox.VerifyAll()
+ self.assertEqual(1, next_local_id)
+
+ def testAllocateNextLocalID_HighestInUse(self):
+ self.SetUpAllocateNextLocalID(789, 14, None)
+ self.mox.ReplayAll()
+ next_local_id = self.services.issue.AllocateNextLocalID(self.cnxn, 789)
+ self.mox.VerifyAll()
+ self.assertEqual(15, next_local_id)
+
+ def testAllocateNextLocalID_HighestWasMoved(self):
+ self.SetUpAllocateNextLocalID(789, 23, 66)
+ self.mox.ReplayAll()
+ next_local_id = self.services.issue.AllocateNextLocalID(self.cnxn, 789)
+ self.mox.VerifyAll()
+ self.assertEqual(67, next_local_id)
+
+ def SetUpGetHighestLocalID(self, project_id, highest_in_use, highest_former):
+ self.services.issue.issue_tbl.SelectValue(
+ self.cnxn, 'MAX(local_id)', project_id=project_id).AndReturn(
+ highest_in_use)
+ self.services.issue.issueformerlocations_tbl.SelectValue(
+ self.cnxn, 'MAX(local_id)', project_id=project_id).AndReturn(
+ highest_former)
+
+ def testGetHighestLocalID_OnlyActiveLocalIDs(self):
+ self.SetUpGetHighestLocalID(789, 14, None)
+ self.mox.ReplayAll()
+ highest_id = self.services.issue.GetHighestLocalID(self.cnxn, 789)
+ self.mox.VerifyAll()
+ self.assertEqual(14, highest_id)
+
+ def testGetHighestLocalID_OnlyFormerIDs(self):
+ self.SetUpGetHighestLocalID(789, None, 97)
+ self.mox.ReplayAll()
+ highest_id = self.services.issue.GetHighestLocalID(self.cnxn, 789)
+ self.mox.VerifyAll()
+ self.assertEqual(97, highest_id)
+
+ def testGetHighestLocalID_BothActiveAndFormer(self):
+ self.SetUpGetHighestLocalID(789, 345, 97)
+ self.mox.ReplayAll()
+ highest_id = self.services.issue.GetHighestLocalID(self.cnxn, 789)
+ self.mox.VerifyAll()
+ self.assertEqual(345, highest_id)
+
+ def testGetAllLocalIDsInProject(self):
+ self.SetUpGetHighestLocalID(789, 14, None)
+ self.mox.ReplayAll()
+ local_id_range = self.services.issue.GetAllLocalIDsInProject(self.cnxn, 789)
+ self.mox.VerifyAll()
+ self.assertEqual(list(range(1, 15)), local_id_range)
+
+ ### Comments
+
+ def testConsolidateAmendments_Empty(self):
+ amendments = []
+ actual = self.services.issue._ConsolidateAmendments(amendments)
+ self.assertEqual([], actual)
+
+ def testConsolidateAmendments_NoOp(self):
+ amendments = [
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
+ oldvalue='old sum', newvalue='new sum'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
+ oldvalue='New', newvalue='Accepted')]
+ actual = self.services.issue._ConsolidateAmendments(amendments)
+ self.assertEqual(amendments, actual)
+
+ def testConsolidateAmendments_StandardFields(self):
+ amendments = [
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
+ oldvalue='New'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
+ newvalue='Accepted'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
+ oldvalue='old sum'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
+ newvalue='new sum')]
+ actual = self.services.issue._ConsolidateAmendments(amendments)
+
+ expected = [
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
+ oldvalue='old sum', newvalue='new sum'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
+ oldvalue='New', newvalue='Accepted')]
+ self.assertEqual(expected, actual)
+
+ def testConsolidateAmendments_BlockerRelations(self):
+ amendments = [
+ tracker_pb2.Amendment(
+ field=tracker_pb2.FieldID('BLOCKEDON'), newvalue='78901'),
+ tracker_pb2.Amendment(
+ field=tracker_pb2.FieldID('BLOCKEDON'), newvalue='-b/3 b/1 b/2'),
+ tracker_pb2.Amendment(
+ field=tracker_pb2.FieldID('BLOCKING'), newvalue='78902'),
+ tracker_pb2.Amendment(
+ field=tracker_pb2.FieldID('BLOCKING'), newvalue='-b/33 b/11 b/22')
+ ]
+
+ actual = self.services.issue._ConsolidateAmendments(amendments)
+
+ expected = [
+ tracker_pb2.Amendment(
+ field=tracker_pb2.FieldID('BLOCKEDON'),
+ newvalue='78901 -b/3 b/1 b/2'),
+ tracker_pb2.Amendment(
+ field=tracker_pb2.FieldID('BLOCKING'),
+ newvalue='78902 -b/33 b/11 b/22')
+ ]
+ self.assertEqual(expected, actual)
+
+ def testConsolidateAmendments_CustomFields(self):
+ amendments = [
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('CUSTOM'),
+ custom_field_name='a', oldvalue='old a'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('CUSTOM'),
+ custom_field_name='b', oldvalue='old b')]
+ actual = self.services.issue._ConsolidateAmendments(amendments)
+ self.assertEqual(amendments, actual)
+
+ def testConsolidateAmendments_SortAmmendments(self):
+ amendments = [
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
+ oldvalue='New', newvalue='Accepted'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
+ oldvalue='old sum', newvalue='new sum'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('LABELS'),
+ oldvalue='Type-Defect', newvalue='-Type-Defect Type-Enhancement'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('CC'),
+ oldvalue='a@google.com', newvalue='b@google.com')]
+ expected = [
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('SUMMARY'),
+ oldvalue='old sum', newvalue='new sum'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('STATUS'),
+ oldvalue='New', newvalue='Accepted'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('CC'),
+ oldvalue='a@google.com', newvalue='b@google.com'),
+ tracker_pb2.Amendment(field=tracker_pb2.FieldID('LABELS'),
+ oldvalue='Type-Defect', newvalue='-Type-Defect Type-Enhancement')]
+ actual = self.services.issue._ConsolidateAmendments(amendments)
+ self.assertEqual(expected, actual)
+
+ def testDeserializeComments_Empty(self):
+ comments = self.services.issue._DeserializeComments([], [], [], [], [], [])
+ self.assertEqual([], comments)
+
+ def SetUpCommentRows(self):
+ comment_rows = [
+ (7890101, 78901, self.now, 789, 111,
+ None, False, False, 'unused_commentcontent_id'),
+ (7890102, 78901, self.now, 789, 111,
+ None, False, False, 'unused_commentcontent_id')]
+ commentcontent_rows = [(7890101, 'content', 'msg'),
+ (7890102, 'content2', 'msg')]
+ amendment_rows = [
+ (1, 78901, 7890101, 'cc', 'old', 'new val', 222, None, None)]
+ attachment_rows = []
+ approval_rows = [(23, 7890102)]
+ importer_rows = []
+ return (comment_rows, commentcontent_rows, amendment_rows,
+ attachment_rows, approval_rows, importer_rows)
+
+ def testDeserializeComments_Normal(self):
+ (comment_rows, commentcontent_rows, amendment_rows,
+ attachment_rows, approval_rows, importer_rows) = self.SetUpCommentRows()
+ commentcontent_rows = [(7890101, 'content', 'msg')]
+ comments = self.services.issue._DeserializeComments(
+ comment_rows, commentcontent_rows, amendment_rows, attachment_rows,
+ approval_rows, importer_rows)
+ self.assertEqual(2, len(comments))
+
+ def testDeserializeComments_Imported(self):
+ (comment_rows, commentcontent_rows, amendment_rows,
+ attachment_rows, approval_rows, _) = self.SetUpCommentRows()
+ importer_rows = [(7890101, 222)]
+ commentcontent_rows = [(7890101, 'content', 'msg')]
+ comments = self.services.issue._DeserializeComments(
+ comment_rows, commentcontent_rows, amendment_rows, attachment_rows,
+ approval_rows, importer_rows)
+ self.assertEqual(2, len(comments))
+ self.assertEqual(222, comments[0].importer_id)
+
+ def MockTheRestOfGetCommentsByID(self, comment_ids):
+ self.services.issue.commentcontent_tbl.Select = Mock(
+ return_value=[
+ (cid + 5000, 'content', None) for cid in comment_ids])
+ self.services.issue.issueupdate_tbl.Select = Mock(
+ return_value=[])
+ self.services.issue.attachment_tbl.Select = Mock(
+ return_value=[])
+ self.services.issue.issueapproval2comment_tbl.Select = Mock(
+ return_value=[])
+ self.services.issue.commentimporter_tbl.Select = Mock(
+ return_value=[])
+
+ def testGetCommentsByID_Normal(self):
+ """We can load comments by comment_ids."""
+ comment_ids = [101001, 101002, 101003]
+ self.services.issue.comment_tbl.Select = Mock(
+ return_value=[
+ (cid, cid - cid % 100, self.now, 789, 111,
+ None, False, False, cid + 5000)
+ for cid in comment_ids])
+ self.MockTheRestOfGetCommentsByID(comment_ids)
+
+ comments = self.services.issue.GetCommentsByID(
+ self.cnxn, comment_ids, [0, 1, 2])
+
+ self.services.issue.comment_tbl.Select.assert_called_with(
+ self.cnxn, cols=issue_svc.COMMENT_COLS,
+ id=comment_ids, shard_id=ANY)
+
+ self.assertEqual(3, len(comments))
+
+ def testGetCommentsByID_CacheReplicationLag(self):
+ self._testGetCommentsByID_ReplicationLag(True)
+
+ def testGetCommentsByID_NoCacheReplicationLag(self):
+ self._testGetCommentsByID_ReplicationLag(False)
+
+ def _testGetCommentsByID_ReplicationLag(self, use_cache):
+ """If not all comments are on the replica, we try the primary DB."""
+ comment_ids = [101001, 101002, 101003]
+ replica_comment_ids = comment_ids[:-1]
+
+ return_value_1 = [
+ (cid, cid - cid % 100, self.now, 789, 111,
+ None, False, False, cid + 5000)
+ for cid in replica_comment_ids]
+ return_value_2 = [
+ (cid, cid - cid % 100, self.now, 789, 111,
+ None, False, False, cid + 5000)
+ for cid in comment_ids]
+ return_values = [return_value_1, return_value_2]
+ self.services.issue.comment_tbl.Select = Mock(
+ side_effect=lambda *_args, **_kwargs: return_values.pop(0))
+
+ self.MockTheRestOfGetCommentsByID(comment_ids)
+
+ comments = self.services.issue.GetCommentsByID(
+ self.cnxn, comment_ids, [0, 1, 2], use_cache=use_cache)
+
+ self.services.issue.comment_tbl.Select.assert_called_with(
+ self.cnxn, cols=issue_svc.COMMENT_COLS,
+ id=comment_ids, shard_id=ANY)
+ self.services.issue.comment_tbl.Select.assert_called_with(
+ self.cnxn, cols=issue_svc.COMMENT_COLS,
+ id=comment_ids, shard_id=ANY)
+ self.assertEqual(3, len(comments))
+
+ def SetUpGetComments(self, issue_ids):
+ # Assumes one comment per issue.
+ cids = [issue_id + 1000 for issue_id in issue_ids]
+ self.services.issue.comment_tbl.Select(
+ self.cnxn, cols=issue_svc.COMMENT_COLS,
+ where=None, issue_id=issue_ids, order_by=[('created', [])],
+ shard_id=mox.IsA(int)).AndReturn([
+ (issue_id + 1000, issue_id, self.now, 789, 111,
+ None, False, False, issue_id + 5000)
+ for issue_id in issue_ids])
+ self.services.issue.commentcontent_tbl.Select(
+ self.cnxn, cols=issue_svc.COMMENTCONTENT_COLS,
+ id=[issue_id + 5000 for issue_id in issue_ids],
+ shard_id=mox.IsA(int)).AndReturn([
+ (issue_id + 5000, 'content', None) for issue_id in issue_ids])
+ self.services.issue.issueapproval2comment_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUEAPPROVAL2COMMENT_COLS,
+ comment_id=cids).AndReturn([
+ (23, cid) for cid in cids])
+
+ # Assume no amendments or attachment for now.
+ self.services.issue.issueupdate_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUEUPDATE_COLS,
+ comment_id=cids, shard_id=mox.IsA(int)).AndReturn([])
+ attachment_rows = []
+ if issue_ids:
+ attachment_rows = [
+ (1234, issue_ids[0], cids[0], 'a_filename', 1024, 'text/plain',
+ False, None)]
+
+ self.services.issue.attachment_tbl.Select(
+ self.cnxn, cols=issue_svc.ATTACHMENT_COLS,
+ comment_id=cids, shard_id=mox.IsA(int)).AndReturn(attachment_rows)
+
+ self.services.issue.commentimporter_tbl.Select(
+ self.cnxn, cols=issue_svc.COMMENTIMPORTER_COLS,
+ comment_id=cids, shard_id=mox.IsA(int)).AndReturn([])
+
+ def testGetComments_Empty(self):
+ self.SetUpGetComments([])
+ self.mox.ReplayAll()
+ comments = self.services.issue.GetComments(
+ self.cnxn, issue_id=[])
+ self.mox.VerifyAll()
+ self.assertEqual(0, len(comments))
+
+ def testGetComments_Normal(self):
+ self.SetUpGetComments([100001, 100002])
+ self.mox.ReplayAll()
+ comments = self.services.issue.GetComments(
+ self.cnxn, issue_id=[100001, 100002])
+ self.mox.VerifyAll()
+ self.assertEqual(2, len(comments))
+ self.assertEqual('content', comments[0].content)
+ self.assertEqual('content', comments[1].content)
+ self.assertEqual(23, comments[0].approval_id)
+ self.assertEqual(23, comments[1].approval_id)
+
+ def SetUpGetComment_Found(self, comment_id):
+ # Assumes one comment per issue.
+ commentcontent_id = comment_id * 10
+ self.services.issue.comment_tbl.Select(
+ self.cnxn, cols=issue_svc.COMMENT_COLS,
+ where=None, id=comment_id, order_by=[('created', [])],
+ shard_id=mox.IsA(int)).AndReturn([
+ (comment_id, int(comment_id // 100), self.now, 789, 111,
+ None, False, True, commentcontent_id)])
+ self.services.issue.commentcontent_tbl.Select(
+ self.cnxn, cols=issue_svc.COMMENTCONTENT_COLS,
+ id=[commentcontent_id], shard_id=mox.IsA(int)).AndReturn([
+ (commentcontent_id, 'content', None)])
+ self.services.issue.issueapproval2comment_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUEAPPROVAL2COMMENT_COLS,
+ comment_id=[comment_id]).AndReturn([(23, comment_id)])
+ # Assume no amendments or attachment for now.
+ self.services.issue.issueupdate_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUEUPDATE_COLS,
+ comment_id=[comment_id], shard_id=mox.IsA(int)).AndReturn([])
+ self.services.issue.attachment_tbl.Select(
+ self.cnxn, cols=issue_svc.ATTACHMENT_COLS,
+ comment_id=[comment_id], shard_id=mox.IsA(int)).AndReturn([])
+ self.services.issue.commentimporter_tbl.Select(
+ self.cnxn, cols=issue_svc.COMMENTIMPORTER_COLS,
+ comment_id=[comment_id], shard_id=mox.IsA(int)).AndReturn([])
+
+ def testGetComment_Found(self):
+ self.SetUpGetComment_Found(7890101)
+ self.mox.ReplayAll()
+ comment = self.services.issue.GetComment(self.cnxn, 7890101)
+ self.mox.VerifyAll()
+ self.assertEqual('content', comment.content)
+ self.assertEqual(23, comment.approval_id)
+
+ def SetUpGetComment_Missing(self, comment_id):
+ # Assumes one comment per issue.
+ self.services.issue.comment_tbl.Select(
+ self.cnxn, cols=issue_svc.COMMENT_COLS,
+ where=None, id=comment_id, order_by=[('created', [])],
+ shard_id=mox.IsA(int)).AndReturn([])
+ self.services.issue.commentcontent_tbl.Select(
+ self.cnxn, cols=issue_svc.COMMENTCONTENT_COLS,
+ id=[], shard_id=mox.IsA(int)).AndReturn([])
+ self.services.issue.issueapproval2comment_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUEAPPROVAL2COMMENT_COLS,
+ comment_id=[]).AndReturn([])
+ # Assume no amendments or attachment for now.
+ self.services.issue.issueupdate_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUEUPDATE_COLS,
+ comment_id=[], shard_id=mox.IsA(int)).AndReturn([])
+ self.services.issue.attachment_tbl.Select(
+ self.cnxn, cols=issue_svc.ATTACHMENT_COLS, comment_id=[],
+ shard_id=mox.IsA(int)).AndReturn([])
+ self.services.issue.commentimporter_tbl.Select(
+ self.cnxn, cols=issue_svc.COMMENTIMPORTER_COLS,
+ comment_id=[], shard_id=mox.IsA(int)).AndReturn([])
+
+ def testGetComment_Missing(self):
+ self.SetUpGetComment_Missing(7890101)
+ self.mox.ReplayAll()
+ self.assertRaises(
+ exceptions.NoSuchCommentException,
+ self.services.issue.GetComment, self.cnxn, 7890101)
+ self.mox.VerifyAll()
+
+ def testGetCommentsForIssue(self):
+ issue = fake.MakeTestIssue(789, 1, 'Summary', 'New', 111)
+ self.SetUpGetComments([issue.issue_id])
+ self.mox.ReplayAll()
+ self.services.issue.GetCommentsForIssue(self.cnxn, issue.issue_id)
+ self.mox.VerifyAll()
+
+ def testGetCommentsForIssues(self):
+ self.SetUpGetComments([100001, 100002])
+ self.mox.ReplayAll()
+ self.services.issue.GetCommentsForIssues(
+ self.cnxn, issue_ids=[100001, 100002])
+ self.mox.VerifyAll()
+
+ def SetUpInsertComment(
+ self, comment_id, is_spam=False, is_description=False, approval_id=None,
+ content=None, amendment_rows=None, commit=True):
+ content = content or 'content'
+ commentcontent_id = comment_id * 10
+ self.services.issue.commentcontent_tbl.InsertRow(
+ self.cnxn, content=content,
+ inbound_message=None, commit=False).AndReturn(commentcontent_id)
+ self.services.issue.comment_tbl.InsertRow(
+ self.cnxn, issue_id=78901, created=self.now, project_id=789,
+ commenter_id=111, deleted_by=None, is_spam=is_spam,
+ is_description=is_description, commentcontent_id=commentcontent_id,
+ commit=False).AndReturn(comment_id)
+
+ amendment_rows = amendment_rows or []
+ self.services.issue.issueupdate_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUEUPDATE_COLS[1:], amendment_rows,
+ commit=False)
+
+ attachment_rows = []
+ self.services.issue.attachment_tbl.InsertRows(
+ self.cnxn, issue_svc.ATTACHMENT_COLS[1:], attachment_rows,
+ commit=False)
+
+ if approval_id:
+ self.services.issue.issueapproval2comment_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUEAPPROVAL2COMMENT_COLS,
+ [(approval_id, comment_id)], commit=False)
+
+ if commit:
+ self.cnxn.Commit()
+
+ def testInsertComment(self):
+ self.SetUpInsertComment(7890101, approval_id=23)
+ self.mox.ReplayAll()
+ comment = tracker_pb2.IssueComment(
+ issue_id=78901, timestamp=self.now, project_id=789, user_id=111,
+ content='content', approval_id=23)
+ self.services.issue.InsertComment(self.cnxn, comment, commit=True)
+ self.mox.VerifyAll()
+ self.assertEqual(7890101, comment.id)
+
+ def SetUpUpdateComment(self, comment_id, delta=None):
+ delta = delta or {
+ 'commenter_id': 111,
+ 'deleted_by': 222,
+ 'is_spam': False,
+ }
+ self.services.issue.comment_tbl.Update(
+ self.cnxn, delta, id=comment_id)
+
+ def testUpdateComment(self):
+ self.SetUpUpdateComment(7890101)
+ self.mox.ReplayAll()
+ comment = tracker_pb2.IssueComment(
+ id=7890101, issue_id=78901, timestamp=self.now, project_id=789,
+ user_id=111, content='new content', deleted_by=222,
+ is_spam=False)
+ self.services.issue._UpdateComment(self.cnxn, comment)
+ self.mox.VerifyAll()
+
+ def testMakeIssueComment(self):
+ comment = self.services.issue._MakeIssueComment(
+ 789, 111, 'content', timestamp=self.now, approval_id=23,
+ importer_id=222)
+ self.assertEqual('content', comment.content)
+ self.assertEqual([], comment.amendments)
+ self.assertEqual([], comment.attachments)
+ self.assertEqual(comment.approval_id, 23)
+ self.assertEqual(222, comment.importer_id)
+
+ def testMakeIssueComment_NonAscii(self):
+ _ = self.services.issue._MakeIssueComment(
+ 789, 111, 'content', timestamp=self.now,
+ inbound_message=u'sent by написа')
+
+ def testCreateIssueComment_Normal(self):
+ issue_1, _issue_2 = self.SetUpGetIssues()
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
+ self.SetUpInsertComment(7890101, approval_id=24)
+ self.mox.ReplayAll()
+ comment = self.services.issue.CreateIssueComment(
+ self.cnxn, issue_1, 111, 'content', timestamp=self.now, approval_id=24)
+ self.mox.VerifyAll()
+ self.assertEqual('content', comment.content)
+
+ def testCreateIssueComment_EditDescription(self):
+ issue_1, _issue_2 = self.SetUpGetIssues()
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
+ self.services.issue.attachment_tbl.Select(
+ self.cnxn, cols=issue_svc.ATTACHMENT_COLS, id=[123])
+ self.SetUpInsertComment(7890101, is_description=True)
+ self.mox.ReplayAll()
+
+ comment = self.services.issue.CreateIssueComment(
+ self.cnxn, issue_1, 111, 'content', is_description=True,
+ kept_attachments=[123], timestamp=self.now)
+ self.mox.VerifyAll()
+ self.assertEqual('content', comment.content)
+
+ def testCreateIssueComment_Spam(self):
+ issue_1, _issue_2 = self.SetUpGetIssues()
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
+ self.SetUpInsertComment(7890101, is_spam=True)
+ self.mox.ReplayAll()
+ comment = self.services.issue.CreateIssueComment(
+ self.cnxn, issue_1, 111, 'content', timestamp=self.now, is_spam=True)
+ self.mox.VerifyAll()
+ self.assertEqual('content', comment.content)
+ self.assertTrue(comment.is_spam)
+
+ def testSoftDeleteComment(self):
+ """Deleting a comment with an attachment marks it and updates count."""
+ issue_1, issue_2 = self.SetUpGetIssues()
+ self.services.issue.issue_2lc = TestableIssueTwoLevelCache(
+ [issue_1, issue_2])
+ issue_1.attachment_count = 1
+ issue_1.assume_stale = False
+ comment = tracker_pb2.IssueComment(id=7890101)
+ comment.attachments = [tracker_pb2.Attachment()]
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901)
+ self.SetUpUpdateComment(
+ comment.id, delta={'deleted_by': 222, 'is_spam': False})
+ self.SetUpUpdateIssues(given_delta={'attachment_count': 0})
+ self.SetUpEnqueueIssuesForIndexing([78901])
+ self.mox.ReplayAll()
+ self.services.issue.SoftDeleteComment(
+ self.cnxn, issue_1, comment, 222, self.services.user)
+ self.mox.VerifyAll()
+
+ ### Approvals
+
+ def testGetIssueApproval(self):
+ av_24 = tracker_pb2.ApprovalValue(approval_id=24)
+ av_25 = tracker_pb2.ApprovalValue(approval_id=25)
+ issue_1 = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901, approval_values=[av_24, av_25])
+ issue_1.project_name = 'proj'
+ self.services.issue.issue_2lc.CacheItem(78901, issue_1)
+
+ issue, actual_approval_value = self.services.issue.GetIssueApproval(
+ self.cnxn, issue_1.issue_id, av_24.approval_id)
+
+ self.assertEqual(av_24, actual_approval_value)
+ self.assertEqual(issue, issue_1)
+
+ def testGetIssueApproval_NoSuchApproval(self):
+ issue_1 = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901)
+ issue_1.project_name = 'proj'
+ self.services.issue.issue_2lc.CacheItem(78901, issue_1)
+ self.assertRaises(
+ exceptions.NoSuchIssueApprovalException,
+ self.services.issue.GetIssueApproval,
+ self.cnxn, issue_1.issue_id, 24)
+
+ def testDeltaUpdateIssueApproval(self):
+ config = self.services.config.GetProjectConfig(
+ self.cnxn, 789)
+ config.field_defs = [
+ tracker_pb2.FieldDef(
+ field_id=1, project_id=789, field_name='EstDays',
+ field_type=tracker_pb2.FieldTypes.INT_TYPE,
+ applicable_type=''),
+ tracker_pb2.FieldDef(
+ field_id=2, project_id=789, field_name='Tag',
+ field_type=tracker_pb2.FieldTypes.STR_TYPE,
+ applicable_type=''),
+ ]
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, summary='summary', status='New',
+ owner_id=999, issue_id=78901, labels=['noodle-puppies'])
+ av = tracker_pb2.ApprovalValue(approval_id=23)
+ final_av = tracker_pb2.ApprovalValue(
+ approval_id=23, setter_id=111, set_on=1234,
+ status=tracker_pb2.ApprovalStatus.REVIEW_REQUESTED,
+ approver_ids=[222, 444])
+ labels_add = ['snakes-are']
+ label_id = 1001
+ labels_remove = ['noodle-puppies']
+ amendments = [
+ tracker_bizobj.MakeApprovalStatusAmendment(
+ tracker_pb2.ApprovalStatus.REVIEW_REQUESTED),
+ tracker_bizobj.MakeApprovalApproversAmendment([222, 444], []),
+ tracker_bizobj.MakeFieldAmendment(1, config, [4], []),
+ tracker_bizobj.MakeFieldClearedAmendment(2, config),
+ tracker_bizobj.MakeLabelsAmendment(labels_add, labels_remove)
+ ]
+ approval_delta = tracker_pb2.ApprovalDelta(
+ status=tracker_pb2.ApprovalStatus.REVIEW_REQUESTED,
+ approver_ids_add=[222, 444], set_on=1234,
+ subfield_vals_add=[
+ tracker_bizobj.MakeFieldValue(1, 4, None, None, None, None, False)
+ ],
+ labels_add=labels_add,
+ labels_remove=labels_remove,
+ subfields_clear=[2]
+ )
+
+ self.services.issue.issue2approvalvalue_tbl.Update = Mock()
+ self.services.issue.issueapproval2approver_tbl.Delete = Mock()
+ self.services.issue.issueapproval2approver_tbl.InsertRows = Mock()
+ self.services.issue.issue2fieldvalue_tbl.Delete = Mock()
+ self.services.issue.issue2fieldvalue_tbl.InsertRows = Mock()
+ self.services.issue.issue2label_tbl.Delete = Mock()
+ self.services.issue.issue2label_tbl.InsertRows = Mock()
+ self.services.issue.CreateIssueComment = Mock()
+ self.services.config.LookupLabelID = Mock(return_value=label_id)
+ shard = issue.issue_id % settings.num_logical_shards
+ fv_rows = [(78901, 1, 4, None, None, None, None, False, None, shard)]
+ label_rows = [(78901, label_id, False, shard)]
+
+ self.services.issue.DeltaUpdateIssueApproval(
+ self.cnxn, 111, config, issue, av, approval_delta, 'some comment',
+ attachments=[], commit=False, kept_attachments=[1, 2, 3])
+
+ self.assertEqual(av, final_av)
+
+ self.services.issue.issue2approvalvalue_tbl.Update.assert_called_once_with(
+ self.cnxn,
+ {'status': 'review_requested', 'setter_id': 111, 'set_on': 1234},
+ approval_id=23, issue_id=78901, commit=False)
+ self.services.issue.issueapproval2approver_tbl.\
+ Delete.assert_called_once_with(
+ self.cnxn, issue_id=78901, approval_id=23, commit=False)
+ self.services.issue.issueapproval2approver_tbl.\
+ InsertRows.assert_called_once_with(
+ self.cnxn, issue_svc.ISSUEAPPROVAL2APPROVER_COLS,
+ [(23, 222, 78901), (23, 444, 78901)], commit=False)
+ self.services.issue.issue2fieldvalue_tbl.\
+ Delete.assert_called_once_with(
+ self.cnxn, issue_id=[78901], commit=False)
+ self.services.issue.issue2fieldvalue_tbl.\
+ InsertRows.assert_called_once_with(
+ self.cnxn, issue_svc.ISSUE2FIELDVALUE_COLS + ['issue_shard'],
+ fv_rows, commit=False)
+ self.services.issue.issue2label_tbl.\
+ Delete.assert_called_once_with(
+ self.cnxn, issue_id=[78901], commit=False)
+ self.services.issue.issue2label_tbl.\
+ InsertRows.assert_called_once_with(
+ self.cnxn, issue_svc.ISSUE2LABEL_COLS + ['issue_shard'],
+ label_rows, ignore=True, commit=False)
+ self.services.issue.CreateIssueComment.assert_called_once_with(
+ self.cnxn, issue, 111, 'some comment', amendments=amendments,
+ approval_id=23, is_description=False, attachments=[], commit=False,
+ kept_attachments=[1, 2, 3])
+
+ def testDeltaUpdateIssueApproval_IsDescription(self):
+ config = self.services.config.GetProjectConfig(
+ self.cnxn, 789)
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, summary='summary', status='New',
+ owner_id=999, issue_id=78901)
+ av = tracker_pb2.ApprovalValue(approval_id=23)
+ approval_delta = tracker_pb2.ApprovalDelta()
+
+ self.services.issue.CreateIssueComment = Mock()
+
+ self.services.issue.DeltaUpdateIssueApproval(
+ self.cnxn, 111, config, issue, av, approval_delta, 'better response',
+ is_description=True, commit=False)
+
+ self.services.issue.CreateIssueComment.assert_called_once_with(
+ self.cnxn, issue, 111, 'better response', amendments=[],
+ approval_id=23, is_description=True, attachments=None, commit=False,
+ kept_attachments=None)
+
+ def testUpdateIssueApprovalStatus(self):
+ av = tracker_pb2.ApprovalValue(approval_id=23, setter_id=111, set_on=1234)
+
+ self.services.issue.issue2approvalvalue_tbl.Update(
+ self.cnxn, {'status': 'not_set', 'setter_id': 111, 'set_on': 1234},
+ approval_id=23, issue_id=78901, commit=False)
+
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssueApprovalStatus(
+ self.cnxn, 78901, av.approval_id, av.status,
+ av.setter_id, av.set_on)
+ self.mox.VerifyAll()
+
+ def testUpdateIssueApprovalApprovers(self):
+ self.services.issue.issueapproval2approver_tbl.Delete(
+ self.cnxn, issue_id=78901, approval_id=23, commit=False)
+ self.services.issue.issueapproval2approver_tbl.InsertRows(
+ self.cnxn, issue_svc.ISSUEAPPROVAL2APPROVER_COLS,
+ [(23, 111, 78901), (23, 222, 78901), (23, 444, 78901)], commit=False)
+
+ self.mox.ReplayAll()
+ self.services.issue._UpdateIssueApprovalApprovers(
+ self.cnxn, 78901, 23, [111, 222, 444])
+ self.mox.VerifyAll()
+
+ ### Attachments
+
+ def testGetAttachmentAndContext(self):
+ # TODO(jrobbins): re-implemnent to use Google Cloud Storage.
+ pass
+
+ def SetUpUpdateAttachment(self, comment_id, attachment_id, delta):
+ self.services.issue.attachment_tbl.Update(
+ self.cnxn, delta, id=attachment_id)
+ self.services.issue.comment_2lc.InvalidateKeys(
+ self.cnxn, [comment_id])
+
+
+ def testUpdateAttachment(self):
+ delta = {
+ 'filename': 'a_filename',
+ 'filesize': 1024,
+ 'mimetype': 'text/plain',
+ 'deleted': False,
+ }
+ self.SetUpUpdateAttachment(5678, 1234, delta)
+ self.mox.ReplayAll()
+ attach = tracker_pb2.Attachment(
+ attachment_id=1234, filename='a_filename', filesize=1024,
+ mimetype='text/plain')
+ comment = tracker_pb2.IssueComment(id=5678)
+ self.services.issue._UpdateAttachment(self.cnxn, comment, attach)
+ self.mox.VerifyAll()
+
+ def testStoreAttachmentBlob(self):
+ # TODO(jrobbins): re-implemnent to use Google Cloud Storage.
+ pass
+
+ def testSoftDeleteAttachment(self):
+ issue = fake.MakeTestIssue(789, 1, 'sum', 'New', 111, issue_id=78901)
+ issue.assume_stale = False
+ issue.attachment_count = 1
+
+ comment = tracker_pb2.IssueComment(
+ project_id=789, content='soon to be deleted', user_id=111,
+ issue_id=issue.issue_id)
+ attachment = tracker_pb2.Attachment(
+ attachment_id=1234)
+ comment.attachments.append(attachment)
+
+ self.SetUpUpdateAttachment(179901, 1234, {'deleted': True})
+ self.SetUpUpdateIssues(given_delta={'attachment_count': 0})
+ self.SetUpEnqueueIssuesForIndexing([78901])
+
+ self.mox.ReplayAll()
+ self.services.issue.SoftDeleteAttachment(
+ self.cnxn, issue, comment, 1234, self.services.user)
+ self.mox.VerifyAll()
+
+ ### Reindex queue
+
+ def SetUpEnqueueIssuesForIndexing(self, issue_ids):
+ reindex_rows = [(issue_id,) for issue_id in issue_ids]
+ self.services.issue.reindexqueue_tbl.InsertRows(
+ self.cnxn, ['issue_id'], reindex_rows, ignore=True, commit=True)
+
+ def testEnqueueIssuesForIndexing(self):
+ self.SetUpEnqueueIssuesForIndexing([78901])
+ self.mox.ReplayAll()
+ self.services.issue.EnqueueIssuesForIndexing(self.cnxn, [78901])
+ self.mox.VerifyAll()
+
+ def SetUpReindexIssues(self, issue_ids):
+ self.services.issue.reindexqueue_tbl.Select(
+ self.cnxn, order_by=[('created', [])],
+ limit=50).AndReturn([(issue_id,) for issue_id in issue_ids])
+
+ if issue_ids:
+ _issue_1, _issue_2 = self.SetUpGetIssues()
+ self.services.issue.reindexqueue_tbl.Delete(
+ self.cnxn, issue_id=issue_ids)
+
+ def testReindexIssues_QueueEmpty(self):
+ self.SetUpReindexIssues([])
+ self.mox.ReplayAll()
+ self.services.issue.ReindexIssues(self.cnxn, 50, self.services.user)
+ self.mox.VerifyAll()
+
+ def testReindexIssues_QueueHasTwoIssues(self):
+ self.SetUpReindexIssues([78901, 78902])
+ self.mox.ReplayAll()
+ self.services.issue.ReindexIssues(self.cnxn, 50, self.services.user)
+ self.mox.VerifyAll()
+
+ ### Search functions
+
+ def SetUpRunIssueQuery(
+ self, rows, limit=settings.search_limit_per_shard):
+ self.services.issue.issue_tbl.Select(
+ self.cnxn, shard_id=1, distinct=True, cols=['Issue.id'],
+ left_joins=[], where=[('Issue.deleted = %s', [False])], order_by=[],
+ limit=limit).AndReturn(rows)
+
+ def testRunIssueQuery_NoResults(self):
+ self.SetUpRunIssueQuery([])
+ self.mox.ReplayAll()
+ result_iids, capped = self.services.issue.RunIssueQuery(
+ self.cnxn, [], [], [], shard_id=1)
+ self.mox.VerifyAll()
+ self.assertEqual([], result_iids)
+ self.assertFalse(capped)
+
+ def testRunIssueQuery_Normal(self):
+ self.SetUpRunIssueQuery([(1,), (11,), (21,)])
+ self.mox.ReplayAll()
+ result_iids, capped = self.services.issue.RunIssueQuery(
+ self.cnxn, [], [], [], shard_id=1)
+ self.mox.VerifyAll()
+ self.assertEqual([1, 11, 21], result_iids)
+ self.assertFalse(capped)
+
+ def testRunIssueQuery_Capped(self):
+ try:
+ orig = settings.search_limit_per_shard
+ settings.search_limit_per_shard = 3
+ self.SetUpRunIssueQuery([(1,), (11,), (21,)], limit=3)
+ self.mox.ReplayAll()
+ result_iids, capped = self.services.issue.RunIssueQuery(
+ self.cnxn, [], [], [], shard_id=1)
+ self.mox.VerifyAll()
+ self.assertEqual([1, 11, 21], result_iids)
+ self.assertTrue(capped)
+ finally:
+ settings.search_limit_per_shard = orig
+
+ def SetUpGetIIDsByLabelIDs(self):
+ self.services.issue.issue_tbl.Select(
+ self.cnxn, shard_id=1, cols=['id'],
+ left_joins=[('Issue2Label ON Issue.id = Issue2Label.issue_id', [])],
+ label_id=[123, 456], project_id=789,
+ where=[('shard = %s', [1])]
+ ).AndReturn([(1,), (2,), (3,)])
+
+ def testGetIIDsByLabelIDs(self):
+ self.SetUpGetIIDsByLabelIDs()
+ self.mox.ReplayAll()
+ iids = self.services.issue.GetIIDsByLabelIDs(self.cnxn, [123, 456], 789, 1)
+ self.mox.VerifyAll()
+ self.assertEqual([1, 2, 3], iids)
+
+ def testGetIIDsByLabelIDsWithEmptyLabelIds(self):
+ self.mox.ReplayAll()
+ iids = self.services.issue.GetIIDsByLabelIDs(self.cnxn, [], 789, 1)
+ self.mox.VerifyAll()
+ self.assertEqual([], iids)
+
+ def SetUpGetIIDsByParticipant(self):
+ self.services.issue.issue_tbl.Select(
+ self.cnxn, shard_id=1, cols=['id'],
+ reporter_id=[111, 888],
+ where=[('shard = %s', [1]), ('Issue.project_id IN (%s)', [789])]
+ ).AndReturn([(1,)])
+ self.services.issue.issue_tbl.Select(
+ self.cnxn, shard_id=1, cols=['id'],
+ owner_id=[111, 888],
+ where=[('shard = %s', [1]), ('Issue.project_id IN (%s)', [789])]
+ ).AndReturn([(2,)])
+ self.services.issue.issue_tbl.Select(
+ self.cnxn, shard_id=1, cols=['id'],
+ derived_owner_id=[111, 888],
+ where=[('shard = %s', [1]), ('Issue.project_id IN (%s)', [789])]
+ ).AndReturn([(3,)])
+ self.services.issue.issue_tbl.Select(
+ self.cnxn, shard_id=1, cols=['id'],
+ left_joins=[('Issue2Cc ON Issue2Cc.issue_id = Issue.id', [])],
+ cc_id=[111, 888],
+ where=[('shard = %s', [1]), ('Issue.project_id IN (%s)', [789]),
+ ('cc_id IS NOT NULL', [])]
+ ).AndReturn([(4,)])
+ self.services.issue.issue_tbl.Select(
+ self.cnxn, shard_id=1, cols=['Issue.id'],
+ left_joins=[
+ ('Issue2FieldValue ON Issue.id = Issue2FieldValue.issue_id', []),
+ ('FieldDef ON Issue2FieldValue.field_id = FieldDef.id', [])],
+ user_id=[111, 888], grants_perm='View',
+ where=[('shard = %s', [1]), ('Issue.project_id IN (%s)', [789]),
+ ('user_id IS NOT NULL', [])]
+ ).AndReturn([(5,)])
+
+ def testGetIIDsByParticipant(self):
+ self.SetUpGetIIDsByParticipant()
+ self.mox.ReplayAll()
+ iids = self.services.issue.GetIIDsByParticipant(
+ self.cnxn, [111, 888], [789], 1)
+ self.mox.VerifyAll()
+ self.assertEqual([1, 2, 3, 4, 5], iids)
+
+ ### Issue Dependency reranking
+
+ def testSortBlockedOn(self):
+ issue = self.SetUpSortBlockedOn()
+ self.mox.ReplayAll()
+ ret = self.services.issue.SortBlockedOn(
+ self.cnxn, issue, issue.blocked_on_iids)
+ self.mox.VerifyAll()
+ self.assertEqual(ret, ([78902, 78903], [20, 10]))
+
+ def SetUpSortBlockedOn(self):
+ issue = fake.MakeTestIssue(
+ project_id=789, local_id=1, owner_id=111, summary='sum',
+ status='Live', issue_id=78901)
+ issue.project_name = 'proj'
+ issue.blocked_on_iids = [78902, 78903]
+ issue.blocked_on_ranks = [20, 10]
+ self.services.issue.issue_2lc.CacheItem(78901, issue)
+ blocked_on_rows = (
+ (78901, 78902, 'blockedon', 20), (78901, 78903, 'blockedon', 10))
+ self.services.issue.issuerelation_tbl.Select(
+ self.cnxn, cols=issue_svc.ISSUERELATION_COLS,
+ issue_id=issue.issue_id, dst_issue_id=issue.blocked_on_iids,
+ kind='blockedon',
+ order_by=[('rank DESC', []), ('dst_issue_id', [])]).AndReturn(
+ blocked_on_rows)
+ return issue
+
+ def testApplyIssueRerank(self):
+ blocker_ids = [78902, 78903]
+ relations_to_change = list(zip(blocker_ids, [20, 10]))
+ self.services.issue.issuerelation_tbl.Delete(
+ self.cnxn, issue_id=78901, dst_issue_id=blocker_ids, commit=False)
+ insert_rows = [(78901, blocker_id, 'blockedon', rank)
+ for blocker_id, rank in relations_to_change]
+ self.services.issue.issuerelation_tbl.InsertRows(
+ self.cnxn, cols=issue_svc.ISSUERELATION_COLS, row_values=insert_rows,
+ commit=True)
+
+ self.mox.StubOutWithMock(self.services.issue, "InvalidateIIDs")
+
+ self.services.issue.InvalidateIIDs(self.cnxn, [78901])
+ self.mox.ReplayAll()
+ self.services.issue.ApplyIssueRerank(self.cnxn, 78901, relations_to_change)
+ self.mox.VerifyAll()
+
+ def testExpungeUsersInIssues(self):
+ comment_id_rows = [(12, 78901, 112), (13, 78902, 113)]
+ comment_ids = [12, 13]
+ content_ids = [112, 113]
+ self.services.issue.comment_tbl.Select = Mock(
+ return_value=comment_id_rows)
+ self.services.issue.commentcontent_tbl.Update = Mock()
+ self.services.issue.comment_tbl.Update = Mock()
+
+ fv_issue_id_rows = [(78902,), (78903,), (78904,)]
+ self.services.issue.issue2fieldvalue_tbl.Select = Mock(
+ return_value=fv_issue_id_rows)
+ self.services.issue.issue2fieldvalue_tbl.Delete = Mock()
+ self.services.issue.issueapproval2approver_tbl.Delete = Mock()
+ self.services.issue.issue2approvalvalue_tbl.Update = Mock()
+
+ self.services.issue.issueupdate_tbl.Update = Mock()
+
+ self.services.issue.issue2notify_tbl.Delete = Mock()
+
+ cc_issue_id_rows = [(78904,), (78905,), (78906,)]
+ self.services.issue.issue2cc_tbl.Select = Mock(
+ return_value=cc_issue_id_rows)
+ self.services.issue.issue2cc_tbl.Delete = Mock()
+ owner_issue_id_rows = [(78907,), (78908,), (78909,)]
+ derived_owner_issue_id_rows = [(78910,), (78911,), (78912,)]
+ reporter_issue_id_rows = [(78912,), (78913,)]
+ self.services.issue.issue_tbl.Select = Mock(
+ side_effect=[owner_issue_id_rows, derived_owner_issue_id_rows,
+ reporter_issue_id_rows])
+ self.services.issue.issue_tbl.Update = Mock()
+
+ self.services.issue.issuesnapshot_tbl.Update = Mock()
+ self.services.issue.issuesnapshot2cc_tbl.Delete = Mock()
+
+ emails = ['cow@farm.com', 'pig@farm.com', 'chicken@farm.com']
+ user_ids = [222, 888, 444]
+ user_ids_by_email = {
+ email: user_id for user_id, email in zip(user_ids, emails)}
+ commit = False
+ limit = 50
+
+ affected_user_ids = self.services.issue.ExpungeUsersInIssues(
+ self.cnxn, user_ids_by_email, limit=limit)
+ self.assertItemsEqual(
+ affected_user_ids,
+ [78901, 78902, 78903, 78904, 78905, 78906, 78907, 78908, 78909,
+ 78910, 78911, 78912, 78913])
+
+ self.services.issue.comment_tbl.Select.assert_called_once()
+ _cnxn, kwargs = self.services.issue.comment_tbl.Select.call_args
+ self.assertEqual(
+ kwargs['cols'], ['Comment.id', 'Comment.issue_id', 'commentcontent_id'])
+ self.assertItemsEqual(kwargs['commenter_id'], user_ids)
+ self.assertEqual(kwargs['limit'], limit)
+
+ # since user_ids are passed to ExpungeUsersInIssues via a dictionary,
+ # we cannot know the order of the user_ids list that the method
+ # ends up using. To be able to use assert_called_with()
+ # rather than extract call_args, we are saving the order of user_ids
+ # used by the method after confirming that it has the correct items.
+ user_ids = kwargs['commenter_id']
+
+ self.services.issue.commentcontent_tbl.Update.assert_called_once_with(
+ self.cnxn, {'inbound_message': None}, id=content_ids, commit=commit)
+ self.assertEqual(
+ len(self.services.issue.comment_tbl.Update.call_args_list), 2)
+ self.services.issue.comment_tbl.Update.assert_any_call(
+ self.cnxn, {'commenter_id': framework_constants.DELETED_USER_ID},
+ id=comment_ids, commit=False)
+ self.services.issue.comment_tbl.Update.assert_any_call(
+ self.cnxn, {'deleted_by': framework_constants.DELETED_USER_ID},
+ deleted_by=user_ids, commit=False, limit=limit)
+
+ # field values
+ self.services.issue.issue2fieldvalue_tbl.Select.assert_called_once_with(
+ self.cnxn, cols=['issue_id'], user_id=user_ids, limit=limit)
+ self.services.issue.issue2fieldvalue_tbl.Delete.assert_called_once_with(
+ self.cnxn, user_id=user_ids, limit=limit, commit=commit)
+
+ # approval values
+ self.services.issue.issueapproval2approver_tbl.\
+Delete.assert_called_once_with(
+ self.cnxn, approver_id=user_ids, commit=commit, limit=limit)
+ self.services.issue.issue2approvalvalue_tbl.Update.assert_called_once_with(
+ self.cnxn, {'setter_id': framework_constants.DELETED_USER_ID},
+ setter_id=user_ids, commit=commit, limit=limit)
+
+ # issue ccs
+ self.services.issue.issue2cc_tbl.Select.assert_called_once_with(
+ self.cnxn, cols=['issue_id'], cc_id=user_ids, limit=limit)
+ self.services.issue.issue2cc_tbl.Delete.assert_called_once_with(
+ self.cnxn, cc_id=user_ids, limit=limit, commit=commit)
+
+ # issue owners
+ self.services.issue.issue_tbl.Select.assert_any_call(
+ self.cnxn, cols=['id'], owner_id=user_ids, limit=limit)
+ self.services.issue.issue_tbl.Update.assert_any_call(
+ self.cnxn, {'owner_id': None},
+ id=[row[0] for row in owner_issue_id_rows], commit=commit)
+ self.services.issue.issue_tbl.Select.assert_any_call(
+ self.cnxn, cols=['id'], derived_owner_id=user_ids, limit=limit)
+ self.services.issue.issue_tbl.Update.assert_any_call(
+ self.cnxn, {'derived_owner_id': None},
+ id=[row[0] for row in derived_owner_issue_id_rows], commit=commit)
+
+ # issue reporter
+ self.services.issue.issue_tbl.Select.assert_any_call(
+ self.cnxn, cols=['id'], reporter_id=user_ids, limit=limit)
+ self.services.issue.issue_tbl.Update.assert_any_call(
+ self.cnxn, {'reporter_id': framework_constants.DELETED_USER_ID},
+ id=[row[0] for row in reporter_issue_id_rows], commit=commit)
+
+ self.assertEqual(
+ 3, len(self.services.issue.issue_tbl.Update.call_args_list))
+
+ # issue updates
+ self.services.issue.issueupdate_tbl.Update.assert_any_call(
+ self.cnxn, {'added_user_id': framework_constants.DELETED_USER_ID},
+ added_user_id=user_ids, commit=commit)
+ self.services.issue.issueupdate_tbl.Update.assert_any_call(
+ self.cnxn, {'removed_user_id': framework_constants.DELETED_USER_ID},
+ removed_user_id=user_ids, commit=commit)
+ self.assertEqual(
+ 2, len(self.services.issue.issueupdate_tbl.Update.call_args_list))
+
+ # issue notify
+ call_args_list = self.services.issue.issue2notify_tbl.Delete.call_args_list
+ self.assertEqual(1, len(call_args_list))
+ _cnxn, kwargs = call_args_list[0]
+ self.assertItemsEqual(kwargs['email'], emails)
+ self.assertEqual(kwargs['commit'], commit)
+
+ # issue snapshots
+ self.services.issue.issuesnapshot_tbl.Update.assert_any_call(
+ self.cnxn, {'owner_id': framework_constants.DELETED_USER_ID},
+ owner_id=user_ids, commit=commit, limit=limit)
+ self.services.issue.issuesnapshot_tbl.Update.assert_any_call(
+ self.cnxn, {'reporter_id': framework_constants.DELETED_USER_ID},
+ reporter_id=user_ids, commit=commit, limit=limit)
+ self.assertEqual(
+ 2, len(self.services.issue.issuesnapshot_tbl.Update.call_args_list))
+
+ self.services.issue.issuesnapshot2cc_tbl.Delete.assert_called_once_with(
+ self.cnxn, cc_id=user_ids, commit=commit, limit=limit)