Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1 | # Copyright 2020 The Chromium Authors |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 4 | """Tests for converting internal protorpc to external protoc.""" |
| 5 | |
| 6 | from __future__ import print_function |
| 7 | from __future__ import division |
| 8 | from __future__ import absolute_import |
| 9 | |
| 10 | import copy |
| 11 | import difflib |
| 12 | import logging |
| 13 | import unittest |
| 14 | |
| 15 | import mock |
| 16 | from google.protobuf import field_mask_pb2 |
| 17 | from google.protobuf import timestamp_pb2 |
| 18 | |
| 19 | from api import resource_name_converters as rnc |
| 20 | from api.v3 import converters |
| 21 | from api.v3.api_proto import feature_objects_pb2 |
| 22 | from api.v3.api_proto import issues_pb2 |
| 23 | from api.v3.api_proto import issue_objects_pb2 |
| 24 | from api.v3.api_proto import user_objects_pb2 |
| 25 | from api.v3.api_proto import project_objects_pb2 |
| 26 | from framework import authdata |
| 27 | from framework import exceptions |
| 28 | from framework import framework_constants |
| 29 | from framework import framework_helpers |
| 30 | from framework import monorailcontext |
| 31 | from testing import fake |
| 32 | from testing import testing_helpers |
| 33 | from tracker import field_helpers |
| 34 | from services import service_manager |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 35 | from mrproto import tracker_pb2 |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 36 | from tracker import tracker_bizobj as tbo |
| 37 | |
| 38 | EXPLICIT_DERIVATION = issue_objects_pb2.Derivation.Value('EXPLICIT') |
| 39 | RULE_DERIVATION = issue_objects_pb2.Derivation.Value('RULE') |
| 40 | Choice = project_objects_pb2.FieldDef.EnumTypeSettings.Choice |
| 41 | |
| 42 | CURRENT_TIME = 12346.78 |
| 43 | |
| 44 | |
| 45 | class ConverterFunctionsTest(unittest.TestCase): |
| 46 | |
| 47 | def setUp(self): |
| 48 | self.services = service_manager.Services( |
| 49 | issue=fake.IssueService(), |
| 50 | project=fake.ProjectService(), |
| 51 | usergroup=fake.UserGroupService(), |
| 52 | user=fake.UserService(), |
| 53 | config=fake.ConfigService(), |
| 54 | template=fake.TemplateService(), |
| 55 | features=fake.FeaturesService()) |
| 56 | self.cnxn = fake.MonorailConnection() |
| 57 | self.mc = monorailcontext.MonorailContext(self.services, cnxn=self.cnxn) |
| 58 | self.converter = converters.Converter(self.mc, self.services) |
| 59 | self.PAST_TIME = int(CURRENT_TIME - 1) |
| 60 | self.project_1 = self.services.project.TestAddProject( |
| 61 | 'proj', project_id=789) |
| 62 | self.project_2 = self.services.project.TestAddProject( |
| 63 | 'goose', project_id=788) |
| 64 | self.user_1 = self.services.user.TestAddUser('one@example.com', 111) |
| 65 | self.user_2 = self.services.user.TestAddUser('two@example.com', 222) |
| 66 | self.user_3 = self.services.user.TestAddUser('three@example.com', 333) |
| 67 | self.services.project.TestAddProjectMembers( |
| 68 | [self.user_1.user_id], self.project_1, 'CONTRIBUTOR_ROLE') |
| 69 | |
| 70 | self.field_def_1_name = 'test_field_1' |
| 71 | self.field_def_1 = self._CreateFieldDef( |
| 72 | self.project_1.project_id, |
| 73 | self.field_def_1_name, |
| 74 | 'STR_TYPE', |
| 75 | admin_ids=[self.user_1.user_id], |
| 76 | is_required=True, |
| 77 | is_multivalued=True, |
| 78 | is_phase_field=True, |
| 79 | regex='abc') |
| 80 | self.field_def_2_name = 'test_field_2' |
| 81 | self.field_def_2 = self._CreateFieldDef( |
| 82 | self.project_1.project_id, |
| 83 | self.field_def_2_name, |
| 84 | 'INT_TYPE', |
| 85 | max_value=37, |
| 86 | is_niche=True) |
| 87 | self.field_def_3_name = 'days' |
| 88 | self.field_def_3 = self._CreateFieldDef( |
| 89 | self.project_1.project_id, self.field_def_3_name, 'ENUM_TYPE') |
| 90 | self.field_def_4_name = 'OS' |
| 91 | self.field_def_4 = self._CreateFieldDef( |
| 92 | self.project_1.project_id, self.field_def_4_name, 'ENUM_TYPE') |
| 93 | self.field_def_5_name = 'yellow' |
| 94 | self.field_def_5 = self._CreateFieldDef( |
| 95 | self.project_1.project_id, self.field_def_5_name, 'ENUM_TYPE') |
| 96 | self.field_def_7_name = 'redredred' |
| 97 | self.field_def_7 = self._CreateFieldDef( |
| 98 | self.project_1.project_id, |
| 99 | self.field_def_7_name, |
| 100 | 'ENUM_TYPE', |
| 101 | is_restricted_field=True, |
| 102 | editor_ids=[self.user_1.user_id]) |
| 103 | self.field_def_8_name = 'dogandcat' |
| 104 | self.field_def_8 = self._CreateFieldDef( |
| 105 | self.project_1.project_id, |
| 106 | self.field_def_8_name, |
| 107 | 'USER_TYPE', |
| 108 | needs_member=True, |
| 109 | needs_perm='EDIT_PROJECT', |
| 110 | notify_on=tracker_pb2.NotifyTriggers.ANY_COMMENT) |
| 111 | self.field_def_9_name = 'catanddog' |
| 112 | self.field_def_9 = self._CreateFieldDef( |
| 113 | self.project_1.project_id, |
| 114 | self.field_def_9_name, |
| 115 | 'DATE_TYPE', |
| 116 | date_action_str='ping_owner_only') |
| 117 | self.field_def_10_name = 'url' |
| 118 | self.field_def_10 = self._CreateFieldDef( |
| 119 | self.project_1.project_id, self.field_def_10_name, 'URL_TYPE') |
| 120 | self.field_def_project2_name = 'lorem' |
| 121 | self.field_def_project2 = self._CreateFieldDef( |
| 122 | self.project_2.project_id, self.field_def_project2_name, 'ENUM_TYPE') |
| 123 | self.approval_def_1_name = 'approval_field_1' |
| 124 | self.approval_def_1_id = self._CreateFieldDef( |
| 125 | self.project_1.project_id, |
| 126 | self.approval_def_1_name, |
| 127 | 'APPROVAL_TYPE', |
| 128 | docstring='ad_1_docstring', |
| 129 | admin_ids=[self.user_1.user_id]) |
| 130 | self.approval_def_1 = tracker_pb2.ApprovalDef( |
| 131 | approval_id=self.approval_def_1_id, |
| 132 | approver_ids=[self.user_2.user_id], |
| 133 | survey='approval_def_1 survey') |
| 134 | self.approval_def_2_name = 'approval_field_1' |
| 135 | self.approval_def_2_id = self._CreateFieldDef( |
| 136 | self.project_1.project_id, |
| 137 | self.approval_def_2_name, |
| 138 | 'APPROVAL_TYPE', |
| 139 | docstring='ad_2_docstring', |
| 140 | admin_ids=[self.user_1.user_id]) |
| 141 | self.approval_def_2 = tracker_pb2.ApprovalDef( |
| 142 | approval_id=self.approval_def_2_id, |
| 143 | approver_ids=[self.user_2.user_id], |
| 144 | survey='approval_def_2 survey') |
| 145 | approval_defs = [self.approval_def_1, self.approval_def_2] |
| 146 | self.field_def_6_name = 'simonsays' |
| 147 | self.field_def_6 = self._CreateFieldDef( |
| 148 | self.project_1.project_id, |
| 149 | self.field_def_6_name, |
| 150 | 'STR_TYPE', |
| 151 | approval_id=self.approval_def_1_id) |
| 152 | self.dne_field_def_id = 999999 |
| 153 | self.fv_1_value = u'some_string_field_value' |
| 154 | self.fv_1 = fake.MakeFieldValue( |
| 155 | field_id=self.field_def_1, str_value=self.fv_1_value, derived=False) |
| 156 | self.fv_1_derived = fake.MakeFieldValue( |
| 157 | field_id=self.field_def_1, str_value=self.fv_1_value, derived=True) |
| 158 | self.fv_6 = fake.MakeFieldValue( |
| 159 | field_id=self.field_def_6, str_value=u'touch-nose', derived=False) |
| 160 | self.phase_1_id = 123123 |
| 161 | self.phase_1 = fake.MakePhase(self.phase_1_id, name='some phase name') |
| 162 | self.av_1 = fake.MakeApprovalValue( |
| 163 | self.approval_def_1_id, |
| 164 | setter_id=self.user_1.user_id, |
| 165 | set_on=self.PAST_TIME, |
| 166 | approver_ids=[self.user_2.user_id], |
| 167 | phase_id=self.phase_1_id) |
| 168 | self.av_2 = fake.MakeApprovalValue( |
| 169 | self.approval_def_1_id, |
| 170 | setter_id=self.user_1.user_id, |
| 171 | set_on=self.PAST_TIME, |
| 172 | approver_ids=[self.user_2.user_id]) |
| 173 | |
| 174 | self.issue_1 = fake.MakeTestIssue( |
| 175 | self.project_1.project_id, |
| 176 | 1, |
| 177 | 'sum', |
| 178 | 'New', |
| 179 | self.user_1.user_id, |
| 180 | cc_ids=[self.user_2.user_id], |
| 181 | derived_cc_ids=[self.user_3.user_id], |
| 182 | project_name=self.project_1.project_name, |
| 183 | star_count=1, |
| 184 | labels=['label-a', 'label-b', 'days-1'], |
| 185 | derived_owner_id=self.user_2.user_id, |
| 186 | derived_status='Fixed', |
| 187 | derived_labels=['label-derived', 'OS-mac', 'label-derived-2'], |
| 188 | component_ids=[1, 2], |
| 189 | merged_into_external='b/1', |
| 190 | derived_component_ids=[3, 4], |
| 191 | attachment_count=5, |
| 192 | field_values=[self.fv_1, self.fv_1_derived], |
| 193 | opened_timestamp=self.PAST_TIME, |
| 194 | modified_timestamp=self.PAST_TIME, |
| 195 | approval_values=[self.av_1], |
| 196 | phases=[self.phase_1]) |
| 197 | self.issue_2 = fake.MakeTestIssue( |
| 198 | self.project_2.project_id, |
| 199 | 2, |
| 200 | 'sum2', |
| 201 | None, |
| 202 | None, |
| 203 | reporter_id=self.user_1.user_id, |
| 204 | project_name=self.project_2.project_name, |
| 205 | merged_into=self.issue_1.issue_id, |
| 206 | opened_timestamp=self.PAST_TIME, |
| 207 | modified_timestamp=self.PAST_TIME, |
| 208 | closed_timestamp=self.PAST_TIME, |
| 209 | derived_status='Fixed', |
| 210 | derived_owner_id=self.user_2.user_id, |
| 211 | is_spam=True) |
| 212 | self.services.issue.TestAddIssue(self.issue_1) |
| 213 | self.services.issue.TestAddIssue(self.issue_2) |
| 214 | |
| 215 | self.template_0 = self.services.template.TestAddIssueTemplateDef( |
| 216 | 11110, self.project_1.project_id, 'template0') |
| 217 | self.template_1_label1_value = '2' |
| 218 | self.template_1_labels = [ |
| 219 | 'pri-1', '{}-{}'.format( |
| 220 | self.field_def_3_name, self.template_1_label1_value) |
| 221 | ] |
| 222 | self.template_1 = self.services.template.TestAddIssueTemplateDef( |
| 223 | 11111, |
| 224 | self.project_1.project_id, |
| 225 | 'template1', |
| 226 | content='foobar', |
| 227 | summary='foo', |
| 228 | admin_ids=[self.user_2.user_id], |
| 229 | owner_id=self.user_1.user_id, |
| 230 | labels=self.template_1_labels, |
| 231 | component_ids=[654], |
| 232 | field_values=[self.fv_1], |
| 233 | approval_values=[self.av_1], |
| 234 | phases=[self.phase_1]) |
| 235 | self.template_2 = self.services.template.TestAddIssueTemplateDef( |
| 236 | 11112, |
| 237 | self.project_1.project_id, |
| 238 | 'template2', |
| 239 | members_only=True, |
| 240 | owner_defaults_to_member=True) |
| 241 | self.template_3 = self.services.template.TestAddIssueTemplateDef( |
| 242 | 11113, |
| 243 | self.project_1.project_id, |
| 244 | 'template3', |
| 245 | field_values=[self.fv_1], |
| 246 | approval_values=[self.av_2], |
| 247 | ) |
| 248 | self.dne_template = tracker_pb2.TemplateDef( |
| 249 | name='dne_template_name', template_id=11114) |
| 250 | self.labeldef_1 = tracker_pb2.LabelDef( |
| 251 | label='white-mountain', |
| 252 | label_docstring='test label doc string for white-mountain') |
| 253 | self.labeldef_2 = tracker_pb2.LabelDef( |
| 254 | label='yellow-submarine', |
| 255 | label_docstring='Submarine choice for yellow enum field') |
| 256 | self.labeldef_3 = tracker_pb2.LabelDef( |
| 257 | label='yellow-basket', |
| 258 | label_docstring='Basket choice for yellow enum field') |
| 259 | self.labeldef_4 = tracker_pb2.LabelDef( |
| 260 | label='yellow-tasket', |
| 261 | label_docstring='Deprecated tasket choice for yellow enum field', |
| 262 | deprecated=True) |
| 263 | self.labeldef_5 = tracker_pb2.LabelDef( |
| 264 | label='mont-blanc', |
| 265 | label_docstring='test label doc string for mont-blanc', |
| 266 | deprecated=True) |
| 267 | self.predefined_labels = [ |
| 268 | self.labeldef_1, self.labeldef_2, self.labeldef_3, self.labeldef_4, |
| 269 | self.labeldef_5 |
| 270 | ] |
| 271 | test_label_ids = {} |
| 272 | for index, ld in enumerate(self.predefined_labels): |
| 273 | test_label_ids[ld.label] = index |
| 274 | self.services.config.TestAddLabelsDict(test_label_ids) |
| 275 | self.status_1 = tracker_pb2.StatusDef( |
| 276 | status='New', means_open=True, status_docstring='status_1 docstring') |
| 277 | self.status_2 = tracker_pb2.StatusDef( |
| 278 | status='Duplicate', |
| 279 | means_open=False, |
| 280 | status_docstring='status_2 docstring') |
| 281 | self.status_3 = tracker_pb2.StatusDef( |
| 282 | status='Accepted', |
| 283 | means_open=True, |
| 284 | status_docstring='status_3_docstring') |
| 285 | self.status_4 = tracker_pb2.StatusDef( |
| 286 | status='Gibberish', |
| 287 | means_open=True, |
| 288 | status_docstring='status_4_docstring', |
| 289 | deprecated=True) |
| 290 | self.predefined_statuses = [ |
| 291 | self.status_1, self.status_2, self.status_3, self.status_4 |
| 292 | ] |
| 293 | self.component_def_1_path = 'foo' |
| 294 | self.component_def_1_id = self.services.config.CreateComponentDef( |
| 295 | self.cnxn, self.project_1.project_id, self.component_def_1_path, |
| 296 | 'cd1_docstring', False, [self.user_1.user_id], [self.user_2.user_id], |
| 297 | self.PAST_TIME, self.user_1.user_id, [0, 1, 2, 3, 4]) |
| 298 | self.component_def_2_path = 'foo>bar' |
| 299 | self.component_def_2_id = self.services.config.CreateComponentDef( |
| 300 | self.cnxn, self.project_1.project_id, self.component_def_2_path, |
| 301 | 'cd2_docstring', True, [self.user_1.user_id], [self.user_2.user_id], |
| 302 | self.PAST_TIME, self.user_1.user_id, []) |
| 303 | self.services.config.UpdateConfig( |
| 304 | self.cnxn, |
| 305 | self.project_1, |
| 306 | statuses_offer_merge=[self.status_2.status], |
| 307 | excl_label_prefixes=['type', 'priority'], |
| 308 | default_template_for_developers=self.template_2.template_id, |
| 309 | default_template_for_users=self.template_1.template_id, |
| 310 | list_prefs=('ID Summary', 'ID', 'status', 'owner', 'owner:me'), |
| 311 | # UpdateConfig accepts tuples rather than protorpc *Defs |
| 312 | well_known_labels=[ |
| 313 | (ld.label, ld.label_docstring, ld.deprecated) |
| 314 | for ld in self.predefined_labels |
| 315 | ], |
| 316 | approval_defs=[ |
| 317 | (ad.approval_id, ad.approver_ids, ad.survey) for ad in approval_defs |
| 318 | ], |
| 319 | well_known_statuses=[ |
| 320 | (sd.status, sd.status_docstring, sd.means_open, sd.deprecated) |
| 321 | for sd in self.predefined_statuses |
| 322 | ]) |
| 323 | # base_query_id 2 equates to "is:open", defined in tracker_constants. |
| 324 | self.psq_1 = tracker_pb2.SavedQuery( |
| 325 | query_id=2, name='psq1 name', base_query_id=2, query='foo=bar') |
| 326 | self.psq_2 = tracker_pb2.SavedQuery( |
| 327 | query_id=3, name='psq2 name', query='fizz=buzz') |
| 328 | self.services.features.UpdateCannedQueries( |
| 329 | self.cnxn, self.project_1.project_id, [self.psq_1, self.psq_2]) |
| 330 | |
| 331 | def _CreateFieldDef( |
| 332 | self, |
| 333 | project_id, |
| 334 | field_name, |
| 335 | field_type_str, |
| 336 | docstring=None, |
| 337 | min_value=None, |
| 338 | max_value=None, |
| 339 | regex=None, |
| 340 | needs_member=None, |
| 341 | needs_perm=None, |
| 342 | grants_perm=None, |
| 343 | notify_on=None, |
| 344 | date_action_str=None, |
| 345 | admin_ids=None, |
| 346 | editor_ids=None, |
| 347 | is_required=False, |
| 348 | is_niche=False, |
| 349 | is_multivalued=False, |
| 350 | is_phase_field=False, |
| 351 | approval_id=None, |
| 352 | is_restricted_field=False): |
| 353 | """Calls CreateFieldDef with reasonable defaults, returns the ID.""" |
| 354 | if admin_ids is None: |
| 355 | admin_ids = [] |
| 356 | if editor_ids is None: |
| 357 | editor_ids = [] |
| 358 | return self.services.config.CreateFieldDef( |
| 359 | self.cnxn, |
| 360 | project_id, |
| 361 | field_name, |
| 362 | field_type_str, |
| 363 | None, |
| 364 | None, |
| 365 | is_required, |
| 366 | is_niche, |
| 367 | is_multivalued, |
| 368 | min_value, |
| 369 | max_value, |
| 370 | regex, |
| 371 | needs_member, |
| 372 | needs_perm, |
| 373 | grants_perm, |
| 374 | notify_on, |
| 375 | date_action_str, |
| 376 | docstring, |
| 377 | admin_ids, |
| 378 | editor_ids, |
| 379 | is_phase_field=is_phase_field, |
| 380 | approval_id=approval_id, |
| 381 | is_restricted_field=is_restricted_field) |
| 382 | |
| 383 | def _GetFieldDefById(self, project_id, fd_id): |
| 384 | config = self.services.config.GetProjectConfig(self.cnxn, project_id) |
| 385 | return [fd for fd in config.field_defs if fd.field_id == fd_id][0] |
| 386 | |
| 387 | def _GetApprovalDefById(self, project_id, ad_id): |
| 388 | config = self.services.config.GetProjectConfig(self.cnxn, project_id) |
| 389 | return [ad for ad in config.approval_defs if ad.approval_id == ad_id][0] |
| 390 | |
| 391 | def testConvertHotlist(self): |
| 392 | """We can convert a Hotlist.""" |
| 393 | hotlist = fake.Hotlist( |
| 394 | 'Hotlist-Name', |
| 395 | 240, |
| 396 | default_col_spec='chicken goose', |
| 397 | is_private=False, |
| 398 | owner_ids=[111], |
| 399 | editor_ids=[222, 333], |
| 400 | summary='Hotlist summary', |
| 401 | description='Hotlist Description') |
| 402 | expected_api_hotlist = feature_objects_pb2.Hotlist( |
| 403 | name='hotlists/240', |
| 404 | display_name=hotlist.name, |
| 405 | owner= 'users/111', |
| 406 | summary=hotlist.summary, |
| 407 | description=hotlist.description, |
| 408 | editors=['users/222', 'users/333'], |
| 409 | hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value( |
| 410 | 'PUBLIC'), |
| 411 | default_columns=[ |
| 412 | issue_objects_pb2.IssuesListColumn(column='chicken'), |
| 413 | issue_objects_pb2.IssuesListColumn(column='goose') |
| 414 | ]) |
| 415 | self.converter.user_auth = authdata.AuthData.FromUser( |
| 416 | self.cnxn, self.user_1, self.services) |
| 417 | self.assertEqual( |
| 418 | expected_api_hotlist, self.converter.ConvertHotlist(hotlist)) |
| 419 | |
| 420 | def testConvertHotlist_DefaultValues(self): |
| 421 | """We can convert a Hotlist with some empty or default values.""" |
| 422 | hotlist = fake.Hotlist( |
| 423 | 'Hotlist-Name', |
| 424 | 241, |
| 425 | is_private=True, |
| 426 | owner_ids=[111], |
| 427 | summary='Hotlist summary', |
| 428 | description='Hotlist Description', |
| 429 | default_col_spec='') |
| 430 | expected_api_hotlist = feature_objects_pb2.Hotlist( |
| 431 | name='hotlists/241', |
| 432 | display_name=hotlist.name, |
| 433 | owner='users/111', |
| 434 | summary=hotlist.summary, |
| 435 | description=hotlist.description, |
| 436 | hotlist_privacy=feature_objects_pb2.Hotlist.HotlistPrivacy.Value( |
| 437 | 'PRIVATE')) |
| 438 | self.converter.user_auth = authdata.AuthData.FromUser( |
| 439 | self.cnxn, self.user_1, self.services) |
| 440 | self.assertEqual( |
| 441 | expected_api_hotlist, self.converter.ConvertHotlist(hotlist)) |
| 442 | |
| 443 | def testConvertHotlists(self): |
| 444 | """We can convert several Hotlists.""" |
| 445 | hotlists = [ |
| 446 | fake.Hotlist( |
| 447 | 'Hotlist-Name', |
| 448 | 241, |
| 449 | owner_ids=[111], |
| 450 | summary='Hotlist summary', |
| 451 | description='Hotlist Description'), |
| 452 | fake.Hotlist( |
| 453 | 'Hotlist-Name', |
| 454 | 241, |
| 455 | owner_ids=[111], |
| 456 | summary='Hotlist summary', |
| 457 | description='Hotlist Description') |
| 458 | ] |
| 459 | self.assertEqual(2, len(self.converter.ConvertHotlists(hotlists))) |
| 460 | |
| 461 | def testConvertHotlistItems(self): |
| 462 | """We can convert HotlistItems.""" |
| 463 | hotlist_item_fields = [ |
| 464 | (self.issue_1.issue_id, 21, 111, self.PAST_TIME, 'note2'), |
| 465 | (78900, 11, 222, self.PAST_TIME, 'note3'), # Does not exist. |
| 466 | (self.issue_2.issue_id, 1, 222, None, 'note1'), |
| 467 | ] |
| 468 | hotlist = fake.Hotlist( |
| 469 | 'Hotlist-Name', 241, hotlist_item_fields=hotlist_item_fields) |
| 470 | self.converter.user_auth = authdata.AuthData.FromUser( |
| 471 | self.cnxn, self.user_1, self.services) |
| 472 | api_items = self.converter.ConvertHotlistItems( |
| 473 | hotlist.hotlist_id, hotlist.items) |
| 474 | expected_create_time = timestamp_pb2.Timestamp() |
| 475 | expected_create_time.FromSeconds(self.PAST_TIME) |
| 476 | expected_items = [ |
| 477 | feature_objects_pb2.HotlistItem( |
| 478 | name='hotlists/241/items/proj.1', |
| 479 | issue='projects/proj/issues/1', |
| 480 | rank=1, |
| 481 | adder= 'users/111', |
| 482 | create_time=expected_create_time, |
| 483 | note='note2'), |
| 484 | feature_objects_pb2.HotlistItem( |
| 485 | name='hotlists/241/items/goose.2', |
| 486 | issue='projects/goose/issues/2', |
| 487 | rank=0, |
| 488 | adder='users/222', |
| 489 | note='note1') |
| 490 | ] |
| 491 | self.assertEqual(api_items, expected_items) |
| 492 | |
| 493 | def testConvertHotlistItems_Empty(self): |
| 494 | hotlist = fake.Hotlist('Hotlist-Name', 241) |
| 495 | self.converter.user_auth = authdata.AuthData.FromUser( |
| 496 | self.cnxn, self.user_1, self.services) |
| 497 | api_items = self.converter.ConvertHotlistItems( |
| 498 | hotlist.hotlist_id, hotlist.items) |
| 499 | self.assertEqual(api_items, []) |
| 500 | |
| 501 | @mock.patch('tracker.attachment_helpers.SignAttachmentID') |
| 502 | def testConvertComments(self, mock_SignAttachmentID): |
| 503 | """We can convert comments.""" |
| 504 | mock_SignAttachmentID.return_value = 2 |
| 505 | attach = tracker_pb2.Attachment( |
| 506 | attachment_id=1, |
| 507 | mimetype='image/png', |
| 508 | filename='example.png', |
| 509 | filesize=12345) |
| 510 | deleted_attach = tracker_pb2.Attachment( |
| 511 | attachment_id=2, |
| 512 | mimetype='image/png', |
| 513 | filename='deleted_example.png', |
| 514 | filesize=67890, |
| 515 | deleted=True) |
| 516 | initial_comment = tracker_pb2.IssueComment( |
| 517 | project_id=self.issue_1.project_id, |
| 518 | issue_id=self.issue_1.issue_id, |
| 519 | user_id=self.issue_1.reporter_id, |
| 520 | timestamp=self.PAST_TIME, |
| 521 | content='initial description', |
| 522 | sequence=0, |
| 523 | is_description=True, |
| 524 | description_num='1', |
| 525 | attachments=[attach, deleted_attach]) |
| 526 | deleted_comment = tracker_pb2.IssueComment( |
| 527 | project_id=self.issue_1.project_id, |
| 528 | issue_id=self.issue_1.issue_id, |
| 529 | timestamp=self.PAST_TIME, |
| 530 | deleted_by=self.issue_1.reporter_id, |
| 531 | sequence=1) |
| 532 | amendments = [ |
| 533 | tracker_pb2.Amendment( |
| 534 | field=tracker_pb2.FieldID.SUMMARY, newvalue='new', oldvalue='old'), |
| 535 | tracker_pb2.Amendment( |
| 536 | field=tracker_pb2.FieldID.OWNER, added_user_ids=[111]), |
| 537 | tracker_pb2.Amendment( |
| 538 | field=tracker_pb2.FieldID.CC, |
| 539 | added_user_ids=[111], |
| 540 | removed_user_ids=[222]), |
| 541 | tracker_pb2.Amendment( |
| 542 | field=tracker_pb2.FieldID.CUSTOM, |
| 543 | custom_field_name='EstDays', |
| 544 | newvalue='12') |
| 545 | ] |
| 546 | amendments_comment = tracker_pb2.IssueComment( |
| 547 | project_id=self.issue_1.project_id, |
| 548 | issue_id=self.issue_1.issue_id, |
| 549 | user_id=self.issue_1.reporter_id, |
| 550 | timestamp=self.PAST_TIME, |
| 551 | content='some amendments', |
| 552 | sequence=2, |
| 553 | amendments=amendments, |
| 554 | importer_id=1, # Not used in conversion, so nothing to verify. |
| 555 | approval_id=self.approval_def_1_id) |
| 556 | inbound_spam_comment = tracker_pb2.IssueComment( |
| 557 | project_id=self.issue_1.project_id, |
| 558 | issue_id=self.issue_1.issue_id, |
| 559 | user_id=self.issue_1.reporter_id, |
| 560 | timestamp=self.PAST_TIME, |
| 561 | content='content', |
| 562 | sequence=3, |
| 563 | inbound_message='inbound message', |
| 564 | is_spam=True) |
| 565 | expected_0 = issue_objects_pb2.Comment( |
| 566 | name='projects/proj/issues/1/comments/0', |
| 567 | state=issue_objects_pb2.IssueContentState.Value('ACTIVE'), |
| 568 | type=issue_objects_pb2.Comment.Type.Value('DESCRIPTION'), |
| 569 | content='initial description', |
| 570 | commenter='users/111', |
| 571 | create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 572 | attachments=[ |
| 573 | issue_objects_pb2.Comment.Attachment( |
| 574 | filename='example.png', |
| 575 | state=issue_objects_pb2.IssueContentState.Value('ACTIVE'), |
| 576 | size=12345, |
| 577 | media_type='image/png', |
| 578 | thumbnail_uri='attachment?aid=1&signed_aid=2&inline=1&thumb=1', |
| 579 | view_uri='attachment?aid=1&signed_aid=2&inline=1', |
| 580 | download_uri='attachment?aid=1&signed_aid=2'), |
| 581 | issue_objects_pb2.Comment.Attachment( |
| 582 | filename='deleted_example.png', |
| 583 | state=issue_objects_pb2.IssueContentState.Value('DELETED'), |
| 584 | media_type='image/png') |
| 585 | ]) |
| 586 | expected_1 = issue_objects_pb2.Comment( |
| 587 | name='projects/proj/issues/1/comments/1', |
| 588 | state=issue_objects_pb2.IssueContentState.Value('DELETED'), |
| 589 | type=issue_objects_pb2.Comment.Type.Value('COMMENT'), |
| 590 | create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME)) |
| 591 | expected_2 = issue_objects_pb2.Comment( |
| 592 | name='projects/proj/issues/1/comments/2', |
| 593 | state=issue_objects_pb2.IssueContentState.Value('ACTIVE'), |
| 594 | type=issue_objects_pb2.Comment.Type.Value('COMMENT'), |
| 595 | content='some amendments', |
| 596 | commenter='users/111', |
| 597 | create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 598 | approval='projects/proj/approvalDefs/%d' % self.approval_def_1_id, |
| 599 | amendments=[ |
| 600 | issue_objects_pb2.Comment.Amendment( |
| 601 | field_name='Summary', new_or_delta_value='new', |
| 602 | old_value='old'), |
| 603 | issue_objects_pb2.Comment.Amendment( |
| 604 | field_name='Owner', new_or_delta_value='o...@example.com'), |
| 605 | issue_objects_pb2.Comment.Amendment( |
| 606 | field_name='Cc', |
| 607 | new_or_delta_value='-t...@example.com o...@example.com'), |
| 608 | issue_objects_pb2.Comment.Amendment( |
| 609 | field_name='EstDays', new_or_delta_value='12') |
| 610 | ]) |
| 611 | expected_3 = issue_objects_pb2.Comment( |
| 612 | name='projects/proj/issues/1/comments/3', |
| 613 | state=issue_objects_pb2.IssueContentState.Value('SPAM'), |
| 614 | type=issue_objects_pb2.Comment.Type.Value('COMMENT'), |
| 615 | content='content', |
| 616 | commenter='users/111', |
| 617 | create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 618 | inbound_message='inbound message') |
| 619 | |
| 620 | comments = [ |
| 621 | initial_comment, deleted_comment, amendments_comment, |
| 622 | inbound_spam_comment |
| 623 | ] |
| 624 | actual = self.converter.ConvertComments(self.issue_1.issue_id, comments) |
| 625 | self.assertEqual(actual, [expected_0, expected_1, expected_2, expected_3]) |
| 626 | |
| 627 | def testConvertComments_Empty(self): |
| 628 | """We can convert an empty list of comments.""" |
| 629 | self.assertEqual( |
| 630 | self.converter.ConvertComments(self.issue_1.issue_id, []), []) |
| 631 | |
| 632 | def testConvertIssue(self): |
| 633 | """We can convert a single issue.""" |
| 634 | self.assertEqual(self.converter.ConvertIssue(self.issue_1), |
| 635 | self.converter.ConvertIssues([self.issue_1])[0]) |
| 636 | |
| 637 | def testConvertIssues(self): |
| 638 | """We can convert Issues.""" |
| 639 | blocked_on_1 = fake.MakeTestIssue( |
| 640 | self.project_1.project_id, |
| 641 | 3, |
| 642 | 'sum3', |
| 643 | 'New', |
| 644 | self.user_1.user_id, |
| 645 | issue_id=301, |
| 646 | project_name=self.project_1.project_name, |
| 647 | ) |
| 648 | blocked_on_2 = fake.MakeTestIssue( |
| 649 | self.project_2.project_id, |
| 650 | 4, |
| 651 | 'sum4', |
| 652 | 'New', |
| 653 | self.user_1.user_id, |
| 654 | issue_id=401, |
| 655 | project_name=self.project_2.project_name, |
| 656 | ) |
| 657 | blocking = fake.MakeTestIssue( |
| 658 | self.project_2.project_id, |
| 659 | 5, |
| 660 | 'sum5', |
| 661 | 'New', |
| 662 | self.user_1.user_id, |
| 663 | issue_id=501, |
| 664 | project_name=self.project_2.project_name, |
| 665 | ) |
| 666 | self.services.issue.TestAddIssue(blocked_on_1) |
| 667 | self.services.issue.TestAddIssue(blocked_on_2) |
| 668 | self.services.issue.TestAddIssue(blocking) |
| 669 | |
| 670 | # Reversing natural ordering to ensure order is respected. |
| 671 | self.issue_1.blocked_on_iids = [ |
| 672 | blocked_on_2.issue_id, blocked_on_1.issue_id |
| 673 | ] |
| 674 | self.issue_1.dangling_blocked_on_refs = [ |
| 675 | tracker_pb2.DanglingIssueRef(ext_issue_identifier='b/555'), |
| 676 | tracker_pb2.DanglingIssueRef(ext_issue_identifier='b/2') |
| 677 | ] |
| 678 | self.issue_1.blocking_iids = [blocking.issue_id] |
| 679 | self.issue_1.dangling_blocking_refs = [ |
| 680 | tracker_pb2.DanglingIssueRef(ext_issue_identifier='b/3') |
| 681 | ] |
| 682 | |
| 683 | issues = [self.issue_1, self.issue_2] |
| 684 | expected_1 = issue_objects_pb2.Issue( |
| 685 | name='projects/proj/issues/1', |
| 686 | summary='sum', |
| 687 | state=issue_objects_pb2.IssueContentState.Value('ACTIVE'), |
| 688 | status=issue_objects_pb2.Issue.StatusValue( |
| 689 | derivation=EXPLICIT_DERIVATION, status='New'), |
| 690 | reporter='users/111', |
| 691 | owner=issue_objects_pb2.Issue.UserValue( |
| 692 | derivation=EXPLICIT_DERIVATION, user='users/111'), |
| 693 | cc_users=[ |
| 694 | issue_objects_pb2.Issue.UserValue( |
| 695 | derivation=EXPLICIT_DERIVATION, user='users/222'), |
| 696 | issue_objects_pb2.Issue.UserValue( |
| 697 | derivation=RULE_DERIVATION, user='users/333') |
| 698 | ], |
| 699 | labels=[ |
| 700 | issue_objects_pb2.Issue.LabelValue( |
| 701 | derivation=EXPLICIT_DERIVATION, label='label-a'), |
| 702 | issue_objects_pb2.Issue.LabelValue( |
| 703 | derivation=EXPLICIT_DERIVATION, label='label-b'), |
| 704 | issue_objects_pb2.Issue.LabelValue( |
| 705 | derivation=RULE_DERIVATION, label='label-derived'), |
| 706 | issue_objects_pb2.Issue.LabelValue( |
| 707 | derivation=RULE_DERIVATION, label='label-derived-2') |
| 708 | ], |
| 709 | components=[ |
| 710 | issue_objects_pb2.Issue.ComponentValue( |
| 711 | derivation=EXPLICIT_DERIVATION, |
| 712 | component='projects/proj/componentDefs/1'), |
| 713 | issue_objects_pb2.Issue.ComponentValue( |
| 714 | derivation=EXPLICIT_DERIVATION, |
| 715 | component='projects/proj/componentDefs/2'), |
| 716 | issue_objects_pb2.Issue.ComponentValue( |
| 717 | derivation=RULE_DERIVATION, |
| 718 | component='projects/proj/componentDefs/3'), |
| 719 | issue_objects_pb2.Issue.ComponentValue( |
| 720 | derivation=RULE_DERIVATION, |
| 721 | component='projects/proj/componentDefs/4'), |
| 722 | ], |
| 723 | field_values=[ |
| 724 | issue_objects_pb2.FieldValue( |
| 725 | derivation=EXPLICIT_DERIVATION, |
| 726 | field='projects/proj/fieldDefs/%d' % self.field_def_1, |
| 727 | value=self.fv_1_value, |
| 728 | ), |
| 729 | issue_objects_pb2.FieldValue( |
| 730 | derivation=RULE_DERIVATION, |
| 731 | field='projects/proj/fieldDefs/%d' % self.field_def_1, |
| 732 | value=self.fv_1_value, |
| 733 | ), |
| 734 | issue_objects_pb2.FieldValue( |
| 735 | derivation=EXPLICIT_DERIVATION, |
| 736 | field='projects/proj/fieldDefs/%d' % self.field_def_3, |
| 737 | value='1', |
| 738 | ), |
| 739 | issue_objects_pb2.FieldValue( |
| 740 | derivation=RULE_DERIVATION, |
| 741 | field='projects/proj/fieldDefs/%d' % self.field_def_4, |
| 742 | value='mac', |
| 743 | ) |
| 744 | ], |
| 745 | merged_into_issue_ref=issue_objects_pb2.IssueRef(ext_identifier='b/1'), |
| 746 | blocked_on_issue_refs=[ |
| 747 | issue_objects_pb2.IssueRef(issue='projects/goose/issues/4'), |
| 748 | issue_objects_pb2.IssueRef(issue='projects/proj/issues/3'), |
| 749 | issue_objects_pb2.IssueRef(ext_identifier='b/555'), |
| 750 | issue_objects_pb2.IssueRef(ext_identifier='b/2') |
| 751 | ], |
| 752 | blocking_issue_refs=[ |
| 753 | issue_objects_pb2.IssueRef(issue='projects/goose/issues/5'), |
| 754 | issue_objects_pb2.IssueRef(ext_identifier='b/3') |
| 755 | ], |
| 756 | create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 757 | modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 758 | component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 759 | status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 760 | owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 761 | star_count=1, |
| 762 | attachment_count=5, |
| 763 | phases=[self.phase_1.name]) |
| 764 | expected_2 = issue_objects_pb2.Issue( |
| 765 | name='projects/goose/issues/2', |
| 766 | summary='sum2', |
| 767 | state=issue_objects_pb2.IssueContentState.Value('SPAM'), |
| 768 | status=issue_objects_pb2.Issue.StatusValue( |
| 769 | derivation=RULE_DERIVATION, status='Fixed'), |
| 770 | reporter='users/111', |
| 771 | owner=issue_objects_pb2.Issue.UserValue( |
| 772 | derivation=RULE_DERIVATION, user='users/222'), |
| 773 | merged_into_issue_ref=issue_objects_pb2.IssueRef( |
| 774 | issue='projects/proj/issues/1'), |
| 775 | create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 776 | close_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 777 | modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 778 | component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 779 | status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 780 | owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME)) |
| 781 | self.assertEqual( |
| 782 | self.converter.ConvertIssues(issues), [expected_1, expected_2]) |
| 783 | |
| 784 | def testConvertIssues_Empty(self): |
| 785 | """ConvertIssues works with no issues passed in.""" |
| 786 | self.assertEqual(self.converter.ConvertIssues([]), []) |
| 787 | |
| 788 | def testConvertIssues_NegativeAttachmentCount(self): |
| 789 | """Negative attachment counts are not set on issues.""" |
| 790 | issue = fake.MakeTestIssue( |
| 791 | self.project_1.project_id, |
| 792 | 3, |
| 793 | 'sum', |
| 794 | 'New', |
| 795 | owner_id=None, |
| 796 | reporter_id=111, |
| 797 | attachment_count=-10, |
| 798 | project_name=self.project_1.project_name, |
| 799 | opened_timestamp=self.PAST_TIME, |
| 800 | modified_timestamp=self.PAST_TIME) |
| 801 | self.services.issue.TestAddIssue(issue) |
| 802 | expected_issue = issue_objects_pb2.Issue( |
| 803 | name='projects/proj/issues/3', |
| 804 | state=issue_objects_pb2.IssueContentState.Value('ACTIVE'), |
| 805 | summary='sum', |
| 806 | status=issue_objects_pb2.Issue.StatusValue( |
| 807 | derivation=EXPLICIT_DERIVATION, status='New'), |
| 808 | reporter='users/111', |
| 809 | create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 810 | modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 811 | component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 812 | status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 813 | owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 814 | ) |
| 815 | self.assertEqual(self.converter.ConvertIssues([issue]), [expected_issue]) |
| 816 | |
| 817 | def testConvertIssues_FilterApprovalFV(self): |
| 818 | issue = fake.MakeTestIssue( |
| 819 | self.project_1.project_id, |
| 820 | 3, |
| 821 | 'sum', |
| 822 | 'New', |
| 823 | owner_id=None, |
| 824 | reporter_id=111, |
| 825 | attachment_count=-10, |
| 826 | project_name=self.project_1.project_name, |
| 827 | opened_timestamp=self.PAST_TIME, |
| 828 | modified_timestamp=self.PAST_TIME, |
| 829 | field_values=[self.fv_1, self.fv_6]) |
| 830 | self.services.issue.TestAddIssue(issue) |
| 831 | actual = self.converter.ConvertIssues([issue])[0] |
| 832 | |
| 833 | expected_fv = issue_objects_pb2.FieldValue( |
| 834 | derivation=EXPLICIT_DERIVATION, |
| 835 | field='projects/proj/fieldDefs/%d' % self.field_def_1, |
| 836 | value=self.fv_1_value, |
| 837 | ) |
| 838 | self.assertEqual(len(actual.field_values), 1) |
| 839 | self.assertEqual(actual.field_values[0], expected_fv) |
| 840 | |
| 841 | def testConvertUser(self): |
| 842 | """We can convert a single User.""" |
| 843 | self.user_1.vacation_message = 'non-empty-string' |
| 844 | self.converter.user_auth = authdata.AuthData.FromUser( |
| 845 | self.cnxn, self.user_1, self.services) |
| 846 | |
| 847 | expected_user = user_objects_pb2.User( |
| 848 | name='users/111', |
| 849 | display_name='one@example.com', |
| 850 | email='one@example.com', |
| 851 | availability_message='non-empty-string') |
| 852 | self.assertEqual(self.converter.ConvertUser(self.user_1), expected_user) |
| 853 | |
| 854 | |
| 855 | def testConvertUsers(self): |
| 856 | user_deleted = self.services.user.TestAddUser( |
| 857 | '', framework_constants.DELETED_USER_ID) |
| 858 | self.user_1.vacation_message = 'non-empty-string' |
| 859 | user_ids = [self.user_1.user_id, user_deleted.user_id] |
| 860 | self.converter.user_auth = authdata.AuthData.FromUser( |
| 861 | self.cnxn, self.user_1, self.services) |
| 862 | |
| 863 | expected_user_dict = { |
| 864 | self.user_1.user_id: |
| 865 | user_objects_pb2.User( |
| 866 | name='users/111', |
| 867 | display_name='one@example.com', |
| 868 | email='one@example.com', |
| 869 | availability_message='non-empty-string'), |
| 870 | user_deleted.user_id: |
| 871 | user_objects_pb2.User( |
| 872 | name='users/1', |
| 873 | display_name=framework_constants.DELETED_USER_NAME, |
| 874 | email='', |
| 875 | availability_message='User never visited'), |
| 876 | } |
| 877 | self.assertEqual(self.converter.ConvertUsers(user_ids), expected_user_dict) |
| 878 | |
| 879 | def testConvertProjectStars(self): |
| 880 | expected_stars = [ |
| 881 | user_objects_pb2.ProjectStar(name='users/111/projectStars/proj'), |
| 882 | user_objects_pb2.ProjectStar(name='users/111/projectStars/goose') |
| 883 | ] |
| 884 | self.assertEqual( |
| 885 | self.converter.ConvertProjectStars( |
| 886 | self.user_1.user_id, [self.project_1, self.project_2]), |
| 887 | expected_stars) |
| 888 | |
| 889 | def _Issue(self, project_id, local_id): |
| 890 | issue = tracker_pb2.Issue(owner_id=0) |
| 891 | issue.project_name = 'proj-%d' % project_id |
| 892 | issue.project_id = project_id |
| 893 | issue.local_id = local_id |
| 894 | issue.issue_id = project_id * 100 + local_id |
| 895 | return issue |
| 896 | |
| 897 | def testIngestAttachmentUploads(self): |
| 898 | up_1 = issues_pb2.AttachmentUpload( |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 899 | filename='clown.gif', content=b'iTs prOUnOuNcED JIF') |
| 900 | up_2 = issues_pb2.AttachmentUpload(filename='mowgli', content=b'cutest dog') |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 901 | |
| 902 | ingested = self.converter.IngestAttachmentUploads([up_1, up_2]) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 903 | expected = [ |
| 904 | framework_helpers.AttachmentUpload( |
| 905 | 'clown.gif', b'iTs prOUnOuNcED JIF', 'image/gif'), |
| 906 | framework_helpers.AttachmentUpload( |
| 907 | 'mowgli', b'cutest dog', 'text/plain') |
| 908 | ] |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 909 | self.assertEqual(ingested, expected) |
| 910 | |
| 911 | def testtIngestAttachmentUploads_Invalid(self): |
| 912 | up_1 = issues_pb2.AttachmentUpload(filename='clown.gif') |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 913 | up_2 = issues_pb2.AttachmentUpload(content=b'cutest dog') |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 914 | |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 915 | with self.assertRaisesRegex(exceptions.InputException, |
| 916 | 'Uploaded .+\nUploaded .+'): |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 917 | self.converter.IngestAttachmentUploads([up_1, up_2]) |
| 918 | |
| 919 | def testIngestIssueDeltas(self): |
| 920 | # Set up. |
| 921 | self.services.project.TestAddProject('proj-780', project_id=780) |
| 922 | config = fake.MakeTestConfig(780, [], []) |
| 923 | self.services.config.StoreConfig(self.cnxn, config) |
| 924 | |
| 925 | issue_1 = self._Issue(780, 1) |
| 926 | self.services.issue.TestAddIssue(issue_1) |
| 927 | issue_2 = self._Issue(780, 2) |
| 928 | self.services.issue.TestAddIssue(issue_2) |
| 929 | comp_1 = fake.MakeTestComponentDef(780, 1) |
| 930 | comp_2 = fake.MakeTestComponentDef(780, 2) |
| 931 | fd_str = fake.MakeTestFieldDef(1, 780, tracker_pb2.FieldTypes.STR_TYPE) |
| 932 | fd_enum = fake.MakeTestFieldDef( |
| 933 | 2, 780, tracker_pb2.FieldTypes.ENUM_TYPE, field_name='Kingdom') |
| 934 | config = fake.MakeTestConfig(780, [], []) |
| 935 | config.component_defs = [comp_1, comp_2] |
| 936 | config.field_defs = [fd_str, fd_enum] |
| 937 | self.services.config.StoreConfig(self.cnxn, config) |
| 938 | |
| 939 | # Issue and delta that changes all things. |
| 940 | api_issue_all = issue_objects_pb2.Issue( |
| 941 | name='projects/proj-780/issues/1', |
| 942 | status=issue_objects_pb2.Issue.StatusValue(status='Fixed'), |
| 943 | owner=issue_objects_pb2.Issue.UserValue(user='users/111'), |
| 944 | summary='honk honk.', |
| 945 | cc_users=[issue_objects_pb2.Issue.UserValue(user='users/222')], |
| 946 | components=[ |
| 947 | issue_objects_pb2.Issue.ComponentValue( |
| 948 | component='projects/proj-780/componentDefs/1') |
| 949 | ], |
| 950 | field_values=[ |
| 951 | issue_objects_pb2.FieldValue( |
| 952 | field='projects/proj-780/fieldDefs/1', value='chicken'), |
| 953 | issue_objects_pb2.FieldValue( |
| 954 | field='projects/proj-780/fieldDefs/2', value='come') |
| 955 | ], |
| 956 | labels=[issue_objects_pb2.Issue.LabelValue(label='ready')]) |
| 957 | mask_all = field_mask_pb2.FieldMask( |
| 958 | paths=[ |
| 959 | 'status', 'owner', 'summary', 'cc_users', 'labels', 'components', |
| 960 | 'field_values' |
| 961 | ]) |
| 962 | api_delta_all = issues_pb2.IssueDelta( |
| 963 | issue=api_issue_all, |
| 964 | update_mask=mask_all, |
| 965 | ccs_remove=['users/333'], |
| 966 | components_remove=['projects/proj-780/componentDefs/2'], |
| 967 | field_vals_remove=[ |
| 968 | issue_objects_pb2.FieldValue( |
| 969 | field='projects/proj-780/fieldDefs/1', value='rooster'), |
| 970 | issue_objects_pb2.FieldValue( |
| 971 | field='projects/proj-780/fieldDefs/2', value='leave') |
| 972 | ], |
| 973 | labels_remove=['not-ready']) |
| 974 | exp_fvs_add = [ |
| 975 | field_helpers.ParseOneFieldValue( |
| 976 | self.cnxn, self.services.user, fd_str, 'chicken') |
| 977 | ] |
| 978 | exp_fvs_remove = [ |
| 979 | field_helpers.ParseOneFieldValue( |
| 980 | self.cnxn, self.services.user, fd_str, 'rooster') |
| 981 | ] |
| 982 | expected_delta_all = tracker_pb2.IssueDelta( |
| 983 | status='Fixed', |
| 984 | owner_id=111, |
| 985 | summary='honk honk.', |
| 986 | cc_ids_add=[222], |
| 987 | cc_ids_remove=[333], |
| 988 | comp_ids_add=[1], |
| 989 | comp_ids_remove=[2], |
| 990 | field_vals_add=exp_fvs_add, |
| 991 | field_vals_remove=exp_fvs_remove, |
| 992 | labels_add=['ready', 'Kingdom-come'], |
| 993 | labels_remove=['not-ready', 'Kingdom-leave']) |
| 994 | |
| 995 | api_deltas = [api_delta_all] |
| 996 | |
| 997 | # Issue with all fields, but an empty mask. |
| 998 | api_issue_all_masked = issue_objects_pb2.Issue( |
| 999 | name='projects/proj-780/issues/2', |
| 1000 | status=issue_objects_pb2.Issue.StatusValue(status='Fixed'), |
| 1001 | owner=issue_objects_pb2.Issue.UserValue(user='users/111'), |
| 1002 | summary='honk honk.', |
| 1003 | cc_users=[issue_objects_pb2.Issue.UserValue(user='users/222')], |
| 1004 | components=[ |
| 1005 | issue_objects_pb2.Issue.ComponentValue( |
| 1006 | component='projects/proj-780/componentDefs/1') |
| 1007 | ], |
| 1008 | field_values=[ |
| 1009 | issue_objects_pb2.FieldValue( |
| 1010 | field='projects/proj-780/fieldDefs/1', value='chicken'), |
| 1011 | issue_objects_pb2.FieldValue( |
| 1012 | field='projects/proj-780/fieldDefs/2', value='come') |
| 1013 | ], |
| 1014 | labels=[issue_objects_pb2.Issue.LabelValue(label='ready')]) |
| 1015 | api_delta_all_masked = issues_pb2.IssueDelta( |
| 1016 | issue=api_issue_all_masked, |
| 1017 | update_mask=field_mask_pb2.FieldMask(paths=[]), |
| 1018 | ccs_remove=['users/333'], |
| 1019 | components_remove=['projects/proj-780/componentDefs/2'], |
| 1020 | field_vals_remove=[ |
| 1021 | issue_objects_pb2.FieldValue( |
| 1022 | field='projects/proj-780/fieldDefs/1', value='rooster'), |
| 1023 | issue_objects_pb2.FieldValue( |
| 1024 | field='projects/proj-780/fieldDefs/2', value='leave') |
| 1025 | ], |
| 1026 | labels_remove=['not-ready']) |
| 1027 | expected_delta_all_masked = tracker_pb2.IssueDelta( |
| 1028 | cc_ids_remove=[333], |
| 1029 | comp_ids_remove=[2], |
| 1030 | labels_remove=['not-ready', 'Kingdom-leave'], |
| 1031 | field_vals_remove=exp_fvs_remove) |
| 1032 | |
| 1033 | api_deltas.append(api_delta_all_masked) |
| 1034 | |
| 1035 | actual = self.converter.IngestIssueDeltas(api_deltas) |
| 1036 | expected = [(78001, expected_delta_all), (78002, expected_delta_all_masked)] |
| 1037 | self.assertEqual(actual, expected) |
| 1038 | |
| 1039 | def testIngestIssueDeltas_IssueRefs(self): |
| 1040 | # Set up. |
| 1041 | self.services.project.TestAddProject('proj-780', project_id=780) |
| 1042 | issue = self._Issue(780, 1) |
| 1043 | self.services.issue.TestAddIssue(issue) |
| 1044 | |
| 1045 | bo_add = self._Issue(780, 2) |
| 1046 | self.services.issue.TestAddIssue(bo_add) |
| 1047 | |
| 1048 | b_add = self._Issue(780, 3) |
| 1049 | self.services.issue.TestAddIssue(b_add) |
| 1050 | |
| 1051 | bo_remove = self._Issue(780, 4) |
| 1052 | self.services.issue.TestAddIssue(bo_remove) |
| 1053 | |
| 1054 | b_remove = self._Issue(780, 5) |
| 1055 | self.services.issue.TestAddIssue(b_remove) |
| 1056 | |
| 1057 | # merge_remove tested in testIngestIssueDeltas_RemoveNonRepeated |
| 1058 | merge_add = self._Issue(780, 6) |
| 1059 | self.services.issue.TestAddIssue(merge_add) |
| 1060 | |
| 1061 | api_issue = issue_objects_pb2.Issue( |
| 1062 | name='projects/proj-780/issues/1', |
| 1063 | blocked_on_issue_refs=[ |
| 1064 | issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/2'), |
| 1065 | issue_objects_pb2.IssueRef(ext_identifier='b/1') |
| 1066 | ], |
| 1067 | blocking_issue_refs=[ |
| 1068 | issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/3'), |
| 1069 | issue_objects_pb2.IssueRef(ext_identifier='b/2') |
| 1070 | ], |
| 1071 | merged_into_issue_ref=issue_objects_pb2.IssueRef( |
| 1072 | issue='projects/proj-780/issues/6')) |
| 1073 | |
| 1074 | api_delta = issues_pb2.IssueDelta( |
| 1075 | issue=api_issue, |
| 1076 | update_mask=field_mask_pb2.FieldMask( |
| 1077 | paths=[ |
| 1078 | 'blocked_on_issue_refs', 'blocking_issue_refs', |
| 1079 | 'merged_into_issue_ref' |
| 1080 | ]), |
| 1081 | blocked_on_issues_remove=[ |
| 1082 | issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/4'), |
| 1083 | issue_objects_pb2.IssueRef(ext_identifier='b/3') |
| 1084 | ], |
| 1085 | blocking_issues_remove=[ |
| 1086 | issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/5'), |
| 1087 | issue_objects_pb2.IssueRef(ext_identifier='b/4') |
| 1088 | ]) |
| 1089 | |
| 1090 | expected_delta = tracker_pb2.IssueDelta( |
| 1091 | blocked_on_add=[bo_add.issue_id], |
| 1092 | blocked_on_remove=[bo_remove.issue_id], |
| 1093 | blocking_add=[b_add.issue_id], |
| 1094 | blocking_remove=[b_remove.issue_id], |
| 1095 | ext_blocked_on_add=['b/1'], |
| 1096 | ext_blocked_on_remove=['b/3'], |
| 1097 | ext_blocking_add=['b/2'], |
| 1098 | ext_blocking_remove=['b/4'], |
| 1099 | merged_into=merge_add.issue_id) |
| 1100 | |
| 1101 | # Test adding an external merged_into_issue. |
| 1102 | api_issue_ext_merged = issue_objects_pb2.Issue( |
| 1103 | name='projects/proj-780/issues/2', |
| 1104 | merged_into_issue_ref=issue_objects_pb2.IssueRef(ext_identifier='b/1')) |
| 1105 | api_delta_ext_merged = issues_pb2.IssueDelta( |
| 1106 | issue=api_issue_ext_merged, |
| 1107 | update_mask=field_mask_pb2.FieldMask(paths=['merged_into_issue_ref'])) |
| 1108 | expected_delta_ext_merged = tracker_pb2.IssueDelta( |
| 1109 | merged_into_external='b/1') |
| 1110 | |
| 1111 | # Test issue with empty mask. |
| 1112 | issue_all_masked = self._Issue(780, 11) |
| 1113 | self.services.issue.TestAddIssue(issue_all_masked) |
| 1114 | |
| 1115 | api_issue_all_masked = copy.deepcopy(api_issue) |
| 1116 | api_issue_all_masked.name = 'projects/proj-780/issues/11' |
| 1117 | api_delta_all_masked = issues_pb2.IssueDelta( |
| 1118 | issue=api_issue_all_masked, update_mask=field_mask_pb2.FieldMask()) |
| 1119 | expected_all_masked_delta = tracker_pb2.IssueDelta() |
| 1120 | |
| 1121 | # Check results. |
| 1122 | actual = self.converter.IngestIssueDeltas( |
| 1123 | [api_delta, api_delta_ext_merged, api_delta_all_masked]) |
| 1124 | |
| 1125 | expected = [ |
| 1126 | (78001, expected_delta), (78002, expected_delta_ext_merged), |
| 1127 | (78011, expected_all_masked_delta) |
| 1128 | ] |
| 1129 | self.assertEqual(actual, expected) |
| 1130 | |
| 1131 | def testIngestIssueDeltas_OwnerAndOwnerDotUser(self): |
| 1132 | # Set up. |
| 1133 | self.services.project.TestAddProject('proj-780', project_id=780) |
| 1134 | issue = self._Issue(780, 1) |
| 1135 | self.services.issue.TestAddIssue(issue) |
| 1136 | |
| 1137 | api_issue = issue_objects_pb2.Issue( |
| 1138 | name='projects/proj-780/issues/1', |
| 1139 | owner=issue_objects_pb2.Issue.UserValue(user='users/111') |
| 1140 | ) |
| 1141 | |
| 1142 | # Expect ingest to work when update_mask has just 'owner'. |
| 1143 | api_delta = issues_pb2.IssueDelta( |
| 1144 | issue=api_issue, |
| 1145 | update_mask=field_mask_pb2.FieldMask(paths=['owner']) |
| 1146 | ) |
| 1147 | expected_delta = tracker_pb2.IssueDelta(owner_id=111) |
| 1148 | expected = [(78001, expected_delta)] |
| 1149 | actual = self.converter.IngestIssueDeltas([api_delta]) |
| 1150 | self.assertEqual(actual, expected) |
| 1151 | |
| 1152 | # Expect ingest to also work when update_mask uses 'owner.user' instead. |
| 1153 | api_delta = issues_pb2.IssueDelta( |
| 1154 | issue=api_issue, |
| 1155 | update_mask=field_mask_pb2.FieldMask(paths=['owner.user']) |
| 1156 | ) |
| 1157 | actual = self.converter.IngestIssueDeltas([api_delta]) |
| 1158 | self.assertEqual(actual, expected) |
| 1159 | |
| 1160 | def testIngestIssueDeltas_StatusAndStatusDotStatus(self): |
| 1161 | # Set up. |
| 1162 | self.services.project.TestAddProject('proj-780', project_id=780) |
| 1163 | issue = self._Issue(780, 1) |
| 1164 | self.services.issue.TestAddIssue(issue) |
| 1165 | |
| 1166 | api_issue = issue_objects_pb2.Issue( |
| 1167 | name='projects/proj-780/issues/1', |
| 1168 | owner=issue_objects_pb2.Issue.UserValue(user='users/111'), |
| 1169 | status=issue_objects_pb2.Issue.StatusValue(status='New') |
| 1170 | ) |
| 1171 | |
| 1172 | # Expect ingest to work when update_mask has just 'status'. |
| 1173 | api_delta = issues_pb2.IssueDelta( |
| 1174 | issue=api_issue, |
| 1175 | update_mask=field_mask_pb2.FieldMask(paths=['status']) |
| 1176 | ) |
| 1177 | expected_delta = tracker_pb2.IssueDelta(status='New') |
| 1178 | expected = [(78001, expected_delta)] |
| 1179 | actual = self.converter.IngestIssueDeltas([api_delta]) |
| 1180 | self.assertEqual(actual, expected) |
| 1181 | |
| 1182 | # Expect ingest to also work when update_mask uses 'status.status' instead. |
| 1183 | api_delta = issues_pb2.IssueDelta( |
| 1184 | issue=api_issue, |
| 1185 | update_mask=field_mask_pb2.FieldMask(paths=['status.status']) |
| 1186 | ) |
| 1187 | actual = self.converter.IngestIssueDeltas([api_delta]) |
| 1188 | self.assertEqual(actual, expected) |
| 1189 | |
| 1190 | def testIngestIssueDeltas_RemoveNonRepeated(self): |
| 1191 | # Set up. |
| 1192 | self.services.project.TestAddProject('proj-780', project_id=780) |
| 1193 | issue_1 = self._Issue(780, 1) |
| 1194 | self.services.issue.TestAddIssue(issue_1) |
| 1195 | issue_2 = self._Issue(780, 2) |
| 1196 | self.services.issue.TestAddIssue(issue_2) |
| 1197 | |
| 1198 | # Check we can remove fields without specifying them in the |
| 1199 | # issue, as long as they're specified in the FieldMask. |
| 1200 | api_issue = issue_objects_pb2.Issue( |
| 1201 | name='projects/proj-780/issues/1') |
| 1202 | api_delta = issues_pb2.IssueDelta( |
| 1203 | issue=api_issue, |
| 1204 | update_mask=field_mask_pb2.FieldMask( |
| 1205 | paths=[ |
| 1206 | 'owner.user', 'status.status', 'summary', |
| 1207 | 'merged_into_issue_ref.issue' |
| 1208 | ])) |
| 1209 | |
| 1210 | # Check thet setting fields to '' result in same behavior as not |
| 1211 | # explicitly setting the values at all. |
| 1212 | api_issue_set = issue_objects_pb2.Issue( |
| 1213 | name='projects/proj-780/issues/2', |
| 1214 | summary='', |
| 1215 | status=issue_objects_pb2.Issue.StatusValue(status=''), |
| 1216 | owner=issue_objects_pb2.Issue.UserValue(user=''), |
| 1217 | merged_into_issue_ref=issue_objects_pb2.IssueRef(issue='')) |
| 1218 | api_delta_set = issues_pb2.IssueDelta( |
| 1219 | issue=api_issue_set, |
| 1220 | update_mask=field_mask_pb2.FieldMask( |
| 1221 | paths=[ |
| 1222 | 'owner.user', 'status.status', 'summary', |
| 1223 | 'merged_into_issue_ref.issue' |
| 1224 | ])) |
| 1225 | |
| 1226 | expected_delta = tracker_pb2.IssueDelta( |
| 1227 | owner_id=framework_constants.NO_USER_SPECIFIED, |
| 1228 | status='', |
| 1229 | summary='', |
| 1230 | merged_into=0) |
| 1231 | |
| 1232 | actual = self.converter.IngestIssueDeltas([api_delta, api_delta_set]) |
| 1233 | expected = [(78001, expected_delta), (78002, expected_delta)] |
| 1234 | self.assertEqual(actual, expected) |
| 1235 | |
| 1236 | def testIngestIssueDeltas_InvalidMask(self): |
| 1237 | self.services.project.TestAddProject('proj-780', project_id=780) |
| 1238 | issue_1 = self._Issue(780, 1) |
| 1239 | self.services.issue.TestAddIssue(issue_1) |
| 1240 | issue_2 = self._Issue(780, 2) |
| 1241 | self.services.issue.TestAddIssue(issue_2) |
| 1242 | issue_3 = self._Issue(780, 3) |
| 1243 | self.services.issue.TestAddIssue(issue_3) |
| 1244 | api_deltas = [] |
| 1245 | err_msgs = [] |
| 1246 | |
| 1247 | api_issue_1 = issue_objects_pb2.Issue(name='projects/proj-780/issues/1') |
| 1248 | api_delta_1 = issues_pb2.IssueDelta(issue=api_issue_1) |
| 1249 | api_deltas.append(api_delta_1) |
| 1250 | err_msgs.append( |
| 1251 | '`update_mask` must be set for projects/proj-780/issues/1 delta.') |
| 1252 | |
| 1253 | api_issue_2 = issue_objects_pb2.Issue(name='projects/proj-780/issues/2') |
| 1254 | api_delta_2 = issues_pb2.IssueDelta( |
| 1255 | issue=api_issue_2, |
| 1256 | update_mask=field_mask_pb2.FieldMask()) # Empty but set is fine. |
| 1257 | api_deltas.append(api_delta_2) |
| 1258 | |
| 1259 | api_issue_3 = issue_objects_pb2.Issue(name='projects/proj-780/issues/3') |
| 1260 | api_delta_3 = issues_pb2.IssueDelta( |
| 1261 | issue=api_issue_3, |
| 1262 | update_mask=field_mask_pb2.FieldMask(paths=['chicken'])) |
| 1263 | api_deltas.append(api_delta_3) |
| 1264 | err_msgs.append( |
| 1265 | 'Invalid `update_mask` for projects/proj-780/issues/3 delta.') |
| 1266 | |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1267 | with self.assertRaisesRegex(exceptions.InputException, '\n'.join(err_msgs)): |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1268 | self.converter.IngestIssueDeltas(api_deltas) |
| 1269 | |
| 1270 | def testIngestIssueDeltas_OutputOnlyIgnored(self): |
| 1271 | # Set up. |
| 1272 | self.services.project.TestAddProject('proj-780', project_id=780) |
| 1273 | issue_1 = self._Issue(780, 1) |
| 1274 | self.services.issue.TestAddIssue(issue_1) |
| 1275 | comp_1 = fake.MakeTestComponentDef(780, 1) |
| 1276 | fd_str = fake.MakeTestFieldDef(1, 780, tracker_pb2.FieldTypes.STR_TYPE) |
| 1277 | config = fake.MakeTestConfig(780, [], []) |
| 1278 | config.component_defs = [comp_1] |
| 1279 | config.field_defs = [fd_str] |
| 1280 | self.services.config.StoreConfig(self.cnxn, config) |
| 1281 | |
| 1282 | api_issue = issue_objects_pb2.Issue( |
| 1283 | name='projects/proj-780/issues/1', |
| 1284 | owner=issue_objects_pb2.Issue.UserValue( |
| 1285 | user='users/111', |
| 1286 | derivation=issue_objects_pb2.Derivation.Value('RULE')), |
| 1287 | status=issue_objects_pb2.Issue.StatusValue( |
| 1288 | status='KingdomCome', |
| 1289 | derivation=issue_objects_pb2.Derivation.Value('RULE')), |
| 1290 | state=issue_objects_pb2.IssueContentState.Value('DELETED'), |
| 1291 | reporter='users/222', |
| 1292 | cc_users=[ |
| 1293 | issue_objects_pb2.Issue.UserValue( |
| 1294 | user='users/333', |
| 1295 | derivation=issue_objects_pb2.Derivation.Value('RULE')) |
| 1296 | ], |
| 1297 | labels=[ |
| 1298 | issue_objects_pb2.Issue.LabelValue( |
| 1299 | label='wikipedia-sections', |
| 1300 | derivation=issue_objects_pb2.Derivation.Value('RULE')) |
| 1301 | ], |
| 1302 | components=[ |
| 1303 | issue_objects_pb2.Issue.ComponentValue( |
| 1304 | component='projects/proj-780/componentDefs/1', |
| 1305 | derivation=issue_objects_pb2.Derivation.Value('RULE')) |
| 1306 | ], |
| 1307 | field_values=[ |
| 1308 | issue_objects_pb2.FieldValue( |
| 1309 | field='projects/proj-780/fieldDefs/1', |
| 1310 | value='bugs', |
| 1311 | derivation=issue_objects_pb2.Derivation.Value('RULE')) |
| 1312 | ], |
| 1313 | create_time=timestamp_pb2.Timestamp(seconds=4044242), |
| 1314 | close_time=timestamp_pb2.Timestamp(seconds=4044242), |
| 1315 | modify_time=timestamp_pb2.Timestamp(seconds=4044242), |
| 1316 | component_modify_time=timestamp_pb2.Timestamp(seconds=4044242), |
| 1317 | status_modify_time=timestamp_pb2.Timestamp(seconds=4044242), |
| 1318 | owner_modify_time=timestamp_pb2.Timestamp(seconds=4044242), |
| 1319 | attachment_count=4, |
| 1320 | star_count=2, |
| 1321 | phases=['EarlyLife', 'CrimesBegin', 'CrimesContinue']) |
| 1322 | paths_with_output_only = [ |
| 1323 | 'owner', 'status', 'state', 'reporter', 'cc_users', 'labels', |
| 1324 | 'components', 'field_values', 'create_time', 'close_time', |
| 1325 | 'modify_time', 'component_modify_time', 'status_modify_time', |
| 1326 | 'owner_modify_time', 'attachment_count', 'star_count', 'phases'] |
| 1327 | api_delta = issues_pb2.IssueDelta( |
| 1328 | issue=api_issue, |
| 1329 | update_mask=field_mask_pb2.FieldMask(paths=paths_with_output_only)) |
| 1330 | |
| 1331 | expected_delta = tracker_pb2.IssueDelta( |
| 1332 | # We ignore all Issue.*Value.derivation OUTPUT_ONLY fields. |
| 1333 | owner_id=111, |
| 1334 | status='KingdomCome', |
| 1335 | cc_ids_add=[333], |
| 1336 | labels_add=['wikipedia-sections'], |
| 1337 | comp_ids_add=[1], |
| 1338 | field_vals_add=[ |
| 1339 | field_helpers.ParseOneFieldValue( |
| 1340 | self.cnxn, self.services.user, fd_str, 'bugs') |
| 1341 | ]) |
| 1342 | |
| 1343 | actual = self.converter.IngestIssueDeltas([api_delta]) |
| 1344 | expected = [(78001, expected_delta)] |
| 1345 | self.assertEqual(actual, expected) |
| 1346 | |
| 1347 | |
| 1348 | def testIngestIssueDeltas_Empty(self): |
| 1349 | actual = self.converter.IngestIssueDeltas([]) |
| 1350 | self.assertEqual(actual, []) |
| 1351 | |
| 1352 | def testIngestIssueDeltas_InvalidValuesForFields(self): |
| 1353 | # Set up. |
| 1354 | self.services.project.TestAddProject('proj-780', project_id=780) |
| 1355 | issue_1 = self._Issue(780, 1) |
| 1356 | self.services.issue.TestAddIssue(issue_1) |
| 1357 | fd_int = fake.MakeTestFieldDef(1, 780, tracker_pb2.FieldTypes.INT_TYPE) |
| 1358 | fd_date = fake.MakeTestFieldDef(2, 780, tracker_pb2.FieldTypes.DATE_TYPE) |
| 1359 | config = fake.MakeTestConfig(780, [], []) |
| 1360 | config.field_defs = [fd_int, fd_date] |
| 1361 | self.services.config.StoreConfig(self.cnxn, config) |
| 1362 | |
| 1363 | api_issue = issue_objects_pb2.Issue( |
| 1364 | name='projects/proj-780/issues/1', |
| 1365 | field_values=[ |
| 1366 | issue_objects_pb2.FieldValue( |
| 1367 | field='projects/proj-780/fieldDefs/1', |
| 1368 | value='NotAnInt', |
| 1369 | derivation=issue_objects_pb2.Derivation.Value('RULE')), |
| 1370 | issue_objects_pb2.FieldValue( |
| 1371 | field='projects/proj-780/fieldDefs/2', |
| 1372 | value='NoDate', |
| 1373 | derivation=issue_objects_pb2.Derivation.Value('EXPLICIT')), |
| 1374 | ], |
| 1375 | ) |
| 1376 | api_delta = issues_pb2.IssueDelta( |
| 1377 | issue=api_issue, |
| 1378 | update_mask=field_mask_pb2.FieldMask(paths=['field_values'])) |
| 1379 | error_messages = [ |
| 1380 | r'Could not ingest value \(NotAnInt\) for FieldDef \(projects/proj-780/' |
| 1381 | r'fieldDefs/1\): Could not parse NotAnInt', |
| 1382 | r'Could not ingest value \(NoDate\) for FieldDef \(projects/proj-780/fi' |
| 1383 | r'eldDefs/2\): Could not parse NoDate', |
| 1384 | ] |
| 1385 | error_messages_re = '\n'.join(error_messages) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1386 | with self.assertRaisesRegex(exceptions.InputException, error_messages_re): |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1387 | self.converter.IngestIssueDeltas([api_delta]) |
| 1388 | |
| 1389 | @mock.patch('time.time', mock.MagicMock(return_value=CURRENT_TIME)) |
| 1390 | def testIngestApprovalDeltas(self): |
| 1391 | mask = field_mask_pb2.FieldMask( |
| 1392 | paths=['approvers', 'status', 'setter', 'phase', 'set_time']) |
| 1393 | av_name = ( |
| 1394 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1395 | approval_delta = issues_pb2.ApprovalDelta( |
| 1396 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1397 | name=av_name, |
| 1398 | status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'), |
| 1399 | approvers=['users/222', 'users/333'], |
| 1400 | approval_def='ignored', |
| 1401 | set_time=timestamp_pb2.Timestamp(), # Ignored. |
| 1402 | setter='ignored', |
| 1403 | phase='ignored'), |
| 1404 | update_mask=mask, |
| 1405 | approvers_remove=['users/222']) |
| 1406 | actual = self.converter.IngestApprovalDeltas( |
| 1407 | [approval_delta], self.user_1.user_id) |
| 1408 | expected_delta = tracker_pb2.ApprovalDelta( |
| 1409 | status=tracker_pb2.ApprovalStatus.NA, |
| 1410 | setter_id=self.user_1.user_id, |
| 1411 | set_on=int(CURRENT_TIME), |
| 1412 | approver_ids_add=[222, 333], |
| 1413 | approver_ids_remove=[222], |
| 1414 | ) |
| 1415 | expected_delta_specifications = [ |
| 1416 | (self.issue_1.issue_id, self.approval_def_1_id, expected_delta) |
| 1417 | ] |
| 1418 | self.assertEqual(actual, expected_delta_specifications) |
| 1419 | |
| 1420 | def testIngestApprovalDeltas_EmptyMask(self): |
| 1421 | av_name = ( |
| 1422 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1423 | # field_def_6 belongs to approval_def_1. |
| 1424 | approval_fv = issue_objects_pb2.FieldValue( |
| 1425 | field='projects/proj/fieldDefs/%d' % self.field_def_6, value=u'x') |
| 1426 | approval_delta = issues_pb2.ApprovalDelta( |
| 1427 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1428 | name=av_name, |
| 1429 | status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'), |
| 1430 | approvers=['users/222', 'users/333'], |
| 1431 | approval_def='ignored', |
| 1432 | field_values=[approval_fv], |
| 1433 | set_time=timestamp_pb2.Timestamp(), # Ignored. |
| 1434 | setter='ignored', |
| 1435 | phase='ignored'), |
| 1436 | update_mask=field_mask_pb2.FieldMask(), |
| 1437 | approvers_remove=['users/222']) |
| 1438 | actual = self.converter.IngestApprovalDeltas( |
| 1439 | [approval_delta], self.user_1.user_id) |
| 1440 | expected_delta = tracker_pb2.ApprovalDelta(approver_ids_remove=[222]) |
| 1441 | expected_delta_specifications = [ |
| 1442 | (self.issue_1.issue_id, self.approval_def_1_id, expected_delta) |
| 1443 | ] |
| 1444 | self.assertEqual(actual, expected_delta_specifications) |
| 1445 | |
| 1446 | def testIngestApprovalDeltas_InvalidMask(self): |
| 1447 | av_name = ( |
| 1448 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1449 | approval_delta = issues_pb2.ApprovalDelta( |
| 1450 | approval_value=issue_objects_pb2.ApprovalValue(name=av_name), |
| 1451 | update_mask=field_mask_pb2.FieldMask(paths=['chicken'])) |
| 1452 | expected_err = 'Invalid `update_mask` for %s delta' % av_name |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1453 | with self.assertRaisesRegex(exceptions.InputException, expected_err): |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1454 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1455 | |
| 1456 | def testIngestApprovalDeltas_FilterFieldValues(self): |
| 1457 | av_name = ( |
| 1458 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1459 | |
| 1460 | # field_def_6 belongs to approval_def_1, should be ingested. |
| 1461 | approval_fv = issue_objects_pb2.FieldValue( |
| 1462 | field='projects/proj/fieldDefs/%d' % self.field_def_6, |
| 1463 | value=u'touch-nose', |
| 1464 | derivation=RULE_DERIVATION, # Ignored. |
| 1465 | ) |
| 1466 | # An enum field belonging to approval_def_1, should be ingested. |
| 1467 | approval_enum_field_id = self._CreateFieldDef( |
| 1468 | self.project_1.project_id, |
| 1469 | 'approval2field', |
| 1470 | 'ENUM_TYPE', |
| 1471 | approval_id=self.approval_def_1_id) |
| 1472 | approval_enum_fv = issue_objects_pb2.FieldValue( |
| 1473 | field='projects/proj/fieldDefs/%d' % approval_enum_field_id, |
| 1474 | value=u'enumval') |
| 1475 | # Create field value that points to different approval, should raise error. |
| 1476 | approval_2_fv = issue_objects_pb2.FieldValue( |
| 1477 | field='projects/proj/fieldDefs/%d' % self.field_def_2, value=u'error') |
| 1478 | av = issue_objects_pb2.ApprovalValue( |
| 1479 | name=av_name, field_values=[approval_fv]) |
| 1480 | approval_delta = issues_pb2.ApprovalDelta( |
| 1481 | update_mask=field_mask_pb2.FieldMask(paths=['field_values']), |
| 1482 | approval_value=av, |
| 1483 | field_vals_remove=[approval_enum_fv, approval_2_fv], |
| 1484 | approvers_remove=['users/222'], |
| 1485 | ) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1486 | with self.assertRaisesRegex(exceptions.InputException, |
| 1487 | 'Field .* does not belong to approval .*'): |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1488 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1489 | |
| 1490 | def testIngestApprovalDeltas_InvalidFieldValues(self): |
| 1491 | av_name = ( |
| 1492 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1493 | approval_fv = issue_objects_pb2.FieldValue( |
| 1494 | field='projects/proj/fieldDefs/%d' % self.field_def_6, |
| 1495 | value=u'touch-nose', |
| 1496 | derivation=RULE_DERIVATION, # Ignored. |
| 1497 | ) |
| 1498 | other_fv = issue_objects_pb2.FieldValue( |
| 1499 | field='projects/proj/fieldDefs/%d' % self.field_def_1, |
| 1500 | value=u'something', |
| 1501 | ) |
| 1502 | # This does not exist, and should throw error. |
| 1503 | dne_fv = issue_objects_pb2.FieldValue( |
| 1504 | field='projects/proj/fieldDefs/404', |
| 1505 | value=u'DoesNotExist', |
| 1506 | ) |
| 1507 | av = issue_objects_pb2.ApprovalValue( |
| 1508 | name=av_name, field_values=[other_fv, approval_fv, dne_fv]) |
| 1509 | approval_delta = issues_pb2.ApprovalDelta( |
| 1510 | update_mask=field_mask_pb2.FieldMask(paths=['field_values']), |
| 1511 | approval_value=av, |
| 1512 | approvers_remove=['users/222'], |
| 1513 | ) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1514 | with self.assertRaisesRegex( |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1515 | exceptions.InputException, |
| 1516 | 'Field projects/proj/fieldDefs/404 is not in this project'): |
| 1517 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1518 | |
| 1519 | def testIngestApprovalDeltas_WrongProject(self): |
| 1520 | approval_def_project2_name = 'project2_approval' |
| 1521 | approval_def_project2_id = self._CreateFieldDef( |
| 1522 | self.project_2.project_id, |
| 1523 | approval_def_project2_name, |
| 1524 | 'APPROVAL_TYPE', |
| 1525 | docstring='project2_ad_docstring', |
| 1526 | admin_ids=[self.user_1.user_id]) |
| 1527 | self.services.config.UpdateConfig( |
| 1528 | self.cnxn, |
| 1529 | self.project_2, |
| 1530 | approval_defs=[ |
| 1531 | (approval_def_project2_id, [self.user_1.user_id], 'survey') |
| 1532 | ]) |
| 1533 | wrong_project_av_name = ( |
| 1534 | 'projects/proj/issues/1/approvalValues/%d' % approval_def_project2_id) |
| 1535 | approval_delta = issues_pb2.ApprovalDelta( |
| 1536 | update_mask=field_mask_pb2.FieldMask(), |
| 1537 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1538 | name=wrong_project_av_name)) |
| 1539 | with self.assertRaises(exceptions.InputException): |
| 1540 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1541 | |
| 1542 | def testIngestApprovalDeltas_DoesNotExist(self): |
| 1543 | dne_av_name = ('projects/proj/issues/1/approvalValues/404') |
| 1544 | approval_delta = issues_pb2.ApprovalDelta( |
| 1545 | approval_value=issue_objects_pb2.ApprovalValue(name=dne_av_name), |
| 1546 | update_mask=field_mask_pb2.FieldMask()) |
| 1547 | with self.assertRaises(exceptions.InputException): |
| 1548 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1549 | |
| 1550 | def testIngestApprovalDeltas_NonApproval(self): |
| 1551 | """We fail if provided a non-approval Field ID in the resource name.""" |
| 1552 | dne_av_name = ( |
| 1553 | 'projects/proj/issues/1/approvalValues/%s' % self.field_def_1) |
| 1554 | approval_delta = issues_pb2.ApprovalDelta( |
| 1555 | approval_value=issue_objects_pb2.ApprovalValue(name=dne_av_name), |
| 1556 | update_mask=field_mask_pb2.FieldMask()) |
| 1557 | with self.assertRaises(exceptions.InputException): |
| 1558 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1559 | |
| 1560 | def testIngestApprovalDeltas_IssueDoesNotExist(self): |
| 1561 | dne_av_name = ( |
| 1562 | 'projects/proj/issues/404/approvalValues/%d' % self.approval_def_1_id) |
| 1563 | approval_delta = issues_pb2.ApprovalDelta( |
| 1564 | approval_value=issue_objects_pb2.ApprovalValue(name=dne_av_name), |
| 1565 | update_mask=field_mask_pb2.FieldMask()) |
| 1566 | with self.assertRaises(exceptions.NoSuchIssueException): |
| 1567 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1568 | |
| 1569 | def testIngestApprovalDeltas_EmptyDelta(self): |
| 1570 | av_name = ( |
| 1571 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1572 | approval_delta = issues_pb2.ApprovalDelta( |
| 1573 | approval_value=issue_objects_pb2.ApprovalValue(name=av_name), |
| 1574 | update_mask=field_mask_pb2.FieldMask()) |
| 1575 | |
| 1576 | actual = self.converter.IngestApprovalDeltas( |
| 1577 | [approval_delta], self.user_1.user_id) |
| 1578 | |
| 1579 | expected_delta = tracker_pb2.ApprovalDelta() |
| 1580 | expected_delta_specifications = [ |
| 1581 | (self.issue_1.issue_id, self.approval_def_1_id, expected_delta) |
| 1582 | ] |
| 1583 | self.assertEqual(actual, expected_delta_specifications) |
| 1584 | |
| 1585 | def testIngestApprovalDeltas_InvalidName(self): |
| 1586 | approval_delta = issues_pb2.ApprovalDelta( |
| 1587 | approval_value=issue_objects_pb2.ApprovalValue(name='x')) |
| 1588 | with self.assertRaises(exceptions.InputException): |
| 1589 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1590 | |
| 1591 | def testIngestApprovalDeltas_NoName(self): |
| 1592 | approval_delta = issues_pb2.ApprovalDelta( |
| 1593 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1594 | status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'))) |
| 1595 | with self.assertRaises(exceptions.InputException): |
| 1596 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1597 | |
| 1598 | def testIngestApprovalDeltas_NoStatus(self): |
| 1599 | """Setter ID isn't set when status isn't set.""" |
| 1600 | av_name = ( |
| 1601 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1602 | approval_delta = issues_pb2.ApprovalDelta( |
| 1603 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1604 | name=av_name, |
| 1605 | status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'), |
| 1606 | approvers=['users/333']), |
| 1607 | # Status left out of update mask. |
| 1608 | update_mask=field_mask_pb2.FieldMask(paths=['approvers']), |
| 1609 | approvers_remove=['users/222']) |
| 1610 | actual = self.converter.IngestApprovalDeltas( |
| 1611 | [approval_delta], self.user_1.user_id) |
| 1612 | expected_delta = tracker_pb2.ApprovalDelta( |
| 1613 | approver_ids_add=[333], approver_ids_remove=[222]) |
| 1614 | expected_delta_specifications = [ |
| 1615 | (self.issue_1.issue_id, self.approval_def_1_id, expected_delta) |
| 1616 | ] |
| 1617 | self.assertEqual(actual, expected_delta_specifications) |
| 1618 | |
| 1619 | def testIngestApprovalDeltas_ApproverRemoveDoesNotExist(self): |
| 1620 | av_name = ( |
| 1621 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1622 | approval_delta = issues_pb2.ApprovalDelta( |
| 1623 | approval_value=issue_objects_pb2.ApprovalValue(name=av_name), |
| 1624 | update_mask=field_mask_pb2.FieldMask(), |
| 1625 | approvers_remove=['users/nobody@404.com']) |
| 1626 | with self.assertRaises(exceptions.NoSuchUserException): |
| 1627 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1628 | |
| 1629 | def testIngestApprovalDeltas_ApproverAddDoesNotExist(self): |
| 1630 | av_name = ( |
| 1631 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1632 | approval_delta = issues_pb2.ApprovalDelta( |
| 1633 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1634 | name=av_name, approvers=['users/nobody@404.com']), |
| 1635 | update_mask=field_mask_pb2.FieldMask(paths=['approvers'])) |
| 1636 | with self.assertRaises(exceptions.NoSuchUserException): |
| 1637 | self.converter.IngestApprovalDeltas([approval_delta], self.user_1.user_id) |
| 1638 | |
| 1639 | def testIngestApprovalDeltas_FirstErrorRaised(self): |
| 1640 | """Until we have error aggregation, we raise the first found error.""" |
| 1641 | av_name = ( |
| 1642 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1643 | user_dne_delta = issues_pb2.ApprovalDelta( |
| 1644 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1645 | name=av_name, approvers=['users/nobody@404.com']), |
| 1646 | update_mask=field_mask_pb2.FieldMask(paths=['approvers'])) |
| 1647 | invalid_name_delta = issues_pb2.ApprovalDelta( |
| 1648 | approval_value=issue_objects_pb2.ApprovalValue(name='garbage')) |
| 1649 | with self.assertRaises(exceptions.NoSuchUserException): |
| 1650 | self.converter.IngestApprovalDeltas( |
| 1651 | [user_dne_delta, invalid_name_delta], self.user_1.user_id) |
| 1652 | |
| 1653 | def testIngestApprovalDeltas_MultipleDeltasSameSetOn(self): |
| 1654 | av_name = ( |
| 1655 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1656 | delta_1 = issues_pb2.ApprovalDelta( |
| 1657 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1658 | name=av_name, |
| 1659 | status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA'), |
| 1660 | approvers=['users/222']), |
| 1661 | update_mask=field_mask_pb2.FieldMask(paths=['approvers', 'status'])) |
| 1662 | # Change status, and also ensure we don't reuse the same mask across deltas |
| 1663 | # Approvers should be ignored for delta_2 because it is not included in the |
| 1664 | # mask. |
| 1665 | delta_2 = issues_pb2.ApprovalDelta( |
| 1666 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1667 | name=av_name, |
| 1668 | status=issue_objects_pb2.ApprovalValue.ApprovalStatus.Value( |
| 1669 | 'NOT_SET'), |
| 1670 | approvers=['users/222']), |
| 1671 | update_mask=field_mask_pb2.FieldMask(paths=['status'])) |
| 1672 | actual = self.converter.IngestApprovalDeltas( |
| 1673 | [delta_1, delta_2], self.user_1.user_id) |
| 1674 | self.assertEqual(len(actual), 2) |
| 1675 | actual_iid_1, actual_approval_id_1, actual_delta_1 = actual[0] |
| 1676 | actual_iid_2, actual_approval_id_2, actual_delta_2 = actual[1] |
| 1677 | self.assertEqual(actual_iid_1, self.issue_1.issue_id) |
| 1678 | self.assertEqual(actual_iid_2, self.issue_1.issue_id) |
| 1679 | self.assertEqual(actual_approval_id_1, self.approval_def_1_id) |
| 1680 | self.assertEqual(actual_approval_id_2, self.approval_def_1_id) |
| 1681 | |
| 1682 | self.assertEqual(actual_delta_1.status, tracker_pb2.ApprovalStatus.NA) |
| 1683 | self.assertEqual(actual_delta_2.status, tracker_pb2.ApprovalStatus.NOT_SET) |
| 1684 | self.assertEqual(actual_delta_1.setter_id, self.user_1.user_id) |
| 1685 | self.assertEqual(actual_delta_2.setter_id, self.user_1.user_id) |
| 1686 | self.assertEqual(actual_delta_1.approver_ids_add, [222]) |
| 1687 | self.assertEqual(actual_delta_2.approver_ids_add, []) |
| 1688 | # We don't patch time.time, so these would be different if the set_on wasn't |
| 1689 | # passed in. |
| 1690 | # Note: More ideal/correct unit test would create a mock that forces |
| 1691 | # time.time to return an incremented value on its subsequent calls. |
| 1692 | self.assertEqual(actual_delta_1.set_on, actual_delta_2.set_on) |
| 1693 | |
| 1694 | def testIngestApprovalDeltas_DifferentProjects(self): |
| 1695 | # Create an ApprovalDef for project2 |
| 1696 | approval_def_project2_name = 'project2_approval' |
| 1697 | approval_def_project2_id = self._CreateFieldDef( |
| 1698 | self.project_2.project_id, |
| 1699 | approval_def_project2_name, |
| 1700 | 'APPROVAL_TYPE', |
| 1701 | docstring='project2_ad_docstring', |
| 1702 | admin_ids=[self.user_1.user_id]) |
| 1703 | self.services.config.UpdateConfig( |
| 1704 | self.cnxn, |
| 1705 | self.project_2, |
| 1706 | approval_defs=[ |
| 1707 | (approval_def_project2_id, [self.user_1.user_id], 'survey') |
| 1708 | ]) |
| 1709 | |
| 1710 | # Define a field belonging to project_2's ApprovalDef. |
| 1711 | project2_field_id = self._CreateFieldDef( |
| 1712 | self.project_2.project_id, |
| 1713 | 'approval2field', |
| 1714 | 'STR_TYPE', |
| 1715 | approval_id=approval_def_project2_id) |
| 1716 | project2_fv = issue_objects_pb2.FieldValue( |
| 1717 | field='projects/proj/fieldDefs/%d' % project2_field_id, value=u'p2') |
| 1718 | |
| 1719 | # field_def_6 belongs to approval_def_1. |
| 1720 | project1_fv = issue_objects_pb2.FieldValue( |
| 1721 | field='projects/proj/fieldDefs/%d' % self.field_def_6, |
| 1722 | value=u'touch-nose', |
| 1723 | ) |
| 1724 | |
| 1725 | # Both ApprovalValues are provided both FieldValues, and we expect them |
| 1726 | # to only include the FieldValues appropriate to their respective approvals. |
| 1727 | project2_av_name = ( |
| 1728 | 'projects/%s/issues/2/approvalValues/%d' % |
| 1729 | (self.project_2.project_name, approval_def_project2_id)) |
| 1730 | project2_delta = issues_pb2.ApprovalDelta( |
| 1731 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1732 | name=project2_av_name, field_values=[project1_fv, project2_fv]), |
| 1733 | update_mask=field_mask_pb2.FieldMask(paths=['field_values'])) |
| 1734 | |
| 1735 | project1_av_name = ( |
| 1736 | 'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id) |
| 1737 | project1_delta = issues_pb2.ApprovalDelta( |
| 1738 | approval_value=issue_objects_pb2.ApprovalValue( |
| 1739 | name=project1_av_name, field_values=[project1_fv, project2_fv]), |
| 1740 | update_mask=field_mask_pb2.FieldMask(paths=['field_values'])) |
| 1741 | |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 1742 | with self.assertRaisesRegex( |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 1743 | exceptions.InputException, |
| 1744 | 'Field projects/proj/fieldDefs/%d is not in this project' % |
| 1745 | self.field_def_6): |
| 1746 | self.converter.IngestApprovalDeltas( |
| 1747 | [project2_delta, project1_delta], self.user_1.user_id) |
| 1748 | |
| 1749 | def testIngestIssue(self): |
| 1750 | ingest = issue_objects_pb2.Issue( |
| 1751 | summary='sum', |
| 1752 | status=issue_objects_pb2.Issue.StatusValue( |
| 1753 | status='new', derivation=RULE_DERIVATION), |
| 1754 | owner=issue_objects_pb2.Issue.UserValue( |
| 1755 | derivation=EXPLICIT_DERIVATION, user='users/111'), |
| 1756 | cc_users=[ |
| 1757 | issue_objects_pb2.Issue.UserValue( |
| 1758 | derivation=EXPLICIT_DERIVATION, user='users/new@user.com'), |
| 1759 | issue_objects_pb2.Issue.UserValue( |
| 1760 | derivation=RULE_DERIVATION, user='users/333') |
| 1761 | ], |
| 1762 | components=[ |
| 1763 | issue_objects_pb2.Issue.ComponentValue( |
| 1764 | component='projects/proj/componentDefs/%d' % |
| 1765 | self.component_def_1_id), |
| 1766 | issue_objects_pb2.Issue.ComponentValue( |
| 1767 | component='projects/proj/componentDefs/%d' % |
| 1768 | self.component_def_2_id), |
| 1769 | ], |
| 1770 | labels=[ |
| 1771 | issue_objects_pb2.Issue.LabelValue( |
| 1772 | derivation=EXPLICIT_DERIVATION, label='a'), |
| 1773 | issue_objects_pb2.Issue.LabelValue( |
| 1774 | derivation=EXPLICIT_DERIVATION, label='key-explicit'), |
| 1775 | issue_objects_pb2.Issue.LabelValue( |
| 1776 | derivation=RULE_DERIVATION, label='derived1'), |
| 1777 | issue_objects_pb2.Issue.LabelValue( |
| 1778 | derivation=RULE_DERIVATION, label='key-derived') |
| 1779 | ], |
| 1780 | field_values=[ |
| 1781 | issue_objects_pb2.FieldValue( |
| 1782 | derivation=EXPLICIT_DERIVATION, |
| 1783 | field='projects/proj/fieldDefs/%d' % self.field_def_1, |
| 1784 | value='multivalue1', |
| 1785 | ), |
| 1786 | issue_objects_pb2.FieldValue( |
| 1787 | derivation=RULE_DERIVATION, |
| 1788 | field='projects/proj/fieldDefs/%d' % self.field_def_1, |
| 1789 | value='multivalue2', |
| 1790 | ), |
| 1791 | issue_objects_pb2.FieldValue( |
| 1792 | derivation=EXPLICIT_DERIVATION, |
| 1793 | field='projects/proj/fieldDefs/%d' % self.field_def_3, |
| 1794 | value='1', |
| 1795 | ), |
| 1796 | issue_objects_pb2.FieldValue( |
| 1797 | derivation=RULE_DERIVATION, |
| 1798 | field='projects/proj/fieldDefs/%d' % self.field_def_4, |
| 1799 | value='mac', |
| 1800 | ), |
| 1801 | issue_objects_pb2.FieldValue( |
| 1802 | field='projects/proj/fieldDefs/%d' % self.field_def_2, |
| 1803 | value='38', # Max value not checked. |
| 1804 | ), |
| 1805 | issue_objects_pb2.FieldValue( # Multivalue not checked. |
| 1806 | field='projects/proj/fieldDefs/%d' % self.field_def_2, |
| 1807 | value='0' # Confirm we ingest 0 rather than None. |
| 1808 | ), |
| 1809 | issue_objects_pb2.FieldValue( |
| 1810 | field='projects/proj/fieldDefs/%d' % self.field_def_8, |
| 1811 | value='users/111', |
| 1812 | ), |
| 1813 | issue_objects_pb2.FieldValue( |
| 1814 | field='projects/proj/fieldDefs/%d' % self.field_def_8, |
| 1815 | value='users/404', # User lookup not attempted. |
| 1816 | ), |
| 1817 | issue_objects_pb2.FieldValue( |
| 1818 | field='projects/proj/fieldDefs/%d' % self.field_def_9, |
| 1819 | value='2020-01-01', |
| 1820 | ), |
| 1821 | issue_objects_pb2.FieldValue( |
| 1822 | field='projects/proj/fieldDefs/%d' % self.field_def_9, |
| 1823 | value='2100-01-01', |
| 1824 | ), |
| 1825 | issue_objects_pb2.FieldValue( |
| 1826 | field='projects/proj/fieldDefs/%d' % self.field_def_9, |
| 1827 | value='1000-01-01', |
| 1828 | ), |
| 1829 | issue_objects_pb2.FieldValue( |
| 1830 | field='projects/proj/fieldDefs/%d' % self.field_def_10, |
| 1831 | value='garbage', |
| 1832 | ), |
| 1833 | ], |
| 1834 | merged_into_issue_ref=issue_objects_pb2.IssueRef(ext_identifier='b/1'), |
| 1835 | blocked_on_issue_refs=[ |
| 1836 | # Reversing natural ordering to ensure order is respected. |
| 1837 | issue_objects_pb2.IssueRef(issue='projects/goose/issues/4'), |
| 1838 | issue_objects_pb2.IssueRef(issue='projects/proj/issues/3'), |
| 1839 | issue_objects_pb2.IssueRef(ext_identifier='b/555'), |
| 1840 | issue_objects_pb2.IssueRef(ext_identifier='b/2') |
| 1841 | ], |
| 1842 | blocking_issue_refs=[ |
| 1843 | issue_objects_pb2.IssueRef(issue='projects/goose/issues/5'), |
| 1844 | issue_objects_pb2.IssueRef(ext_identifier='b/3') |
| 1845 | ], |
| 1846 | # All the following fields should be ignored. |
| 1847 | name='projects/proj/issues/1', |
| 1848 | state=issue_objects_pb2.IssueContentState.Value('SPAM'), |
| 1849 | reporter='users/111', |
| 1850 | create_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 1851 | modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 1852 | component_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 1853 | status_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 1854 | owner_modify_time=timestamp_pb2.Timestamp(seconds=self.PAST_TIME), |
| 1855 | star_count=1, |
| 1856 | attachment_count=5, |
| 1857 | phases=[self.phase_1.name]) |
| 1858 | |
| 1859 | blocked_on_1 = fake.MakeTestIssue( |
| 1860 | self.project_1.project_id, |
| 1861 | 3, |
| 1862 | 'sum3', |
| 1863 | 'New', |
| 1864 | self.user_1.user_id, |
| 1865 | issue_id=301, |
| 1866 | project_name=self.project_1.project_name, |
| 1867 | ) |
| 1868 | blocked_on_2 = fake.MakeTestIssue( |
| 1869 | self.project_2.project_id, |
| 1870 | 4, |
| 1871 | 'sum4', |
| 1872 | 'New', |
| 1873 | self.user_1.user_id, |
| 1874 | issue_id=401, |
| 1875 | project_name=self.project_2.project_name, |
| 1876 | ) |
| 1877 | blocking = fake.MakeTestIssue( |
| 1878 | self.project_2.project_id, |
| 1879 | 5, |
| 1880 | 'sum5', |
| 1881 | 'New', |
| 1882 | self.user_1.user_id, |
| 1883 | issue_id=501, |
| 1884 | project_name=self.project_2.project_name, |
| 1885 | ) |
| 1886 | self.services.issue.TestAddIssue(blocked_on_1) |
| 1887 | self.services.issue.TestAddIssue(blocked_on_2) |
| 1888 | self.services.issue.TestAddIssue(blocking) |
| 1889 | |
| 1890 | actual = self.converter.IngestIssue(ingest, self.project_1.project_id) |
| 1891 | |
| 1892 | expected_cc1_id = self.services.user.LookupUserID( |
| 1893 | self.cnxn, 'new@user.com', autocreate=False) |
| 1894 | expected_field_values = [ |
| 1895 | tracker_pb2.FieldValue( |
| 1896 | field_id=self.field_def_1, |
| 1897 | str_value=u'multivalue1', |
| 1898 | derived=False, |
| 1899 | ), |
| 1900 | tracker_pb2.FieldValue( |
| 1901 | field_id=self.field_def_1, |
| 1902 | str_value=u'multivalue2', |
| 1903 | derived=False, |
| 1904 | ), |
| 1905 | tracker_pb2.FieldValue( |
| 1906 | field_id=self.field_def_2, int_value=38, derived=False), |
| 1907 | tracker_pb2.FieldValue( |
| 1908 | field_id=self.field_def_2, int_value=0, derived=False), |
| 1909 | tracker_pb2.FieldValue( |
| 1910 | field_id=self.field_def_8, user_id=111, derived=False), |
| 1911 | tracker_pb2.FieldValue( |
| 1912 | field_id=self.field_def_8, user_id=404, derived=False), |
| 1913 | tracker_pb2.FieldValue( |
| 1914 | field_id=self.field_def_9, date_value=1577836800, derived=False), |
| 1915 | tracker_pb2.FieldValue( |
| 1916 | field_id=self.field_def_9, date_value=4102444800, derived=False), |
| 1917 | tracker_pb2.FieldValue( |
| 1918 | field_id=self.field_def_9, date_value=-30610224000, derived=False), |
| 1919 | tracker_pb2.FieldValue( |
| 1920 | field_id=self.field_def_10, |
| 1921 | url_value=u'http://garbage', |
| 1922 | derived=False), |
| 1923 | ] |
| 1924 | expected = tracker_pb2.Issue( |
| 1925 | project_id=self.project_1.project_id, |
| 1926 | summary=u'sum', |
| 1927 | status=u'new', |
| 1928 | owner_id=111, |
| 1929 | cc_ids=[expected_cc1_id, 333], |
| 1930 | component_ids=[self.component_def_1_id, self.component_def_2_id], |
| 1931 | merged_into_external=u'b/1', |
| 1932 | labels=[ |
| 1933 | u'a', u'key-explicit', u'derived1', u'key-derived', u'days-1', |
| 1934 | u'OS-mac' |
| 1935 | ], |
| 1936 | field_values=expected_field_values, |
| 1937 | blocked_on_iids=[blocked_on_2.issue_id, blocked_on_1.issue_id], |
| 1938 | blocking_iids=[blocking.issue_id], |
| 1939 | dangling_blocked_on_refs=[ |
| 1940 | tracker_pb2.DanglingIssueRef(ext_issue_identifier=u'b/555'), |
| 1941 | tracker_pb2.DanglingIssueRef(ext_issue_identifier=u'b/2') |
| 1942 | ], |
| 1943 | dangling_blocking_refs=[ |
| 1944 | tracker_pb2.DanglingIssueRef(ext_issue_identifier=u'b/3') |
| 1945 | ], |
| 1946 | ) |
| 1947 | self.AssertProtosEqual(actual, expected) |
| 1948 | |
| 1949 | def AssertProtosEqual(self, actual, expected): |
| 1950 | """Asserts equal, printing a diff if not.""" |
| 1951 | # TODO(jessan): If others find this useful, move to a shared testing lib. |
| 1952 | try: |
| 1953 | self.assertEqual(actual, expected) |
| 1954 | except AssertionError as e: |
| 1955 | # Append a diff to the normal error message. |
| 1956 | expected_str = str(expected).splitlines(1) |
| 1957 | actual_str = str(actual).splitlines(1) |
| 1958 | diff = difflib.unified_diff(actual_str, expected_str) |
| 1959 | err_msg = '%s\nProto actual vs expected diff:\n %s' % (e, ''.join(diff)) |
| 1960 | raise AssertionError(err_msg) |
| 1961 | |
| 1962 | def testIngestIssue_Minimal(self): |
| 1963 | """Test IngestIssue with as few fields set as possible.""" |
| 1964 | minimal = issue_objects_pb2.Issue( |
| 1965 | status=issue_objects_pb2.Issue.StatusValue(status='new') |
| 1966 | ) |
| 1967 | expected = tracker_pb2.Issue( |
| 1968 | project_id=self.project_1.project_id, |
| 1969 | summary='', # Summary gets set to empty str on conversion. |
| 1970 | status='new', |
| 1971 | owner_id=0 |
| 1972 | ) |
| 1973 | actual = self.converter.IngestIssue(minimal, self.project_1.project_id) |
| 1974 | self.assertEqual(actual, expected) |
| 1975 | |
| 1976 | def testIngestIssue_NoSuchProject(self): |
| 1977 | self.services.config.strict = True |
| 1978 | ingest = issue_objects_pb2.Issue( |
| 1979 | status=issue_objects_pb2.Issue.StatusValue(status='new')) |
| 1980 | with self.assertRaises(exceptions.NoSuchProjectException): |
| 1981 | self.converter.IngestIssue(ingest, -1) |
| 1982 | |
| 1983 | def testIngestIssue_Errors(self): |
| 1984 | invalid_issue_ref = issue_objects_pb2.IssueRef( |
| 1985 | ext_identifier='b/1', |
| 1986 | issue='projects/proj/issues/1') |
| 1987 | ingest = issue_objects_pb2.Issue( |
| 1988 | summary='sum', |
| 1989 | owner=issue_objects_pb2.Issue.UserValue( |
| 1990 | derivation=EXPLICIT_DERIVATION, user='users/nonexisting@user.com'), |
| 1991 | cc_users=[ |
| 1992 | issue_objects_pb2.Issue.UserValue( |
| 1993 | derivation=EXPLICIT_DERIVATION, user='invalidFormat1'), |
| 1994 | issue_objects_pb2.Issue.UserValue( |
| 1995 | derivation=RULE_DERIVATION, user='invalidFormat2') |
| 1996 | ], |
| 1997 | components=[ |
| 1998 | issue_objects_pb2.Issue.ComponentValue( |
| 1999 | component='projects/proj/componentDefs/404') |
| 2000 | ], |
| 2001 | field_values=[ |
| 2002 | issue_objects_pb2.FieldValue(), |
| 2003 | issue_objects_pb2.FieldValue(field='garbage'), |
| 2004 | issue_objects_pb2.FieldValue( |
| 2005 | field='projects/proj/fieldDefs/%d' % self.field_def_8, |
| 2006 | value='users/nonexisting@user.com', |
| 2007 | ), |
| 2008 | ], |
| 2009 | merged_into_issue_ref=invalid_issue_ref, |
| 2010 | blocked_on_issue_refs=[ |
| 2011 | issue_objects_pb2.IssueRef(), |
| 2012 | issue_objects_pb2.IssueRef(issue='projects/404/issues/1') |
| 2013 | ], |
| 2014 | blocking_issue_refs=[ |
| 2015 | issue_objects_pb2.IssueRef(issue='projects/proj/issues/404') |
| 2016 | ], |
| 2017 | ) |
| 2018 | error_messages = [ |
| 2019 | r'.+not found when ingesting owner', |
| 2020 | r'.+cc_users: Invalid resource name: invalidFormat1.', |
| 2021 | r'Status is required when creating an issue', |
| 2022 | r'.+components: Component not found: 404.', |
| 2023 | r'.+: Invalid resource name: .', r'.+: Invalid resource name: garbage.', |
| 2024 | r'.+not found when ingesting user field:.+', |
| 2025 | r'.+issue:.+[\n\r]+ext_identifier:.+[\n\r]+: IssueRefs MUST NOT have.+', |
| 2026 | r'.+: IssueRefs MUST have one of.+', |
| 2027 | r'.+issue:.+[\n\r]+: Project 404 not found.', |
| 2028 | r'.+issue:.+[\n\r]+: Issue.+404.+not found' |
| 2029 | ] |
| 2030 | error_messages_re = '\n'.join(error_messages) |
Adrià Vilanova Martínez | f19ea43 | 2024-01-23 20:20:52 +0100 | [diff] [blame] | 2031 | with self.assertRaisesRegex(exceptions.InputException, error_messages_re): |
Copybara | 854996b | 2021-09-07 19:36:02 +0000 | [diff] [blame] | 2032 | self.converter.IngestIssue(ingest, self.project_1.project_id) |
| 2033 | |
| 2034 | def testIngestIssuesListColumns(self): |
| 2035 | columns = [ |
| 2036 | issue_objects_pb2.IssuesListColumn(column='chicken'), |
| 2037 | issue_objects_pb2.IssuesListColumn(column='boiled-egg') |
| 2038 | ] |
| 2039 | self.assertEqual( |
| 2040 | self.converter.IngestIssuesListColumns(columns), 'chicken boiled-egg') |
| 2041 | |
| 2042 | def testIngestIssuesListColumns_Empty(self): |
| 2043 | self.assertEqual(self.converter.IngestIssuesListColumns([]), '') |
| 2044 | |
| 2045 | def test_ComputeIssuesListColumns(self): |
| 2046 | """Can convert string to sequence of IssuesListColumns""" |
| 2047 | expected_columns = [ |
| 2048 | issue_objects_pb2.IssuesListColumn(column='chicken'), |
| 2049 | issue_objects_pb2.IssuesListColumn(column='boiled-egg') |
| 2050 | ] |
| 2051 | self.assertEqual( |
| 2052 | expected_columns, |
| 2053 | self.converter._ComputeIssuesListColumns('chicken boiled-egg')) |
| 2054 | |
| 2055 | def test_ComputeIssuesListColumns_Empty(self): |
| 2056 | """Can handle empty strings""" |
| 2057 | self.assertEqual([], self.converter._ComputeIssuesListColumns('')) |
| 2058 | |
| 2059 | def test_Conversion_IssuesListColumns(self): |
| 2060 | """_Ingest and _Compute converts to and from each other""" |
| 2061 | expected_columns = 'foo bar fizz buzz' |
| 2062 | converted_columns = self.converter._ComputeIssuesListColumns( |
| 2063 | expected_columns) |
| 2064 | self.assertEqual( |
| 2065 | expected_columns, |
| 2066 | self.converter.IngestIssuesListColumns(converted_columns)) |
| 2067 | |
| 2068 | expected_columns = [ |
| 2069 | issue_objects_pb2.IssuesListColumn(column='foo'), |
| 2070 | issue_objects_pb2.IssuesListColumn(column='bar'), |
| 2071 | issue_objects_pb2.IssuesListColumn(column='fizz'), |
| 2072 | issue_objects_pb2.IssuesListColumn(column='buzz') |
| 2073 | ] |
| 2074 | converted_columns = self.converter.IngestIssuesListColumns(expected_columns) |
| 2075 | self.assertEqual( |
| 2076 | expected_columns, |
| 2077 | self.converter._ComputeIssuesListColumns(converted_columns)) |
| 2078 | |
| 2079 | def testIngestNotifyType(self): |
| 2080 | notify = issues_pb2.NotifyType.Value('NOTIFY_TYPE_UNSPECIFIED') |
| 2081 | actual = self.converter.IngestNotifyType(notify) |
| 2082 | self.assertEqual(actual, True) |
| 2083 | notify = issues_pb2.NotifyType.Value('EMAIL') |
| 2084 | actual = self.converter.IngestNotifyType(notify) |
| 2085 | self.assertEqual(actual, True) |
| 2086 | notify = issues_pb2.NotifyType.Value('NO_NOTIFICATION') |
| 2087 | actual = self.converter.IngestNotifyType(notify) |
| 2088 | self.assertEqual(actual, False) |
| 2089 | |
| 2090 | def test_GetNonApprovalFieldValues(self): |
| 2091 | """It filters out field values that belong to approvals""" |
| 2092 | expected_str = 'some_string_field_value' |
| 2093 | fv_expected = fake.MakeFieldValue( |
| 2094 | field_id=self.field_def_1, str_value=expected_str, derived=False) |
| 2095 | actual = self.converter._GetNonApprovalFieldValues( |
| 2096 | [fv_expected, self.fv_6], self.project_1.project_id) |
| 2097 | self.assertEqual(len(actual), 1) |
| 2098 | self.assertEqual(actual[0], fv_expected) |
| 2099 | |
| 2100 | def test_GetNonApprovalFieldValues_Empty(self): |
| 2101 | actual = self.converter._GetNonApprovalFieldValues( |
| 2102 | [], self.project_1.project_id) |
| 2103 | self.assertEqual(actual, []) |
| 2104 | |
| 2105 | def testConvertFieldValues(self): |
| 2106 | """It ignores field values referencing a non-existent field""" |
| 2107 | expected_str = 'some_string_field_value' |
| 2108 | fv = fake.MakeFieldValue( |
| 2109 | field_id=self.field_def_1, str_value=expected_str, derived=False) |
| 2110 | expected_name = rnc.ConvertFieldDefNames( |
| 2111 | self.cnxn, [self.field_def_1], self.project_1.project_id, |
| 2112 | self.services)[self.field_def_1] |
| 2113 | expected_value = issue_objects_pb2.FieldValue( |
| 2114 | field=expected_name, |
| 2115 | value=expected_str, |
| 2116 | derivation=EXPLICIT_DERIVATION, |
| 2117 | phase=None) |
| 2118 | output = self.converter.ConvertFieldValues( |
| 2119 | [fv], self.project_1.project_id, []) |
| 2120 | self.assertEqual([expected_value], output) |
| 2121 | |
| 2122 | def testConvertFieldValues_Empty(self): |
| 2123 | output = self.converter.ConvertFieldValues( |
| 2124 | [], self.project_1.project_id, []) |
| 2125 | self.assertEqual([], output) |
| 2126 | |
| 2127 | def testConvertFieldValues_PreservesOrder(self): |
| 2128 | """It ignores field values referencing a non-existent field""" |
| 2129 | expected_str = 'some_string_field_value' |
| 2130 | fv_1 = fake.MakeFieldValue( |
| 2131 | field_id=self.field_def_1, str_value=expected_str, derived=False) |
| 2132 | name_1 = rnc.ConvertFieldDefNames( |
| 2133 | self.cnxn, [self.field_def_1], self.project_1.project_id, |
| 2134 | self.services)[self.field_def_1] |
| 2135 | expected_1 = issue_objects_pb2.FieldValue( |
| 2136 | field=name_1, |
| 2137 | value=expected_str, |
| 2138 | derivation=EXPLICIT_DERIVATION, |
| 2139 | phase=None) |
| 2140 | |
| 2141 | expected_int = 111111 |
| 2142 | fv_2 = fake.MakeFieldValue( |
| 2143 | field_id=self.field_def_2, int_value=expected_int, derived=True) |
| 2144 | name_2 = rnc.ConvertFieldDefNames( |
| 2145 | self.cnxn, [self.field_def_2], self.project_1.project_id, |
| 2146 | self.services).get(self.field_def_2) |
| 2147 | expected_2 = issue_objects_pb2.FieldValue( |
| 2148 | field=name_2, |
| 2149 | value=str(expected_int), |
| 2150 | derivation=RULE_DERIVATION, |
| 2151 | phase=None) |
| 2152 | output = self.converter.ConvertFieldValues( |
| 2153 | [fv_1, fv_2], self.project_1.project_id, []) |
| 2154 | self.assertEqual([expected_1, expected_2], output) |
| 2155 | |
| 2156 | def testConvertFieldValues_IgnoresNullFieldDefs(self): |
| 2157 | """It ignores field values referencing a non-existent field""" |
| 2158 | expected_str = 'some_string_field_value' |
| 2159 | fv_1 = fake.MakeFieldValue( |
| 2160 | field_id=self.field_def_1, str_value=expected_str, derived=False) |
| 2161 | name_1 = rnc.ConvertFieldDefNames( |
| 2162 | self.cnxn, [self.field_def_1], self.project_1.project_id, |
| 2163 | self.services)[self.field_def_1] |
| 2164 | expected_1 = issue_objects_pb2.FieldValue( |
| 2165 | field=name_1, |
| 2166 | value=expected_str, |
| 2167 | derivation=EXPLICIT_DERIVATION, |
| 2168 | phase=None) |
| 2169 | |
| 2170 | fv_2 = fake.MakeFieldValue( |
| 2171 | field_id=self.dne_field_def_id, int_value=111111, derived=True) |
| 2172 | output = self.converter.ConvertFieldValues( |
| 2173 | [fv_1, fv_2], self.project_1.project_id, []) |
| 2174 | self.assertEqual([expected_1], output) |
| 2175 | |
| 2176 | def test_ComputeFieldValueString_None(self): |
| 2177 | with self.assertRaises(exceptions.InputException): |
| 2178 | self.converter._ComputeFieldValueString(None) |
| 2179 | |
| 2180 | def test_ComputeFieldValueString_INT_TYPE(self): |
| 2181 | expected = 123158 |
| 2182 | fv = fake.MakeFieldValue(field_id=self.field_def_2, int_value=expected) |
| 2183 | output = self.converter._ComputeFieldValueString(fv) |
| 2184 | self.assertEqual(str(expected), output) |
| 2185 | |
| 2186 | def test_ComputeFieldValueString_STR_TYPE(self): |
| 2187 | expected = 'some_string_field_value' |
| 2188 | fv = fake.MakeFieldValue(field_id=self.field_def_1, str_value=expected) |
| 2189 | output = self.converter._ComputeFieldValueString(fv) |
| 2190 | self.assertEqual(expected, output) |
| 2191 | |
| 2192 | def test_ComputeFieldValueString_USER_TYPE(self): |
| 2193 | user_id = self.user_1.user_id |
| 2194 | expected = rnc.ConvertUserName(user_id) |
| 2195 | fv = fake.MakeFieldValue(field_id=self.dne_field_def_id, user_id=user_id) |
| 2196 | output = self.converter._ComputeFieldValueString(fv) |
| 2197 | self.assertEqual(expected, output) |
| 2198 | |
| 2199 | def test_ComputeFieldValueString_DATE_TYPE(self): |
| 2200 | expected = 1234567890 |
| 2201 | fv = fake.MakeFieldValue( |
| 2202 | field_id=self.dne_field_def_id, date_value=expected) |
| 2203 | output = self.converter._ComputeFieldValueString(fv) |
| 2204 | self.assertEqual(str(expected), output) |
| 2205 | |
| 2206 | def test_ComputeFieldValueString_URL_TYPE(self): |
| 2207 | expected = 'some URL' |
| 2208 | fv = fake.MakeFieldValue(field_id=self.dne_field_def_id, url_value=expected) |
| 2209 | output = self.converter._ComputeFieldValueString(fv) |
| 2210 | self.assertEqual(expected, output) |
| 2211 | |
| 2212 | def test_ComputeFieldValueDerivation_RULE(self): |
| 2213 | expected = RULE_DERIVATION |
| 2214 | fv = fake.MakeFieldValue( |
| 2215 | field_id=self.field_def_1, str_value='something', derived=True) |
| 2216 | output = self.converter._ComputeFieldValueDerivation(fv) |
| 2217 | self.assertEqual(expected, output) |
| 2218 | |
| 2219 | def test_ComputeFieldValueDerivation_EXPLICIT(self): |
| 2220 | expected = EXPLICIT_DERIVATION |
| 2221 | fv = fake.MakeFieldValue( |
| 2222 | field_id=self.field_def_1, str_value='something', derived=False) |
| 2223 | output = self.converter._ComputeFieldValueDerivation(fv) |
| 2224 | self.assertEqual(expected, output) |
| 2225 | |
| 2226 | def testConvertApprovalValues_Issue(self): |
| 2227 | """We can convert issue approval_values.""" |
| 2228 | name = rnc.ConvertApprovalValueNames( |
| 2229 | self.cnxn, self.issue_1.issue_id, self.services)[self.av_1.approval_id] |
| 2230 | approval_def_name = rnc.ConvertApprovalDefNames( |
| 2231 | self.cnxn, [self.approval_def_1_id], self.project_1.project_id, |
| 2232 | self.services)[self.approval_def_1_id] |
| 2233 | approvers = [rnc.ConvertUserName(self.user_2.user_id)] |
| 2234 | status = issue_objects_pb2.ApprovalValue.ApprovalStatus.Value( |
| 2235 | 'NOT_SET') |
| 2236 | setter = rnc.ConvertUserName(self.user_1.user_id) |
| 2237 | api_fvs = self.converter.ConvertFieldValues( |
| 2238 | [self.fv_6], self.project_1.project_id, [self.phase_1]) |
| 2239 | # Check we can handle converting a None `set_on`. |
| 2240 | self.av_1.set_on = None |
| 2241 | |
| 2242 | output = self.converter.ConvertApprovalValues( |
| 2243 | [self.av_1], [self.fv_1, self.fv_6], [self.phase_1], |
| 2244 | issue_id=self.issue_1.issue_id) |
| 2245 | expected = issue_objects_pb2.ApprovalValue( |
| 2246 | name=name, |
| 2247 | approval_def=approval_def_name, |
| 2248 | approvers=approvers, |
| 2249 | status=status, |
| 2250 | setter=setter, |
| 2251 | phase=self.phase_1.name, |
| 2252 | field_values=api_fvs) |
| 2253 | self.assertEqual([expected], output) |
| 2254 | |
| 2255 | def testConvertApprovalValues_Templates(self): |
| 2256 | """We can convert template approval_values.""" |
| 2257 | approval_def_name = rnc.ConvertApprovalDefNames( |
| 2258 | self.cnxn, [self.approval_def_1_id], self.project_1.project_id, |
| 2259 | self.services)[self.approval_def_1_id] |
| 2260 | approvers = [rnc.ConvertUserName(self.user_2.user_id)] |
| 2261 | status = issue_objects_pb2.ApprovalValue.ApprovalStatus.Value( |
| 2262 | 'NOT_SET') |
| 2263 | set_time = timestamp_pb2.Timestamp() |
| 2264 | set_time.FromSeconds(self.PAST_TIME) |
| 2265 | setter = rnc.ConvertUserName(self.user_1.user_id) |
| 2266 | api_fvs = self.converter.ConvertFieldValues( |
| 2267 | [self.fv_6], self.project_1.project_id, [self.phase_1]) |
| 2268 | |
| 2269 | output = self.converter.ConvertApprovalValues( |
| 2270 | [self.av_1], [self.fv_1, self.fv_6], [self.phase_1], |
| 2271 | project_id=self.project_1.project_id) |
| 2272 | expected = issue_objects_pb2.ApprovalValue( |
| 2273 | approval_def=approval_def_name, |
| 2274 | approvers=approvers, |
| 2275 | status=status, |
| 2276 | set_time=set_time, |
| 2277 | setter=setter, |
| 2278 | phase=self.phase_1.name, |
| 2279 | field_values=api_fvs) |
| 2280 | self.assertEqual([expected], output) |
| 2281 | |
| 2282 | def testConvertApprovalValues_NoPhase(self): |
| 2283 | approval_def_name = rnc.ConvertApprovalDefNames( |
| 2284 | self.cnxn, [self.approval_def_1_id], self.project_1.project_id, |
| 2285 | self.services)[self.approval_def_1_id] |
| 2286 | approvers = [rnc.ConvertUserName(self.user_2.user_id)] |
| 2287 | status = issue_objects_pb2.ApprovalValue.ApprovalStatus.Value( |
| 2288 | 'NOT_SET') |
| 2289 | set_time = timestamp_pb2.Timestamp() |
| 2290 | set_time.FromSeconds(self.PAST_TIME) |
| 2291 | setter = rnc.ConvertUserName(self.user_1.user_id) |
| 2292 | expected = issue_objects_pb2.ApprovalValue( |
| 2293 | approval_def=approval_def_name, |
| 2294 | approvers=approvers, |
| 2295 | status=status, |
| 2296 | set_time=set_time, |
| 2297 | setter=setter) |
| 2298 | |
| 2299 | output = self.converter.ConvertApprovalValues( |
| 2300 | [self.av_1], [], [], project_id=self.project_1.project_id) |
| 2301 | self.assertEqual([expected], output) |
| 2302 | |
| 2303 | def testConvertApprovalValues_Empty(self): |
| 2304 | output = self.converter.ConvertApprovalValues( |
| 2305 | [], [], [], project_id=self.project_1.project_id) |
| 2306 | self.assertEqual([], output) |
| 2307 | |
| 2308 | def testConvertApprovalValues_IgnoresNullFieldDefs(self): |
| 2309 | """It ignores approval values referencing a non-existent field""" |
| 2310 | av = fake.MakeApprovalValue(self.dne_field_def_id) |
| 2311 | |
| 2312 | output = self.converter.ConvertApprovalValues( |
| 2313 | [av], [], [], issue_id=self.issue_1.issue_id) |
| 2314 | self.assertEqual([], output) |
| 2315 | |
| 2316 | def test_ComputeApprovalValueStatus_NOT_SET(self): |
| 2317 | self.assertEqual( |
| 2318 | self.converter._ComputeApprovalValueStatus( |
| 2319 | tracker_pb2.ApprovalStatus.NOT_SET), |
| 2320 | issue_objects_pb2.ApprovalValue.ApprovalStatus.Value( |
| 2321 | 'NOT_SET')) |
| 2322 | |
| 2323 | def test_ComputeApprovalValueStatus_NEEDS_REVIEW(self): |
| 2324 | self.assertEqual( |
| 2325 | self.converter._ComputeApprovalValueStatus( |
| 2326 | tracker_pb2.ApprovalStatus.NEEDS_REVIEW), |
| 2327 | issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NEEDS_REVIEW')) |
| 2328 | |
| 2329 | def test_ComputeApprovalValueStatus_NA(self): |
| 2330 | self.assertEqual( |
| 2331 | self.converter._ComputeApprovalValueStatus( |
| 2332 | tracker_pb2.ApprovalStatus.NA), |
| 2333 | issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NA')) |
| 2334 | |
| 2335 | def test_ComputeApprovalValueStatus_REVIEW_REQUESTED(self): |
| 2336 | self.assertEqual( |
| 2337 | self.converter._ComputeApprovalValueStatus( |
| 2338 | tracker_pb2.ApprovalStatus.REVIEW_REQUESTED), |
| 2339 | issue_objects_pb2.ApprovalValue.ApprovalStatus.Value( |
| 2340 | 'REVIEW_REQUESTED')) |
| 2341 | |
| 2342 | def test_ComputeApprovalValueStatus_REVIEW_STARTED(self): |
| 2343 | self.assertEqual( |
| 2344 | self.converter._ComputeApprovalValueStatus( |
| 2345 | tracker_pb2.ApprovalStatus.REVIEW_STARTED), |
| 2346 | issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('REVIEW_STARTED')) |
| 2347 | |
| 2348 | def test_ComputeApprovalValueStatus_NEED_INFO(self): |
| 2349 | self.assertEqual( |
| 2350 | self.converter._ComputeApprovalValueStatus( |
| 2351 | tracker_pb2.ApprovalStatus.NEED_INFO), |
| 2352 | issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NEED_INFO')) |
| 2353 | |
| 2354 | def test_ComputeApprovalValueStatus_APPROVED(self): |
| 2355 | self.assertEqual( |
| 2356 | self.converter._ComputeApprovalValueStatus( |
| 2357 | tracker_pb2.ApprovalStatus.APPROVED), |
| 2358 | issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('APPROVED')) |
| 2359 | |
| 2360 | def test_ComputeApprovalValueStatus_NOT_APPROVED(self): |
| 2361 | self.assertEqual( |
| 2362 | self.converter._ComputeApprovalValueStatus( |
| 2363 | tracker_pb2.ApprovalStatus.NOT_APPROVED), |
| 2364 | issue_objects_pb2.ApprovalValue.ApprovalStatus.Value('NOT_APPROVED')) |
| 2365 | |
| 2366 | def test_ComputeTemplatePrivacy_PUBLIC(self): |
| 2367 | self.assertEqual( |
| 2368 | self.converter._ComputeTemplatePrivacy(self.template_1), |
| 2369 | project_objects_pb2.IssueTemplate.TemplatePrivacy.Value('PUBLIC')) |
| 2370 | |
| 2371 | def test_ComputeTemplatePrivacy_MEMBERS_ONLY(self): |
| 2372 | self.assertEqual( |
| 2373 | self.converter._ComputeTemplatePrivacy(self.template_2), |
| 2374 | project_objects_pb2.IssueTemplate.TemplatePrivacy.Value('MEMBERS_ONLY')) |
| 2375 | |
| 2376 | def test_ComputeTemplateDefaultOwner_UNSPECIFIED(self): |
| 2377 | self.assertEqual( |
| 2378 | self.converter._ComputeTemplateDefaultOwner(self.template_1), |
| 2379 | project_objects_pb2.IssueTemplate.DefaultOwner.Value( |
| 2380 | 'DEFAULT_OWNER_UNSPECIFIED')) |
| 2381 | |
| 2382 | def test_ComputeTemplateDefaultOwner_REPORTER(self): |
| 2383 | self.assertEqual( |
| 2384 | self.converter._ComputeTemplateDefaultOwner(self.template_2), |
| 2385 | project_objects_pb2.IssueTemplate.DefaultOwner.Value( |
| 2386 | 'PROJECT_MEMBER_REPORTER')) |
| 2387 | |
| 2388 | def test_ComputePhases(self): |
| 2389 | """It sorts by rank""" |
| 2390 | phase1 = fake.MakePhase(123111, name='phase1name', rank=3) |
| 2391 | phase2 = fake.MakePhase(123112, name='phase2name', rank=2) |
| 2392 | phase3 = fake.MakePhase(123113, name='phase3name', rank=1) |
| 2393 | expected = ['phase3name', 'phase2name', 'phase1name'] |
| 2394 | self.assertEqual( |
| 2395 | self.converter._ComputePhases([phase1, phase2, phase3]), expected) |
| 2396 | |
| 2397 | def test_ComputePhases_EMPTY(self): |
| 2398 | self.assertEqual(self.converter._ComputePhases([]), []) |
| 2399 | |
| 2400 | def test_FillIssueFromTemplate(self): |
| 2401 | result = self.converter._FillIssueFromTemplate( |
| 2402 | self.template_1, self.project_1.project_id) |
| 2403 | self.assertFalse(result.name) |
| 2404 | self.assertEqual(result.summary, self.template_1.summary) |
| 2405 | self.assertEqual( |
| 2406 | result.state, issue_objects_pb2.IssueContentState.Value('ACTIVE')) |
| 2407 | self.assertEqual(result.status.status, 'New') |
| 2408 | self.assertFalse(result.reporter) |
| 2409 | self.assertEqual(result.owner.user, 'users/{}'.format(self.user_1.user_id)) |
| 2410 | self.assertEqual(len(result.cc_users), 0) |
| 2411 | self.assertFalse(result.cc_users) |
| 2412 | self.assertEqual(len(result.labels), 1) |
| 2413 | self.assertEqual(result.labels[0].label, self.template_1.labels[0]) |
| 2414 | self.assertEqual(result.labels[0].derivation, EXPLICIT_DERIVATION) |
| 2415 | self.assertEqual(len(result.components), 1) |
| 2416 | self.assertEqual( |
| 2417 | result.components[0].component, 'projects/{}/componentDefs/{}'.format( |
| 2418 | self.project_1.project_name, self.template_1.component_ids[0])) |
| 2419 | self.assertEqual(result.components[0].derivation, EXPLICIT_DERIVATION) |
| 2420 | self.assertEqual(len(result.field_values), 2) |
| 2421 | self.assertEqual( |
| 2422 | result.field_values[0].field, 'projects/{}/fieldDefs/{}'.format( |
| 2423 | self.project_1.project_name, self.field_def_1)) |
| 2424 | self.assertEqual(result.field_values[0].value, self.fv_1_value) |
| 2425 | self.assertEqual(result.field_values[0].derivation, EXPLICIT_DERIVATION) |
| 2426 | expected_name = rnc.ConvertFieldDefNames( |
| 2427 | self.cnxn, [self.field_def_3], self.project_1.project_id, |
| 2428 | self.services).get(self.field_def_3) |
| 2429 | self.assertEqual( |
| 2430 | result.field_values[1], |
| 2431 | issue_objects_pb2.FieldValue( |
| 2432 | field=expected_name, |
| 2433 | value=self.template_1_label1_value, |
| 2434 | derivation=EXPLICIT_DERIVATION)) |
| 2435 | self.assertFalse(result.blocked_on_issue_refs) |
| 2436 | self.assertFalse(result.blocking_issue_refs) |
| 2437 | self.assertFalse(result.attachment_count) |
| 2438 | self.assertFalse(result.star_count) |
| 2439 | self.assertEqual(len(result.phases), 1) |
| 2440 | self.assertEqual(result.phases[0], self.phase_1.name) |
| 2441 | |
| 2442 | def test_FillIssueFromTemplate_NoPhase(self): |
| 2443 | result = self.converter._FillIssueFromTemplate( |
| 2444 | self.template_3, self.project_1.project_id) |
| 2445 | self.assertEqual(len(result.field_values), 1) |
| 2446 | self.assertEqual( |
| 2447 | result.field_values[0].field, 'projects/{}/fieldDefs/{}'.format( |
| 2448 | self.project_1.project_name, self.field_def_1)) |
| 2449 | self.assertEqual(result.field_values[0].value, self.fv_1_value) |
| 2450 | self.assertEqual(result.field_values[0].derivation, EXPLICIT_DERIVATION) |
| 2451 | self.assertEqual(len(result.phases), 0) |
| 2452 | |
| 2453 | def test_FillIssueFromTemplate_FilterApprovalFV(self): |
| 2454 | template = self.services.template.TestAddIssueTemplateDef( |
| 2455 | 11114, |
| 2456 | self.project_1.project_id, |
| 2457 | 'template3', |
| 2458 | field_values=[self.fv_1, self.fv_6], |
| 2459 | approval_values=[self.av_2], |
| 2460 | ) |
| 2461 | result = self.converter._FillIssueFromTemplate( |
| 2462 | template, self.project_1.project_id) |
| 2463 | self.assertEqual(len(result.field_values), 1) |
| 2464 | self.assertEqual( |
| 2465 | result.field_values[0].field, 'projects/{}/fieldDefs/{}'.format( |
| 2466 | self.project_1.project_name, self.field_def_1)) |
| 2467 | self.assertEqual(result.field_values[0].value, self.fv_1_value) |
| 2468 | self.assertEqual(result.field_values[0].derivation, EXPLICIT_DERIVATION) |
| 2469 | |
| 2470 | def testConvertIssueTemplates(self): |
| 2471 | result = self.converter.ConvertIssueTemplates( |
| 2472 | self.project_1.project_id, [self.template_1]) |
| 2473 | self.assertEqual(len(result), 1) |
| 2474 | actual = result[0] |
| 2475 | self.assertEqual( |
| 2476 | actual.name, 'projects/{}/templates/{}'.format( |
| 2477 | self.project_1.project_name, self.template_1.template_id)) |
| 2478 | self.assertEqual(actual.display_name, self.template_1.name) |
| 2479 | self.assertEqual(actual.summary_must_be_edited, False) |
| 2480 | self.assertEqual( |
| 2481 | actual.template_privacy, |
| 2482 | project_objects_pb2.IssueTemplate.TemplatePrivacy.Value('PUBLIC')) |
| 2483 | self.assertEqual( |
| 2484 | actual.default_owner, |
| 2485 | project_objects_pb2.IssueTemplate.DefaultOwner.Value( |
| 2486 | 'DEFAULT_OWNER_UNSPECIFIED')) |
| 2487 | self.assertEqual(actual.component_required, False) |
| 2488 | self.assertEqual(actual.admins, ['users/{}'.format(self.user_2.user_id)]) |
| 2489 | self.assertEqual( |
| 2490 | actual.issue, |
| 2491 | self.converter._FillIssueFromTemplate( |
| 2492 | self.template_1, self.project_1.project_id)) |
| 2493 | self.assertListEqual( |
| 2494 | [av for av in actual.approval_values], |
| 2495 | self.converter.ConvertApprovalValues( |
| 2496 | self.template_1.approval_values, self.template_1.field_values, |
| 2497 | self.template_1.phases, project_id=self.project_1.project_id)) |
| 2498 | |
| 2499 | def testConvertIssueTemplates_IgnoresNonExistentTemplate(self): |
| 2500 | result = self.converter.ConvertIssueTemplates( |
| 2501 | self.project_1.project_id, [self.dne_template]) |
| 2502 | self.assertEqual(len(result), 0) |
| 2503 | |
| 2504 | def testConvertLabels_OmitsFieldDefs(self): |
| 2505 | """It omits field def labels""" |
| 2506 | input_labels = ['pri-1', '{}-2'.format(self.field_def_3_name)] |
| 2507 | result = self.converter.ConvertLabels( |
| 2508 | input_labels, [], self.project_1.project_id) |
| 2509 | self.assertEqual(len(result), 1) |
| 2510 | expected = issue_objects_pb2.Issue.LabelValue( |
| 2511 | label=input_labels[0], derivation=EXPLICIT_DERIVATION) |
| 2512 | self.assertEqual(result[0], expected) |
| 2513 | |
| 2514 | def testConvertLabels_DerivedLabels(self): |
| 2515 | """It handles derived labels""" |
| 2516 | input_labels = ['pri-1'] |
| 2517 | result = self.converter.ConvertLabels( |
| 2518 | [], input_labels, self.project_1.project_id) |
| 2519 | self.assertEqual(len(result), 1) |
| 2520 | expected = issue_objects_pb2.Issue.LabelValue( |
| 2521 | label=input_labels[0], derivation=RULE_DERIVATION) |
| 2522 | self.assertEqual(result[0], expected) |
| 2523 | |
| 2524 | def testConvertLabels(self): |
| 2525 | """It includes both non-derived and derived labels""" |
| 2526 | input_labels = ['pri-1', '{}-2'.format(self.field_def_3_name)] |
| 2527 | input_der_labels = ['{}-3'.format(self.field_def_3_name), 'job-secret'] |
| 2528 | result = self.converter.ConvertLabels( |
| 2529 | input_labels, input_der_labels, self.project_1.project_id) |
| 2530 | self.assertEqual(len(result), 2) |
| 2531 | expected_0 = issue_objects_pb2.Issue.LabelValue( |
| 2532 | label=input_labels[0], derivation=EXPLICIT_DERIVATION) |
| 2533 | self.assertEqual(result[0], expected_0) |
| 2534 | expected_1 = issue_objects_pb2.Issue.LabelValue( |
| 2535 | label=input_der_labels[1], derivation=RULE_DERIVATION) |
| 2536 | self.assertEqual(result[1], expected_1) |
| 2537 | |
| 2538 | def testConvertLabels_Empty(self): |
| 2539 | result = self.converter.ConvertLabels([], [], self.project_1.project_id) |
| 2540 | self.assertEqual(result, []) |
| 2541 | |
| 2542 | def testConvertEnumFieldValues_OnlyFieldDefs(self): |
| 2543 | """It only returns enum field values""" |
| 2544 | expected_value = '2' |
| 2545 | input_labels = [ |
| 2546 | 'pri-1', '{}-{}'.format(self.field_def_3_name, expected_value) |
| 2547 | ] |
| 2548 | result = self.converter.ConvertEnumFieldValues( |
| 2549 | input_labels, [], self.project_1.project_id) |
| 2550 | self.assertEqual(len(result), 1) |
| 2551 | expected_name = rnc.ConvertFieldDefNames( |
| 2552 | self.cnxn, [self.field_def_3], self.project_1.project_id, |
| 2553 | self.services).get(self.field_def_3) |
| 2554 | expected = issue_objects_pb2.FieldValue( |
| 2555 | field=expected_name, |
| 2556 | value=expected_value, |
| 2557 | derivation=EXPLICIT_DERIVATION) |
| 2558 | self.assertEqual(result[0], expected) |
| 2559 | |
| 2560 | def testConvertEnumFieldValues_DerivedLabels(self): |
| 2561 | """It handles derived enum field values""" |
| 2562 | expected_value = '2' |
| 2563 | input_der_labels = [ |
| 2564 | 'pri-1', '{}-{}'.format(self.field_def_3_name, expected_value) |
| 2565 | ] |
| 2566 | result = self.converter.ConvertEnumFieldValues( |
| 2567 | [], input_der_labels, self.project_1.project_id) |
| 2568 | self.assertEqual(len(result), 1) |
| 2569 | expected_name = rnc.ConvertFieldDefNames( |
| 2570 | self.cnxn, [self.field_def_3], self.project_1.project_id, |
| 2571 | self.services).get(self.field_def_3) |
| 2572 | expected = issue_objects_pb2.FieldValue( |
| 2573 | field=expected_name, value=expected_value, derivation=RULE_DERIVATION) |
| 2574 | self.assertEqual(result[0], expected) |
| 2575 | |
| 2576 | def testConvertEnumFieldValues_Empty(self): |
| 2577 | result = self.converter.ConvertEnumFieldValues( |
| 2578 | [], [], self.project_1.project_id) |
| 2579 | self.assertEqual(result, []) |
| 2580 | |
| 2581 | def testConvertEnumFieldValues_ProjectSpecific(self): |
| 2582 | """It only considers field defs from specified project""" |
| 2583 | expected_value = '2' |
| 2584 | input_labels = [ |
| 2585 | '{}-{}'.format(self.field_def_3_name, expected_value), |
| 2586 | '{}-ipsum'.format(self.field_def_project2_name) |
| 2587 | ] |
| 2588 | result = self.converter.ConvertEnumFieldValues( |
| 2589 | input_labels, [], self.project_1.project_id) |
| 2590 | self.assertEqual(len(result), 1) |
| 2591 | expected_name = rnc.ConvertFieldDefNames( |
| 2592 | self.cnxn, [self.field_def_3], self.project_1.project_id, |
| 2593 | self.services).get(self.field_def_3) |
| 2594 | expected = issue_objects_pb2.FieldValue( |
| 2595 | field=expected_name, |
| 2596 | value=expected_value, |
| 2597 | derivation=EXPLICIT_DERIVATION) |
| 2598 | self.assertEqual(result[0], expected) |
| 2599 | |
| 2600 | def testConvertEnumFieldValues(self): |
| 2601 | """It handles derived enum field values""" |
| 2602 | expected_value_0 = '2' |
| 2603 | expected_value_1 = 'macOS' |
| 2604 | input_labels = [ |
| 2605 | 'pri-1', '{}-{}'.format(self.field_def_3_name, expected_value_0), |
| 2606 | '{}-ipsum'.format(self.field_def_project2_name) |
| 2607 | ] |
| 2608 | input_der_labels = [ |
| 2609 | '{}-{}'.format(self.field_def_4_name, expected_value_1), 'foo-bar' |
| 2610 | ] |
| 2611 | result = self.converter.ConvertEnumFieldValues( |
| 2612 | input_labels, input_der_labels, self.project_1.project_id) |
| 2613 | self.assertEqual(len(result), 2) |
| 2614 | expected_0_name = rnc.ConvertFieldDefNames( |
| 2615 | self.cnxn, [self.field_def_3], self.project_1.project_id, |
| 2616 | self.services).get(self.field_def_3) |
| 2617 | expected_0 = issue_objects_pb2.FieldValue( |
| 2618 | field=expected_0_name, |
| 2619 | value=expected_value_0, |
| 2620 | derivation=EXPLICIT_DERIVATION) |
| 2621 | self.assertEqual(result[0], expected_0) |
| 2622 | expected_1_name = rnc.ConvertFieldDefNames( |
| 2623 | self.cnxn, [self.field_def_4], self.project_1.project_id, |
| 2624 | self.services).get(self.field_def_4) |
| 2625 | expected_1 = issue_objects_pb2.FieldValue( |
| 2626 | field=expected_1_name, |
| 2627 | value=expected_value_1, |
| 2628 | derivation=RULE_DERIVATION) |
| 2629 | self.assertEqual(result[1], expected_1) |
| 2630 | |
| 2631 | @mock.patch('project.project_helpers.GetThumbnailUrl') |
| 2632 | def testConvertProject(self, mock_GetThumbnailUrl): |
| 2633 | """We can convert a Project.""" |
| 2634 | mock_GetThumbnailUrl.return_value = 'xyz' |
| 2635 | expected_api_project = project_objects_pb2.Project( |
| 2636 | name='projects/{}'.format(self.project_1.project_name), |
| 2637 | display_name=self.project_1.project_name, |
| 2638 | summary=self.project_1.summary, |
| 2639 | thumbnail_url='xyz') |
| 2640 | self.assertEqual( |
| 2641 | expected_api_project, self.converter.ConvertProject(self.project_1)) |
| 2642 | |
| 2643 | @mock.patch('project.project_helpers.GetThumbnailUrl') |
| 2644 | def testConvertProjects(self, mock_GetThumbnailUrl): |
| 2645 | """We can convert a Sequence of Projects.""" |
| 2646 | mock_GetThumbnailUrl.return_value = 'xyz' |
| 2647 | expected_api_projects = [ |
| 2648 | project_objects_pb2.Project( |
| 2649 | name='projects/{}'.format(self.project_1.project_name), |
| 2650 | display_name=self.project_1.project_name, |
| 2651 | summary=self.project_1.summary, |
| 2652 | thumbnail_url='xyz'), |
| 2653 | project_objects_pb2.Project( |
| 2654 | name='projects/{}'.format(self.project_2.project_name), |
| 2655 | display_name=self.project_2.project_name, |
| 2656 | summary=self.project_2.summary, |
| 2657 | thumbnail_url='xyz') |
| 2658 | ] |
| 2659 | self.assertEqual( |
| 2660 | expected_api_projects, |
| 2661 | self.converter.ConvertProjects([self.project_1, self.project_2])) |
| 2662 | |
| 2663 | def testConvertProjectConfig(self): |
| 2664 | """We can convert a project_config""" |
| 2665 | project_config = self.services.config.GetProjectConfig( |
| 2666 | self.cnxn, self.project_1.project_id) |
| 2667 | expected_grid_config = project_objects_pb2.ProjectConfig.GridViewConfig( |
| 2668 | default_x_attr=project_config.default_x_attr, |
| 2669 | default_y_attr=project_config.default_y_attr) |
| 2670 | template_names = rnc.ConvertTemplateNames( |
| 2671 | self.cnxn, project_config.project_id, [ |
| 2672 | project_config.default_template_for_developers, |
| 2673 | project_config.default_template_for_users |
| 2674 | ], self.services) |
| 2675 | expected_api_config = project_objects_pb2.ProjectConfig( |
| 2676 | name=rnc.ConvertProjectConfigName( |
| 2677 | self.cnxn, self.project_1.project_id, self.services), |
| 2678 | exclusive_label_prefixes=project_config.exclusive_label_prefixes, |
| 2679 | member_default_query=project_config.member_default_query, |
| 2680 | default_sort=project_config.default_sort_spec, |
| 2681 | default_columns=[ |
| 2682 | issue_objects_pb2.IssuesListColumn(column=col) |
| 2683 | for col in project_config.default_col_spec.split() |
| 2684 | ], |
| 2685 | project_grid_config=expected_grid_config, |
| 2686 | member_default_template=template_names.get( |
| 2687 | project_config.default_template_for_developers), |
| 2688 | non_members_default_template=template_names.get( |
| 2689 | project_config.default_template_for_users), |
| 2690 | revision_url_format=self.project_1.revision_url_format, |
| 2691 | custom_issue_entry_url=project_config.custom_issue_entry_url) |
| 2692 | self.converter.user_auth = authdata.AuthData.FromUser( |
| 2693 | self.cnxn, self.user_1, self.services) |
| 2694 | self.assertEqual( |
| 2695 | expected_api_config, |
| 2696 | self.converter.ConvertProjectConfig(project_config)) |
| 2697 | |
| 2698 | def testConvertProjectConfig_NonMembers(self): |
| 2699 | """We can convert a project_config for non project members""" |
| 2700 | self.converter.user_auth = authdata.AuthData.FromUser( |
| 2701 | self.cnxn, self.user_2, self.services) |
| 2702 | project_config = self.services.config.GetProjectConfig( |
| 2703 | self.cnxn, self.project_1.project_id) |
| 2704 | api_config = self.converter.ConvertProjectConfig(project_config) |
| 2705 | |
| 2706 | expected_default_query = project_config.member_default_query |
| 2707 | self.assertEqual(expected_default_query, api_config.member_default_query) |
| 2708 | |
| 2709 | expected_member_default_template = rnc.ConvertTemplateNames( |
| 2710 | self.cnxn, project_config.project_id, |
| 2711 | [project_config.default_template_for_developers], self.services).get( |
| 2712 | project_config.default_template_for_developers) |
| 2713 | self.assertEqual( |
| 2714 | expected_member_default_template, api_config.member_default_template) |
| 2715 | |
| 2716 | def testCreateProjectMember(self): |
| 2717 | """We can create a ProjectMember.""" |
| 2718 | expected_project_member = project_objects_pb2.ProjectMember( |
| 2719 | name='projects/proj/members/111', |
| 2720 | role=project_objects_pb2.ProjectMember.ProjectRole.Value('OWNER')) |
| 2721 | self.assertEqual( |
| 2722 | expected_project_member, |
| 2723 | self.converter.CreateProjectMember(self.cnxn, 789, 111, 'OWNER')) |
| 2724 | |
| 2725 | def test_ConvertDateAction(self): |
| 2726 | """We can convert from protorpc to protoc FieldDef.DateAction""" |
| 2727 | date_type_settings = project_objects_pb2.FieldDef.DateTypeSettings |
| 2728 | |
| 2729 | input_type = tracker_pb2.DateAction.NO_ACTION |
| 2730 | actual = self.converter._ConvertDateAction(input_type) |
| 2731 | expected = date_type_settings.DateAction.Value('NO_ACTION') |
| 2732 | self.assertEqual(expected, actual) |
| 2733 | |
| 2734 | input_type = tracker_pb2.DateAction.PING_OWNER_ONLY |
| 2735 | actual = self.converter._ConvertDateAction(input_type) |
| 2736 | expected = date_type_settings.DateAction.Value('NOTIFY_OWNER') |
| 2737 | self.assertEqual(expected, actual) |
| 2738 | |
| 2739 | input_type = tracker_pb2.DateAction.PING_PARTICIPANTS |
| 2740 | actual = self.converter._ConvertDateAction(input_type) |
| 2741 | expected = date_type_settings.DateAction.Value('NOTIFY_PARTICIPANTS') |
| 2742 | self.assertEqual(expected, actual) |
| 2743 | |
| 2744 | def test_ConvertRoleRequirements(self): |
| 2745 | """We can convert from protorpc to protoc FieldDef.RoleRequirements""" |
| 2746 | user_type_settings = project_objects_pb2.FieldDef.UserTypeSettings |
| 2747 | |
| 2748 | actual = self.converter._ConvertRoleRequirements(False) |
| 2749 | expected = user_type_settings.RoleRequirements.Value('NO_ROLE_REQUIREMENT') |
| 2750 | self.assertEqual(expected, actual) |
| 2751 | |
| 2752 | actual = self.converter._ConvertRoleRequirements(True) |
| 2753 | expected = user_type_settings.RoleRequirements.Value('PROJECT_MEMBER') |
| 2754 | self.assertEqual(expected, actual) |
| 2755 | |
| 2756 | def test_ConvertNotifyTriggers(self): |
| 2757 | """We can convert from protorpc to protoc FieldDef.NotifyTriggers""" |
| 2758 | user_type_settings = project_objects_pb2.FieldDef.UserTypeSettings |
| 2759 | |
| 2760 | input_type = tracker_pb2.NotifyTriggers.NEVER |
| 2761 | actual = self.converter._ConvertNotifyTriggers(input_type) |
| 2762 | expected = user_type_settings.NotifyTriggers.Value('NEVER') |
| 2763 | self.assertEqual(expected, actual) |
| 2764 | |
| 2765 | input_type = tracker_pb2.NotifyTriggers.ANY_COMMENT |
| 2766 | actual = self.converter._ConvertNotifyTriggers(input_type) |
| 2767 | expected = user_type_settings.NotifyTriggers.Value('ANY_COMMENT') |
| 2768 | self.assertEqual(expected, actual) |
| 2769 | |
| 2770 | def test_ConvertFieldDefType(self): |
| 2771 | """We can convert from protorpc FieldType to protoc FieldDef.Type""" |
| 2772 | input_type = tracker_pb2.FieldTypes.ENUM_TYPE |
| 2773 | actual = self.converter._ConvertFieldDefType(input_type) |
| 2774 | expected = project_objects_pb2.FieldDef.Type.Value('ENUM') |
| 2775 | self.assertEqual(expected, actual) |
| 2776 | |
| 2777 | input_type = tracker_pb2.FieldTypes.INT_TYPE |
| 2778 | actual = self.converter._ConvertFieldDefType(input_type) |
| 2779 | expected = project_objects_pb2.FieldDef.Type.Value('INT') |
| 2780 | self.assertEqual(expected, actual) |
| 2781 | |
| 2782 | input_type = tracker_pb2.FieldTypes.STR_TYPE |
| 2783 | actual = self.converter._ConvertFieldDefType(input_type) |
| 2784 | expected = project_objects_pb2.FieldDef.Type.Value('STR') |
| 2785 | self.assertEqual(expected, actual) |
| 2786 | |
| 2787 | input_type = tracker_pb2.FieldTypes.USER_TYPE |
| 2788 | actual = self.converter._ConvertFieldDefType(input_type) |
| 2789 | expected = project_objects_pb2.FieldDef.Type.Value('USER') |
| 2790 | self.assertEqual(expected, actual) |
| 2791 | |
| 2792 | input_type = tracker_pb2.FieldTypes.DATE_TYPE |
| 2793 | actual = self.converter._ConvertFieldDefType(input_type) |
| 2794 | expected = project_objects_pb2.FieldDef.Type.Value('DATE') |
| 2795 | self.assertEqual(expected, actual) |
| 2796 | |
| 2797 | input_type = tracker_pb2.FieldTypes.URL_TYPE |
| 2798 | actual = self.converter._ConvertFieldDefType(input_type) |
| 2799 | expected = project_objects_pb2.FieldDef.Type.Value('URL') |
| 2800 | self.assertEqual(expected, actual) |
| 2801 | |
| 2802 | def test_ConvertFieldDefType_BOOL(self): |
| 2803 | """We raise exception for unsupported input type BOOL""" |
| 2804 | input_type = tracker_pb2.FieldTypes.BOOL_TYPE |
| 2805 | with self.assertRaises(ValueError) as cm: |
| 2806 | self.converter._ConvertFieldDefType(input_type) |
| 2807 | self.assertEqual( |
| 2808 | 'Unsupported tracker_pb2.FieldType enum. Boolean types ' |
| 2809 | 'are unsupported and approval types are found in ApprovalDefs', |
| 2810 | str(cm.exception)) |
| 2811 | |
| 2812 | def test_ConvertFieldDefType_APPROVAL(self): |
| 2813 | """We raise exception for input type APPROVAL""" |
| 2814 | input_type = tracker_pb2.FieldTypes.APPROVAL_TYPE |
| 2815 | with self.assertRaises(ValueError) as cm: |
| 2816 | self.converter._ConvertFieldDefType(input_type) |
| 2817 | self.assertEqual( |
| 2818 | 'Unsupported tracker_pb2.FieldType enum. Boolean types ' |
| 2819 | 'are unsupported and approval types are found in ApprovalDefs', |
| 2820 | str(cm.exception)) |
| 2821 | |
| 2822 | def testConvertFieldDefs(self): |
| 2823 | """We can convert field defs""" |
| 2824 | project_config = self.services.config.GetProjectConfig( |
| 2825 | self.cnxn, self.project_1.project_id) |
| 2826 | input_fds = project_config.field_defs |
| 2827 | output = self.converter.ConvertFieldDefs( |
| 2828 | input_fds, self.project_1.project_id) |
| 2829 | fd1_rn = rnc.ConvertFieldDefNames( |
| 2830 | self.cnxn, [self.field_def_1], self.project_1.project_id, |
| 2831 | self.services).get(self.field_def_1) |
| 2832 | self.assertEqual(fd1_rn, output[0].name) |
| 2833 | self.assertEqual(self.field_def_1_name, output[0].display_name) |
| 2834 | self.assertEqual('', output[0].docstring) |
| 2835 | self.assertEqual( |
| 2836 | project_objects_pb2.FieldDef.Type.Value('STR'), output[0].type) |
| 2837 | self.assertEqual( |
| 2838 | project_objects_pb2.FieldDef.Type.Value('INT'), output[1].type) |
| 2839 | self.assertEqual('', output[1].applicable_issue_type) |
| 2840 | fd1_admin_editor = [rnc.ConvertUserName(self.user_1.user_id)] |
| 2841 | self.assertEqual(fd1_admin_editor, output[0].admins) |
| 2842 | self.assertEqual(fd1_admin_editor, output[5].editors) |
| 2843 | |
| 2844 | def testConvertFieldDefs_Traits(self): |
| 2845 | """We can convert FieldDefs with traits""" |
| 2846 | input_fd = self._GetFieldDefById( |
| 2847 | self.project_1.project_id, self.field_def_1) |
| 2848 | output = self.converter.ConvertFieldDefs( |
| 2849 | [input_fd], self.project_1.project_id) |
| 2850 | self.assertEqual(1, len(output)) |
| 2851 | expected_traits = [ |
| 2852 | project_objects_pb2.FieldDef.Traits.Value('REQUIRED'), |
| 2853 | project_objects_pb2.FieldDef.Traits.Value('MULTIVALUED'), |
| 2854 | project_objects_pb2.FieldDef.Traits.Value('PHASE') |
| 2855 | ] |
| 2856 | self.assertEqual(expected_traits, output[0].traits) |
| 2857 | |
| 2858 | input_fd = self._GetFieldDefById( |
| 2859 | self.project_1.project_id, self.field_def_2) |
| 2860 | output = self.converter.ConvertFieldDefs( |
| 2861 | [input_fd], self.project_1.project_id) |
| 2862 | self.assertEqual(1, len(output)) |
| 2863 | expected_traits = [ |
| 2864 | project_objects_pb2.FieldDef.Traits.Value('DEFAULT_HIDDEN') |
| 2865 | ] |
| 2866 | self.assertEqual(expected_traits, output[0].traits) |
| 2867 | |
| 2868 | def testConvertFieldDefs_ApprovalParent(self): |
| 2869 | """We can convert FieldDef with approval parents""" |
| 2870 | input_fd = self._GetFieldDefById( |
| 2871 | self.project_1.project_id, self.field_def_6) |
| 2872 | output = self.converter.ConvertFieldDefs( |
| 2873 | [input_fd], self.project_1.project_id) |
| 2874 | self.assertEqual(1, len(output)) |
| 2875 | |
| 2876 | approval_names_dict = rnc.ConvertApprovalDefNames( |
| 2877 | self.cnxn, [self.approval_def_1_id], self.project_1.project_id, |
| 2878 | self.services) |
| 2879 | expected_approval_parent = approval_names_dict.get(input_fd.approval_id) |
| 2880 | self.assertEqual(expected_approval_parent, output[0].approval_parent) |
| 2881 | |
| 2882 | def testConvertFieldDefs_EnumTypeSettings(self): |
| 2883 | """We can convert enum FieldDef and its settings""" |
| 2884 | input_fd = self._GetFieldDefById( |
| 2885 | self.project_1.project_id, self.field_def_5) |
| 2886 | output = self.converter.ConvertFieldDefs( |
| 2887 | [input_fd], self.project_1.project_id) |
| 2888 | self.assertEqual(1, len(output)) |
| 2889 | |
| 2890 | expected_settings = project_objects_pb2.FieldDef.EnumTypeSettings( |
| 2891 | choices=[ |
| 2892 | Choice( |
| 2893 | value='submarine', docstring=self.labeldef_2.label_docstring), |
| 2894 | Choice(value='basket', docstring=self.labeldef_3.label_docstring) |
| 2895 | ]) |
| 2896 | self.assertEqual(expected_settings, output[0].enum_settings) |
| 2897 | |
| 2898 | def testConvertFieldDefs_IntTypeSettings(self): |
| 2899 | """We can convert int FieldDef and its settings""" |
| 2900 | input_fd = self._GetFieldDefById( |
| 2901 | self.project_1.project_id, self.field_def_2) |
| 2902 | output = self.converter.ConvertFieldDefs( |
| 2903 | [input_fd], self.project_1.project_id) |
| 2904 | self.assertEqual(1, len(output)) |
| 2905 | |
| 2906 | expected_settings = project_objects_pb2.FieldDef.IntTypeSettings( |
| 2907 | max_value=37) |
| 2908 | self.assertEqual(expected_settings, output[0].int_settings) |
| 2909 | |
| 2910 | def testConvertFieldDefs_StrTypeSettings(self): |
| 2911 | """We can convert str FieldDef and its settings""" |
| 2912 | input_fd = self._GetFieldDefById( |
| 2913 | self.project_1.project_id, self.field_def_1) |
| 2914 | output = self.converter.ConvertFieldDefs( |
| 2915 | [input_fd], self.project_1.project_id) |
| 2916 | self.assertEqual(1, len(output)) |
| 2917 | |
| 2918 | expected_settings = project_objects_pb2.FieldDef.StrTypeSettings( |
| 2919 | regex='abc') |
| 2920 | self.assertEqual(expected_settings, output[0].str_settings) |
| 2921 | |
| 2922 | def testConvertFieldDefs_UserTypeSettings(self): |
| 2923 | """We can convert user FieldDef and its settings""" |
| 2924 | input_fd = self._GetFieldDefById( |
| 2925 | self.project_1.project_id, self.field_def_8) |
| 2926 | output = self.converter.ConvertFieldDefs( |
| 2927 | [input_fd], self.project_1.project_id) |
| 2928 | self.assertEqual(1, len(output)) |
| 2929 | |
| 2930 | user_settings = project_objects_pb2.FieldDef.UserTypeSettings |
| 2931 | expected_settings = project_objects_pb2.FieldDef.UserTypeSettings( |
| 2932 | role_requirements=user_settings.RoleRequirements.Value( |
| 2933 | 'PROJECT_MEMBER'), |
| 2934 | needs_perm='EDIT_PROJECT', |
| 2935 | notify_triggers=user_settings.NotifyTriggers.Value('ANY_COMMENT')) |
| 2936 | self.assertEqual(expected_settings, output[0].user_settings) |
| 2937 | |
| 2938 | def testConvertFieldDefs_DateTypeSettings(self): |
| 2939 | """We can convert user FieldDef and its settings""" |
| 2940 | input_fd = self._GetFieldDefById( |
| 2941 | self.project_1.project_id, self.field_def_9) |
| 2942 | output = self.converter.ConvertFieldDefs( |
| 2943 | [input_fd], self.project_1.project_id) |
| 2944 | self.assertEqual(1, len(output)) |
| 2945 | |
| 2946 | date_settings = project_objects_pb2.FieldDef.DateTypeSettings |
| 2947 | expected_settings = project_objects_pb2.FieldDef.DateTypeSettings( |
| 2948 | date_action=date_settings.DateAction.Value('NOTIFY_OWNER')) |
| 2949 | self.assertEqual(expected_settings, output[0].date_settings) |
| 2950 | |
| 2951 | def testConvertFieldDefs_SkipsApprovals(self): |
| 2952 | """We skip over approval defs""" |
| 2953 | project_config = self.services.config.GetProjectConfig( |
| 2954 | self.cnxn, self.project_1.project_id) |
| 2955 | input_fds = project_config.field_defs |
| 2956 | # project_1 is set up to have 10 non-approval fields and 2 approval fields. |
| 2957 | self.assertEqual(12, len(input_fds)) |
| 2958 | output = self.converter.ConvertFieldDefs( |
| 2959 | input_fds, self.project_1.project_id) |
| 2960 | # assert we skip approval fields |
| 2961 | self.assertEqual(10, len(output)) |
| 2962 | |
| 2963 | def testConvertFieldDefs_NonexistentID(self): |
| 2964 | """We skip over any field defs whose ID does not exist.""" |
| 2965 | input_fd = tracker_pb2.FieldDef( |
| 2966 | field_id=self.dne_field_def_id, |
| 2967 | project_id=self.project_1.project_id, |
| 2968 | field_name='foobar', |
| 2969 | field_type=tracker_pb2.FieldTypes('STR_TYPE')) |
| 2970 | |
| 2971 | output = self.converter.ConvertFieldDefs( |
| 2972 | [input_fd], self.project_1.project_id) |
| 2973 | self.assertEqual(0, len(output)) |
| 2974 | |
| 2975 | def testConvertFieldDefs_Empty(self): |
| 2976 | """We can handle empty list input""" |
| 2977 | self.assertEqual( |
| 2978 | [], self.converter.ConvertFieldDefs([], self.project_1.project_id)) |
| 2979 | |
| 2980 | def test_ComputeFieldDefTraits(self): |
| 2981 | """We can get Sequence of Traits for a FieldDef""" |
| 2982 | input_fd = self._GetFieldDefById( |
| 2983 | self.project_1.project_id, self.field_def_1) |
| 2984 | actual = self.converter._ComputeFieldDefTraits(input_fd) |
| 2985 | expected = [ |
| 2986 | project_objects_pb2.FieldDef.Traits.Value('REQUIRED'), |
| 2987 | project_objects_pb2.FieldDef.Traits.Value('MULTIVALUED'), |
| 2988 | project_objects_pb2.FieldDef.Traits.Value('PHASE') |
| 2989 | ] |
| 2990 | self.assertEqual(expected, actual) |
| 2991 | |
| 2992 | input_fd = self._GetFieldDefById( |
| 2993 | self.project_1.project_id, self.field_def_2) |
| 2994 | actual = self.converter._ComputeFieldDefTraits(input_fd) |
| 2995 | expected = [project_objects_pb2.FieldDef.Traits.Value('DEFAULT_HIDDEN')] |
| 2996 | self.assertEqual(expected, actual) |
| 2997 | |
| 2998 | input_fd = self._GetFieldDefById( |
| 2999 | self.project_1.project_id, self.field_def_7) |
| 3000 | actual = self.converter._ComputeFieldDefTraits(input_fd) |
| 3001 | expected = [project_objects_pb2.FieldDef.Traits.Value('RESTRICTED')] |
| 3002 | self.assertEqual(expected, actual) |
| 3003 | |
| 3004 | def test_ComputeFieldDefTraits_Empty(self): |
| 3005 | """We return an empty Sequence of Traits for plain FieldDef""" |
| 3006 | input_fd = self._GetFieldDefById( |
| 3007 | self.project_1.project_id, self.field_def_3) |
| 3008 | actual = self.converter._ComputeFieldDefTraits(input_fd) |
| 3009 | self.assertEqual([], actual) |
| 3010 | |
| 3011 | def test_GetEnumFieldChoices(self): |
| 3012 | """We can get all choices for an enum field""" |
| 3013 | input_fd = self._GetFieldDefById( |
| 3014 | self.project_1.project_id, self.field_def_5) |
| 3015 | actual = self.converter._GetEnumFieldChoices(input_fd) |
| 3016 | expected = [ |
| 3017 | Choice( |
| 3018 | value=self.labeldef_2.label.split('-')[1], |
| 3019 | docstring=self.labeldef_2.label_docstring), |
| 3020 | Choice( |
| 3021 | value=self.labeldef_3.label.split('-')[1], |
| 3022 | docstring=self.labeldef_3.label_docstring), |
| 3023 | ] |
| 3024 | self.assertEqual(expected, actual) |
| 3025 | |
| 3026 | def test_GetEnumFieldChoices_NotEnumField(self): |
| 3027 | """We raise exception for non-enum-field""" |
| 3028 | input_fd = self._GetFieldDefById( |
| 3029 | self.project_1.project_id, self.field_def_1) |
| 3030 | with self.assertRaises(ValueError) as cm: |
| 3031 | self.converter._GetEnumFieldChoices(input_fd) |
| 3032 | self.assertEqual( |
| 3033 | 'Cannot get value from label for non-enum-type field', str( |
| 3034 | cm.exception)) |
| 3035 | |
| 3036 | def testConvertApprovalDefs(self): |
| 3037 | """We can convert ApprovalDefs""" |
| 3038 | input_ad = self._GetApprovalDefById( |
| 3039 | self.project_1.project_id, self.approval_def_1_id) |
| 3040 | actual = self.converter.ConvertApprovalDefs( |
| 3041 | [input_ad], self.project_1.project_id) |
| 3042 | |
| 3043 | resource_names_dict = rnc.ConvertApprovalDefNames( |
| 3044 | self.cnxn, [self.approval_def_1_id], self.project_1.project_id, |
| 3045 | self.services) |
| 3046 | expected_name = resource_names_dict.get(self.approval_def_1_id) |
| 3047 | self.assertEqual(actual[0].name, expected_name) |
| 3048 | self.assertEqual(actual[0].display_name, self.approval_def_1_name) |
| 3049 | matching_fd = self._GetFieldDefById( |
| 3050 | self.project_1.project_id, self.approval_def_1_id) |
| 3051 | expected_docstring = matching_fd.docstring |
| 3052 | self.assertEqual(actual[0].docstring, expected_docstring) |
| 3053 | self.assertEqual(actual[0].survey, self.approval_def_1.survey) |
| 3054 | expected_approvers = [rnc.ConvertUserName(self.user_2.user_id)] |
| 3055 | self.assertEqual(actual[0].approvers, expected_approvers) |
| 3056 | expected_admins = [rnc.ConvertUserName(self.user_1.user_id)] |
| 3057 | self.assertEqual(actual[0].admins, expected_admins) |
| 3058 | |
| 3059 | def testConvertApprovalDefs_Empty(self): |
| 3060 | """We can handle empty case""" |
| 3061 | actual = self.converter.ConvertApprovalDefs([], self.project_1.project_id) |
| 3062 | self.assertEqual(actual, []) |
| 3063 | |
| 3064 | def testConvertApprovalDefs_SkipsNonApprovalDefs(self): |
| 3065 | """We skip if no matching field def exists""" |
| 3066 | input_ad = tracker_pb2.ApprovalDef( |
| 3067 | approval_id=self.dne_field_def_id, |
| 3068 | approver_ids=[self.user_2.user_id], |
| 3069 | survey='anything goes') |
| 3070 | actual = self.converter.ConvertApprovalDefs( |
| 3071 | [input_ad], self.project_1.project_id) |
| 3072 | self.assertEqual(actual, []) |
| 3073 | |
| 3074 | def testConvertLabelDefs(self): |
| 3075 | """We can convert LabelDefs""" |
| 3076 | actual = self.converter.ConvertLabelDefs( |
| 3077 | [self.labeldef_1, self.labeldef_5], self.project_1.project_id) |
| 3078 | resource_names_dict = rnc.ConvertLabelDefNames( |
| 3079 | self.cnxn, [self.labeldef_1.label, self.labeldef_5.label], |
| 3080 | self.project_1.project_id, self.services) |
| 3081 | expected_0_name = resource_names_dict.get(self.labeldef_1.label) |
| 3082 | expected_0 = project_objects_pb2.LabelDef( |
| 3083 | name=expected_0_name, |
| 3084 | value=self.labeldef_1.label, |
| 3085 | docstring=self.labeldef_1.label_docstring, |
| 3086 | state=project_objects_pb2.LabelDef.LabelDefState.Value('ACTIVE')) |
| 3087 | self.assertEqual(expected_0, actual[0]) |
| 3088 | expected_1_name = resource_names_dict.get(self.labeldef_5.label) |
| 3089 | expected_1 = project_objects_pb2.LabelDef( |
| 3090 | name=expected_1_name, |
| 3091 | value=self.labeldef_5.label, |
| 3092 | docstring=self.labeldef_5.label_docstring, |
| 3093 | state=project_objects_pb2.LabelDef.LabelDefState.Value('DEPRECATED')) |
| 3094 | self.assertEqual(expected_1, actual[1]) |
| 3095 | |
| 3096 | def testConvertLabelDefs_Empty(self): |
| 3097 | """We can handle empty input case""" |
| 3098 | actual = self.converter.ConvertLabelDefs([], self.project_1.project_id) |
| 3099 | self.assertEqual([], actual) |
| 3100 | |
| 3101 | def testConvertStatusDefs(self): |
| 3102 | """We can convert StatusDefs""" |
| 3103 | actual = self.converter.ConvertStatusDefs( |
| 3104 | self.predefined_statuses, self.project_1.project_id) |
| 3105 | self.assertEqual(len(actual), 4) |
| 3106 | |
| 3107 | input_names = [sd.status for sd in self.predefined_statuses] |
| 3108 | names = rnc.ConvertStatusDefNames( |
| 3109 | self.cnxn, input_names, self.project_1.project_id, self.services) |
| 3110 | self.assertEqual(names[self.status_1.status], actual[0].name) |
| 3111 | self.assertEqual(names[self.status_2.status], actual[1].name) |
| 3112 | self.assertEqual(names[self.status_3.status], actual[2].name) |
| 3113 | self.assertEqual(names[self.status_4.status], actual[3].name) |
| 3114 | |
| 3115 | self.assertEqual(self.status_1.status, actual[0].value) |
| 3116 | self.assertEqual( |
| 3117 | project_objects_pb2.StatusDef.StatusDefType.Value('OPEN'), |
| 3118 | actual[0].type) |
| 3119 | self.assertEqual(0, actual[0].rank) |
| 3120 | self.assertEqual(self.status_1.status_docstring, actual[0].docstring) |
| 3121 | self.assertEqual( |
| 3122 | project_objects_pb2.StatusDef.StatusDefState.Value('ACTIVE'), |
| 3123 | actual[0].state) |
| 3124 | |
| 3125 | def testConvertStatusDefs_Empty(self): |
| 3126 | """Can handle empty input case""" |
| 3127 | actual = self.converter.ConvertStatusDefs([], self.project_1.project_id) |
| 3128 | self.assertEqual([], actual) |
| 3129 | |
| 3130 | def testConvertStatusDefs_Rank(self): |
| 3131 | """Rank is indepdendent of input order""" |
| 3132 | input_sds = [self.status_2, self.status_4, self.status_3, self.status_1] |
| 3133 | actual = self.converter.ConvertStatusDefs( |
| 3134 | input_sds, self.project_1.project_id) |
| 3135 | self.assertEqual(1, actual[0].rank) |
| 3136 | self.assertEqual(3, actual[1].rank) |
| 3137 | |
| 3138 | def testConvertStatusDefs_type_MERGED(self): |
| 3139 | """Includes mergeable status when parsed from project config""" |
| 3140 | actual = self.converter.ConvertStatusDefs( |
| 3141 | [self.status_2], self.project_1.project_id) |
| 3142 | self.assertEqual( |
| 3143 | project_objects_pb2.StatusDef.StatusDefType.Value('MERGED'), |
| 3144 | actual[0].type) |
| 3145 | |
| 3146 | def testConvertStatusDefs_state_DEPRECATED(self): |
| 3147 | """Includes deprecated status""" |
| 3148 | actual = self.converter.ConvertStatusDefs( |
| 3149 | [self.status_4], self.project_1.project_id) |
| 3150 | self.assertEqual( |
| 3151 | project_objects_pb2.StatusDef.StatusDefState.Value('DEPRECATED'), |
| 3152 | actual[0].state) |
| 3153 | |
| 3154 | def testConvertComponentDef(self): |
| 3155 | now = 123 |
| 3156 | project = self.services.project.TestAddProject('comp-test', project_id=987) |
| 3157 | config = fake.MakeTestConfig(project.project_id, [], []) |
| 3158 | component_def = fake.MakeTestComponentDef( |
| 3159 | project.project_id, 1, path='Chickens>Dickens') |
| 3160 | component_def.created = now |
| 3161 | config.component_defs = [component_def] |
| 3162 | self.services.config.StoreConfig(self.cnxn, config) |
| 3163 | |
| 3164 | actual = self.converter.ConvertComponentDef(component_def) |
| 3165 | expected = project_objects_pb2.ComponentDef( |
| 3166 | name='projects/comp-test/componentDefs/1', |
| 3167 | value='Chickens>Dickens', |
| 3168 | state=project_objects_pb2.ComponentDef.ComponentDefState.Value( |
| 3169 | 'ACTIVE'), |
| 3170 | create_time=timestamp_pb2.Timestamp(seconds=now), |
| 3171 | modify_time=timestamp_pb2.Timestamp()) |
| 3172 | self.assertEqual(actual, expected) |
| 3173 | |
| 3174 | def testConvertComponentDefs(self): |
| 3175 | """We can convert ComponentDefs""" |
| 3176 | project_config = self.services.config.GetProjectConfig( |
| 3177 | self.cnxn, self.project_1.project_id) |
| 3178 | self.assertEqual(len(project_config.component_defs), 2) |
| 3179 | |
| 3180 | actual = self.converter.ConvertComponentDefs( |
| 3181 | project_config.component_defs, self.project_1.project_id) |
| 3182 | self.assertEqual(2, len(actual)) |
| 3183 | |
| 3184 | resource_names_dict = rnc.ConvertComponentDefNames( |
| 3185 | self.cnxn, [self.component_def_1_id, self.component_def_2_id], |
| 3186 | self.project_1.project_id, self.services) |
| 3187 | self.assertEqual( |
| 3188 | resource_names_dict.get(self.component_def_1_id), actual[0].name) |
| 3189 | self.assertEqual( |
| 3190 | resource_names_dict.get(self.component_def_2_id), actual[1].name) |
| 3191 | self.assertEqual(self.component_def_1_path, actual[0].value) |
| 3192 | self.assertEqual(self.component_def_2_path, actual[1].value) |
| 3193 | self.assertEqual('cd1_docstring', actual[0].docstring) |
| 3194 | self.assertEqual( |
| 3195 | project_objects_pb2.ComponentDef.ComponentDefState.Value('ACTIVE'), |
| 3196 | actual[0].state) |
| 3197 | self.assertEqual( |
| 3198 | project_objects_pb2.ComponentDef.ComponentDefState.Value('DEPRECATED'), |
| 3199 | actual[1].state) |
| 3200 | # component_def 1 and 2 have the same admins, ccs, creator, and create_time |
| 3201 | expected_admins = [rnc.ConvertUserName(self.user_1.user_id)] |
| 3202 | self.assertEqual(expected_admins, actual[0].admins) |
| 3203 | expected_ccs = [rnc.ConvertUserName(self.user_2.user_id)] |
| 3204 | self.assertEqual(expected_ccs, actual[0].ccs) |
| 3205 | expected_creator = rnc.ConvertUserName(self.user_1.user_id) |
| 3206 | self.assertEqual(expected_creator, actual[0].creator) |
| 3207 | expected_create_time = timestamp_pb2.Timestamp(seconds=self.PAST_TIME) |
| 3208 | self.assertEqual(expected_create_time, actual[0].create_time) |
| 3209 | |
| 3210 | expected_labels = [ld.label for ld in self.predefined_labels] |
| 3211 | self.assertEqual(expected_labels, actual[0].labels) |
| 3212 | self.assertEqual([], actual[1].labels) |
| 3213 | |
| 3214 | def testConvertComponentDefs_Empty(self): |
| 3215 | """Can handle empty input case""" |
| 3216 | actual = self.converter.ConvertComponentDefs([], self.project_1.project_id) |
| 3217 | self.assertEqual([], actual) |
| 3218 | |
| 3219 | def testConvertProjectSavedQueries(self): |
| 3220 | """We can convert ProjectSavedQueries""" |
| 3221 | input_psqs = [self.psq_2] |
| 3222 | actual = self.converter.ConvertProjectSavedQueries( |
| 3223 | input_psqs, self.project_1.project_id) |
| 3224 | self.assertEqual(1, len(actual)) |
| 3225 | |
| 3226 | resource_names_dict = rnc.ConvertProjectSavedQueryNames( |
| 3227 | self.cnxn, [self.psq_2.query_id], self.project_1.project_id, |
| 3228 | self.services) |
| 3229 | self.assertEqual( |
| 3230 | resource_names_dict.get(self.psq_2.query_id), actual[0].name) |
| 3231 | self.assertEqual(self.psq_2.name, actual[0].display_name) |
| 3232 | self.assertEqual(self.psq_2.query, actual[0].query) |
| 3233 | |
| 3234 | def testConvertProjectSavedQueries_ExpandsBasedOn(self): |
| 3235 | """We expand query to include base_query_id""" |
| 3236 | actual = self.converter.ConvertProjectSavedQueries( |
| 3237 | [self.psq_1], self.project_1.project_id) |
| 3238 | expected_query = '{} {}'.format( |
| 3239 | tbo.GetBuiltInQuery(self.psq_1.base_query_id), self.psq_1.query) |
| 3240 | self.assertEqual(expected_query, actual[0].query) |
| 3241 | |
| 3242 | def testConvertProjectSavedQueries_NotInProject(self): |
| 3243 | """We skip over saved queries that don't belong to this project""" |
| 3244 | psq_not_registered = tracker_pb2.SavedQuery( |
| 3245 | query_id=4, name='psq no registered name', query='no registered') |
| 3246 | actual = self.converter.ConvertProjectSavedQueries( |
| 3247 | [psq_not_registered], self.project_1.project_id) |
| 3248 | self.assertEqual([], actual) |
| 3249 | |
| 3250 | def testConvertProjectSavedQueries_Empty(self): |
| 3251 | """We can handle empty inputs""" |
| 3252 | actual = self.converter.ConvertProjectSavedQueries( |
| 3253 | [], self.project_1.project_id) |
| 3254 | self.assertEqual([], actual) |