Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/v3/test/converters_test.py b/api/v3/test/converters_test.py
new file mode 100644
index 0000000..1bbd12c
--- /dev/null
+++ b/api/v3/test/converters_test.py
@@ -0,0 +1,3254 @@
+# Copyright 2020 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file.
+"""Tests for converting internal protorpc to external protoc."""
+
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import copy
+import difflib
+import logging
+import unittest
+
+import mock
+from google.protobuf import field_mask_pb2
+from google.protobuf import timestamp_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import converters
+from api.v3.api_proto import feature_objects_pb2
+from api.v3.api_proto import issues_pb2
+from api.v3.api_proto import issue_objects_pb2
+from api.v3.api_proto import user_objects_pb2
+from api.v3.api_proto import project_objects_pb2
+from framework import authdata
+from framework import exceptions
+from framework import framework_constants
+from framework import framework_helpers
+from framework import monorailcontext
+from testing import fake
+from testing import testing_helpers
+from tracker import field_helpers
+from services import service_manager
+from proto import tracker_pb2
+from tracker import tracker_bizobj as tbo
+
+EXPLICIT_DERIVATION = issue_objects_pb2.Derivation.Value('EXPLICIT')
+RULE_DERIVATION = issue_objects_pb2.Derivation.Value('RULE')
+Choice = project_objects_pb2.FieldDef.EnumTypeSettings.Choice
+
+CURRENT_TIME = 12346.78
+
+
+class ConverterFunctionsTest(unittest.TestCase):
+
+ def setUp(self):
+ self.services = service_manager.Services(
+ issue=fake.IssueService(),
+ project=fake.ProjectService(),
+ usergroup=fake.UserGroupService(),
+ user=fake.UserService(),
+ config=fake.ConfigService(),
+ template=fake.TemplateService(),
+ features=fake.FeaturesService())
+ self.cnxn = fake.MonorailConnection()
+ self.mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn)
+ self.converter = converters.Converter(self.mc, self.services)
+ self.PAST_TIME = int(CURRENT_TIME - 1)
+ self.project_1 = self.services.project.TestAddProject(
+ 'proj', project_id=789)
+ self.project_2 = self.services.project.TestAddProject(
+ 'goose', project_id=788)
+ self.user_1 = self.services.user.TestAddUser('one@example.com', 111)
+ self.user_2 = self.services.user.TestAddUser('two@example.com', 222)
+ self.user_3 = self.services.user.TestAddUser('three@example.com', 333)
+ self.services.project.TestAddProjectMembers(
+ [self.user_1.user_id], self.project_1, 'CONTRIBUTOR_ROLE')
+
+ self.field_def_1_name = 'test_field_1'
+ self.field_def_1 = self._CreateFieldDef(
+ self.project_1.project_id,
+ self.field_def_1_name,
+ 'STR_TYPE',
+ admin_ids=[self.user_1.user_id],
+ is_required=True,
+ is_multivalued=True,
+ is_phase_field=True,
+ regex='abc')
+ self.field_def_2_name = 'test_field_2'
+ self.field_def_2 = self._CreateFieldDef(
+ self.project_1.project_id,
+ self.field_def_2_name,
+ 'INT_TYPE',
+ max_value=37,
+ is_niche=True)
+ self.field_def_3_name = 'days'
+ self.field_def_3 = self._CreateFieldDef(
+ self.project_1.project_id, self.field_def_3_name, 'ENUM_TYPE')
+ self.field_def_4_name = 'OS'
+ self.field_def_4 = self._CreateFieldDef(
+ self.project_1.project_id, self.field_def_4_name, 'ENUM_TYPE')
+ self.field_def_5_name = 'yellow'
+ self.field_def_5 = self._CreateFieldDef(
+ self.project_1.project_id, self.field_def_5_name, 'ENUM_TYPE')
+ self.field_def_7_name = 'redredred'
+ self.field_def_7 = self._CreateFieldDef(
+ self.project_1.project_id,
+ self.field_def_7_name,
+ 'ENUM_TYPE',
+ is_restricted_field=True,
+ editor_ids=[self.user_1.user_id])
+ self.field_def_8_name = 'dogandcat'
+ self.field_def_8 = self._CreateFieldDef(
+ self.project_1.project_id,
+ self.field_def_8_name,
+ 'USER_TYPE',
+ needs_member=True,
+ needs_perm='EDIT_PROJECT',
+ notify_on=tracker_pb2.NotifyTriggers.ANY_COMMENT)
+ self.field_def_9_name = 'catanddog'
+ self.field_def_9 = self._CreateFieldDef(
+ self.project_1.project_id,
+ self.field_def_9_name,
+ 'DATE_TYPE',
+ date_action_str='ping_owner_only')
+ self.field_def_10_name = 'url'
+ self.field_def_10 = self._CreateFieldDef(
+ self.project_1.project_id, self.field_def_10_name, 'URL_TYPE')
+ self.field_def_project2_name = 'lorem'
+ self.field_def_project2 = self._CreateFieldDef(
+ self.project_2.project_id, self.field_def_project2_name, 'ENUM_TYPE')
+ self.approval_def_1_name = 'approval_field_1'
+ self.approval_def_1_id = self._CreateFieldDef(
+ self.project_1.project_id,
+ self.approval_def_1_name,
+ 'APPROVAL_TYPE',
+ docstring='ad_1_docstring',
+ admin_ids=[self.user_1.user_id])
+ self.approval_def_1 = tracker_pb2.ApprovalDef(
+ approval_id=self.approval_def_1_id,
+ approver_ids=[self.user_2.user_id],
+ survey='approval_def_1 survey')
+ self.approval_def_2_name = 'approval_field_1'
+ self.approval_def_2_id = self._CreateFieldDef(
+ self.project_1.project_id,
+ self.approval_def_2_name,
+ 'APPROVAL_TYPE',
+ docstring='ad_2_docstring',
+ admin_ids=[self.user_1.user_id])
+ self.approval_def_2 = tracker_pb2.ApprovalDef(
+ approval_id=self.approval_def_2_id,
+ approver_ids=[self.user_2.user_id],
+ survey='approval_def_2 survey')
+ approval_defs = [self.approval_def_1, self.approval_def_2]
+ self.field_def_6_name = 'simonsays'
+ self.field_def_6 = self._CreateFieldDef(
+ self.project_1.project_id,
+ self.field_def_6_name,
+ 'STR_TYPE',
+ approval_id=self.approval_def_1_id)
+ self.dne_field_def_id = 999999
+ self.fv_1_value = u'some_string_field_value'
+ self.fv_1 = fake.MakeFieldValue(
+ field_id=self.field_def_1, str_value=self.fv_1_value, derived=False)
+ self.fv_1_derived = fake.MakeFieldValue(
+ field_id=self.field_def_1, str_value=self.fv_1_value, derived=True)
+ self.fv_6 = fake.MakeFieldValue(
+ field_id=self.field_def_6, str_value=u'touch-nose', derived=False)
+ self.phase_1_id = 123123
+ self.phase_1 = fake.MakePhase(self.phase_1_id, name='some phase name')
+ self.av_1 = fake.MakeApprovalValue(
+ self.approval_def_1_id,
+ setter_id=self.user_1.user_id,
+ set_on=self.PAST_TIME,
+ approver_ids=[self.user_2.user_id],
+ phase_id=self.phase_1_id)
+ self.av_2 = fake.MakeApprovalValue(
+ self.approval_def_1_id,
+ setter_id=self.user_1.user_id,
+ set_on=self.PAST_TIME,
+ approver_ids=[self.user_2.user_id])
+
+ self.issue_1 = fake.MakeTestIssue(
+ self.project_1.project_id,
+ 1,
+ 'sum',
+ 'New',
+ self.user_1.user_id,
+ cc_ids=[self.user_2.user_id],
+ derived_cc_ids=[self.user_3.user_id],
+ project_name=self.project_1.project_name,
+ star_count=1,
+ labels=['label-a', 'label-b', 'days-1'],
+ derived_owner_id=self.user_2.user_id,
+ derived_status='Fixed',
+ derived_labels=['label-derived', 'OS-mac', 'label-derived-2'],
+ component_ids=[1, 2],
+ merged_into_external='b/1',
+ derived_component_ids=[3, 4],
+ attachment_count=5,
+ field_values=[self.fv_1, self.fv_1_derived],
+ opened_timestamp=self.PAST_TIME,
+ modified_timestamp=self.PAST_TIME,
+ approval_values=[self.av_1],
+ phases=[self.phase_1])
+ self.issue_2 = fake.MakeTestIssue(
+ self.project_2.project_id,
+ 2,
+ 'sum2',
+ None,
+ None,
+ reporter_id=self.user_1.user_id,
+ project_name=self.project_2.project_name,
+ merged_into=self.issue_1.issue_id,
+ opened_timestamp=self.PAST_TIME,
+ modified_timestamp=self.PAST_TIME,
+ closed_timestamp=self.PAST_TIME,
+ derived_status='Fixed',
+ derived_owner_id=self.user_2.user_id,
+ is_spam=True)
+ self.services.issue.TestAddIssue(self.issue_1)
+ self.services.issue.TestAddIssue(self.issue_2)
+
+ self.template_0 = self.services.template.TestAddIssueTemplateDef(
+ 11110, self.project_1.project_id, 'template0')
+ self.template_1_label1_value = '2'
+ self.template_1_labels = [
+ 'pri-1', '{}-{}'.format(
+ self.field_def_3_name, self.template_1_label1_value)
+ ]
+ self.template_1 = self.services.template.TestAddIssueTemplateDef(
+ 11111,
+ self.project_1.project_id,
+ 'template1',
+ content='foobar',
+ summary='foo',
+ admin_ids=[self.user_2.user_id],
+ owner_id=self.user_1.user_id,
+ labels=self.template_1_labels,
+ component_ids=[654],
+ field_values=[self.fv_1],
+ approval_values=[self.av_1],
+ phases=[self.phase_1])
+ self.template_2 = self.services.template.TestAddIssueTemplateDef(
+ 11112,
+ self.project_1.project_id,
+ 'template2',
+ members_only=True,
+ owner_defaults_to_member=True)
+ self.template_3 = self.services.template.TestAddIssueTemplateDef(
+ 11113,
+ self.project_1.project_id,
+ 'template3',
+ field_values=[self.fv_1],
+ approval_values=[self.av_2],
+ )
+ self.dne_template = tracker_pb2.TemplateDef(
+ name='dne_template_name', template_id=11114)
+ self.labeldef_1 = tracker_pb2.LabelDef(
+ label='white-mountain',
+ label_docstring='test label doc string for white-mountain')
+ self.labeldef_2 = tracker_pb2.LabelDef(
+ label='yellow-submarine',
+ label_docstring='Submarine choice for yellow enum field')
+ self.labeldef_3 = tracker_pb2.LabelDef(
+ label='yellow-basket',
+ label_docstring='Basket choice for yellow enum field')
+ self.labeldef_4 = tracker_pb2.LabelDef(
+ label='yellow-tasket',
+ label_docstring='Deprecated tasket choice for yellow enum field',
+ deprecated=True)
+ self.labeldef_5 = tracker_pb2.LabelDef(
+ label='mont-blanc',
+ label_docstring='test label doc string for mont-blanc',
+ deprecated=True)
+ self.predefined_labels = [
+ self.labeldef_1, self.labeldef_2, self.labeldef_3, self.labeldef_4,
+ self.labeldef_5
+ ]
+ test_label_ids = {}
+ for index, ld in enumerate(self.predefined_labels):
+ test_label_ids[ld.label] = index
+ self.services.config.TestAddLabelsDict(test_label_ids)
+ self.status_1 = tracker_pb2.StatusDef(
+ status='New', means_open=True, status_docstring='status_1 docstring')
+ self.status_2 = tracker_pb2.StatusDef(
+ status='Duplicate',
+ means_open=False,
+ status_docstring='status_2 docstring')
+ self.status_3 = tracker_pb2.StatusDef(
+ status='Accepted',
+ means_open=True,
+ status_docstring='status_3_docstring')
+ self.status_4 = tracker_pb2.StatusDef(
+ status='Gibberish',
+ means_open=True,
+ status_docstring='status_4_docstring',
+ deprecated=True)
+ self.predefined_statuses = [
+ self.status_1, self.status_2, self.status_3, self.status_4
+ ]
+ self.component_def_1_path = 'foo'
+ self.component_def_1_id = self.services.config.CreateComponentDef(
+ self.cnxn, self.project_1.project_id, self.component_def_1_path,
+ 'cd1_docstring', False, [self.user_1.user_id], [self.user_2.user_id],
+ self.PAST_TIME, self.user_1.user_id, [0, 1, 2, 3, 4])
+ self.component_def_2_path = 'foo>bar'
+ self.component_def_2_id = self.services.config.CreateComponentDef(
+ self.cnxn, self.project_1.project_id, self.component_def_2_path,
+ 'cd2_docstring', True, [self.user_1.user_id], [self.user_2.user_id],
+ self.PAST_TIME, self.user_1.user_id, [])
+ self.services.config.UpdateConfig(
+ self.cnxn,
+ self.project_1,
+ statuses_offer_merge=[self.status_2.status],
+ excl_label_prefixes=['type', 'priority'],
+ default_template_for_developers=self.template_2.template_id,
+ default_template_for_users=self.template_1.template_id,
+ list_prefs=('ID Summary', 'ID', 'status', 'owner', 'owner:me'),
+ # UpdateConfig accepts tuples rather than protorpc *Defs
+ well_known_labels=[
+ (ld.label, ld.label_docstring, ld.deprecated)
+ for ld in self.predefined_labels
+ ],
+ approval_defs=[
+ (ad.approval_id, ad.approver_ids, ad.survey) for ad in approval_defs
+ ],
+ well_known_statuses=[
+ (sd.status, sd.status_docstring, sd.means_open, sd.deprecated)
+ for sd in self.predefined_statuses
+ ])
+ # base_query_id 2 equates to "is:open", defined in tracker_constants.
+ self.psq_1 = tracker_pb2.SavedQuery(
+ query_id=2, name='psq1 name', base_query_id=2, query='foo=bar')
+ self.psq_2 = tracker_pb2.SavedQuery(
+ query_id=3, name='psq2 name', query='fizz=buzz')
+ self.services.features.UpdateCannedQueries(
+ self.cnxn, self.project_1.project_id, [self.psq_1, self.psq_2])
+
+ def _CreateFieldDef(
+ self,
+ project_id,
+ field_name,
+ field_type_str,
+ docstring=None,
+ min_value=None,
+ max_value=None,
+ regex=None,
+ needs_member=None,
+ needs_perm=None,
+ grants_perm=None,
+ notify_on=None,
+ date_action_str=None,
+ admin_ids=None,
+ editor_ids=None,
+ is_required=False,
+ is_niche=False,
+ is_multivalued=False,
+ is_phase_field=False,
+ approval_id=None,
+ is_restricted_field=False):
+ """Calls CreateFieldDef with reasonable defaults, returns the ID."""
+ if admin_ids is None:
+ admin_ids = []
+ if editor_ids is None:
+ editor_ids = []
+ return self.services.config.CreateFieldDef(
+ self.cnxn,
+ project_id,
+ field_name,
+ field_type_str,
+ None,
+ None,
+ is_required,
+ is_niche,
+ is_multivalued,
+ min_value,
+ max_value,
+ regex,
+ needs_member,
+ needs_perm,
+ grants_perm,
+ notify_on,
+ date_action_str,
+ docstring,
+ admin_ids,
+ editor_ids,
+ is_phase_field=is_phase_field,
+ approval_id=approval_id,
+ is_restricted_field=is_restricted_field)
+
+ def _GetFieldDefById(self, project_id, fd_id):
+ config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+ return [fd for fd in config.field_defs if fd.field_id == fd_id][0]
+
+ def _GetApprovalDefById(self, project_id, ad_id):
+ config = self.services.config.GetProjectConfig(self.cnxn, project_id)
+ return [ad for ad in config.approval_defs if ad.approval_id == ad_id][0]
+
+ def testConvertHotlist(self):
+ """We can convert a Hotlist."""
+ hotlist = fake.Hotlist(
+ 'Hotlist-Name',
+ 240,
+ default_col_spec='chicken goose',
+ is_private=False,
+ owner_ids=[111],
+ editor_ids=[222, 333],
+ summary='Hotlist summary',
+ description='Hotlist Description')
+ expected_api_hotlist = feature_objects_pb2.Hotlist(
+ name='hotlists/240',
+ display_name=hotlist.name,
+ owner= 'users/111',
+ summary=hotlist.summary,
+ description=hotlist.description,
+ editors=['users/222', 'users/333'],
+ hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+ 'PUBLIC'),
+ default_columns=[
+ issue_objects_pb2.IssuesListColumn(column='chicken'),
+ issue_objects_pb2.IssuesListColumn(column='goose')
+ ])
+ self.converter.user_auth = authdata.AuthData.FromUser(
+ self.cnxn, self.user_1, self.services)
+ self.assertEqual(
+ expected_api_hotlist, self.converter.ConvertHotlist(hotlist))
+
+ def testConvertHotlist_DefaultValues(self):
+ """We can convert a Hotlist with some empty or default values."""
+ hotlist = fake.Hotlist(
+ 'Hotlist-Name',
+ 241,
+ is_private=True,
+ owner_ids=[111],
+ summary='Hotlist summary',
+ description='Hotlist Description',
+ default_col_spec='')
+ expected_api_hotlist = feature_objects_pb2.Hotlist(
+ name='hotlists/241',
+ display_name=hotlist.name,
+ owner='users/111',
+ summary=hotlist.summary,
+ description=hotlist.description,
+ hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value(
+ 'PRIVATE'))
+ self.converter.user_auth = authdata.AuthData.FromUser(
+ self.cnxn, self.user_1, self.services)
+ self.assertEqual(
+ expected_api_hotlist, self.converter.ConvertHotlist(hotlist))
+
+ def testConvertHotlists(self):
+ """We can convert several Hotlists."""
+ hotlists = [
+ fake.Hotlist(
+ 'Hotlist-Name',
+ 241,
+ owner_ids=[111],
+ summary='Hotlist summary',
+ description='Hotlist Description'),
+ fake.Hotlist(
+ 'Hotlist-Name',
+ 241,
+ owner_ids=[111],
+ summary='Hotlist summary',
+ description='Hotlist Description')
+ ]
+ self.assertEqual(2, len(self.converter.ConvertHotlists(hotlists)))
+
+ def testConvertHotlistItems(self):
+ """We can convert HotlistItems."""
+ hotlist_item_fields = [
+ (self.issue_1.issue_id, 21, 111, self.PAST_TIME, 'note2'),
+ (78900, 11, 222, self.PAST_TIME, 'note3'), # Does not exist.
+ (self.issue_2.issue_id, 1, 222, None, 'note1'),
+ ]
+ hotlist = fake.Hotlist(
+ 'Hotlist-Name', 241, hotlist_item_fields=hotlist_item_fields)
+ self.converter.user_auth = authdata.AuthData.FromUser(
+ self.cnxn, self.user_1, self.services)
+ api_items = self.converter.ConvertHotlistItems(
+ hotlist.hotlist_id, hotlist.items)
+ expected_create_time = timestamp_pb2.Timestamp()
+ expected_create_time.FromSeconds(self.PAST_TIME)
+ expected_items = [
+ feature_objects_pb2.HotlistItem(
+ name='hotlists/241/items/proj.1',
+ issue='projects/proj/issues/1',
+ rank=1,
+ adder= 'users/111',
+ create_time=expected_create_time,
+ note='note2'),
+ feature_objects_pb2.HotlistItem(
+ name='hotlists/241/items/goose.2',
+ issue='projects/goose/issues/2',
+ rank=0,
+ adder='users/222',
+ note='note1')
+ ]
+ self.assertEqual(api_items, expected_items)
+
+ def testConvertHotlistItems_Empty(self):
+ hotlist = fake.Hotlist('Hotlist-Name', 241)
+ self.converter.user_auth = authdata.AuthData.FromUser(
+ self.cnxn, self.user_1, self.services)
+ api_items = self.converter.ConvertHotlistItems(
+ hotlist.hotlist_id, hotlist.items)
+ self.assertEqual(api_items, [])
+
+ @mock.patch('tracker.attachment_helpers.SignAttachmentID')
+ def testConvertComments(self, mock_SignAttachmentID):
+ """We can convert comments."""
+ mock_SignAttachmentID.return_value = 2
+ attach = tracker_pb2.Attachment(
+ attachment_id=1,
+ mimetype='image/png',
+ filename='example.png',
+ filesize=12345)
+ deleted_attach = tracker_pb2.Attachment(
+ attachment_id=2,
+ mimetype='image/png',
+ filename='deleted_example.png',
+ filesize=67890,
+ deleted=True)
+ initial_comment = tracker_pb2.IssueComment(
+ project_id=self.issue_1.project_id,
+ issue_id=self.issue_1.issue_id,
+ user_id=self.issue_1.reporter_id,
+ timestamp=self.PAST_TIME,
+ content='initial description',
+ sequence=0,
+ is_description=True,
+ description_num='1',
+ attachments=[attach, deleted_attach])
+ deleted_comment = tracker_pb2.IssueComment(
+ project_id=self.issue_1.project_id,
+ issue_id=self.issue_1.issue_id,
+ timestamp=self.PAST_TIME,
+ deleted_by=self.issue_1.reporter_id,
+ sequence=1)
+ amendments = [
+ tracker_pb2.Amendment(
+ field=tracker_pb2.FieldID.SUMMARY, newvalue='new', oldvalue='old'),
+ tracker_pb2.Amendment(
+ field=tracker_pb2.FieldID.OWNER, added_user_ids=[111]),
+ tracker_pb2.Amendment(
+ field=tracker_pb2.FieldID.CC,
+ added_user_ids=[111],
+ removed_user_ids=[222]),
+ tracker_pb2.Amendment(
+ field=tracker_pb2.FieldID.CUSTOM,
+ custom_field_name='EstDays',
+ newvalue='12')
+ ]
+ amendments_comment = tracker_pb2.IssueComment(
+ project_id=self.issue_1.project_id,
+ issue_id=self.issue_1.issue_id,
+ user_id=self.issue_1.reporter_id,
+ timestamp=self.PAST_TIME,
+ content='some amendments',
+ sequence=2,
+ amendments=amendments,
+ importer_id=1, # Not used in conversion, so nothing to verify.
+ approval_id=self.approval_def_1_id)
+ inbound_spam_comment = tracker_pb2.IssueComment(
+ project_id=self.issue_1.project_id,
+ issue_id=self.issue_1.issue_id,
+ user_id=self.issue_1.reporter_id,
+ timestamp=self.PAST_TIME,
+ content='content',
+ sequence=3,
+ inbound_message='inbound message',
+ is_spam=True)
+ expected_0 = issue_objects_pb2.Comment(
+ name='projects/proj/issues/1/comments/0',
+ state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+ type=issue_objects_pb2.Comment.Type.Value('DESCRIPTION'),
+ content='initial description',
+ commenter='users/111',
+ create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ attachments=[
+ issue_objects_pb2.Comment.Attachment(
+ filename='example.png',
+ state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+ size=12345,
+ media_type='image/png',
+ thumbnail_uri='attachment?aid=1&signed_aid=2&inline=1&thumb=1',
+ view_uri='attachment?aid=1&signed_aid=2&inline=1',
+ download_uri='attachment?aid=1&signed_aid=2'),
+ issue_objects_pb2.Comment.Attachment(
+ filename='deleted_example.png',
+ state=issue_objects_pb2.IssueContentState.Value('DELETED'),
+ media_type='image/png')
+ ])
+ expected_1 = issue_objects_pb2.Comment(
+ name='projects/proj/issues/1/comments/1',
+ state=issue_objects_pb2.IssueContentState.Value('DELETED'),
+ type=issue_objects_pb2.Comment.Type.Value('COMMENT'),
+ create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME))
+ expected_2 = issue_objects_pb2.Comment(
+ name='projects/proj/issues/1/comments/2',
+ state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+ type=issue_objects_pb2.Comment.Type.Value('COMMENT'),
+ content='some amendments',
+ commenter='users/111',
+ create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ approval='projects/proj/approvalDefs/%d' % self.approval_def_1_id,
+ amendments=[
+ issue_objects_pb2.Comment.Amendment(
+ field_name='Summary', new_or_delta_value='new',
+ old_value='old'),
+ issue_objects_pb2.Comment.Amendment(
+ field_name='Owner', new_or_delta_value='o...@example.com'),
+ issue_objects_pb2.Comment.Amendment(
+ field_name='Cc',
+ new_or_delta_value='-t...@example.com o...@example.com'),
+ issue_objects_pb2.Comment.Amendment(
+ field_name='EstDays', new_or_delta_value='12')
+ ])
+ expected_3 = issue_objects_pb2.Comment(
+ name='projects/proj/issues/1/comments/3',
+ state=issue_objects_pb2.IssueContentState.Value('SPAM'),
+ type=issue_objects_pb2.Comment.Type.Value('COMMENT'),
+ content='content',
+ commenter='users/111',
+ create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ inbound_message='inbound message')
+
+ comments = [
+ initial_comment, deleted_comment, amendments_comment,
+ inbound_spam_comment
+ ]
+ actual = self.converter.ConvertComments(self.issue_1.issue_id, comments)
+ self.assertEqual(actual, [expected_0, expected_1, expected_2, expected_3])
+
+ def testConvertComments_Empty(self):
+ """We can convert an empty list of comments."""
+ self.assertEqual(
+ self.converter.ConvertComments(self.issue_1.issue_id, []), [])
+
+ def testConvertIssue(self):
+ """We can convert a single issue."""
+ self.assertEqual(self.converter.ConvertIssue(self.issue_1),
+ self.converter.ConvertIssues([self.issue_1])[0])
+
+ def testConvertIssues(self):
+ """We can convert Issues."""
+ blocked_on_1 = fake.MakeTestIssue(
+ self.project_1.project_id,
+ 3,
+ 'sum3',
+ 'New',
+ self.user_1.user_id,
+ issue_id=301,
+ project_name=self.project_1.project_name,
+ )
+ blocked_on_2 = fake.MakeTestIssue(
+ self.project_2.project_id,
+ 4,
+ 'sum4',
+ 'New',
+ self.user_1.user_id,
+ issue_id=401,
+ project_name=self.project_2.project_name,
+ )
+ blocking = fake.MakeTestIssue(
+ self.project_2.project_id,
+ 5,
+ 'sum5',
+ 'New',
+ self.user_1.user_id,
+ issue_id=501,
+ project_name=self.project_2.project_name,
+ )
+ self.services.issue.TestAddIssue(blocked_on_1)
+ self.services.issue.TestAddIssue(blocked_on_2)
+ self.services.issue.TestAddIssue(blocking)
+
+ # Reversing natural ordering to ensure order is respected.
+ self.issue_1.blocked_on_iids = [
+ blocked_on_2.issue_id, blocked_on_1.issue_id
+ ]
+ self.issue_1.dangling_blocked_on_refs = [
+ tracker_pb2.DanglingIssueRef(ext_issue_identifier='b/555'),
+ tracker_pb2.DanglingIssueRef(ext_issue_identifier='b/2')
+ ]
+ self.issue_1.blocking_iids = [blocking.issue_id]
+ self.issue_1.dangling_blocking_refs = [
+ tracker_pb2.DanglingIssueRef(ext_issue_identifier='b/3')
+ ]
+
+ issues = [self.issue_1, self.issue_2]
+ expected_1 = issue_objects_pb2.Issue(
+ name='projects/proj/issues/1',
+ summary='sum',
+ state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+ status=issue_objects_pb2.Issue.StatusValue(
+ derivation=EXPLICIT_DERIVATION, status='New'),
+ reporter='users/111',
+ owner=issue_objects_pb2.Issue.UserValue(
+ derivation=EXPLICIT_DERIVATION, user='users/111'),
+ cc_users=[
+ issue_objects_pb2.Issue.UserValue(
+ derivation=EXPLICIT_DERIVATION, user='users/222'),
+ issue_objects_pb2.Issue.UserValue(
+ derivation=RULE_DERIVATION, user='users/333')
+ ],
+ labels=[
+ issue_objects_pb2.Issue.LabelValue(
+ derivation=EXPLICIT_DERIVATION, label='label-a'),
+ issue_objects_pb2.Issue.LabelValue(
+ derivation=EXPLICIT_DERIVATION, label='label-b'),
+ issue_objects_pb2.Issue.LabelValue(
+ derivation=RULE_DERIVATION, label='label-derived'),
+ issue_objects_pb2.Issue.LabelValue(
+ derivation=RULE_DERIVATION, label='label-derived-2')
+ ],
+ components=[
+ issue_objects_pb2.Issue.ComponentValue(
+ derivation=EXPLICIT_DERIVATION,
+ component='projects/proj/componentDefs/1'),
+ issue_objects_pb2.Issue.ComponentValue(
+ derivation=EXPLICIT_DERIVATION,
+ component='projects/proj/componentDefs/2'),
+ issue_objects_pb2.Issue.ComponentValue(
+ derivation=RULE_DERIVATION,
+ component='projects/proj/componentDefs/3'),
+ issue_objects_pb2.Issue.ComponentValue(
+ derivation=RULE_DERIVATION,
+ component='projects/proj/componentDefs/4'),
+ ],
+ field_values=[
+ issue_objects_pb2.FieldValue(
+ derivation=EXPLICIT_DERIVATION,
+ field='projects/proj/fieldDefs/%d' % self.field_def_1,
+ value=self.fv_1_value,
+ ),
+ issue_objects_pb2.FieldValue(
+ derivation=RULE_DERIVATION,
+ field='projects/proj/fieldDefs/%d' % self.field_def_1,
+ value=self.fv_1_value,
+ ),
+ issue_objects_pb2.FieldValue(
+ derivation=EXPLICIT_DERIVATION,
+ field='projects/proj/fieldDefs/%d' % self.field_def_3,
+ value='1',
+ ),
+ issue_objects_pb2.FieldValue(
+ derivation=RULE_DERIVATION,
+ field='projects/proj/fieldDefs/%d' % self.field_def_4,
+ value='mac',
+ )
+ ],
+ merged_into_issue_ref=issue_objects_pb2.IssueRef(ext_identifier='b/1'),
+ blocked_on_issue_refs=[
+ issue_objects_pb2.IssueRef(issue='projects/goose/issues/4'),
+ issue_objects_pb2.IssueRef(issue='projects/proj/issues/3'),
+ issue_objects_pb2.IssueRef(ext_identifier='b/555'),
+ issue_objects_pb2.IssueRef(ext_identifier='b/2')
+ ],
+ blocking_issue_refs=[
+ issue_objects_pb2.IssueRef(issue='projects/goose/issues/5'),
+ issue_objects_pb2.IssueRef(ext_identifier='b/3')
+ ],
+ create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ star_count=1,
+ attachment_count=5,
+ phases=[self.phase_1.name])
+ expected_2 = issue_objects_pb2.Issue(
+ name='projects/goose/issues/2',
+ summary='sum2',
+ state=issue_objects_pb2.IssueContentState.Value('SPAM'),
+ status=issue_objects_pb2.Issue.StatusValue(
+ derivation=RULE_DERIVATION, status='Fixed'),
+ reporter='users/111',
+ owner=issue_objects_pb2.Issue.UserValue(
+ derivation=RULE_DERIVATION, user='users/222'),
+ merged_into_issue_ref=issue_objects_pb2.IssueRef(
+ issue='projects/proj/issues/1'),
+ create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ close_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME))
+ self.assertEqual(
+ self.converter.ConvertIssues(issues), [expected_1, expected_2])
+
+ def testConvertIssues_Empty(self):
+ """ConvertIssues works with no issues passed in."""
+ self.assertEqual(self.converter.ConvertIssues([]), [])
+
+ def testConvertIssues_NegativeAttachmentCount(self):
+ """Negative attachment counts are not set on issues."""
+ issue = fake.MakeTestIssue(
+ self.project_1.project_id,
+ 3,
+ 'sum',
+ 'New',
+ owner_id=None,
+ reporter_id=111,
+ attachment_count=-10,
+ project_name=self.project_1.project_name,
+ opened_timestamp=self.PAST_TIME,
+ modified_timestamp=self.PAST_TIME)
+ self.services.issue.TestAddIssue(issue)
+ expected_issue = issue_objects_pb2.Issue(
+ name='projects/proj/issues/3',
+ state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+ summary='sum',
+ status=issue_objects_pb2.Issue.StatusValue(
+ derivation=EXPLICIT_DERIVATION, status='New'),
+ reporter='users/111',
+ create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ )
+ self.assertEqual(self.converter.ConvertIssues([issue]), [expected_issue])
+
+ def testConvertIssues_FilterApprovalFV(self):
+ issue = fake.MakeTestIssue(
+ self.project_1.project_id,
+ 3,
+ 'sum',
+ 'New',
+ owner_id=None,
+ reporter_id=111,
+ attachment_count=-10,
+ project_name=self.project_1.project_name,
+ opened_timestamp=self.PAST_TIME,
+ modified_timestamp=self.PAST_TIME,
+ field_values=[self.fv_1, self.fv_6])
+ self.services.issue.TestAddIssue(issue)
+ actual = self.converter.ConvertIssues([issue])[0]
+
+ expected_fv = issue_objects_pb2.FieldValue(
+ derivation=EXPLICIT_DERIVATION,
+ field='projects/proj/fieldDefs/%d' % self.field_def_1,
+ value=self.fv_1_value,
+ )
+ self.assertEqual(len(actual.field_values), 1)
+ self.assertEqual(actual.field_values[0], expected_fv)
+
+ def testConvertUser(self):
+ """We can convert a single User."""
+ self.user_1.vacation_message = 'non-empty-string'
+ self.converter.user_auth = authdata.AuthData.FromUser(
+ self.cnxn, self.user_1, self.services)
+
+ expected_user = user_objects_pb2.User(
+ name='users/111',
+ display_name='one@example.com',
+ email='one@example.com',
+ availability_message='non-empty-string')
+ self.assertEqual(self.converter.ConvertUser(self.user_1), expected_user)
+
+
+ def testConvertUsers(self):
+ user_deleted = self.services.user.TestAddUser(
+ '', framework_constants.DELETED_USER_ID)
+ self.user_1.vacation_message = 'non-empty-string'
+ user_ids = [self.user_1.user_id, user_deleted.user_id]
+ self.converter.user_auth = authdata.AuthData.FromUser(
+ self.cnxn, self.user_1, self.services)
+
+ expected_user_dict = {
+ self.user_1.user_id:
+ user_objects_pb2.User(
+ name='users/111',
+ display_name='one@example.com',
+ email='one@example.com',
+ availability_message='non-empty-string'),
+ user_deleted.user_id:
+ user_objects_pb2.User(
+ name='users/1',
+ display_name=framework_constants.DELETED_USER_NAME,
+ email='',
+ availability_message='User never visited'),
+ }
+ self.assertEqual(self.converter.ConvertUsers(user_ids), expected_user_dict)
+
+ def testConvertProjectStars(self):
+ expected_stars = [
+ user_objects_pb2.ProjectStar(name='users/111/projectStars/proj'),
+ user_objects_pb2.ProjectStar(name='users/111/projectStars/goose')
+ ]
+ self.assertEqual(
+ self.converter.ConvertProjectStars(
+ self.user_1.user_id, [self.project_1, self.project_2]),
+ expected_stars)
+
+ def _Issue(self, project_id, local_id):
+ issue = tracker_pb2.Issue(owner_id=0)
+ issue.project_name = 'proj-%d' % project_id
+ issue.project_id = project_id
+ issue.local_id = local_id
+ issue.issue_id = project_id * 100 + local_id
+ return issue
+
+ def testIngestAttachmentUploads(self):
+ up_1 = issues_pb2.AttachmentUpload(
+ filename='clown.gif', content='iTs prOUnOuNcED JIF')
+ up_2 = issues_pb2.AttachmentUpload(
+ filename='mowgli', content='cutest dog')
+
+ ingested = self.converter.IngestAttachmentUploads([up_1, up_2])
+ expected = [framework_helpers.AttachmentUpload(
+ 'clown.gif', 'iTs prOUnOuNcED JIF', 'image/gif'),
+ framework_helpers.AttachmentUpload(
+ 'mowgli', 'cutest dog', 'text/plain')]
+ self.assertEqual(ingested, expected)
+
+ def testtIngestAttachmentUploads_Invalid(self):
+ up_1 = issues_pb2.AttachmentUpload(filename='clown.gif')
+ up_2 = issues_pb2.AttachmentUpload(content='cutest dog')
+
+ with self.assertRaisesRegexp(
+ exceptions.InputException, 'Uploaded .+\nUploaded .+'):
+ self.converter.IngestAttachmentUploads([up_1, up_2])
+
+ def testIngestIssueDeltas(self):
+ # Set up.
+ self.services.project.TestAddProject('proj-780', project_id=780)
+ config = fake.MakeTestConfig(780, [], [])
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ issue_1 = self._Issue(780, 1)
+ self.services.issue.TestAddIssue(issue_1)
+ issue_2 = self._Issue(780, 2)
+ self.services.issue.TestAddIssue(issue_2)
+ comp_1 = fake.MakeTestComponentDef(780, 1)
+ comp_2 = fake.MakeTestComponentDef(780, 2)
+ fd_str = fake.MakeTestFieldDef(1, 780, tracker_pb2.FieldTypes.STR_TYPE)
+ fd_enum = fake.MakeTestFieldDef(
+ 2, 780, tracker_pb2.FieldTypes.ENUM_TYPE, field_name='Kingdom')
+ config = fake.MakeTestConfig(780, [], [])
+ config.component_defs = [comp_1, comp_2]
+ config.field_defs = [fd_str, fd_enum]
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ # Issue and delta that changes all things.
+ api_issue_all = issue_objects_pb2.Issue(
+ name='projects/proj-780/issues/1',
+ status=issue_objects_pb2.Issue.StatusValue(status='Fixed'),
+ owner=issue_objects_pb2.Issue.UserValue(user='users/111'),
+ summary='honk honk.',
+ cc_users=[issue_objects_pb2.Issue.UserValue(user='users/222')],
+ components=[
+ issue_objects_pb2.Issue.ComponentValue(
+ component='projects/proj-780/componentDefs/1')
+ ],
+ field_values=[
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/1', value='chicken'),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/2', value='come')
+ ],
+ labels=[issue_objects_pb2.Issue.LabelValue(label='ready')])
+ mask_all = field_mask_pb2.FieldMask(
+ paths=[
+ 'status', 'owner', 'summary', 'cc_users', 'labels', 'components',
+ 'field_values'
+ ])
+ api_delta_all = issues_pb2.IssueDelta(
+ issue=api_issue_all,
+ update_mask=mask_all,
+ ccs_remove=['users/333'],
+ components_remove=['projects/proj-780/componentDefs/2'],
+ field_vals_remove=[
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/1', value='rooster'),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/2', value='leave')
+ ],
+ labels_remove=['not-ready'])
+ exp_fvs_add = [
+ field_helpers.ParseOneFieldValue(
+ self.cnxn, self.services.user, fd_str, 'chicken')
+ ]
+ exp_fvs_remove = [
+ field_helpers.ParseOneFieldValue(
+ self.cnxn, self.services.user, fd_str, 'rooster')
+ ]
+ expected_delta_all = tracker_pb2.IssueDelta(
+ status='Fixed',
+ owner_id=111,
+ summary='honk honk.',
+ cc_ids_add=[222],
+ cc_ids_remove=[333],
+ comp_ids_add=[1],
+ comp_ids_remove=[2],
+ field_vals_add=exp_fvs_add,
+ field_vals_remove=exp_fvs_remove,
+ labels_add=['ready', 'Kingdom-come'],
+ labels_remove=['not-ready', 'Kingdom-leave'])
+
+ api_deltas = [api_delta_all]
+
+ # Issue with all fields, but an empty mask.
+ api_issue_all_masked = issue_objects_pb2.Issue(
+ name='projects/proj-780/issues/2',
+ status=issue_objects_pb2.Issue.StatusValue(status='Fixed'),
+ owner=issue_objects_pb2.Issue.UserValue(user='users/111'),
+ summary='honk honk.',
+ cc_users=[issue_objects_pb2.Issue.UserValue(user='users/222')],
+ components=[
+ issue_objects_pb2.Issue.ComponentValue(
+ component='projects/proj-780/componentDefs/1')
+ ],
+ field_values=[
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/1', value='chicken'),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/2', value='come')
+ ],
+ labels=[issue_objects_pb2.Issue.LabelValue(label='ready')])
+ api_delta_all_masked = issues_pb2.IssueDelta(
+ issue=api_issue_all_masked,
+ update_mask=field_mask_pb2.FieldMask(paths=[]),
+ ccs_remove=['users/333'],
+ components_remove=['projects/proj-780/componentDefs/2'],
+ field_vals_remove=[
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/1', value='rooster'),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/2', value='leave')
+ ],
+ labels_remove=['not-ready'])
+ expected_delta_all_masked = tracker_pb2.IssueDelta(
+ cc_ids_remove=[333],
+ comp_ids_remove=[2],
+ labels_remove=['not-ready', 'Kingdom-leave'],
+ field_vals_remove=exp_fvs_remove)
+
+ api_deltas.append(api_delta_all_masked)
+
+ actual = self.converter.IngestIssueDeltas(api_deltas)
+ expected = [(78001, expected_delta_all), (78002, expected_delta_all_masked)]
+ self.assertEqual(actual, expected)
+
+ def testIngestIssueDeltas_IssueRefs(self):
+ # Set up.
+ self.services.project.TestAddProject('proj-780', project_id=780)
+ issue = self._Issue(780, 1)
+ self.services.issue.TestAddIssue(issue)
+
+ bo_add = self._Issue(780, 2)
+ self.services.issue.TestAddIssue(bo_add)
+
+ b_add = self._Issue(780, 3)
+ self.services.issue.TestAddIssue(b_add)
+
+ bo_remove = self._Issue(780, 4)
+ self.services.issue.TestAddIssue(bo_remove)
+
+ b_remove = self._Issue(780, 5)
+ self.services.issue.TestAddIssue(b_remove)
+
+ # merge_remove tested in testIngestIssueDeltas_RemoveNonRepeated
+ merge_add = self._Issue(780, 6)
+ self.services.issue.TestAddIssue(merge_add)
+
+ api_issue = issue_objects_pb2.Issue(
+ name='projects/proj-780/issues/1',
+ blocked_on_issue_refs=[
+ issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/2'),
+ issue_objects_pb2.IssueRef(ext_identifier='b/1')
+ ],
+ blocking_issue_refs=[
+ issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/3'),
+ issue_objects_pb2.IssueRef(ext_identifier='b/2')
+ ],
+ merged_into_issue_ref=issue_objects_pb2.IssueRef(
+ issue='projects/proj-780/issues/6'))
+
+ api_delta = issues_pb2.IssueDelta(
+ issue=api_issue,
+ update_mask=field_mask_pb2.FieldMask(
+ paths=[
+ 'blocked_on_issue_refs', 'blocking_issue_refs',
+ 'merged_into_issue_ref'
+ ]),
+ blocked_on_issues_remove=[
+ issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/4'),
+ issue_objects_pb2.IssueRef(ext_identifier='b/3')
+ ],
+ blocking_issues_remove=[
+ issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/5'),
+ issue_objects_pb2.IssueRef(ext_identifier='b/4')
+ ])
+
+ expected_delta = tracker_pb2.IssueDelta(
+ blocked_on_add=[bo_add.issue_id],
+ blocked_on_remove=[bo_remove.issue_id],
+ blocking_add=[b_add.issue_id],
+ blocking_remove=[b_remove.issue_id],
+ ext_blocked_on_add=['b/1'],
+ ext_blocked_on_remove=['b/3'],
+ ext_blocking_add=['b/2'],
+ ext_blocking_remove=['b/4'],
+ merged_into=merge_add.issue_id)
+
+ # Test adding an external merged_into_issue.
+ api_issue_ext_merged = issue_objects_pb2.Issue(
+ name='projects/proj-780/issues/2',
+ merged_into_issue_ref=issue_objects_pb2.IssueRef(ext_identifier='b/1'))
+ api_delta_ext_merged = issues_pb2.IssueDelta(
+ issue=api_issue_ext_merged,
+ update_mask=field_mask_pb2.FieldMask(paths=['merged_into_issue_ref']))
+ expected_delta_ext_merged = tracker_pb2.IssueDelta(
+ merged_into_external='b/1')
+
+ # Test issue with empty mask.
+ issue_all_masked = self._Issue(780, 11)
+ self.services.issue.TestAddIssue(issue_all_masked)
+
+ api_issue_all_masked = copy.deepcopy(api_issue)
+ api_issue_all_masked.name = 'projects/proj-780/issues/11'
+ api_delta_all_masked = issues_pb2.IssueDelta(
+ issue=api_issue_all_masked, update_mask=field_mask_pb2.FieldMask())
+ expected_all_masked_delta = tracker_pb2.IssueDelta()
+
+ # Check results.
+ actual = self.converter.IngestIssueDeltas(
+ [api_delta, api_delta_ext_merged, api_delta_all_masked])
+
+ expected = [
+ (78001, expected_delta), (78002, expected_delta_ext_merged),
+ (78011, expected_all_masked_delta)
+ ]
+ self.assertEqual(actual, expected)
+
+ def testIngestIssueDeltas_OwnerAndOwnerDotUser(self):
+ # Set up.
+ self.services.project.TestAddProject('proj-780', project_id=780)
+ issue = self._Issue(780, 1)
+ self.services.issue.TestAddIssue(issue)
+
+ api_issue = issue_objects_pb2.Issue(
+ name='projects/proj-780/issues/1',
+ owner=issue_objects_pb2.Issue.UserValue(user='users/111')
+ )
+
+ # Expect ingest to work when update_mask has just 'owner'.
+ api_delta = issues_pb2.IssueDelta(
+ issue=api_issue,
+ update_mask=field_mask_pb2.FieldMask(paths=['owner'])
+ )
+ expected_delta = tracker_pb2.IssueDelta(owner_id=111)
+ expected = [(78001, expected_delta)]
+ actual = self.converter.IngestIssueDeltas([api_delta])
+ self.assertEqual(actual, expected)
+
+ # Expect ingest to also work when update_mask uses 'owner.user' instead.
+ api_delta = issues_pb2.IssueDelta(
+ issue=api_issue,
+ update_mask=field_mask_pb2.FieldMask(paths=['owner.user'])
+ )
+ actual = self.converter.IngestIssueDeltas([api_delta])
+ self.assertEqual(actual, expected)
+
+ def testIngestIssueDeltas_StatusAndStatusDotStatus(self):
+ # Set up.
+ self.services.project.TestAddProject('proj-780', project_id=780)
+ issue = self._Issue(780, 1)
+ self.services.issue.TestAddIssue(issue)
+
+ api_issue = issue_objects_pb2.Issue(
+ name='projects/proj-780/issues/1',
+ owner=issue_objects_pb2.Issue.UserValue(user='users/111'),
+ status=issue_objects_pb2.Issue.StatusValue(status='New')
+ )
+
+ # Expect ingest to work when update_mask has just 'status'.
+ api_delta = issues_pb2.IssueDelta(
+ issue=api_issue,
+ update_mask=field_mask_pb2.FieldMask(paths=['status'])
+ )
+ expected_delta = tracker_pb2.IssueDelta(status='New')
+ expected = [(78001, expected_delta)]
+ actual = self.converter.IngestIssueDeltas([api_delta])
+ self.assertEqual(actual, expected)
+
+ # Expect ingest to also work when update_mask uses 'status.status' instead.
+ api_delta = issues_pb2.IssueDelta(
+ issue=api_issue,
+ update_mask=field_mask_pb2.FieldMask(paths=['status.status'])
+ )
+ actual = self.converter.IngestIssueDeltas([api_delta])
+ self.assertEqual(actual, expected)
+
+ def testIngestIssueDeltas_RemoveNonRepeated(self):
+ # Set up.
+ self.services.project.TestAddProject('proj-780', project_id=780)
+ issue_1 = self._Issue(780, 1)
+ self.services.issue.TestAddIssue(issue_1)
+ issue_2 = self._Issue(780, 2)
+ self.services.issue.TestAddIssue(issue_2)
+
+ # Check we can remove fields without specifying them in the
+ # issue, as long as they're specified in the FieldMask.
+ api_issue = issue_objects_pb2.Issue(
+ name='projects/proj-780/issues/1')
+ api_delta = issues_pb2.IssueDelta(
+ issue=api_issue,
+ update_mask=field_mask_pb2.FieldMask(
+ paths=[
+ 'owner.user', 'status.status', 'summary',
+ 'merged_into_issue_ref.issue'
+ ]))
+
+ # Check thet setting fields to '' result in same behavior as not
+ # explicitly setting the values at all.
+ api_issue_set = issue_objects_pb2.Issue(
+ name='projects/proj-780/issues/2',
+ summary='',
+ status=issue_objects_pb2.Issue.StatusValue(status=''),
+ owner=issue_objects_pb2.Issue.UserValue(user=''),
+ merged_into_issue_ref=issue_objects_pb2.IssueRef(issue=''))
+ api_delta_set = issues_pb2.IssueDelta(
+ issue=api_issue_set,
+ update_mask=field_mask_pb2.FieldMask(
+ paths=[
+ 'owner.user', 'status.status', 'summary',
+ 'merged_into_issue_ref.issue'
+ ]))
+
+ expected_delta = tracker_pb2.IssueDelta(
+ owner_id=framework_constants.NO_USER_SPECIFIED,
+ status='',
+ summary='',
+ merged_into=0)
+
+ actual = self.converter.IngestIssueDeltas([api_delta, api_delta_set])
+ expected = [(78001, expected_delta), (78002, expected_delta)]
+ self.assertEqual(actual, expected)
+
+ def testIngestIssueDeltas_InvalidMask(self):
+ self.services.project.TestAddProject('proj-780', project_id=780)
+ issue_1 = self._Issue(780, 1)
+ self.services.issue.TestAddIssue(issue_1)
+ issue_2 = self._Issue(780, 2)
+ self.services.issue.TestAddIssue(issue_2)
+ issue_3 = self._Issue(780, 3)
+ self.services.issue.TestAddIssue(issue_3)
+ api_deltas = []
+ err_msgs = []
+
+ api_issue_1 = issue_objects_pb2.Issue(name='projects/proj-780/issues/1')
+ api_delta_1 = issues_pb2.IssueDelta(issue=api_issue_1)
+ api_deltas.append(api_delta_1)
+ err_msgs.append(
+ '`update_mask` must be set for projects/proj-780/issues/1 delta.')
+
+ api_issue_2 = issue_objects_pb2.Issue(name='projects/proj-780/issues/2')
+ api_delta_2 = issues_pb2.IssueDelta(
+ issue=api_issue_2,
+ update_mask=field_mask_pb2.FieldMask()) # Empty but set is fine.
+ api_deltas.append(api_delta_2)
+
+ api_issue_3 = issue_objects_pb2.Issue(name='projects/proj-780/issues/3')
+ api_delta_3 = issues_pb2.IssueDelta(
+ issue=api_issue_3,
+ update_mask=field_mask_pb2.FieldMask(paths=['chicken']))
+ api_deltas.append(api_delta_3)
+ err_msgs.append(
+ 'Invalid `update_mask` for projects/proj-780/issues/3 delta.')
+
+ with self.assertRaisesRegexp(exceptions.InputException,
+ '\n'.join(err_msgs)):
+ self.converter.IngestIssueDeltas(api_deltas)
+
+ def testIngestIssueDeltas_OutputOnlyIgnored(self):
+ # Set up.
+ self.services.project.TestAddProject('proj-780', project_id=780)
+ issue_1 = self._Issue(780, 1)
+ self.services.issue.TestAddIssue(issue_1)
+ comp_1 = fake.MakeTestComponentDef(780, 1)
+ fd_str = fake.MakeTestFieldDef(1, 780, tracker_pb2.FieldTypes.STR_TYPE)
+ config = fake.MakeTestConfig(780, [], [])
+ config.component_defs = [comp_1]
+ config.field_defs = [fd_str]
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ api_issue = issue_objects_pb2.Issue(
+ name='projects/proj-780/issues/1',
+ owner=issue_objects_pb2.Issue.UserValue(
+ user='users/111',
+ derivation=issue_objects_pb2.Derivation.Value('RULE')),
+ status=issue_objects_pb2.Issue.StatusValue(
+ status='KingdomCome',
+ derivation=issue_objects_pb2.Derivation.Value('RULE')),
+ state=issue_objects_pb2.IssueContentState.Value('DELETED'),
+ reporter='users/222',
+ cc_users=[
+ issue_objects_pb2.Issue.UserValue(
+ user='users/333',
+ derivation=issue_objects_pb2.Derivation.Value('RULE'))
+ ],
+ labels=[
+ issue_objects_pb2.Issue.LabelValue(
+ label='wikipedia-sections',
+ derivation=issue_objects_pb2.Derivation.Value('RULE'))
+ ],
+ components=[
+ issue_objects_pb2.Issue.ComponentValue(
+ component='projects/proj-780/componentDefs/1',
+ derivation=issue_objects_pb2.Derivation.Value('RULE'))
+ ],
+ field_values=[
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/1',
+ value='bugs',
+ derivation=issue_objects_pb2.Derivation.Value('RULE'))
+ ],
+ create_time=timestamp_pb2.Timestamp(seconds=4044242),
+ close_time=timestamp_pb2.Timestamp(seconds=4044242),
+ modify_time=timestamp_pb2.Timestamp(seconds=4044242),
+ component_modify_time=timestamp_pb2.Timestamp(seconds=4044242),
+ status_modify_time=timestamp_pb2.Timestamp(seconds=4044242),
+ owner_modify_time=timestamp_pb2.Timestamp(seconds=4044242),
+ attachment_count=4,
+ star_count=2,
+ phases=['EarlyLife', 'CrimesBegin', 'CrimesContinue'])
+ paths_with_output_only = [
+ 'owner', 'status', 'state', 'reporter', 'cc_users', 'labels',
+ 'components', 'field_values', 'create_time', 'close_time',
+ 'modify_time', 'component_modify_time', 'status_modify_time',
+ 'owner_modify_time', 'attachment_count', 'star_count', 'phases']
+ api_delta = issues_pb2.IssueDelta(
+ issue=api_issue,
+ update_mask=field_mask_pb2.FieldMask(paths=paths_with_output_only))
+
+ expected_delta = tracker_pb2.IssueDelta(
+ # We ignore all Issue.*Value.derivation OUTPUT_ONLY fields.
+ owner_id=111,
+ status='KingdomCome',
+ cc_ids_add=[333],
+ labels_add=['wikipedia-sections'],
+ comp_ids_add=[1],
+ field_vals_add=[
+ field_helpers.ParseOneFieldValue(
+ self.cnxn, self.services.user, fd_str, 'bugs')
+ ])
+
+ actual = self.converter.IngestIssueDeltas([api_delta])
+ expected = [(78001, expected_delta)]
+ self.assertEqual(actual, expected)
+
+
+ def testIngestIssueDeltas_Empty(self):
+ actual = self.converter.IngestIssueDeltas([])
+ self.assertEqual(actual, [])
+
+ def testIngestIssueDeltas_InvalidValuesForFields(self):
+ # Set up.
+ self.services.project.TestAddProject('proj-780', project_id=780)
+ issue_1 = self._Issue(780, 1)
+ self.services.issue.TestAddIssue(issue_1)
+ fd_int = fake.MakeTestFieldDef(1, 780, tracker_pb2.FieldTypes.INT_TYPE)
+ fd_date = fake.MakeTestFieldDef(2, 780, tracker_pb2.FieldTypes.DATE_TYPE)
+ config = fake.MakeTestConfig(780, [], [])
+ config.field_defs = [fd_int, fd_date]
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ api_issue = issue_objects_pb2.Issue(
+ name='projects/proj-780/issues/1',
+ field_values=[
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/1',
+ value='NotAnInt',
+ derivation=issue_objects_pb2.Derivation.Value('RULE')),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj-780/fieldDefs/2',
+ value='NoDate',
+ derivation=issue_objects_pb2.Derivation.Value('EXPLICIT')),
+ ],
+ )
+ api_delta = issues_pb2.IssueDelta(
+ issue=api_issue,
+ update_mask=field_mask_pb2.FieldMask(paths=['field_values']))
+ error_messages = [
+ r'Could not ingest value \(NotAnInt\) for FieldDef \(projects/proj-780/'
+ r'fieldDefs/1\): Could not parse NotAnInt',
+ r'Could not ingest value \(NoDate\) for FieldDef \(projects/proj-780/fi'
+ r'eldDefs/2\): Could not parse NoDate',
+ ]
+ error_messages_re = '\n'.join(error_messages)
+ with self.assertRaisesRegexp(exceptions.InputException, error_messages_re):
+ self.converter.IngestIssueDeltas([api_delta])
+
+ @mock.patch('time.time', mock.MagicMock(return_value=CURRENT_TIME))
+ def testIngestApprovalDeltas(self):
+ mask = field_mask_pb2.FieldMask(
+ paths=['approvers', 'status', 'setter', 'phase', 'set_time'])
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(
+ name=av_name,
+ status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'),
+ approvers=['users/222', 'users/333'],
+ approval_def='ignored',
+ set_time=timestamp_pb2.Timestamp(), # Ignored.
+ setter='ignored',
+ phase='ignored'),
+ update_mask=mask,
+ approvers_remove=['users/222'])
+ actual = self.converter.IngestApprovalDeltas(
+ [approval_delta], self.user_1.user_id)
+ expected_delta = tracker_pb2.ApprovalDelta(
+ status=tracker_pb2.ApprovalStatus.NA,
+ setter_id=self.user_1.user_id,
+ set_on=int(CURRENT_TIME),
+ approver_ids_add=[222, 333],
+ approver_ids_remove=[222],
+ )
+ expected_delta_specifications = [
+ (self.issue_1.issue_id, self.approval_def_1_id, expected_delta)
+ ]
+ self.assertEqual(actual, expected_delta_specifications)
+
+ def testIngestApprovalDeltas_EmptyMask(self):
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ # field_def_6 belongs to approval_def_1.
+ approval_fv = issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_6, value=u'x')
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(
+ name=av_name,
+ status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'),
+ approvers=['users/222', 'users/333'],
+ approval_def='ignored',
+ field_values=[approval_fv],
+ set_time=timestamp_pb2.Timestamp(), # Ignored.
+ setter='ignored',
+ phase='ignored'),
+ update_mask=field_mask_pb2.FieldMask(),
+ approvers_remove=['users/222'])
+ actual = self.converter.IngestApprovalDeltas(
+ [approval_delta], self.user_1.user_id)
+ expected_delta = tracker_pb2.ApprovalDelta(approver_ids_remove=[222])
+ expected_delta_specifications = [
+ (self.issue_1.issue_id, self.approval_def_1_id, expected_delta)
+ ]
+ self.assertEqual(actual, expected_delta_specifications)
+
+ def testIngestApprovalDeltas_InvalidMask(self):
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(name=av_name),
+ update_mask=field_mask_pb2.FieldMask(paths=['chicken']))
+ expected_err = 'Invalid `update_mask` for %s delta' % av_name
+ with self.assertRaisesRegexp(exceptions.InputException, expected_err):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_FilterFieldValues(self):
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+
+ # field_def_6 belongs to approval_def_1, should be ingested.
+ approval_fv = issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_6,
+ value=u'touch-nose',
+ derivation=RULE_DERIVATION, # Ignored.
+ )
+ # An enum field belonging to approval_def_1, should be ingested.
+ approval_enum_field_id = self._CreateFieldDef(
+ self.project_1.project_id,
+ 'approval2field',
+ 'ENUM_TYPE',
+ approval_id=self.approval_def_1_id)
+ approval_enum_fv = issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % approval_enum_field_id,
+ value=u'enumval')
+ # Create field value that points to different approval, should raise error.
+ approval_2_fv = issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_2, value=u'error')
+ av = issue_objects_pb2.ApprovalValue(
+ name=av_name, field_values=[approval_fv])
+ approval_delta = issues_pb2.ApprovalDelta(
+ update_mask=field_mask_pb2.FieldMask(paths=['field_values']),
+ approval_value=av,
+ field_vals_remove=[approval_enum_fv, approval_2_fv],
+ approvers_remove=['users/222'],
+ )
+ with self.assertRaisesRegexp(exceptions.InputException,
+ 'Field .* does not belong to approval .*'):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_InvalidFieldValues(self):
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ approval_fv = issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_6,
+ value=u'touch-nose',
+ derivation=RULE_DERIVATION, # Ignored.
+ )
+ other_fv = issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_1,
+ value=u'something',
+ )
+ # This does not exist, and should throw error.
+ dne_fv = issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/404',
+ value=u'DoesNotExist',
+ )
+ av = issue_objects_pb2.ApprovalValue(
+ name=av_name, field_values=[other_fv, approval_fv, dne_fv])
+ approval_delta = issues_pb2.ApprovalDelta(
+ update_mask=field_mask_pb2.FieldMask(paths=['field_values']),
+ approval_value=av,
+ approvers_remove=['users/222'],
+ )
+ with self.assertRaisesRegexp(
+ exceptions.InputException,
+ 'Field projects/proj/fieldDefs/404 is not in this project'):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_WrongProject(self):
+ approval_def_project2_name = 'project2_approval'
+ approval_def_project2_id = self._CreateFieldDef(
+ self.project_2.project_id,
+ approval_def_project2_name,
+ 'APPROVAL_TYPE',
+ docstring='project2_ad_docstring',
+ admin_ids=[self.user_1.user_id])
+ self.services.config.UpdateConfig(
+ self.cnxn,
+ self.project_2,
+ approval_defs=[
+ (approval_def_project2_id, [self.user_1.user_id], 'survey')
+ ])
+ wrong_project_av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % approval_def_project2_id)
+ approval_delta = issues_pb2.ApprovalDelta(
+ update_mask=field_mask_pb2.FieldMask(),
+ approval_value=issue_objects_pb2.ApprovalValue(
+ name=wrong_project_av_name))
+ with self.assertRaises(exceptions.InputException):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_DoesNotExist(self):
+ dne_av_name = ('projects/proj/issues/1/approvalValues/404')
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(name=dne_av_name),
+ update_mask=field_mask_pb2.FieldMask())
+ with self.assertRaises(exceptions.InputException):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_NonApproval(self):
+ """We fail if provided a non-approval Field ID in the resource name."""
+ dne_av_name = (
+ 'projects/proj/issues/1/approvalValues/%s' % self.field_def_1)
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(name=dne_av_name),
+ update_mask=field_mask_pb2.FieldMask())
+ with self.assertRaises(exceptions.InputException):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_IssueDoesNotExist(self):
+ dne_av_name = (
+ 'projects/proj/issues/404/approvalValues/%d' % self.approval_def_1_id)
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(name=dne_av_name),
+ update_mask=field_mask_pb2.FieldMask())
+ with self.assertRaises(exceptions.NoSuchIssueException):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_EmptyDelta(self):
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(name=av_name),
+ update_mask=field_mask_pb2.FieldMask())
+
+ actual = self.converter.IngestApprovalDeltas(
+ [approval_delta], self.user_1.user_id)
+
+ expected_delta = tracker_pb2.ApprovalDelta()
+ expected_delta_specifications = [
+ (self.issue_1.issue_id, self.approval_def_1_id, expected_delta)
+ ]
+ self.assertEqual(actual, expected_delta_specifications)
+
+ def testIngestApprovalDeltas_InvalidName(self):
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(name='x'))
+ with self.assertRaises(exceptions.InputException):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_NoName(self):
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(
+ status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA')))
+ with self.assertRaises(exceptions.InputException):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_NoStatus(self):
+ """Setter ID isn't set when status isn't set."""
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(
+ name=av_name,
+ status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'),
+ approvers=['users/333']),
+ # Status left out of update mask.
+ update_mask=field_mask_pb2.FieldMask(paths=['approvers']),
+ approvers_remove=['users/222'])
+ actual = self.converter.IngestApprovalDeltas(
+ [approval_delta], self.user_1.user_id)
+ expected_delta = tracker_pb2.ApprovalDelta(
+ approver_ids_add=[333], approver_ids_remove=[222])
+ expected_delta_specifications = [
+ (self.issue_1.issue_id, self.approval_def_1_id, expected_delta)
+ ]
+ self.assertEqual(actual, expected_delta_specifications)
+
+ def testIngestApprovalDeltas_ApproverRemoveDoesNotExist(self):
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(name=av_name),
+ update_mask=field_mask_pb2.FieldMask(),
+ approvers_remove=['users/nobody@404.com'])
+ with self.assertRaises(exceptions.NoSuchUserException):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_ApproverAddDoesNotExist(self):
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ approval_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(
+ name=av_name, approvers=['users/nobody@404.com']),
+ update_mask=field_mask_pb2.FieldMask(paths=['approvers']))
+ with self.assertRaises(exceptions.NoSuchUserException):
+ self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_FirstErrorRaised(self):
+ """Until we have error aggregation, we raise the first found error."""
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ user_dne_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(
+ name=av_name, approvers=['users/nobody@404.com']),
+ update_mask=field_mask_pb2.FieldMask(paths=['approvers']))
+ invalid_name_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(name='garbage'))
+ with self.assertRaises(exceptions.NoSuchUserException):
+ self.converter.IngestApprovalDeltas(
+ [user_dne_delta, invalid_name_delta], self.user_1.user_id)
+
+ def testIngestApprovalDeltas_MultipleDeltasSameSetOn(self):
+ av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ delta_1 = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(
+ name=av_name,
+ status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'),
+ approvers=['users/222']),
+ update_mask=field_mask_pb2.FieldMask(paths=['approvers', 'status']))
+ # Change status, and also ensure we don't reuse the same mask across deltas
+ # Approvers should be ignored for delta_2 because it is not included in the
+ # mask.
+ delta_2 = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(
+ name=av_name,
+ status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+ 'NOT_SET'),
+ approvers=['users/222']),
+ update_mask=field_mask_pb2.FieldMask(paths=['status']))
+ actual = self.converter.IngestApprovalDeltas(
+ [delta_1, delta_2], self.user_1.user_id)
+ self.assertEqual(len(actual), 2)
+ actual_iid_1, actual_approval_id_1, actual_delta_1 = actual[0]
+ actual_iid_2, actual_approval_id_2, actual_delta_2 = actual[1]
+ self.assertEqual(actual_iid_1, self.issue_1.issue_id)
+ self.assertEqual(actual_iid_2, self.issue_1.issue_id)
+ self.assertEqual(actual_approval_id_1, self.approval_def_1_id)
+ self.assertEqual(actual_approval_id_2, self.approval_def_1_id)
+
+ self.assertEqual(actual_delta_1.status, tracker_pb2.ApprovalStatus.NA)
+ self.assertEqual(actual_delta_2.status, tracker_pb2.ApprovalStatus.NOT_SET)
+ self.assertEqual(actual_delta_1.setter_id, self.user_1.user_id)
+ self.assertEqual(actual_delta_2.setter_id, self.user_1.user_id)
+ self.assertEqual(actual_delta_1.approver_ids_add, [222])
+ self.assertEqual(actual_delta_2.approver_ids_add, [])
+ # We don't patch time.time, so these would be different if the set_on wasn't
+ # passed in.
+ # Note: More ideal/correct unit test would create a mock that forces
+ # time.time to return an incremented value on its subsequent calls.
+ self.assertEqual(actual_delta_1.set_on, actual_delta_2.set_on)
+
+ def testIngestApprovalDeltas_DifferentProjects(self):
+ # Create an ApprovalDef for project2
+ approval_def_project2_name = 'project2_approval'
+ approval_def_project2_id = self._CreateFieldDef(
+ self.project_2.project_id,
+ approval_def_project2_name,
+ 'APPROVAL_TYPE',
+ docstring='project2_ad_docstring',
+ admin_ids=[self.user_1.user_id])
+ self.services.config.UpdateConfig(
+ self.cnxn,
+ self.project_2,
+ approval_defs=[
+ (approval_def_project2_id, [self.user_1.user_id], 'survey')
+ ])
+
+ # Define a field belonging to project_2's ApprovalDef.
+ project2_field_id = self._CreateFieldDef(
+ self.project_2.project_id,
+ 'approval2field',
+ 'STR_TYPE',
+ approval_id=approval_def_project2_id)
+ project2_fv = issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % project2_field_id, value=u'p2')
+
+ # field_def_6 belongs to approval_def_1.
+ project1_fv = issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_6,
+ value=u'touch-nose',
+ )
+
+ # Both ApprovalValues are provided both FieldValues, and we expect them
+ # to only include the FieldValues appropriate to their respective approvals.
+ project2_av_name = (
+ 'projects/%s/issues/2/approvalValues/%d' %
+ (self.project_2.project_name, approval_def_project2_id))
+ project2_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(
+ name=project2_av_name, field_values=[project1_fv, project2_fv]),
+ update_mask=field_mask_pb2.FieldMask(paths=['field_values']))
+
+ project1_av_name = (
+ 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+ project1_delta = issues_pb2.ApprovalDelta(
+ approval_value=issue_objects_pb2.ApprovalValue(
+ name=project1_av_name, field_values=[project1_fv, project2_fv]),
+ update_mask=field_mask_pb2.FieldMask(paths=['field_values']))
+
+ with self.assertRaisesRegexp(
+ exceptions.InputException,
+ 'Field projects/proj/fieldDefs/%d is not in this project' %
+ self.field_def_6):
+ self.converter.IngestApprovalDeltas(
+ [project2_delta, project1_delta], self.user_1.user_id)
+
+ def testIngestIssue(self):
+ ingest = issue_objects_pb2.Issue(
+ summary='sum',
+ status=issue_objects_pb2.Issue.StatusValue(
+ status='new', derivation=RULE_DERIVATION),
+ owner=issue_objects_pb2.Issue.UserValue(
+ derivation=EXPLICIT_DERIVATION, user='users/111'),
+ cc_users=[
+ issue_objects_pb2.Issue.UserValue(
+ derivation=EXPLICIT_DERIVATION, user='users/new@user.com'),
+ issue_objects_pb2.Issue.UserValue(
+ derivation=RULE_DERIVATION, user='users/333')
+ ],
+ components=[
+ issue_objects_pb2.Issue.ComponentValue(
+ component='projects/proj/componentDefs/%d' %
+ self.component_def_1_id),
+ issue_objects_pb2.Issue.ComponentValue(
+ component='projects/proj/componentDefs/%d' %
+ self.component_def_2_id),
+ ],
+ labels=[
+ issue_objects_pb2.Issue.LabelValue(
+ derivation=EXPLICIT_DERIVATION, label='a'),
+ issue_objects_pb2.Issue.LabelValue(
+ derivation=EXPLICIT_DERIVATION, label='key-explicit'),
+ issue_objects_pb2.Issue.LabelValue(
+ derivation=RULE_DERIVATION, label='derived1'),
+ issue_objects_pb2.Issue.LabelValue(
+ derivation=RULE_DERIVATION, label='key-derived')
+ ],
+ field_values=[
+ issue_objects_pb2.FieldValue(
+ derivation=EXPLICIT_DERIVATION,
+ field='projects/proj/fieldDefs/%d' % self.field_def_1,
+ value='multivalue1',
+ ),
+ issue_objects_pb2.FieldValue(
+ derivation=RULE_DERIVATION,
+ field='projects/proj/fieldDefs/%d' % self.field_def_1,
+ value='multivalue2',
+ ),
+ issue_objects_pb2.FieldValue(
+ derivation=EXPLICIT_DERIVATION,
+ field='projects/proj/fieldDefs/%d' % self.field_def_3,
+ value='1',
+ ),
+ issue_objects_pb2.FieldValue(
+ derivation=RULE_DERIVATION,
+ field='projects/proj/fieldDefs/%d' % self.field_def_4,
+ value='mac',
+ ),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_2,
+ value='38', # Max value not checked.
+ ),
+ issue_objects_pb2.FieldValue( # Multivalue not checked.
+ field='projects/proj/fieldDefs/%d' % self.field_def_2,
+ value='0' # Confirm we ingest 0 rather than None.
+ ),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_8,
+ value='users/111',
+ ),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_8,
+ value='users/404', # User lookup not attempted.
+ ),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_9,
+ value='2020-01-01',
+ ),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_9,
+ value='2100-01-01',
+ ),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_9,
+ value='1000-01-01',
+ ),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_10,
+ value='garbage',
+ ),
+ ],
+ merged_into_issue_ref=issue_objects_pb2.IssueRef(ext_identifier='b/1'),
+ blocked_on_issue_refs=[
+ # Reversing natural ordering to ensure order is respected.
+ issue_objects_pb2.IssueRef(issue='projects/goose/issues/4'),
+ issue_objects_pb2.IssueRef(issue='projects/proj/issues/3'),
+ issue_objects_pb2.IssueRef(ext_identifier='b/555'),
+ issue_objects_pb2.IssueRef(ext_identifier='b/2')
+ ],
+ blocking_issue_refs=[
+ issue_objects_pb2.IssueRef(issue='projects/goose/issues/5'),
+ issue_objects_pb2.IssueRef(ext_identifier='b/3')
+ ],
+ # All the following fields should be ignored.
+ name='projects/proj/issues/1',
+ state=issue_objects_pb2.IssueContentState.Value('SPAM'),
+ reporter='users/111',
+ create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME),
+ star_count=1,
+ attachment_count=5,
+ phases=[self.phase_1.name])
+
+ blocked_on_1 = fake.MakeTestIssue(
+ self.project_1.project_id,
+ 3,
+ 'sum3',
+ 'New',
+ self.user_1.user_id,
+ issue_id=301,
+ project_name=self.project_1.project_name,
+ )
+ blocked_on_2 = fake.MakeTestIssue(
+ self.project_2.project_id,
+ 4,
+ 'sum4',
+ 'New',
+ self.user_1.user_id,
+ issue_id=401,
+ project_name=self.project_2.project_name,
+ )
+ blocking = fake.MakeTestIssue(
+ self.project_2.project_id,
+ 5,
+ 'sum5',
+ 'New',
+ self.user_1.user_id,
+ issue_id=501,
+ project_name=self.project_2.project_name,
+ )
+ self.services.issue.TestAddIssue(blocked_on_1)
+ self.services.issue.TestAddIssue(blocked_on_2)
+ self.services.issue.TestAddIssue(blocking)
+
+ actual = self.converter.IngestIssue(ingest, self.project_1.project_id)
+
+ expected_cc1_id = self.services.user.LookupUserID(
+ self.cnxn, 'new@user.com', autocreate=False)
+ expected_field_values = [
+ tracker_pb2.FieldValue(
+ field_id=self.field_def_1,
+ str_value=u'multivalue1',
+ derived=False,
+ ),
+ tracker_pb2.FieldValue(
+ field_id=self.field_def_1,
+ str_value=u'multivalue2',
+ derived=False,
+ ),
+ tracker_pb2.FieldValue(
+ field_id=self.field_def_2, int_value=38, derived=False),
+ tracker_pb2.FieldValue(
+ field_id=self.field_def_2, int_value=0, derived=False),
+ tracker_pb2.FieldValue(
+ field_id=self.field_def_8, user_id=111, derived=False),
+ tracker_pb2.FieldValue(
+ field_id=self.field_def_8, user_id=404, derived=False),
+ tracker_pb2.FieldValue(
+ field_id=self.field_def_9, date_value=1577836800, derived=False),
+ tracker_pb2.FieldValue(
+ field_id=self.field_def_9, date_value=4102444800, derived=False),
+ tracker_pb2.FieldValue(
+ field_id=self.field_def_9, date_value=-30610224000, derived=False),
+ tracker_pb2.FieldValue(
+ field_id=self.field_def_10,
+ url_value=u'http://garbage',
+ derived=False),
+ ]
+ expected = tracker_pb2.Issue(
+ project_id=self.project_1.project_id,
+ summary=u'sum',
+ status=u'new',
+ owner_id=111,
+ cc_ids=[expected_cc1_id, 333],
+ component_ids=[self.component_def_1_id, self.component_def_2_id],
+ merged_into_external=u'b/1',
+ labels=[
+ u'a', u'key-explicit', u'derived1', u'key-derived', u'days-1',
+ u'OS-mac'
+ ],
+ field_values=expected_field_values,
+ blocked_on_iids=[blocked_on_2.issue_id, blocked_on_1.issue_id],
+ blocking_iids=[blocking.issue_id],
+ dangling_blocked_on_refs=[
+ tracker_pb2.DanglingIssueRef(ext_issue_identifier=u'b/555'),
+ tracker_pb2.DanglingIssueRef(ext_issue_identifier=u'b/2')
+ ],
+ dangling_blocking_refs=[
+ tracker_pb2.DanglingIssueRef(ext_issue_identifier=u'b/3')
+ ],
+ )
+ self.AssertProtosEqual(actual, expected)
+
+ def AssertProtosEqual(self, actual, expected):
+ """Asserts equal, printing a diff if not."""
+ # TODO(jessan): If others find this useful, move to a shared testing lib.
+ try:
+ self.assertEqual(actual, expected)
+ except AssertionError as e:
+ # Append a diff to the normal error message.
+ expected_str = str(expected).splitlines(1)
+ actual_str = str(actual).splitlines(1)
+ diff = difflib.unified_diff(actual_str, expected_str)
+ err_msg = '%s\nProto actual vs expected diff:\n %s' % (e, ''.join(diff))
+ raise AssertionError(err_msg)
+
+ def testIngestIssue_Minimal(self):
+ """Test IngestIssue with as few fields set as possible."""
+ minimal = issue_objects_pb2.Issue(
+ status=issue_objects_pb2.Issue.StatusValue(status='new')
+ )
+ expected = tracker_pb2.Issue(
+ project_id=self.project_1.project_id,
+ summary='', # Summary gets set to empty str on conversion.
+ status='new',
+ owner_id=0
+ )
+ actual = self.converter.IngestIssue(minimal, self.project_1.project_id)
+ self.assertEqual(actual, expected)
+
+ def testIngestIssue_NoSuchProject(self):
+ self.services.config.strict = True
+ ingest = issue_objects_pb2.Issue(
+ status=issue_objects_pb2.Issue.StatusValue(status='new'))
+ with self.assertRaises(exceptions.NoSuchProjectException):
+ self.converter.IngestIssue(ingest, -1)
+
+ def testIngestIssue_Errors(self):
+ invalid_issue_ref = issue_objects_pb2.IssueRef(
+ ext_identifier='b/1',
+ issue='projects/proj/issues/1')
+ ingest = issue_objects_pb2.Issue(
+ summary='sum',
+ owner=issue_objects_pb2.Issue.UserValue(
+ derivation=EXPLICIT_DERIVATION, user='users/nonexisting@user.com'),
+ cc_users=[
+ issue_objects_pb2.Issue.UserValue(
+ derivation=EXPLICIT_DERIVATION, user='invalidFormat1'),
+ issue_objects_pb2.Issue.UserValue(
+ derivation=RULE_DERIVATION, user='invalidFormat2')
+ ],
+ components=[
+ issue_objects_pb2.Issue.ComponentValue(
+ component='projects/proj/componentDefs/404')
+ ],
+ field_values=[
+ issue_objects_pb2.FieldValue(),
+ issue_objects_pb2.FieldValue(field='garbage'),
+ issue_objects_pb2.FieldValue(
+ field='projects/proj/fieldDefs/%d' % self.field_def_8,
+ value='users/nonexisting@user.com',
+ ),
+ ],
+ merged_into_issue_ref=invalid_issue_ref,
+ blocked_on_issue_refs=[
+ issue_objects_pb2.IssueRef(),
+ issue_objects_pb2.IssueRef(issue='projects/404/issues/1')
+ ],
+ blocking_issue_refs=[
+ issue_objects_pb2.IssueRef(issue='projects/proj/issues/404')
+ ],
+ )
+ error_messages = [
+ r'.+not found when ingesting owner',
+ r'.+cc_users: Invalid resource name: invalidFormat1.',
+ r'Status is required when creating an issue',
+ r'.+components: Component not found: 404.',
+ r'.+: Invalid resource name: .', r'.+: Invalid resource name: garbage.',
+ r'.+not found when ingesting user field:.+',
+ r'.+issue:.+[\n\r]+ext_identifier:.+[\n\r]+: IssueRefs MUST NOT have.+',
+ r'.+: IssueRefs MUST have one of.+',
+ r'.+issue:.+[\n\r]+: Project 404 not found.',
+ r'.+issue:.+[\n\r]+: Issue.+404.+not found'
+ ]
+ error_messages_re = '\n'.join(error_messages)
+ with self.assertRaisesRegexp(exceptions.InputException, error_messages_re):
+ self.converter.IngestIssue(ingest, self.project_1.project_id)
+
+ def testIngestIssuesListColumns(self):
+ columns = [
+ issue_objects_pb2.IssuesListColumn(column='chicken'),
+ issue_objects_pb2.IssuesListColumn(column='boiled-egg')
+ ]
+ self.assertEqual(
+ self.converter.IngestIssuesListColumns(columns), 'chicken boiled-egg')
+
+ def testIngestIssuesListColumns_Empty(self):
+ self.assertEqual(self.converter.IngestIssuesListColumns([]), '')
+
+ def test_ComputeIssuesListColumns(self):
+ """Can convert string to sequence of IssuesListColumns"""
+ expected_columns = [
+ issue_objects_pb2.IssuesListColumn(column='chicken'),
+ issue_objects_pb2.IssuesListColumn(column='boiled-egg')
+ ]
+ self.assertEqual(
+ expected_columns,
+ self.converter._ComputeIssuesListColumns('chicken boiled-egg'))
+
+ def test_ComputeIssuesListColumns_Empty(self):
+ """Can handle empty strings"""
+ self.assertEqual([], self.converter._ComputeIssuesListColumns(''))
+
+ def test_Conversion_IssuesListColumns(self):
+ """_Ingest and _Compute converts to and from each other"""
+ expected_columns = 'foo bar fizz buzz'
+ converted_columns = self.converter._ComputeIssuesListColumns(
+ expected_columns)
+ self.assertEqual(
+ expected_columns,
+ self.converter.IngestIssuesListColumns(converted_columns))
+
+ expected_columns = [
+ issue_objects_pb2.IssuesListColumn(column='foo'),
+ issue_objects_pb2.IssuesListColumn(column='bar'),
+ issue_objects_pb2.IssuesListColumn(column='fizz'),
+ issue_objects_pb2.IssuesListColumn(column='buzz')
+ ]
+ converted_columns = self.converter.IngestIssuesListColumns(expected_columns)
+ self.assertEqual(
+ expected_columns,
+ self.converter._ComputeIssuesListColumns(converted_columns))
+
+ def testIngestNotifyType(self):
+ notify = issues_pb2.NotifyType.Value('NOTIFY_TYPE_UNSPECIFIED')
+ actual = self.converter.IngestNotifyType(notify)
+ self.assertEqual(actual, True)
+ notify = issues_pb2.NotifyType.Value('EMAIL')
+ actual = self.converter.IngestNotifyType(notify)
+ self.assertEqual(actual, True)
+ notify = issues_pb2.NotifyType.Value('NO_NOTIFICATION')
+ actual = self.converter.IngestNotifyType(notify)
+ self.assertEqual(actual, False)
+
+ def test_GetNonApprovalFieldValues(self):
+ """It filters out field values that belong to approvals"""
+ expected_str = 'some_string_field_value'
+ fv_expected = fake.MakeFieldValue(
+ field_id=self.field_def_1, str_value=expected_str, derived=False)
+ actual = self.converter._GetNonApprovalFieldValues(
+ [fv_expected, self.fv_6], self.project_1.project_id)
+ self.assertEqual(len(actual), 1)
+ self.assertEqual(actual[0], fv_expected)
+
+ def test_GetNonApprovalFieldValues_Empty(self):
+ actual = self.converter._GetNonApprovalFieldValues(
+ [], self.project_1.project_id)
+ self.assertEqual(actual, [])
+
+ def testConvertFieldValues(self):
+ """It ignores field values referencing a non-existent field"""
+ expected_str = 'some_string_field_value'
+ fv = fake.MakeFieldValue(
+ field_id=self.field_def_1, str_value=expected_str, derived=False)
+ expected_name = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_1], self.project_1.project_id,
+ self.services)[self.field_def_1]
+ expected_value = issue_objects_pb2.FieldValue(
+ field=expected_name,
+ value=expected_str,
+ derivation=EXPLICIT_DERIVATION,
+ phase=None)
+ output = self.converter.ConvertFieldValues(
+ [fv], self.project_1.project_id, [])
+ self.assertEqual([expected_value], output)
+
+ def testConvertFieldValues_Empty(self):
+ output = self.converter.ConvertFieldValues(
+ [], self.project_1.project_id, [])
+ self.assertEqual([], output)
+
+ def testConvertFieldValues_PreservesOrder(self):
+ """It ignores field values referencing a non-existent field"""
+ expected_str = 'some_string_field_value'
+ fv_1 = fake.MakeFieldValue(
+ field_id=self.field_def_1, str_value=expected_str, derived=False)
+ name_1 = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_1], self.project_1.project_id,
+ self.services)[self.field_def_1]
+ expected_1 = issue_objects_pb2.FieldValue(
+ field=name_1,
+ value=expected_str,
+ derivation=EXPLICIT_DERIVATION,
+ phase=None)
+
+ expected_int = 111111
+ fv_2 = fake.MakeFieldValue(
+ field_id=self.field_def_2, int_value=expected_int, derived=True)
+ name_2 = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_2], self.project_1.project_id,
+ self.services).get(self.field_def_2)
+ expected_2 = issue_objects_pb2.FieldValue(
+ field=name_2,
+ value=str(expected_int),
+ derivation=RULE_DERIVATION,
+ phase=None)
+ output = self.converter.ConvertFieldValues(
+ [fv_1, fv_2], self.project_1.project_id, [])
+ self.assertEqual([expected_1, expected_2], output)
+
+ def testConvertFieldValues_IgnoresNullFieldDefs(self):
+ """It ignores field values referencing a non-existent field"""
+ expected_str = 'some_string_field_value'
+ fv_1 = fake.MakeFieldValue(
+ field_id=self.field_def_1, str_value=expected_str, derived=False)
+ name_1 = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_1], self.project_1.project_id,
+ self.services)[self.field_def_1]
+ expected_1 = issue_objects_pb2.FieldValue(
+ field=name_1,
+ value=expected_str,
+ derivation=EXPLICIT_DERIVATION,
+ phase=None)
+
+ fv_2 = fake.MakeFieldValue(
+ field_id=self.dne_field_def_id, int_value=111111, derived=True)
+ output = self.converter.ConvertFieldValues(
+ [fv_1, fv_2], self.project_1.project_id, [])
+ self.assertEqual([expected_1], output)
+
+ def test_ComputeFieldValueString_None(self):
+ with self.assertRaises(exceptions.InputException):
+ self.converter._ComputeFieldValueString(None)
+
+ def test_ComputeFieldValueString_INT_TYPE(self):
+ expected = 123158
+ fv = fake.MakeFieldValue(field_id=self.field_def_2, int_value=expected)
+ output = self.converter._ComputeFieldValueString(fv)
+ self.assertEqual(str(expected), output)
+
+ def test_ComputeFieldValueString_STR_TYPE(self):
+ expected = 'some_string_field_value'
+ fv = fake.MakeFieldValue(field_id=self.field_def_1, str_value=expected)
+ output = self.converter._ComputeFieldValueString(fv)
+ self.assertEqual(expected, output)
+
+ def test_ComputeFieldValueString_USER_TYPE(self):
+ user_id = self.user_1.user_id
+ expected = rnc.ConvertUserName(user_id)
+ fv = fake.MakeFieldValue(field_id=self.dne_field_def_id, user_id=user_id)
+ output = self.converter._ComputeFieldValueString(fv)
+ self.assertEqual(expected, output)
+
+ def test_ComputeFieldValueString_DATE_TYPE(self):
+ expected = 1234567890
+ fv = fake.MakeFieldValue(
+ field_id=self.dne_field_def_id, date_value=expected)
+ output = self.converter._ComputeFieldValueString(fv)
+ self.assertEqual(str(expected), output)
+
+ def test_ComputeFieldValueString_URL_TYPE(self):
+ expected = 'some URL'
+ fv = fake.MakeFieldValue(field_id=self.dne_field_def_id, url_value=expected)
+ output = self.converter._ComputeFieldValueString(fv)
+ self.assertEqual(expected, output)
+
+ def test_ComputeFieldValueDerivation_RULE(self):
+ expected = RULE_DERIVATION
+ fv = fake.MakeFieldValue(
+ field_id=self.field_def_1, str_value='something', derived=True)
+ output = self.converter._ComputeFieldValueDerivation(fv)
+ self.assertEqual(expected, output)
+
+ def test_ComputeFieldValueDerivation_EXPLICIT(self):
+ expected = EXPLICIT_DERIVATION
+ fv = fake.MakeFieldValue(
+ field_id=self.field_def_1, str_value='something', derived=False)
+ output = self.converter._ComputeFieldValueDerivation(fv)
+ self.assertEqual(expected, output)
+
+ def testConvertApprovalValues_Issue(self):
+ """We can convert issue approval_values."""
+ name = rnc.ConvertApprovalValueNames(
+ self.cnxn, self.issue_1.issue_id, self.services)[self.av_1.approval_id]
+ approval_def_name = rnc.ConvertApprovalDefNames(
+ self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+ self.services)[self.approval_def_1_id]
+ approvers = [rnc.ConvertUserName(self.user_2.user_id)]
+ status = issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+ 'NOT_SET')
+ setter = rnc.ConvertUserName(self.user_1.user_id)
+ api_fvs = self.converter.ConvertFieldValues(
+ [self.fv_6], self.project_1.project_id, [self.phase_1])
+ # Check we can handle converting a None `set_on`.
+ self.av_1.set_on = None
+
+ output = self.converter.ConvertApprovalValues(
+ [self.av_1], [self.fv_1, self.fv_6], [self.phase_1],
+ issue_id=self.issue_1.issue_id)
+ expected = issue_objects_pb2.ApprovalValue(
+ name=name,
+ approval_def=approval_def_name,
+ approvers=approvers,
+ status=status,
+ setter=setter,
+ phase=self.phase_1.name,
+ field_values=api_fvs)
+ self.assertEqual([expected], output)
+
+ def testConvertApprovalValues_Templates(self):
+ """We can convert template approval_values."""
+ approval_def_name = rnc.ConvertApprovalDefNames(
+ self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+ self.services)[self.approval_def_1_id]
+ approvers = [rnc.ConvertUserName(self.user_2.user_id)]
+ status = issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+ 'NOT_SET')
+ set_time = timestamp_pb2.Timestamp()
+ set_time.FromSeconds(self.PAST_TIME)
+ setter = rnc.ConvertUserName(self.user_1.user_id)
+ api_fvs = self.converter.ConvertFieldValues(
+ [self.fv_6], self.project_1.project_id, [self.phase_1])
+
+ output = self.converter.ConvertApprovalValues(
+ [self.av_1], [self.fv_1, self.fv_6], [self.phase_1],
+ project_id=self.project_1.project_id)
+ expected = issue_objects_pb2.ApprovalValue(
+ approval_def=approval_def_name,
+ approvers=approvers,
+ status=status,
+ set_time=set_time,
+ setter=setter,
+ phase=self.phase_1.name,
+ field_values=api_fvs)
+ self.assertEqual([expected], output)
+
+ def testConvertApprovalValues_NoPhase(self):
+ approval_def_name = rnc.ConvertApprovalDefNames(
+ self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+ self.services)[self.approval_def_1_id]
+ approvers = [rnc.ConvertUserName(self.user_2.user_id)]
+ status = issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+ 'NOT_SET')
+ set_time = timestamp_pb2.Timestamp()
+ set_time.FromSeconds(self.PAST_TIME)
+ setter = rnc.ConvertUserName(self.user_1.user_id)
+ expected = issue_objects_pb2.ApprovalValue(
+ approval_def=approval_def_name,
+ approvers=approvers,
+ status=status,
+ set_time=set_time,
+ setter=setter)
+
+ output = self.converter.ConvertApprovalValues(
+ [self.av_1], [], [], project_id=self.project_1.project_id)
+ self.assertEqual([expected], output)
+
+ def testConvertApprovalValues_Empty(self):
+ output = self.converter.ConvertApprovalValues(
+ [], [], [], project_id=self.project_1.project_id)
+ self.assertEqual([], output)
+
+ def testConvertApprovalValues_IgnoresNullFieldDefs(self):
+ """It ignores approval values referencing a non-existent field"""
+ av = fake.MakeApprovalValue(self.dne_field_def_id)
+
+ output = self.converter.ConvertApprovalValues(
+ [av], [], [], issue_id=self.issue_1.issue_id)
+ self.assertEqual([], output)
+
+ def test_ComputeApprovalValueStatus_NOT_SET(self):
+ self.assertEqual(
+ self.converter._ComputeApprovalValueStatus(
+ tracker_pb2.ApprovalStatus.NOT_SET),
+ issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+ 'NOT_SET'))
+
+ def test_ComputeApprovalValueStatus_NEEDS_REVIEW(self):
+ self.assertEqual(
+ self.converter._ComputeApprovalValueStatus(
+ tracker_pb2.ApprovalStatus.NEEDS_REVIEW),
+ issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NEEDS_REVIEW'))
+
+ def test_ComputeApprovalValueStatus_NA(self):
+ self.assertEqual(
+ self.converter._ComputeApprovalValueStatus(
+ tracker_pb2.ApprovalStatus.NA),
+ issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'))
+
+ def test_ComputeApprovalValueStatus_REVIEW_REQUESTED(self):
+ self.assertEqual(
+ self.converter._ComputeApprovalValueStatus(
+ tracker_pb2.ApprovalStatus.REVIEW_REQUESTED),
+ issue_objects_pb2.ApprovalValue.ApprovalStatus.Value(
+ 'REVIEW_REQUESTED'))
+
+ def test_ComputeApprovalValueStatus_REVIEW_STARTED(self):
+ self.assertEqual(
+ self.converter._ComputeApprovalValueStatus(
+ tracker_pb2.ApprovalStatus.REVIEW_STARTED),
+ issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('REVIEW_STARTED'))
+
+ def test_ComputeApprovalValueStatus_NEED_INFO(self):
+ self.assertEqual(
+ self.converter._ComputeApprovalValueStatus(
+ tracker_pb2.ApprovalStatus.NEED_INFO),
+ issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NEED_INFO'))
+
+ def test_ComputeApprovalValueStatus_APPROVED(self):
+ self.assertEqual(
+ self.converter._ComputeApprovalValueStatus(
+ tracker_pb2.ApprovalStatus.APPROVED),
+ issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('APPROVED'))
+
+ def test_ComputeApprovalValueStatus_NOT_APPROVED(self):
+ self.assertEqual(
+ self.converter._ComputeApprovalValueStatus(
+ tracker_pb2.ApprovalStatus.NOT_APPROVED),
+ issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NOT_APPROVED'))
+
+ def test_ComputeTemplatePrivacy_PUBLIC(self):
+ self.assertEqual(
+ self.converter._ComputeTemplatePrivacy(self.template_1),
+ project_objects_pb2.IssueTemplate.TemplatePrivacy.Value('PUBLIC'))
+
+ def test_ComputeTemplatePrivacy_MEMBERS_ONLY(self):
+ self.assertEqual(
+ self.converter._ComputeTemplatePrivacy(self.template_2),
+ project_objects_pb2.IssueTemplate.TemplatePrivacy.Value('MEMBERS_ONLY'))
+
+ def test_ComputeTemplateDefaultOwner_UNSPECIFIED(self):
+ self.assertEqual(
+ self.converter._ComputeTemplateDefaultOwner(self.template_1),
+ project_objects_pb2.IssueTemplate.DefaultOwner.Value(
+ 'DEFAULT_OWNER_UNSPECIFIED'))
+
+ def test_ComputeTemplateDefaultOwner_REPORTER(self):
+ self.assertEqual(
+ self.converter._ComputeTemplateDefaultOwner(self.template_2),
+ project_objects_pb2.IssueTemplate.DefaultOwner.Value(
+ 'PROJECT_MEMBER_REPORTER'))
+
+ def test_ComputePhases(self):
+ """It sorts by rank"""
+ phase1 = fake.MakePhase(123111, name='phase1name', rank=3)
+ phase2 = fake.MakePhase(123112, name='phase2name', rank=2)
+ phase3 = fake.MakePhase(123113, name='phase3name', rank=1)
+ expected = ['phase3name', 'phase2name', 'phase1name']
+ self.assertEqual(
+ self.converter._ComputePhases([phase1, phase2, phase3]), expected)
+
+ def test_ComputePhases_EMPTY(self):
+ self.assertEqual(self.converter._ComputePhases([]), [])
+
+ def test_FillIssueFromTemplate(self):
+ result = self.converter._FillIssueFromTemplate(
+ self.template_1, self.project_1.project_id)
+ self.assertFalse(result.name)
+ self.assertEqual(result.summary, self.template_1.summary)
+ self.assertEqual(
+ result.state, issue_objects_pb2.IssueContentState.Value('ACTIVE'))
+ self.assertEqual(result.status.status, 'New')
+ self.assertFalse(result.reporter)
+ self.assertEqual(result.owner.user, 'users/{}'.format(self.user_1.user_id))
+ self.assertEqual(len(result.cc_users), 0)
+ self.assertFalse(result.cc_users)
+ self.assertEqual(len(result.labels), 1)
+ self.assertEqual(result.labels[0].label, self.template_1.labels[0])
+ self.assertEqual(result.labels[0].derivation, EXPLICIT_DERIVATION)
+ self.assertEqual(len(result.components), 1)
+ self.assertEqual(
+ result.components[0].component, 'projects/{}/componentDefs/{}'.format(
+ self.project_1.project_name, self.template_1.component_ids[0]))
+ self.assertEqual(result.components[0].derivation, EXPLICIT_DERIVATION)
+ self.assertEqual(len(result.field_values), 2)
+ self.assertEqual(
+ result.field_values[0].field, 'projects/{}/fieldDefs/{}'.format(
+ self.project_1.project_name, self.field_def_1))
+ self.assertEqual(result.field_values[0].value, self.fv_1_value)
+ self.assertEqual(result.field_values[0].derivation, EXPLICIT_DERIVATION)
+ expected_name = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_3], self.project_1.project_id,
+ self.services).get(self.field_def_3)
+ self.assertEqual(
+ result.field_values[1],
+ issue_objects_pb2.FieldValue(
+ field=expected_name,
+ value=self.template_1_label1_value,
+ derivation=EXPLICIT_DERIVATION))
+ self.assertFalse(result.blocked_on_issue_refs)
+ self.assertFalse(result.blocking_issue_refs)
+ self.assertFalse(result.attachment_count)
+ self.assertFalse(result.star_count)
+ self.assertEqual(len(result.phases), 1)
+ self.assertEqual(result.phases[0], self.phase_1.name)
+
+ def test_FillIssueFromTemplate_NoPhase(self):
+ result = self.converter._FillIssueFromTemplate(
+ self.template_3, self.project_1.project_id)
+ self.assertEqual(len(result.field_values), 1)
+ self.assertEqual(
+ result.field_values[0].field, 'projects/{}/fieldDefs/{}'.format(
+ self.project_1.project_name, self.field_def_1))
+ self.assertEqual(result.field_values[0].value, self.fv_1_value)
+ self.assertEqual(result.field_values[0].derivation, EXPLICIT_DERIVATION)
+ self.assertEqual(len(result.phases), 0)
+
+ def test_FillIssueFromTemplate_FilterApprovalFV(self):
+ template = self.services.template.TestAddIssueTemplateDef(
+ 11114,
+ self.project_1.project_id,
+ 'template3',
+ field_values=[self.fv_1, self.fv_6],
+ approval_values=[self.av_2],
+ )
+ result = self.converter._FillIssueFromTemplate(
+ template, self.project_1.project_id)
+ self.assertEqual(len(result.field_values), 1)
+ self.assertEqual(
+ result.field_values[0].field, 'projects/{}/fieldDefs/{}'.format(
+ self.project_1.project_name, self.field_def_1))
+ self.assertEqual(result.field_values[0].value, self.fv_1_value)
+ self.assertEqual(result.field_values[0].derivation, EXPLICIT_DERIVATION)
+
+ def testConvertIssueTemplates(self):
+ result = self.converter.ConvertIssueTemplates(
+ self.project_1.project_id, [self.template_1])
+ self.assertEqual(len(result), 1)
+ actual = result[0]
+ self.assertEqual(
+ actual.name, 'projects/{}/templates/{}'.format(
+ self.project_1.project_name, self.template_1.template_id))
+ self.assertEqual(actual.display_name, self.template_1.name)
+ self.assertEqual(actual.summary_must_be_edited, False)
+ self.assertEqual(
+ actual.template_privacy,
+ project_objects_pb2.IssueTemplate.TemplatePrivacy.Value('PUBLIC'))
+ self.assertEqual(
+ actual.default_owner,
+ project_objects_pb2.IssueTemplate.DefaultOwner.Value(
+ 'DEFAULT_OWNER_UNSPECIFIED'))
+ self.assertEqual(actual.component_required, False)
+ self.assertEqual(actual.admins, ['users/{}'.format(self.user_2.user_id)])
+ self.assertEqual(
+ actual.issue,
+ self.converter._FillIssueFromTemplate(
+ self.template_1, self.project_1.project_id))
+ self.assertListEqual(
+ [av for av in actual.approval_values],
+ self.converter.ConvertApprovalValues(
+ self.template_1.approval_values, self.template_1.field_values,
+ self.template_1.phases, project_id=self.project_1.project_id))
+
+ def testConvertIssueTemplates_IgnoresNonExistentTemplate(self):
+ result = self.converter.ConvertIssueTemplates(
+ self.project_1.project_id, [self.dne_template])
+ self.assertEqual(len(result), 0)
+
+ def testConvertLabels_OmitsFieldDefs(self):
+ """It omits field def labels"""
+ input_labels = ['pri-1', '{}-2'.format(self.field_def_3_name)]
+ result = self.converter.ConvertLabels(
+ input_labels, [], self.project_1.project_id)
+ self.assertEqual(len(result), 1)
+ expected = issue_objects_pb2.Issue.LabelValue(
+ label=input_labels[0], derivation=EXPLICIT_DERIVATION)
+ self.assertEqual(result[0], expected)
+
+ def testConvertLabels_DerivedLabels(self):
+ """It handles derived labels"""
+ input_labels = ['pri-1']
+ result = self.converter.ConvertLabels(
+ [], input_labels, self.project_1.project_id)
+ self.assertEqual(len(result), 1)
+ expected = issue_objects_pb2.Issue.LabelValue(
+ label=input_labels[0], derivation=RULE_DERIVATION)
+ self.assertEqual(result[0], expected)
+
+ def testConvertLabels(self):
+ """It includes both non-derived and derived labels"""
+ input_labels = ['pri-1', '{}-2'.format(self.field_def_3_name)]
+ input_der_labels = ['{}-3'.format(self.field_def_3_name), 'job-secret']
+ result = self.converter.ConvertLabels(
+ input_labels, input_der_labels, self.project_1.project_id)
+ self.assertEqual(len(result), 2)
+ expected_0 = issue_objects_pb2.Issue.LabelValue(
+ label=input_labels[0], derivation=EXPLICIT_DERIVATION)
+ self.assertEqual(result[0], expected_0)
+ expected_1 = issue_objects_pb2.Issue.LabelValue(
+ label=input_der_labels[1], derivation=RULE_DERIVATION)
+ self.assertEqual(result[1], expected_1)
+
+ def testConvertLabels_Empty(self):
+ result = self.converter.ConvertLabels([], [], self.project_1.project_id)
+ self.assertEqual(result, [])
+
+ def testConvertEnumFieldValues_OnlyFieldDefs(self):
+ """It only returns enum field values"""
+ expected_value = '2'
+ input_labels = [
+ 'pri-1', '{}-{}'.format(self.field_def_3_name, expected_value)
+ ]
+ result = self.converter.ConvertEnumFieldValues(
+ input_labels, [], self.project_1.project_id)
+ self.assertEqual(len(result), 1)
+ expected_name = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_3], self.project_1.project_id,
+ self.services).get(self.field_def_3)
+ expected = issue_objects_pb2.FieldValue(
+ field=expected_name,
+ value=expected_value,
+ derivation=EXPLICIT_DERIVATION)
+ self.assertEqual(result[0], expected)
+
+ def testConvertEnumFieldValues_DerivedLabels(self):
+ """It handles derived enum field values"""
+ expected_value = '2'
+ input_der_labels = [
+ 'pri-1', '{}-{}'.format(self.field_def_3_name, expected_value)
+ ]
+ result = self.converter.ConvertEnumFieldValues(
+ [], input_der_labels, self.project_1.project_id)
+ self.assertEqual(len(result), 1)
+ expected_name = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_3], self.project_1.project_id,
+ self.services).get(self.field_def_3)
+ expected = issue_objects_pb2.FieldValue(
+ field=expected_name, value=expected_value, derivation=RULE_DERIVATION)
+ self.assertEqual(result[0], expected)
+
+ def testConvertEnumFieldValues_Empty(self):
+ result = self.converter.ConvertEnumFieldValues(
+ [], [], self.project_1.project_id)
+ self.assertEqual(result, [])
+
+ def testConvertEnumFieldValues_ProjectSpecific(self):
+ """It only considers field defs from specified project"""
+ expected_value = '2'
+ input_labels = [
+ '{}-{}'.format(self.field_def_3_name, expected_value),
+ '{}-ipsum'.format(self.field_def_project2_name)
+ ]
+ result = self.converter.ConvertEnumFieldValues(
+ input_labels, [], self.project_1.project_id)
+ self.assertEqual(len(result), 1)
+ expected_name = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_3], self.project_1.project_id,
+ self.services).get(self.field_def_3)
+ expected = issue_objects_pb2.FieldValue(
+ field=expected_name,
+ value=expected_value,
+ derivation=EXPLICIT_DERIVATION)
+ self.assertEqual(result[0], expected)
+
+ def testConvertEnumFieldValues(self):
+ """It handles derived enum field values"""
+ expected_value_0 = '2'
+ expected_value_1 = 'macOS'
+ input_labels = [
+ 'pri-1', '{}-{}'.format(self.field_def_3_name, expected_value_0),
+ '{}-ipsum'.format(self.field_def_project2_name)
+ ]
+ input_der_labels = [
+ '{}-{}'.format(self.field_def_4_name, expected_value_1), 'foo-bar'
+ ]
+ result = self.converter.ConvertEnumFieldValues(
+ input_labels, input_der_labels, self.project_1.project_id)
+ self.assertEqual(len(result), 2)
+ expected_0_name = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_3], self.project_1.project_id,
+ self.services).get(self.field_def_3)
+ expected_0 = issue_objects_pb2.FieldValue(
+ field=expected_0_name,
+ value=expected_value_0,
+ derivation=EXPLICIT_DERIVATION)
+ self.assertEqual(result[0], expected_0)
+ expected_1_name = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_4], self.project_1.project_id,
+ self.services).get(self.field_def_4)
+ expected_1 = issue_objects_pb2.FieldValue(
+ field=expected_1_name,
+ value=expected_value_1,
+ derivation=RULE_DERIVATION)
+ self.assertEqual(result[1], expected_1)
+
+ @mock.patch('project.project_helpers.GetThumbnailUrl')
+ def testConvertProject(self, mock_GetThumbnailUrl):
+ """We can convert a Project."""
+ mock_GetThumbnailUrl.return_value = 'xyz'
+ expected_api_project = project_objects_pb2.Project(
+ name='projects/{}'.format(self.project_1.project_name),
+ display_name=self.project_1.project_name,
+ summary=self.project_1.summary,
+ thumbnail_url='xyz')
+ self.assertEqual(
+ expected_api_project, self.converter.ConvertProject(self.project_1))
+
+ @mock.patch('project.project_helpers.GetThumbnailUrl')
+ def testConvertProjects(self, mock_GetThumbnailUrl):
+ """We can convert a Sequence of Projects."""
+ mock_GetThumbnailUrl.return_value = 'xyz'
+ expected_api_projects = [
+ project_objects_pb2.Project(
+ name='projects/{}'.format(self.project_1.project_name),
+ display_name=self.project_1.project_name,
+ summary=self.project_1.summary,
+ thumbnail_url='xyz'),
+ project_objects_pb2.Project(
+ name='projects/{}'.format(self.project_2.project_name),
+ display_name=self.project_2.project_name,
+ summary=self.project_2.summary,
+ thumbnail_url='xyz')
+ ]
+ self.assertEqual(
+ expected_api_projects,
+ self.converter.ConvertProjects([self.project_1, self.project_2]))
+
+ def testConvertProjectConfig(self):
+ """We can convert a project_config"""
+ project_config = self.services.config.GetProjectConfig(
+ self.cnxn, self.project_1.project_id)
+ expected_grid_config = project_objects_pb2.ProjectConfig.GridViewConfig(
+ default_x_attr=project_config.default_x_attr,
+ default_y_attr=project_config.default_y_attr)
+ template_names = rnc.ConvertTemplateNames(
+ self.cnxn, project_config.project_id, [
+ project_config.default_template_for_developers,
+ project_config.default_template_for_users
+ ], self.services)
+ expected_api_config = project_objects_pb2.ProjectConfig(
+ name=rnc.ConvertProjectConfigName(
+ self.cnxn, self.project_1.project_id, self.services),
+ exclusive_label_prefixes=project_config.exclusive_label_prefixes,
+ member_default_query=project_config.member_default_query,
+ default_sort=project_config.default_sort_spec,
+ default_columns=[
+ issue_objects_pb2.IssuesListColumn(column=col)
+ for col in project_config.default_col_spec.split()
+ ],
+ project_grid_config=expected_grid_config,
+ member_default_template=template_names.get(
+ project_config.default_template_for_developers),
+ non_members_default_template=template_names.get(
+ project_config.default_template_for_users),
+ revision_url_format=self.project_1.revision_url_format,
+ custom_issue_entry_url=project_config.custom_issue_entry_url)
+ self.converter.user_auth = authdata.AuthData.FromUser(
+ self.cnxn, self.user_1, self.services)
+ self.assertEqual(
+ expected_api_config,
+ self.converter.ConvertProjectConfig(project_config))
+
+ def testConvertProjectConfig_NonMembers(self):
+ """We can convert a project_config for non project members"""
+ self.converter.user_auth = authdata.AuthData.FromUser(
+ self.cnxn, self.user_2, self.services)
+ project_config = self.services.config.GetProjectConfig(
+ self.cnxn, self.project_1.project_id)
+ api_config = self.converter.ConvertProjectConfig(project_config)
+
+ expected_default_query = project_config.member_default_query
+ self.assertEqual(expected_default_query, api_config.member_default_query)
+
+ expected_member_default_template = rnc.ConvertTemplateNames(
+ self.cnxn, project_config.project_id,
+ [project_config.default_template_for_developers], self.services).get(
+ project_config.default_template_for_developers)
+ self.assertEqual(
+ expected_member_default_template, api_config.member_default_template)
+
+ def testCreateProjectMember(self):
+ """We can create a ProjectMember."""
+ expected_project_member = project_objects_pb2.ProjectMember(
+ name='projects/proj/members/111',
+ role=project_objects_pb2.ProjectMember.ProjectRole.Value('OWNER'))
+ self.assertEqual(
+ expected_project_member,
+ self.converter.CreateProjectMember(self.cnxn, 789, 111, 'OWNER'))
+
+ def test_ConvertDateAction(self):
+ """We can convert from protorpc to protoc FieldDef.DateAction"""
+ date_type_settings = project_objects_pb2.FieldDef.DateTypeSettings
+
+ input_type = tracker_pb2.DateAction.NO_ACTION
+ actual = self.converter._ConvertDateAction(input_type)
+ expected = date_type_settings.DateAction.Value('NO_ACTION')
+ self.assertEqual(expected, actual)
+
+ input_type = tracker_pb2.DateAction.PING_OWNER_ONLY
+ actual = self.converter._ConvertDateAction(input_type)
+ expected = date_type_settings.DateAction.Value('NOTIFY_OWNER')
+ self.assertEqual(expected, actual)
+
+ input_type = tracker_pb2.DateAction.PING_PARTICIPANTS
+ actual = self.converter._ConvertDateAction(input_type)
+ expected = date_type_settings.DateAction.Value('NOTIFY_PARTICIPANTS')
+ self.assertEqual(expected, actual)
+
+ def test_ConvertRoleRequirements(self):
+ """We can convert from protorpc to protoc FieldDef.RoleRequirements"""
+ user_type_settings = project_objects_pb2.FieldDef.UserTypeSettings
+
+ actual = self.converter._ConvertRoleRequirements(False)
+ expected = user_type_settings.RoleRequirements.Value('NO_ROLE_REQUIREMENT')
+ self.assertEqual(expected, actual)
+
+ actual = self.converter._ConvertRoleRequirements(True)
+ expected = user_type_settings.RoleRequirements.Value('PROJECT_MEMBER')
+ self.assertEqual(expected, actual)
+
+ def test_ConvertNotifyTriggers(self):
+ """We can convert from protorpc to protoc FieldDef.NotifyTriggers"""
+ user_type_settings = project_objects_pb2.FieldDef.UserTypeSettings
+
+ input_type = tracker_pb2.NotifyTriggers.NEVER
+ actual = self.converter._ConvertNotifyTriggers(input_type)
+ expected = user_type_settings.NotifyTriggers.Value('NEVER')
+ self.assertEqual(expected, actual)
+
+ input_type = tracker_pb2.NotifyTriggers.ANY_COMMENT
+ actual = self.converter._ConvertNotifyTriggers(input_type)
+ expected = user_type_settings.NotifyTriggers.Value('ANY_COMMENT')
+ self.assertEqual(expected, actual)
+
+ def test_ConvertFieldDefType(self):
+ """We can convert from protorpc FieldType to protoc FieldDef.Type"""
+ input_type = tracker_pb2.FieldTypes.ENUM_TYPE
+ actual = self.converter._ConvertFieldDefType(input_type)
+ expected = project_objects_pb2.FieldDef.Type.Value('ENUM')
+ self.assertEqual(expected, actual)
+
+ input_type = tracker_pb2.FieldTypes.INT_TYPE
+ actual = self.converter._ConvertFieldDefType(input_type)
+ expected = project_objects_pb2.FieldDef.Type.Value('INT')
+ self.assertEqual(expected, actual)
+
+ input_type = tracker_pb2.FieldTypes.STR_TYPE
+ actual = self.converter._ConvertFieldDefType(input_type)
+ expected = project_objects_pb2.FieldDef.Type.Value('STR')
+ self.assertEqual(expected, actual)
+
+ input_type = tracker_pb2.FieldTypes.USER_TYPE
+ actual = self.converter._ConvertFieldDefType(input_type)
+ expected = project_objects_pb2.FieldDef.Type.Value('USER')
+ self.assertEqual(expected, actual)
+
+ input_type = tracker_pb2.FieldTypes.DATE_TYPE
+ actual = self.converter._ConvertFieldDefType(input_type)
+ expected = project_objects_pb2.FieldDef.Type.Value('DATE')
+ self.assertEqual(expected, actual)
+
+ input_type = tracker_pb2.FieldTypes.URL_TYPE
+ actual = self.converter._ConvertFieldDefType(input_type)
+ expected = project_objects_pb2.FieldDef.Type.Value('URL')
+ self.assertEqual(expected, actual)
+
+ def test_ConvertFieldDefType_BOOL(self):
+ """We raise exception for unsupported input type BOOL"""
+ input_type = tracker_pb2.FieldTypes.BOOL_TYPE
+ with self.assertRaises(ValueError) as cm:
+ self.converter._ConvertFieldDefType(input_type)
+ self.assertEqual(
+ 'Unsupported tracker_pb2.FieldType enum. Boolean types '
+ 'are unsupported and approval types are found in ApprovalDefs',
+ str(cm.exception))
+
+ def test_ConvertFieldDefType_APPROVAL(self):
+ """We raise exception for input type APPROVAL"""
+ input_type = tracker_pb2.FieldTypes.APPROVAL_TYPE
+ with self.assertRaises(ValueError) as cm:
+ self.converter._ConvertFieldDefType(input_type)
+ self.assertEqual(
+ 'Unsupported tracker_pb2.FieldType enum. Boolean types '
+ 'are unsupported and approval types are found in ApprovalDefs',
+ str(cm.exception))
+
+ def testConvertFieldDefs(self):
+ """We can convert field defs"""
+ project_config = self.services.config.GetProjectConfig(
+ self.cnxn, self.project_1.project_id)
+ input_fds = project_config.field_defs
+ output = self.converter.ConvertFieldDefs(
+ input_fds, self.project_1.project_id)
+ fd1_rn = rnc.ConvertFieldDefNames(
+ self.cnxn, [self.field_def_1], self.project_1.project_id,
+ self.services).get(self.field_def_1)
+ self.assertEqual(fd1_rn, output[0].name)
+ self.assertEqual(self.field_def_1_name, output[0].display_name)
+ self.assertEqual('', output[0].docstring)
+ self.assertEqual(
+ project_objects_pb2.FieldDef.Type.Value('STR'), output[0].type)
+ self.assertEqual(
+ project_objects_pb2.FieldDef.Type.Value('INT'), output[1].type)
+ self.assertEqual('', output[1].applicable_issue_type)
+ fd1_admin_editor = [rnc.ConvertUserName(self.user_1.user_id)]
+ self.assertEqual(fd1_admin_editor, output[0].admins)
+ self.assertEqual(fd1_admin_editor, output[5].editors)
+
+ def testConvertFieldDefs_Traits(self):
+ """We can convert FieldDefs with traits"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_1)
+ output = self.converter.ConvertFieldDefs(
+ [input_fd], self.project_1.project_id)
+ self.assertEqual(1, len(output))
+ expected_traits = [
+ project_objects_pb2.FieldDef.Traits.Value('REQUIRED'),
+ project_objects_pb2.FieldDef.Traits.Value('MULTIVALUED'),
+ project_objects_pb2.FieldDef.Traits.Value('PHASE')
+ ]
+ self.assertEqual(expected_traits, output[0].traits)
+
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_2)
+ output = self.converter.ConvertFieldDefs(
+ [input_fd], self.project_1.project_id)
+ self.assertEqual(1, len(output))
+ expected_traits = [
+ project_objects_pb2.FieldDef.Traits.Value('DEFAULT_HIDDEN')
+ ]
+ self.assertEqual(expected_traits, output[0].traits)
+
+ def testConvertFieldDefs_ApprovalParent(self):
+ """We can convert FieldDef with approval parents"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_6)
+ output = self.converter.ConvertFieldDefs(
+ [input_fd], self.project_1.project_id)
+ self.assertEqual(1, len(output))
+
+ approval_names_dict = rnc.ConvertApprovalDefNames(
+ self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+ self.services)
+ expected_approval_parent = approval_names_dict.get(input_fd.approval_id)
+ self.assertEqual(expected_approval_parent, output[0].approval_parent)
+
+ def testConvertFieldDefs_EnumTypeSettings(self):
+ """We can convert enum FieldDef and its settings"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_5)
+ output = self.converter.ConvertFieldDefs(
+ [input_fd], self.project_1.project_id)
+ self.assertEqual(1, len(output))
+
+ expected_settings = project_objects_pb2.FieldDef.EnumTypeSettings(
+ choices=[
+ Choice(
+ value='submarine', docstring=self.labeldef_2.label_docstring),
+ Choice(value='basket', docstring=self.labeldef_3.label_docstring)
+ ])
+ self.assertEqual(expected_settings, output[0].enum_settings)
+
+ def testConvertFieldDefs_IntTypeSettings(self):
+ """We can convert int FieldDef and its settings"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_2)
+ output = self.converter.ConvertFieldDefs(
+ [input_fd], self.project_1.project_id)
+ self.assertEqual(1, len(output))
+
+ expected_settings = project_objects_pb2.FieldDef.IntTypeSettings(
+ max_value=37)
+ self.assertEqual(expected_settings, output[0].int_settings)
+
+ def testConvertFieldDefs_StrTypeSettings(self):
+ """We can convert str FieldDef and its settings"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_1)
+ output = self.converter.ConvertFieldDefs(
+ [input_fd], self.project_1.project_id)
+ self.assertEqual(1, len(output))
+
+ expected_settings = project_objects_pb2.FieldDef.StrTypeSettings(
+ regex='abc')
+ self.assertEqual(expected_settings, output[0].str_settings)
+
+ def testConvertFieldDefs_UserTypeSettings(self):
+ """We can convert user FieldDef and its settings"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_8)
+ output = self.converter.ConvertFieldDefs(
+ [input_fd], self.project_1.project_id)
+ self.assertEqual(1, len(output))
+
+ user_settings = project_objects_pb2.FieldDef.UserTypeSettings
+ expected_settings = project_objects_pb2.FieldDef.UserTypeSettings(
+ role_requirements=user_settings.RoleRequirements.Value(
+ 'PROJECT_MEMBER'),
+ needs_perm='EDIT_PROJECT',
+ notify_triggers=user_settings.NotifyTriggers.Value('ANY_COMMENT'))
+ self.assertEqual(expected_settings, output[0].user_settings)
+
+ def testConvertFieldDefs_DateTypeSettings(self):
+ """We can convert user FieldDef and its settings"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_9)
+ output = self.converter.ConvertFieldDefs(
+ [input_fd], self.project_1.project_id)
+ self.assertEqual(1, len(output))
+
+ date_settings = project_objects_pb2.FieldDef.DateTypeSettings
+ expected_settings = project_objects_pb2.FieldDef.DateTypeSettings(
+ date_action=date_settings.DateAction.Value('NOTIFY_OWNER'))
+ self.assertEqual(expected_settings, output[0].date_settings)
+
+ def testConvertFieldDefs_SkipsApprovals(self):
+ """We skip over approval defs"""
+ project_config = self.services.config.GetProjectConfig(
+ self.cnxn, self.project_1.project_id)
+ input_fds = project_config.field_defs
+ # project_1 is set up to have 10 non-approval fields and 2 approval fields.
+ self.assertEqual(12, len(input_fds))
+ output = self.converter.ConvertFieldDefs(
+ input_fds, self.project_1.project_id)
+ # assert we skip approval fields
+ self.assertEqual(10, len(output))
+
+ def testConvertFieldDefs_NonexistentID(self):
+ """We skip over any field defs whose ID does not exist."""
+ input_fd = tracker_pb2.FieldDef(
+ field_id=self.dne_field_def_id,
+ project_id=self.project_1.project_id,
+ field_name='foobar',
+ field_type=tracker_pb2.FieldTypes('STR_TYPE'))
+
+ output = self.converter.ConvertFieldDefs(
+ [input_fd], self.project_1.project_id)
+ self.assertEqual(0, len(output))
+
+ def testConvertFieldDefs_Empty(self):
+ """We can handle empty list input"""
+ self.assertEqual(
+ [], self.converter.ConvertFieldDefs([], self.project_1.project_id))
+
+ def test_ComputeFieldDefTraits(self):
+ """We can get Sequence of Traits for a FieldDef"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_1)
+ actual = self.converter._ComputeFieldDefTraits(input_fd)
+ expected = [
+ project_objects_pb2.FieldDef.Traits.Value('REQUIRED'),
+ project_objects_pb2.FieldDef.Traits.Value('MULTIVALUED'),
+ project_objects_pb2.FieldDef.Traits.Value('PHASE')
+ ]
+ self.assertEqual(expected, actual)
+
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_2)
+ actual = self.converter._ComputeFieldDefTraits(input_fd)
+ expected = [project_objects_pb2.FieldDef.Traits.Value('DEFAULT_HIDDEN')]
+ self.assertEqual(expected, actual)
+
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_7)
+ actual = self.converter._ComputeFieldDefTraits(input_fd)
+ expected = [project_objects_pb2.FieldDef.Traits.Value('RESTRICTED')]
+ self.assertEqual(expected, actual)
+
+ def test_ComputeFieldDefTraits_Empty(self):
+ """We return an empty Sequence of Traits for plain FieldDef"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_3)
+ actual = self.converter._ComputeFieldDefTraits(input_fd)
+ self.assertEqual([], actual)
+
+ def test_GetEnumFieldChoices(self):
+ """We can get all choices for an enum field"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_5)
+ actual = self.converter._GetEnumFieldChoices(input_fd)
+ expected = [
+ Choice(
+ value=self.labeldef_2.label.split('-')[1],
+ docstring=self.labeldef_2.label_docstring),
+ Choice(
+ value=self.labeldef_3.label.split('-')[1],
+ docstring=self.labeldef_3.label_docstring),
+ ]
+ self.assertEqual(expected, actual)
+
+ def test_GetEnumFieldChoices_NotEnumField(self):
+ """We raise exception for non-enum-field"""
+ input_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.field_def_1)
+ with self.assertRaises(ValueError) as cm:
+ self.converter._GetEnumFieldChoices(input_fd)
+ self.assertEqual(
+ 'Cannot get value from label for non-enum-type field', str(
+ cm.exception))
+
+ def testConvertApprovalDefs(self):
+ """We can convert ApprovalDefs"""
+ input_ad = self._GetApprovalDefById(
+ self.project_1.project_id, self.approval_def_1_id)
+ actual = self.converter.ConvertApprovalDefs(
+ [input_ad], self.project_1.project_id)
+
+ resource_names_dict = rnc.ConvertApprovalDefNames(
+ self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+ self.services)
+ expected_name = resource_names_dict.get(self.approval_def_1_id)
+ self.assertEqual(actual[0].name, expected_name)
+ self.assertEqual(actual[0].display_name, self.approval_def_1_name)
+ matching_fd = self._GetFieldDefById(
+ self.project_1.project_id, self.approval_def_1_id)
+ expected_docstring = matching_fd.docstring
+ self.assertEqual(actual[0].docstring, expected_docstring)
+ self.assertEqual(actual[0].survey, self.approval_def_1.survey)
+ expected_approvers = [rnc.ConvertUserName(self.user_2.user_id)]
+ self.assertEqual(actual[0].approvers, expected_approvers)
+ expected_admins = [rnc.ConvertUserName(self.user_1.user_id)]
+ self.assertEqual(actual[0].admins, expected_admins)
+
+ def testConvertApprovalDefs_Empty(self):
+ """We can handle empty case"""
+ actual = self.converter.ConvertApprovalDefs([], self.project_1.project_id)
+ self.assertEqual(actual, [])
+
+ def testConvertApprovalDefs_SkipsNonApprovalDefs(self):
+ """We skip if no matching field def exists"""
+ input_ad = tracker_pb2.ApprovalDef(
+ approval_id=self.dne_field_def_id,
+ approver_ids=[self.user_2.user_id],
+ survey='anything goes')
+ actual = self.converter.ConvertApprovalDefs(
+ [input_ad], self.project_1.project_id)
+ self.assertEqual(actual, [])
+
+ def testConvertLabelDefs(self):
+ """We can convert LabelDefs"""
+ actual = self.converter.ConvertLabelDefs(
+ [self.labeldef_1, self.labeldef_5], self.project_1.project_id)
+ resource_names_dict = rnc.ConvertLabelDefNames(
+ self.cnxn, [self.labeldef_1.label, self.labeldef_5.label],
+ self.project_1.project_id, self.services)
+ expected_0_name = resource_names_dict.get(self.labeldef_1.label)
+ expected_0 = project_objects_pb2.LabelDef(
+ name=expected_0_name,
+ value=self.labeldef_1.label,
+ docstring=self.labeldef_1.label_docstring,
+ state=project_objects_pb2.LabelDef.LabelDefState.Value('ACTIVE'))
+ self.assertEqual(expected_0, actual[0])
+ expected_1_name = resource_names_dict.get(self.labeldef_5.label)
+ expected_1 = project_objects_pb2.LabelDef(
+ name=expected_1_name,
+ value=self.labeldef_5.label,
+ docstring=self.labeldef_5.label_docstring,
+ state=project_objects_pb2.LabelDef.LabelDefState.Value('DEPRECATED'))
+ self.assertEqual(expected_1, actual[1])
+
+ def testConvertLabelDefs_Empty(self):
+ """We can handle empty input case"""
+ actual = self.converter.ConvertLabelDefs([], self.project_1.project_id)
+ self.assertEqual([], actual)
+
+ def testConvertStatusDefs(self):
+ """We can convert StatusDefs"""
+ actual = self.converter.ConvertStatusDefs(
+ self.predefined_statuses, self.project_1.project_id)
+ self.assertEqual(len(actual), 4)
+
+ input_names = [sd.status for sd in self.predefined_statuses]
+ names = rnc.ConvertStatusDefNames(
+ self.cnxn, input_names, self.project_1.project_id, self.services)
+ self.assertEqual(names[self.status_1.status], actual[0].name)
+ self.assertEqual(names[self.status_2.status], actual[1].name)
+ self.assertEqual(names[self.status_3.status], actual[2].name)
+ self.assertEqual(names[self.status_4.status], actual[3].name)
+
+ self.assertEqual(self.status_1.status, actual[0].value)
+ self.assertEqual(
+ project_objects_pb2.StatusDef.StatusDefType.Value('OPEN'),
+ actual[0].type)
+ self.assertEqual(0, actual[0].rank)
+ self.assertEqual(self.status_1.status_docstring, actual[0].docstring)
+ self.assertEqual(
+ project_objects_pb2.StatusDef.StatusDefState.Value('ACTIVE'),
+ actual[0].state)
+
+ def testConvertStatusDefs_Empty(self):
+ """Can handle empty input case"""
+ actual = self.converter.ConvertStatusDefs([], self.project_1.project_id)
+ self.assertEqual([], actual)
+
+ def testConvertStatusDefs_Rank(self):
+ """Rank is indepdendent of input order"""
+ input_sds = [self.status_2, self.status_4, self.status_3, self.status_1]
+ actual = self.converter.ConvertStatusDefs(
+ input_sds, self.project_1.project_id)
+ self.assertEqual(1, actual[0].rank)
+ self.assertEqual(3, actual[1].rank)
+
+ def testConvertStatusDefs_type_MERGED(self):
+ """Includes mergeable status when parsed from project config"""
+ actual = self.converter.ConvertStatusDefs(
+ [self.status_2], self.project_1.project_id)
+ self.assertEqual(
+ project_objects_pb2.StatusDef.StatusDefType.Value('MERGED'),
+ actual[0].type)
+
+ def testConvertStatusDefs_state_DEPRECATED(self):
+ """Includes deprecated status"""
+ actual = self.converter.ConvertStatusDefs(
+ [self.status_4], self.project_1.project_id)
+ self.assertEqual(
+ project_objects_pb2.StatusDef.StatusDefState.Value('DEPRECATED'),
+ actual[0].state)
+
+ def testConvertComponentDef(self):
+ now = 123
+ project = self.services.project.TestAddProject('comp-test', project_id=987)
+ config = fake.MakeTestConfig(project.project_id, [], [])
+ component_def = fake.MakeTestComponentDef(
+ project.project_id, 1, path='Chickens>Dickens')
+ component_def.created = now
+ config.component_defs = [component_def]
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ actual = self.converter.ConvertComponentDef(component_def)
+ expected = project_objects_pb2.ComponentDef(
+ name='projects/comp-test/componentDefs/1',
+ value='Chickens>Dickens',
+ state=project_objects_pb2.ComponentDef.ComponentDefState.Value(
+ 'ACTIVE'),
+ create_time=timestamp_pb2.Timestamp(seconds=now),
+ modify_time=timestamp_pb2.Timestamp())
+ self.assertEqual(actual, expected)
+
+ def testConvertComponentDefs(self):
+ """We can convert ComponentDefs"""
+ project_config = self.services.config.GetProjectConfig(
+ self.cnxn, self.project_1.project_id)
+ self.assertEqual(len(project_config.component_defs), 2)
+
+ actual = self.converter.ConvertComponentDefs(
+ project_config.component_defs, self.project_1.project_id)
+ self.assertEqual(2, len(actual))
+
+ resource_names_dict = rnc.ConvertComponentDefNames(
+ self.cnxn, [self.component_def_1_id, self.component_def_2_id],
+ self.project_1.project_id, self.services)
+ self.assertEqual(
+ resource_names_dict.get(self.component_def_1_id), actual[0].name)
+ self.assertEqual(
+ resource_names_dict.get(self.component_def_2_id), actual[1].name)
+ self.assertEqual(self.component_def_1_path, actual[0].value)
+ self.assertEqual(self.component_def_2_path, actual[1].value)
+ self.assertEqual('cd1_docstring', actual[0].docstring)
+ self.assertEqual(
+ project_objects_pb2.ComponentDef.ComponentDefState.Value('ACTIVE'),
+ actual[0].state)
+ self.assertEqual(
+ project_objects_pb2.ComponentDef.ComponentDefState.Value('DEPRECATED'),
+ actual[1].state)
+ # component_def 1 and 2 have the same admins, ccs, creator, and create_time
+ expected_admins = [rnc.ConvertUserName(self.user_1.user_id)]
+ self.assertEqual(expected_admins, actual[0].admins)
+ expected_ccs = [rnc.ConvertUserName(self.user_2.user_id)]
+ self.assertEqual(expected_ccs, actual[0].ccs)
+ expected_creator = rnc.ConvertUserName(self.user_1.user_id)
+ self.assertEqual(expected_creator, actual[0].creator)
+ expected_create_time = timestamp_pb2.Timestamp(seconds=self.PAST_TIME)
+ self.assertEqual(expected_create_time, actual[0].create_time)
+
+ expected_labels = [ld.label for ld in self.predefined_labels]
+ self.assertEqual(expected_labels, actual[0].labels)
+ self.assertEqual([], actual[1].labels)
+
+ def testConvertComponentDefs_Empty(self):
+ """Can handle empty input case"""
+ actual = self.converter.ConvertComponentDefs([], self.project_1.project_id)
+ self.assertEqual([], actual)
+
+ def testConvertProjectSavedQueries(self):
+ """We can convert ProjectSavedQueries"""
+ input_psqs = [self.psq_2]
+ actual = self.converter.ConvertProjectSavedQueries(
+ input_psqs, self.project_1.project_id)
+ self.assertEqual(1, len(actual))
+
+ resource_names_dict = rnc.ConvertProjectSavedQueryNames(
+ self.cnxn, [self.psq_2.query_id], self.project_1.project_id,
+ self.services)
+ self.assertEqual(
+ resource_names_dict.get(self.psq_2.query_id), actual[0].name)
+ self.assertEqual(self.psq_2.name, actual[0].display_name)
+ self.assertEqual(self.psq_2.query, actual[0].query)
+
+ def testConvertProjectSavedQueries_ExpandsBasedOn(self):
+ """We expand query to include base_query_id"""
+ actual = self.converter.ConvertProjectSavedQueries(
+ [self.psq_1], self.project_1.project_id)
+ expected_query = '{} {}'.format(
+ tbo.GetBuiltInQuery(self.psq_1.base_query_id), self.psq_1.query)
+ self.assertEqual(expected_query, actual[0].query)
+
+ def testConvertProjectSavedQueries_NotInProject(self):
+ """We skip over saved queries that don't belong to this project"""
+ psq_not_registered = tracker_pb2.SavedQuery(
+ query_id=4, name='psq no registered name', query='no registered')
+ actual = self.converter.ConvertProjectSavedQueries(
+ [psq_not_registered], self.project_1.project_id)
+ self.assertEqual([], actual)
+
+ def testConvertProjectSavedQueries_Empty(self):
+ """We can handle empty inputs"""
+ actual = self.converter.ConvertProjectSavedQueries(
+ [], self.project_1.project_id)
+ self.assertEqual([], actual)