| # -*- coding: utf-8 -*- |
| # Copyright 2016 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| """Unit tests for issue_svc module.""" |
| |
| from __future__ import division |
| from __future__ import print_function |
| from __future__ import absolute_import |
| |
| import logging |
| import six |
| import time |
| import unittest |
| from mock import patch, Mock, ANY |
| |
| try: |
| from mox3 import mox |
| except ImportError: |
| 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 mrproto 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() |
| six.assertCountEqual(self, project_local_ids_list, list(issue_dict.keys())) |
| six.assertCountEqual(self, 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, 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) |
| six.assertCountEqual(self, [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) |
| six.assertCountEqual(self, 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() |
| six.assertCountEqual(self, 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() |
| six.assertCountEqual(self, 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, 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, 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, |
| 'migration_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', |
| None, None) |
| 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.issue_id: |
| (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, 'migration_modified': self.now} |
| 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, 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 testUpackAmendment(self): |
| amendment_row = ( |
| 1, 78901, 7890101, 'cc', 'old', 'new val', 222, None, None, None, None) |
| amendment, comment_id = self.services.issue._UnpackAmendment(amendment_row) |
| self.assertEqual(comment_id, 7890101) |
| self.assertEqual(amendment.field, tracker_pb2.FieldID('CC')) |
| self.assertEqual(amendment.newvalue, 'new val') |
| self.assertEqual(amendment.oldvalue, 'old') |
| self.assertEqual(amendment.added_user_ids, [222]) |
| |
| def testUpackAmendment_With_Unicode(self): |
| amendment_row = ( |
| 1, 78901, 7890102, 'custom', None, None, None, None, None, u'123', None) |
| amendment, comment_id = self.services.issue._UnpackAmendment(amendment_row) |
| self.assertEqual(comment_id, 7890102) |
| self.assertEqual(amendment.field, tracker_pb2.FieldID('CUSTOM')) |
| self.assertEqual(amendment.added_component_ids, [123]) |
| |
| 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 testInsertComment_WithIssueUpdate(self): |
| amendment = tracker_bizobj.MakeAmendment( |
| tracker_pb2.FieldID.COMPONENTS, 'aaa', [], [], added_component_ids=[1]) |
| amendment_rows = [ |
| ( |
| 78901, 7890101, 'components', None, 'aaa', None, None, None, None, |
| None), |
| (78901, 7890101, 'components', None, None, None, None, None, 1, None) |
| ] |
| comment = tracker_pb2.IssueComment( |
| issue_id=78901, |
| timestamp=self.now, |
| project_id=789, |
| user_id=111, |
| content='content', |
| amendments=[amendment]) |
| self.services.issue.commentcontent_tbl.InsertRow = Mock( |
| return_value=78901010) |
| self.services.issue.comment_tbl.InsertRow = Mock(return_value=7890101) |
| self.services.issue.issueupdate_tbl.InsertRows = Mock() |
| |
| self.services.issue.InsertComment(self.cnxn, comment, commit=True) |
| |
| self.services.issue.issueupdate_tbl.InsertRows.assert_called_once_with( |
| self.cnxn, issue_svc.ISSUEUPDATE_COLS[1:], amendment_rows, commit=False) |
| |
| 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, |
| 'migration_modified': self.now |
| }) |
| 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, |
| 'migration_modified': self.now |
| }) |
| 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() |
| |
| issue_update_id_rows = [(78914,), (78915,)] |
| self.services.issue.issueupdate_tbl.Select = Mock( |
| return_value=issue_update_id_rows) |
| 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_issue_ids = self.services.issue.ExpungeUsersInIssues( |
| self.cnxn, user_ids_by_email, limit=limit) |
| six.assertCountEqual( |
| self, affected_issue_ids, [ |
| 78901, 78902, 78903, 78904, 78905, 78906, 78907, 78908, 78909, |
| 78910, 78911, 78912, 78913, 78914, 78915 |
| ]) |
| |
| 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']) |
| six.assertCountEqual(self, 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) |
| |
| # 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)) |
| |
| # check updates across all issues |
| self.services.issue.issue_tbl.Update.assert_any_call( |
| self.cnxn, {'migration_modified': self.now}, |
| id=affected_issue_ids, |
| commit=commit) |
| self.assertEqual( |
| 4, len(self.services.issue.issue_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] |
| six.assertCountEqual(self, 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) |