Index: appengine/monorail/services/test/issue_svc_test.py |
diff --git a/appengine/monorail/services/test/issue_svc_test.py b/appengine/monorail/services/test/issue_svc_test.py |
new file mode 100644 |
index 0000000000000000000000000000000000000000..a43ffb15911b4f8d4174b37f8c5623c5b18850fb |
--- /dev/null |
+++ b/appengine/monorail/services/test/issue_svc_test.py |
@@ -0,0 +1,1469 @@ |
+# Copyright 2016 The Chromium Authors. All rights reserved. |
+# Use of this source code is govered by a BSD-style |
+# license that can be found in the LICENSE file or at |
+# https://developers.google.com/open-source/licenses/bsd |
+ |
+"""Unit tests for issue_svc module.""" |
+ |
+import time |
+import unittest |
+ |
+import mox |
+ |
+from google.appengine.api import search |
+from google.appengine.ext import testbed |
+ |
+import settings |
+from framework import sql |
+from proto import tracker_pb2 |
+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 tracker import tracker_bizobj |
+ |
+ |
+class MockIndex(object): |
+ |
+ def delete(self, string_list): |
+ pass |
+ |
+ |
+def MakeIssueService(project_service, config_service, cache_manager, my_mox): |
+ issue_service = issue_svc.IssueService( |
+ project_service, config_service, cache_manager) |
+ 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', 'issueupdate_tbl', |
+ 'attachment_tbl', 'reindexqueue_tbl', 'localidcounter_tbl']: |
+ setattr(issue_service, table_var, my_mox.CreateMock(sql.SQLTableManager)) |
+ |
+ return issue_service |
+ |
+ |
+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.issue_service = MakeIssueService( |
+ self.project_service, self.config_service, self.cache_manager, |
+ self.mox) |
+ self.issue_id_2lc = self.issue_service.issue_id_2lc |
+ self.spam_service = fake.SpamService() |
+ |
+ def tearDown(self): |
+ self.mox.UnsetStubs() |
+ self.mox.ResetAll() |
+ |
+ def testDeserializeIssueIDs_Empty(self): |
+ issue_id_dict = self.issue_id_2lc._DeserializeIssueIDs([]) |
+ self.assertEqual({}, issue_id_dict) |
+ |
+ def testDeserializeIssueIDs_Normal(self): |
+ rows = [(789, 1, 78901), (789, 2, 78902), (789, 3, 78903)] |
+ issue_id_dict = self.issue_id_2lc._DeserializeIssueIDs(rows) |
+ expected = { |
+ (789, 1): 78901, |
+ (789, 2): 78902, |
+ (789, 3): 78903, |
+ } |
+ self.assertEqual(expected, issue_id_dict) |
+ |
+ def SetUpFetchItems(self): |
+ where = [ |
+ ('(Issue.project_id = %s AND Issue.local_id IN (%s,%s,%s))', |
+ [789, 1, 2, 3])] |
+ rows = [(789, 1, 78901), (789, 2, 78902), (789, 3, 78903)] |
+ self.issue_service.issue_tbl.Select( |
+ self.cnxn, cols=['project_id', 'local_id', 'id'], |
+ where=where, or_where_conds=True).AndReturn(rows) |
+ |
+ def testFetchItems(self): |
+ project_local_ids_list = [(789, 1), (789, 2), (789, 3)] |
+ issue_ids = [78901, 78902, 78903] |
+ self.SetUpFetchItems() |
+ self.mox.ReplayAll() |
+ issue_dict = self.issue_id_2lc.FetchItems( |
+ self.cnxn, project_local_ids_list) |
+ self.mox.VerifyAll() |
+ self.assertItemsEqual(project_local_ids_list, issue_dict.keys()) |
+ self.assertItemsEqual(issue_ids, 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.issue_service = MakeIssueService( |
+ self.project_service, self.config_service, self.cache_manager, |
+ 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, 111L, 222L, 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, 333L, 0)] |
+ self.notify_rows = [] |
+ self.fieldvalue_rows = [] |
+ self.relation_rows = [ |
+ (78901, 78902, 'blockedon'), (78903, 78901, 'blockedon')] |
+ self.dangling_relation_rows = [ |
+ (78901, 'codesite', 5001, 'blocking'), |
+ (78901, 'codesite', 5002, 'blockedon')] |
+ |
+ def tearDown(self): |
+ self.mox.UnsetStubs() |
+ self.mox.ResetAll() |
+ |
+ 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.assertItemsEqual([78901], issue_dict.keys()) |
+ |
+ 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) |
+ |
+ def testDeserializeIssues_UnexpectedIssueRelation(self): |
+ unexpected_relation_rows = [ |
+ (78990, 78999, 'blockedon') |
+ ] |
+ 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) |
+ |
+ def SetUpFetchItems(self, issue_ids): |
+ 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) |
+ self.issue_service.issuerelation_tbl.Select( |
+ self.cnxn, cols=issue_svc.ISSUERELATION_COLS, # Note: no shard |
+ where=[('(issue_id IN (%s) OR dst_issue_id IN (%s))', |
+ issue_ids + issue_ids)]).AndReturn(self.relation_rows) |
+ self.issue_service.danglingrelation_tbl.Select( |
+ self.cnxn, cols=issue_svc.DANGLINGRELATION_COLS, # Note: no shard |
+ issue_id=issue_ids).AndReturn(self.dangling_relation_rows) |
+ |
+ def testFetchItems(self): |
+ issue_ids = [78901] |
+ self.SetUpFetchItems(issue_ids) |
+ self.mox.ReplayAll() |
+ issue_dict = self.issue_2lc.FetchItems(self.cnxn, issue_ids) |
+ self.mox.VerifyAll() |
+ self.assertItemsEqual(issue_ids, issue_dict.keys()) |
+ |
+ |
+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.services.project = fake.ProjectService() |
+ self.services.config = fake.ConfigService() |
+ self.services.features = fake.FeaturesService() |
+ self.cache_manager = fake.CacheManager() |
+ self.services.issue = MakeIssueService( |
+ self.services.project, self.services.config, self.cache_manager, |
+ self.mox) |
+ self.services.spam = self.mox.CreateMock(spam_svc.SpamService) |
+ self.now = int(time.time()) |
+ self.orig_index_issues = tracker_fulltext.IndexIssues |
+ tracker_fulltext.IndexIssues = lambda *args: None |
+ |
+ def classifierResult(self, label, score): |
+ return {'outputLabel': label, |
+ 'outputMulti': [{'label': label, 'score': score}]} |
+ |
+ def tearDown(self): |
+ self.testbed.deactivate() |
+ self.mox.UnsetStubs() |
+ self.mox.ResetAll() |
+ tracker_fulltext.IndexIssues = self.orig_index_issues |
+ |
+ ### Issue ID lookups |
+ |
+ 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 = 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 = self.services.issue.ResolveIssueRefs( |
+ self.cnxn, prefetched_projects, 'proj', refs) |
+ self.assertEqual([78901, 78902], actual) |
+ |
+ ### Issue objects |
+ |
+ def testCreateIssue(self): |
+ settings.classifier_spam_thresh = 0.9 |
+ self.SetUpAllocateNextLocalID(789, None, None) |
+ self.SetUpInsertIssue() |
+ self.SetUpInsertComment(7890101, True) |
+ self.services.spam.ClassifyIssue(mox.IgnoreArg(), |
+ mox.IgnoreArg()).AndReturn( |
+ self.classifierResult('ham', 1.0)) |
+ self.services.spam.RecordClassifierIssueVerdict(self.cnxn, |
+ mox.IsA(tracker_pb2.Issue), False, 1.0) |
+ self.SetUpUpdateIssuesModified(set()) |
+ |
+ self.mox.ReplayAll() |
+ actual_local_id = self.services.issue.CreateIssue( |
+ self.cnxn, self.services, 789, 'sum', |
+ 'New', 111L, [], ['Type-Defect'], [], [], 111L, 'content', |
+ index_now=False, timestamp=self.now) |
+ self.mox.VerifyAll() |
+ self.assertEqual(1, actual_local_id) |
+ |
+ def testCreateIssue_EmptyStringLabels(self): |
+ settings.classifier_spam_thresh = 0.9 |
+ self.SetUpAllocateNextLocalID(789, None, None) |
+ self.SetUpInsertIssue(label_rows=[]) |
+ self.SetUpInsertComment(7890101, True) |
+ self.services.spam.ClassifyIssue(mox.IgnoreArg(), |
+ mox.IgnoreArg()).AndReturn( |
+ self.classifierResult('ham', 1.0)) |
+ self.services.spam.RecordClassifierIssueVerdict(self.cnxn, |
+ mox.IsA(tracker_pb2.Issue), False, 1.0) |
+ self.SetUpUpdateIssuesModified(set(), modified_timestamp=self.now) |
+ |
+ self.mox.ReplayAll() |
+ actual_local_id = self.services.issue.CreateIssue( |
+ self.cnxn, self.services, 789, 'sum', |
+ 'New', 111L, [], [',', '', ' ', ', '], [], [], 111L, 'content', |
+ index_now=False, timestamp=self.now) |
+ self.mox.VerifyAll() |
+ self.assertEqual(1, actual_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_spam(self): |
+ settings.classifier_spam_thresh = 0.9 |
+ self.SetUpAllocateNextSpamID(789, None, None) |
+ self.SetUpInsertSpamIssue() |
+ self.SetUpInsertComment(7890101, True) |
+ |
+ self.services.spam.ClassifyIssue(mox.IsA(tracker_pb2.Issue), |
+ mox.IsA(tracker_pb2.IssueComment)).AndReturn( |
+ self.classifierResult('spam', 1.0)) |
+ self.services.spam.RecordClassifierIssueVerdict(self.cnxn, |
+ mox.IsA(tracker_pb2.Issue), True, 1.0) |
+ self.SetUpUpdateIssuesModified(set()) |
+ |
+ self.mox.ReplayAll() |
+ actual_local_id = self.services.issue.CreateIssue( |
+ self.cnxn, self.services, 789, 'sum', |
+ 'New', 111L, [], ['Type-Defect'], [], [], 111L, 'content', |
+ index_now=False, timestamp=self.now) |
+ self.mox.VerifyAll() |
+ self.assertEqual(-1, actual_local_id) |
+ |
+ 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=111L, summary='sum', |
+ status='Live', issue_id=78901) |
+ issue_1.project_name = 'proj' |
+ issue_2 = fake.MakeTestIssue( |
+ project_id=789, local_id=2, owner_id=111L, 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] |
+ issue_1, issue_2 = self.SetUpGetIssues() |
+ issues_dict = self.services.issue.GetIssuesDict(self.cnxn, issue_ids) |
+ self.assertEqual( |
+ {78901: issue_1, 78902: issue_2}, |
+ issues_dict) |
+ |
+ 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=111L, 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): |
+ row = (789, 1, 1, 111L, 111L, self.now, 0, 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() |
+ |
+ def SetUpInsertSpamIssue(self): |
+ row = (789, -1, 1, 111L, 111L, self.now, 0, 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() |
+ |
+ 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.Delete( |
+ self.cnxn, issue_id=[78901], commit=False) |
+ self.services.issue.issuerelation_tbl.Delete( |
+ self.cnxn, dst_issue_id=[78901], kind='blockedon', |
+ 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 testInsertIssue(self): |
+ self.SetUpInsertIssue() |
+ self.mox.ReplayAll() |
+ issue = fake.MakeTestIssue( |
+ project_id=789, local_id=1, owner_id=111L, reporter_id=111L, |
+ 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': 111L, |
+ 'status_id': 1, |
+ 'opened': 123456789, |
+ 'closed': 0, |
+ '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.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=111L, summary='sum', |
+ status='Live', labels=['Type-Defect'], issue_id=78901, |
+ opened_timestamp=123456789, modified_timestamp=123456789, |
+ star_count=12) |
+ self.SetUpUpdateIssues() |
+ self.mox.ReplayAll() |
+ self.services.issue.UpdateIssues(self.cnxn, [issue]) |
+ self.mox.VerifyAll() |
+ |
+ def testUpdateIssue(self): |
+ issue = fake.MakeTestIssue( |
+ project_id=789, local_id=1, owner_id=111L, summary='sum', |
+ status='Live', labels=['Type-Defect'], issue_id=78901, |
+ opened_timestamp=123456789, modified_timestamp=123456789, |
+ star_count=12) |
+ self.SetUpUpdateIssues() |
+ self.mox.ReplayAll() |
+ self.services.issue.UpdateIssue(self.cnxn, issue) |
+ self.mox.VerifyAll() |
+ |
+ def testUpdateIssuesSummary(self): |
+ issue = fake.MakeTestIssue( |
+ local_id=1, issue_id=78901, owner_id=111L, summary='sum', status='New', |
+ project_id=789) |
+ 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=111L, summary='sum', status='New', |
+ labels=['Type-Defect'], project_id=789) |
+ self.SetUpUpdateIssuesLabels() |
+ self.mox.ReplayAll() |
+ self.services.issue._UpdateIssuesLabels( |
+ self.cnxn, [issue], 789, commit=False) |
+ self.mox.VerifyAll() |
+ |
+ def testUpdateIssuesFields_Empty(self): |
+ issue = fake.MakeTestIssue( |
+ local_id=1, issue_id=78901, owner_id=111L, 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=111L, summary='sum', status='New', |
+ project_id=789) |
+ issue_shard = issue.issue_id % settings.num_logical_shards |
+ fv1 = tracker_bizobj.MakeFieldValue(345, 679, '', 0L, False) |
+ issue.field_values.append(fv1) |
+ fv2 = tracker_bizobj.MakeFieldValue(346, 0, 'Blue', 0L, True) |
+ issue.field_values.append(fv2) |
+ self.SetUpUpdateIssuesFields(issue2fieldvalue_rows=[ |
+ (issue.issue_id, fv1.field_id, fv1.int_value, fv1.str_value, |
+ None, fv1.derived, issue_shard), |
+ (issue.issue_id, fv2.field_id, fv2.int_value, fv2.str_value, |
+ None, fv2.derived, 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=111L, 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=111L, 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=111L, summary='sum', |
+ status='Live', issue_id=78901) |
+ issue.cc_ids = [222L, 333L] |
+ issue.derived_cc_ids = [888L] |
+ issue_shard = issue.issue_id % settings.num_logical_shards |
+ self.SetUpUpdateIssuesCc(issue2cc_rows=[ |
+ (issue.issue_id, 222L, False, issue_shard), |
+ (issue.issue_id, 333L, False, issue_shard), |
+ (issue.issue_id, 888L, 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=111L, 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=111L, 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 testDeltaUpdateIssue(self): |
+ pass # TODO(jrobbins): write more tests |
+ |
+ def testDeltaUpdateIssue_MergedInto(self): |
+ commenter_id = 222L |
+ issue = fake.MakeTestIssue( |
+ project_id=789, local_id=1, owner_id=111L, summary='sum', |
+ status='Live', issue_id=78901, project_name='proj') |
+ target_issue = fake.MakeTestIssue( |
+ project_id=789, local_id=2, owner_id=111L, 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(issue_svc.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, 789, 1, commenter_id, 'comment text', |
+ amendments=amendments, commit=False) |
+ self.services.issue._UpdateIssuesModified( |
+ self.cnxn, {issue.issue_id, target_issue.issue_id}, |
+ modified_timestamp=self.now, invalidate=True) |
+ |
+ self.mox.ReplayAll() |
+ self.services.issue.DeltaUpdateIssue( |
+ self.cnxn, self.services, commenter_id, issue.project_id, config, |
+ issue, issue.status, issue.owner_id, |
+ [], [], [], [], [], [], [], [], [], |
+ merged_into=target_issue.issue_id, comment='comment text', |
+ index_now=False, timestamp=self.now) |
+ self.mox.VerifyAll() |
+ |
+ def testApplyIssueComment(self): |
+ issue = fake.MakeTestIssue( |
+ project_id=789, local_id=1, owner_id=111L, summary='sum', |
+ status='Live', issue_id=78901) |
+ |
+ self.mox.StubOutWithMock(self.services.issue, 'GetIssueByLocalID') |
+ self.mox.StubOutWithMock(self.services.issue, 'UpdateIssues') |
+ self.mox.StubOutWithMock(self.services.issue, 'GetCommentsForIssue') |
+ self.mox.StubOutWithMock(self.services.issue, 'SoftDeleteComment') |
+ self.mox.StubOutWithMock(self.services.issue, "CreateIssueComment") |
+ self.mox.StubOutWithMock(self.services.issue, "_UpdateIssuesModified") |
+ |
+ self.services.issue.GetIssueByLocalID(self.cnxn, issue.project_id, |
+ issue.local_id).AndReturn(issue) |
+ self.services.issue.CreateIssueComment(self.cnxn, issue.project_id, |
+ issue.local_id, issue.reporter_id, 'comment text', |
+ amendments=[], attachments=None, inbound_message=None, |
+ is_spam=False) |
+ self.services.issue.UpdateIssues(self.cnxn, [issue], |
+ just_derived=False, update_cols=None, commit=True, invalidate=True) |
+ self.services.spam.ClassifyComment('comment text').AndReturn( |
+ self.classifierResult('ham', 1.0)) |
+ self.services.spam.RecordClassifierCommentVerdict(self.cnxn, |
+ None, False, 1.0) |
+ self.services.issue._UpdateIssuesModified( |
+ self.cnxn, set(), modified_timestamp=self.now) |
+ |
+ self.mox.ReplayAll() |
+ self.services.issue.ApplyIssueComment(self.cnxn, self.services, |
+ issue.reporter_id, issue.project_id, issue.local_id, issue.summary, |
+ issue.status, issue.owner_id, issue.cc_ids, issue.labels, |
+ issue.field_values, issue.component_ids, [], |
+ [], [], [], issue.merged_into, comment='comment text', |
+ timestamp=self.now) |
+ self.mox.VerifyAll() |
+ |
+ def testApplyIssueComment_spam(self): |
+ settings.classifier_spam_thresh = 0.5 |
+ |
+ issue = fake.MakeTestIssue( |
+ project_id=789, local_id=1, owner_id=111L, summary='sum', |
+ status='Live', issue_id=78901) |
+ |
+ self.mox.StubOutWithMock(self.services.issue, "GetIssueByLocalID") |
+ self.mox.StubOutWithMock(self.services.issue, "UpdateIssues") |
+ self.mox.StubOutWithMock(self.services.issue, "GetCommentsForIssue") |
+ self.mox.StubOutWithMock(self.services.issue, "CreateIssueComment") |
+ self.mox.StubOutWithMock(self.services.issue, "SoftDeleteComment") |
+ self.mox.StubOutWithMock(self.services.issue, "_UpdateIssuesModified") |
+ |
+ self.services.issue.GetIssueByLocalID(self.cnxn, 789, 1).AndReturn(issue) |
+ self.services.issue.UpdateIssues(self.cnxn, [issue], |
+ just_derived=False, update_cols=None, commit=True, invalidate=True) |
+ self.services.issue.GetCommentsForIssue(self.cnxn, |
+ issue.issue_id).AndReturn([""]) |
+ self.services.issue.SoftDeleteComment(self.cnxn, |
+ issue.project_id, issue.local_id, 0, issue.reporter_id, |
+ self.services.user, is_spam=True) |
+ self.services.spam.ClassifyComment('comment text').AndReturn( |
+ self.classifierResult('spam', 1.0)) |
+ self.services.spam.RecordClassifierCommentVerdict(self.cnxn, |
+ mox.IsA(tracker_pb2.IssueComment), True, 1.0) |
+ self.services.issue.CreateIssueComment(self.cnxn, issue.project_id, |
+ issue.local_id, issue.reporter_id, 'comment text', |
+ amendments=[], attachments=None, inbound_message=None, |
+ is_spam=True).AndReturn(tracker_pb2.IssueComment()) |
+ self.services.issue._UpdateIssuesModified( |
+ self.cnxn, set(), modified_timestamp=self.now) |
+ |
+ self.mox.ReplayAll() |
+ self.services.issue.ApplyIssueComment(self.cnxn, self.services, |
+ issue.reporter_id, issue.project_id, issue.local_id, issue.summary, |
+ issue.status, issue.owner_id, issue.cc_ids, issue.labels, |
+ issue.field_values, issue.component_ids, [], |
+ [], [], [], issue.merged_into, comment='comment text', |
+ timestamp=self.now) |
+ self.mox.VerifyAll() |
+ |
+ def testApplyIssueComment_blockedon(self): |
+ issue = fake.MakeTestIssue( |
+ project_id=789, local_id=1, owner_id=111L, summary='sum', |
+ status='Live', issue_id=78901) |
+ blockedon_issue = fake.MakeTestIssue( |
+ project_id=789, local_id=2, owner_id=111L, summary='sum', |
+ status='Live', issue_id=78902) |
+ |
+ self.mox.StubOutWithMock(self.services.issue, "GetIssueByLocalID") |
+ self.mox.StubOutWithMock(self.services.issue, "UpdateIssues") |
+ self.mox.StubOutWithMock(self.services.issue, "CreateIssueComment") |
+ self.mox.StubOutWithMock(self.services.issue, "GetIssues") |
+ self.mox.StubOutWithMock(self.services.issue, "_UpdateIssuesModified") |
+ # Call to find added blockedon issues. |
+ self.services.issue.GetIssues( |
+ self.cnxn, [blockedon_issue.issue_id]).AndReturn([blockedon_issue]) |
+ # Call to find removed blockedon issues. |
+ self.services.issue.GetIssues(self.cnxn, []).AndReturn([]) |
+ |
+ self.services.issue.GetIssueByLocalID(self.cnxn, 789, 1).AndReturn(issue) |
+ self.services.issue.UpdateIssues(self.cnxn, [issue], |
+ just_derived=False, update_cols=None, commit=True, invalidate=True) |
+ self.services.spam.ClassifyComment('comment text').AndReturn( |
+ self.classifierResult('ham', 1.0)) |
+ self.services.spam.RecordClassifierCommentVerdict(self.cnxn, |
+ mox.IsA(tracker_pb2.IssueComment), False, 1.0) |
+ self.services.issue.CreateIssueComment(self.cnxn, issue.project_id, |
+ issue.local_id, issue.reporter_id, 'comment text', |
+ amendments=[ |
+ tracker_bizobj.MakeBlockedOnAmendment( |
+ [(blockedon_issue.project_name, blockedon_issue.local_id)], [], |
+ default_project_name=blockedon_issue.project_name)], |
+ attachments=None, inbound_message=None, |
+ is_spam=False).AndReturn(tracker_pb2.IssueComment()) |
+ # Add a comment on the blockedon issue. |
+ self.services.issue.CreateIssueComment( |
+ self.cnxn, blockedon_issue.project_id, blockedon_issue.local_id, |
+ blockedon_issue.reporter_id, content='', |
+ amendments=[tracker_bizobj.MakeBlockingAmendment( |
+ [(issue.project_name, issue.local_id)], [], |
+ default_project_name=issue.project_name)]) |
+ self.services.issue._UpdateIssuesModified( |
+ self.cnxn, {blockedon_issue.issue_id}, modified_timestamp=self.now) |
+ |
+ self.mox.ReplayAll() |
+ self.services.issue.ApplyIssueComment(self.cnxn, self.services, |
+ issue.reporter_id, issue.project_id, issue.local_id, issue.summary, |
+ issue.status, issue.owner_id, issue.cc_ids, issue.labels, |
+ issue.field_values, issue.component_ids, [blockedon_issue.issue_id], |
+ [], [], [], issue.merged_into, comment='comment text', |
+ timestamp=self.now) |
+ self.mox.VerifyAll() |
+ |
+ 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=111L, summary='sum', |
+ status='Live', labels=['Type-Defect'], issue_id=78901, |
+ opened_timestamp=123456789, modified_timestamp=123456789, |
+ star_count=12) |
+ 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_id_2lc.CacheItem((789, 1), 78901) |
+ delta = {'deleted': True} |
+ self.services.issue.issue_tbl.Update( |
+ self.cnxn, delta, id=78901, 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 SetUpAllocateNextSpamID( |
+ 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_spam_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(range(1, 15), local_id_range) |
+ |
+ ### Comments |
+ |
+ def testDeserializeComments_Empty(self): |
+ comments = self.services.issue._DeserializeComments([], [], []) |
+ self.assertEqual([], comments) |
+ |
+ def SetUpCommentRows(self): |
+ comment_rows = [ |
+ (7890101, 78901, self.now, 789, 111L, |
+ 'content', None, True, None, False)] |
+ amendment_rows = [ |
+ (1, 78901, 7890101, 'cc', 'old', 'new val', 222, None, None)] |
+ attachment_rows = [] |
+ return comment_rows, amendment_rows, attachment_rows |
+ |
+ def testDeserializeComments(self): |
+ comment_rows, amendment_rows, attachment_rows = self.SetUpCommentRows() |
+ comments = self.services.issue._DeserializeComments( |
+ comment_rows, amendment_rows, attachment_rows) |
+ self.assertEqual(1, 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=['Comment.id'] + issue_svc.COMMENT_COLS[1:], |
+ where=None, issue_id=issue_ids, order_by=[('created', [])]).AndReturn([ |
+ (issue_id + 1000, issue_id, self.now, 789, 111L, 'content', |
+ None, True, None, False) for issue_id in issue_ids]) |
+ # Assume no amendments or attachment for now. |
+ self.services.issue.issueupdate_tbl.Select( |
+ self.cnxn, cols=issue_svc.ISSUEUPDATE_COLS, |
+ comment_id=cids).AndReturn([]) |
+ if issue_ids: |
+ attachment_rows = [ |
+ (1234, issue_ids[0], cids[0], 'a_filename', 1024, 'text/plain', |
+ False, None)] |
+ else: |
+ attachment_rows = [] |
+ |
+ self.services.issue.attachment_tbl.Select( |
+ self.cnxn, cols=issue_svc.ATTACHMENT_COLS, |
+ comment_id=cids).AndReturn(attachment_rows) |
+ |
+ def testGetComments(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) |
+ |
+ def SetUpGetComment_Found(self, comment_id): |
+ # Assumes one comment per issue. |
+ self.services.issue.comment_tbl.Select( |
+ self.cnxn, cols=['Comment.id'] + issue_svc.COMMENT_COLS[1:], |
+ where=None, id=comment_id, order_by=[('created', [])]).AndReturn([ |
+ (comment_id, int(comment_id / 100), self.now, 789, 111L, 'content', |
+ None, True, None, False)]) |
+ # Assume no amendments or attachment for now. |
+ self.services.issue.issueupdate_tbl.Select( |
+ self.cnxn, cols=issue_svc.ISSUEUPDATE_COLS, |
+ comment_id=[comment_id]).AndReturn([]) |
+ self.services.issue.attachment_tbl.Select( |
+ self.cnxn, cols=issue_svc.ATTACHMENT_COLS, |
+ comment_id=[comment_id]).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) |
+ |
+ def SetUpGetComment_Missing(self, comment_id): |
+ # Assumes one comment per issue. |
+ self.services.issue.comment_tbl.Select( |
+ self.cnxn, cols=['Comment.id'] + issue_svc.COMMENT_COLS[1:], |
+ where=None, id=comment_id, order_by=[('created', [])]).AndReturn([]) |
+ # Assume no amendments or attachment for now. |
+ self.services.issue.issueupdate_tbl.Select( |
+ self.cnxn, cols=issue_svc.ISSUEUPDATE_COLS, |
+ comment_id=[]).AndReturn([]) |
+ self.services.issue.attachment_tbl.Select( |
+ self.cnxn, cols=issue_svc.ATTACHMENT_COLS, comment_id=[]).AndReturn([]) |
+ |
+ def testGetComment_Missing(self): |
+ self.SetUpGetComment_Missing(7890101) |
+ self.mox.ReplayAll() |
+ self.assertRaises( |
+ issue_svc.NoSuchCommentException, |
+ self.services.issue.GetComment, self.cnxn, 7890101) |
+ self.mox.VerifyAll() |
+ |
+ def testGetCommentsForIssue(self): |
+ issue = fake.MakeTestIssue(789, 1, 'Summary', 'New', 111L) |
+ 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, was_escaped, is_spam=False): |
+ self.services.issue.comment_tbl.InsertRow( |
+ self.cnxn, issue_id=78901, created=self.now, project_id=789, |
+ commenter_id=111L, content='content', inbound_message=None, |
+ was_escaped=was_escaped, deleted_by=None, is_spam=is_spam, |
+ commit=True).AndReturn(comment_id) |
+ |
+ amendment_rows = [] |
+ self.services.issue.issueupdate_tbl.InsertRows( |
+ self.cnxn, issue_svc.ISSUEUPDATE_COLS[1:], amendment_rows, |
+ commit=True) |
+ |
+ attachment_rows = [] |
+ self.services.issue.attachment_tbl.InsertRows( |
+ self.cnxn, issue_svc.ATTACHMENT_COLS[1:], attachment_rows, |
+ commit=True) |
+ |
+ def testInsertComment(self): |
+ self.SetUpInsertComment(7890101, False) |
+ self.mox.ReplayAll() |
+ comment = tracker_pb2.IssueComment( |
+ issue_id=78901, timestamp=self.now, project_id=789, user_id=111L, |
+ content='content', was_escaped=False) |
+ self.services.issue.InsertComment(self.cnxn, comment, commit=True) |
+ self.mox.VerifyAll() |
+ self.assertEqual(7890101, comment.id) |
+ |
+ def SetUpUpdateComment(self, comment_id, delta=None): |
+ delta = delta or { |
+ 'commenter_id': 111L, |
+ 'content': 'new content', |
+ 'deleted_by': 222L, |
+ '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=111L, content='new content', was_escaped=True, deleted_by=222L, |
+ is_spam=False) |
+ self.services.issue._UpdateComment(self.cnxn, comment) |
+ self.mox.VerifyAll() |
+ |
+ def testMakeIssueComment(self): |
+ comment = self.services.issue._MakeIssueComment( |
+ 789, 111L, 'content', timestamp=self.now) |
+ self.assertEqual('content', comment.content) |
+ self.assertEqual([], comment.amendments) |
+ self.assertEqual([], comment.attachments) |
+ |
+ def testCreateIssueComment(self): |
+ _issue_1, _issue_2 = self.SetUpGetIssues() |
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901) |
+ self.SetUpInsertComment(7890101, False) |
+ self.mox.ReplayAll() |
+ comment = self.services.issue.CreateIssueComment( |
+ self.cnxn, 789, 1, 111L, 'content', 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, False, is_spam=True) |
+ self.mox.ReplayAll() |
+ comment = self.services.issue.CreateIssueComment( |
+ self.cnxn, 789, 1, 111L, 'content', timestamp=self.now, is_spam=True) |
+ self.mox.VerifyAll() |
+ self.assertEqual('content', comment.content) |
+ self.assertTrue(comment.is_spam) |
+ |
+ def testSoftDeleteComment(self): |
+ issue_1, _issue_2 = self.SetUpGetIssues() |
+ issue_1.attachment_count = 1 |
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901) |
+ self.SetUpGetComments([78901]) |
+ self.SetUpUpdateComment(79901, delta={'deleted_by': 222L, 'is_spam': False}) |
+ self.SetUpUpdateIssues(given_delta={'attachment_count': 0}) |
+ self.mox.ReplayAll() |
+ self.services.issue.SoftDeleteComment( |
+ self.cnxn, 789, 1, 0, 222L, self.services.user) |
+ self.mox.VerifyAll() |
+ |
+ ### Attachments |
+ |
+ def testGetAttachmentAndContext(self): |
+ # TODO(jrobbins): re-implemnent to use Google Cloud Storage. |
+ pass |
+ |
+ def SetUpUpdateAttachment(self, attachment_id, delta): |
+ self.services.issue.attachment_tbl.Update( |
+ self.cnxn, delta, id=attachment_id) |
+ |
+ def testUpdateAttachment(self): |
+ delta = { |
+ 'filename': 'a_filename', |
+ 'filesize': 1024, |
+ 'mimetype': 'text/plain', |
+ 'deleted': False, |
+ } |
+ self.SetUpUpdateAttachment(1234, delta) |
+ self.mox.ReplayAll() |
+ attach = tracker_pb2.Attachment( |
+ attachment_id=1234, filename='a_filename', filesize=1024, |
+ mimetype='text/plain') |
+ self.services.issue._UpdateAttachment(self.cnxn, attach) |
+ self.mox.VerifyAll() |
+ |
+ def testStoreAttachmentBlob(self): |
+ # TODO(jrobbins): re-implemnent to use Google Cloud Storage. |
+ pass |
+ |
+ def testSoftDeleteAttachment(self): |
+ issue_1, _issue_2 = self.SetUpGetIssues() |
+ issue_1.attachment_count = 1 |
+ self.services.issue.issue_id_2lc.CacheItem((789, 1), 78901) |
+ self.SetUpGetComments([78901]) |
+ self.SetUpUpdateAttachment(1234, {'deleted': True}) |
+ self.SetUpUpdateIssues(given_delta={'attachment_count': 0}) |
+ |
+ self.mox.ReplayAll() |
+ self.services.issue.SoftDeleteAttachment( |
+ self.cnxn, 789, 1, 0, 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) |
+ |
+ 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 SetUpGetIIDsByParticipant(self): |
+ self.services.issue.issue_tbl.Select( |
+ self.cnxn, shard_id=1, cols=['id'], |
+ reporter_id=[111L, 888L], |
+ 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=[111L, 888L], |
+ 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=[111L, 888L], |
+ 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=[111L, 888L], |
+ 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=[111L, 888L], 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, [111L, 888L], [789], 1) |
+ self.mox.VerifyAll() |
+ self.assertEqual([1, 2, 3, 4, 5], iids) |
+ |
+ |
+class IssueServiceFunctionsTest(unittest.TestCase): |
+ |
+ def testUpdateClosedTimestamp(self): |
+ config = tracker_pb2.ProjectIssueConfig() |
+ config.well_known_statuses.append(tracker_pb2.StatusDef( |
+ status='New', means_open=True)) |
+ config.well_known_statuses.append(tracker_pb2.StatusDef( |
+ status='Accepted', means_open=True)) |
+ config.well_known_statuses.append(tracker_pb2.StatusDef( |
+ status='Old', means_open=False)) |
+ config.well_known_statuses.append(tracker_pb2.StatusDef( |
+ status='Closed', means_open=False)) |
+ |
+ issue = tracker_pb2.Issue() |
+ issue.local_id = 1234 |
+ issue.status = 'New' |
+ |
+ # ensure the default value is undef |
+ self.assert_(not issue.closed_timestamp) |
+ |
+ # ensure transitioning to the same and other open states |
+ # doesn't set the timestamp |
+ issue.status = 'New' |
+ issue_svc._UpdateClosedTimestamp(config, issue, 'New') |
+ self.assert_(not issue.closed_timestamp) |
+ |
+ issue.status = 'Accepted' |
+ issue_svc._UpdateClosedTimestamp(config, issue, 'New') |
+ self.assert_(not issue.closed_timestamp) |
+ |
+ # ensure transitioning from open to closed sets the timestamp |
+ issue.status = 'Closed' |
+ issue_svc._UpdateClosedTimestamp(config, issue, 'Accepted') |
+ self.assert_(issue.closed_timestamp) |
+ |
+ # ensure that the timestamp is cleared when transitioning from |
+ # closed to open |
+ issue.status = 'New' |
+ issue_svc._UpdateClosedTimestamp(config, issue, 'Closed') |
+ self.assert_(not issue.closed_timestamp) |
+ |
+ |
+if __name__ == '__main__': |
+ unittest.main() |