blob: 1bbd12c7aad682f52abb50be24cb5d3e8e09657b [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2020 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style
3# license that can be found in the LICENSE file.
4"""Tests for converting internal protorpc to external protoc."""
5
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
10import copy
11import difflib
12import logging
13import unittest
14
15import mock
16from google.protobuf import field_mask_pb2
17from google.protobuf import timestamp_pb2
18
19from api import resource_name_converters as rnc
20from api.v3 import converters
21from api.v3.api_proto import feature_objects_pb2
22from api.v3.api_proto import issues_pb2
23from api.v3.api_proto import issue_objects_pb2
24from api.v3.api_proto import user_objects_pb2
25from api.v3.api_proto import project_objects_pb2
26from framework import authdata
27from framework import exceptions
28from framework import framework_constants
29from framework import framework_helpers
30from framework import monorailcontext
31from testing import fake
32from testing import testing_helpers
33from tracker import field_helpers
34from services import service_manager
35from proto import tracker_pb2
36from tracker import tracker_bizobj as tbo
37
38EXPLICIT_DERIVATION = issue_objects_pb2.Derivation.Value('EXPLICIT')
39RULE_DERIVATION = issue_objects_pb2.Derivation.Value('RULE')
40Choice = project_objects_pb2.FieldDef.EnumTypeSettings.Choice
41
42CURRENT_TIME = 12346.78
43
44
45class 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(
899 filename='clown.gif', content='iTs prOUnOuNcED JIF')
900 up_2 = issues_pb2.AttachmentUpload(
901 filename='mowgli', content='cutest dog')
902
903 ingested = self.converter.IngestAttachmentUploads([up_1, up_2])
904 expected = [framework_helpers.AttachmentUpload(
905 'clown.gif', 'iTs prOUnOuNcED JIF', 'image/gif'),
906 framework_helpers.AttachmentUpload(
907 'mowgli', 'cutest dog', 'text/plain')]
908 self.assertEqual(ingested, expected)
909
910 def testtIngestAttachmentUploads_Invalid(self):
911 up_1 = issues_pb2.AttachmentUpload(filename='clown.gif')
912 up_2 = issues_pb2.AttachmentUpload(content='cutest dog')
913
914 with self.assertRaisesRegexp(
915 exceptions.InputException, 'Uploaded .+\nUploaded .+'):
916 self.converter.IngestAttachmentUploads([up_1, up_2])
917
918 def testIngestIssueDeltas(self):
919 # Set up.
920 self.services.project.TestAddProject('proj-780', project_id=780)
921 config = fake.MakeTestConfig(780, [], [])
922 self.services.config.StoreConfig(self.cnxn, config)
923
924 issue_1 = self._Issue(780, 1)
925 self.services.issue.TestAddIssue(issue_1)
926 issue_2 = self._Issue(780, 2)
927 self.services.issue.TestAddIssue(issue_2)
928 comp_1 = fake.MakeTestComponentDef(780, 1)
929 comp_2 = fake.MakeTestComponentDef(780, 2)
930 fd_str = fake.MakeTestFieldDef(1, 780, tracker_pb2.FieldTypes.STR_TYPE)
931 fd_enum = fake.MakeTestFieldDef(
932 2, 780, tracker_pb2.FieldTypes.ENUM_TYPE, field_name='Kingdom')
933 config = fake.MakeTestConfig(780, [], [])
934 config.component_defs = [comp_1, comp_2]
935 config.field_defs = [fd_str, fd_enum]
936 self.services.config.StoreConfig(self.cnxn, config)
937
938 # Issue and delta that changes all things.
939 api_issue_all = issue_objects_pb2.Issue(
940 name='projects/proj-780/issues/1',
941 status=issue_objects_pb2.Issue.StatusValue(status='Fixed'),
942 owner=issue_objects_pb2.Issue.UserValue(user='users/111'),
943 summary='honk honk.',
944 cc_users=[issue_objects_pb2.Issue.UserValue(user='users/222')],
945 components=[
946 issue_objects_pb2.Issue.ComponentValue(
947 component='projects/proj-780/componentDefs/1')
948 ],
949 field_values=[
950 issue_objects_pb2.FieldValue(
951 field='projects/proj-780/fieldDefs/1', value='chicken'),
952 issue_objects_pb2.FieldValue(
953 field='projects/proj-780/fieldDefs/2', value='come')
954 ],
955 labels=[issue_objects_pb2.Issue.LabelValue(label='ready')])
956 mask_all = field_mask_pb2.FieldMask(
957 paths=[
958 'status', 'owner', 'summary', 'cc_users', 'labels', 'components',
959 'field_values'
960 ])
961 api_delta_all = issues_pb2.IssueDelta(
962 issue=api_issue_all,
963 update_mask=mask_all,
964 ccs_remove=['users/333'],
965 components_remove=['projects/proj-780/componentDefs/2'],
966 field_vals_remove=[
967 issue_objects_pb2.FieldValue(
968 field='projects/proj-780/fieldDefs/1', value='rooster'),
969 issue_objects_pb2.FieldValue(
970 field='projects/proj-780/fieldDefs/2', value='leave')
971 ],
972 labels_remove=['not-ready'])
973 exp_fvs_add = [
974 field_helpers.ParseOneFieldValue(
975 self.cnxn, self.services.user, fd_str, 'chicken')
976 ]
977 exp_fvs_remove = [
978 field_helpers.ParseOneFieldValue(
979 self.cnxn, self.services.user, fd_str, 'rooster')
980 ]
981 expected_delta_all = tracker_pb2.IssueDelta(
982 status='Fixed',
983 owner_id=111,
984 summary='honk honk.',
985 cc_ids_add=[222],
986 cc_ids_remove=[333],
987 comp_ids_add=[1],
988 comp_ids_remove=[2],
989 field_vals_add=exp_fvs_add,
990 field_vals_remove=exp_fvs_remove,
991 labels_add=['ready', 'Kingdom-come'],
992 labels_remove=['not-ready', 'Kingdom-leave'])
993
994 api_deltas = [api_delta_all]
995
996 # Issue with all fields, but an empty mask.
997 api_issue_all_masked = issue_objects_pb2.Issue(
998 name='projects/proj-780/issues/2',
999 status=issue_objects_pb2.Issue.StatusValue(status='Fixed'),
1000 owner=issue_objects_pb2.Issue.UserValue(user='users/111'),
1001 summary='honk honk.',
1002 cc_users=[issue_objects_pb2.Issue.UserValue(user='users/222')],
1003 components=[
1004 issue_objects_pb2.Issue.ComponentValue(
1005 component='projects/proj-780/componentDefs/1')
1006 ],
1007 field_values=[
1008 issue_objects_pb2.FieldValue(
1009 field='projects/proj-780/fieldDefs/1', value='chicken'),
1010 issue_objects_pb2.FieldValue(
1011 field='projects/proj-780/fieldDefs/2', value='come')
1012 ],
1013 labels=[issue_objects_pb2.Issue.LabelValue(label='ready')])
1014 api_delta_all_masked = issues_pb2.IssueDelta(
1015 issue=api_issue_all_masked,
1016 update_mask=field_mask_pb2.FieldMask(paths=[]),
1017 ccs_remove=['users/333'],
1018 components_remove=['projects/proj-780/componentDefs/2'],
1019 field_vals_remove=[
1020 issue_objects_pb2.FieldValue(
1021 field='projects/proj-780/fieldDefs/1', value='rooster'),
1022 issue_objects_pb2.FieldValue(
1023 field='projects/proj-780/fieldDefs/2', value='leave')
1024 ],
1025 labels_remove=['not-ready'])
1026 expected_delta_all_masked = tracker_pb2.IssueDelta(
1027 cc_ids_remove=[333],
1028 comp_ids_remove=[2],
1029 labels_remove=['not-ready', 'Kingdom-leave'],
1030 field_vals_remove=exp_fvs_remove)
1031
1032 api_deltas.append(api_delta_all_masked)
1033
1034 actual = self.converter.IngestIssueDeltas(api_deltas)
1035 expected = [(78001, expected_delta_all), (78002, expected_delta_all_masked)]
1036 self.assertEqual(actual, expected)
1037
1038 def testIngestIssueDeltas_IssueRefs(self):
1039 # Set up.
1040 self.services.project.TestAddProject('proj-780', project_id=780)
1041 issue = self._Issue(780, 1)
1042 self.services.issue.TestAddIssue(issue)
1043
1044 bo_add = self._Issue(780, 2)
1045 self.services.issue.TestAddIssue(bo_add)
1046
1047 b_add = self._Issue(780, 3)
1048 self.services.issue.TestAddIssue(b_add)
1049
1050 bo_remove = self._Issue(780, 4)
1051 self.services.issue.TestAddIssue(bo_remove)
1052
1053 b_remove = self._Issue(780, 5)
1054 self.services.issue.TestAddIssue(b_remove)
1055
1056 # merge_remove tested in testIngestIssueDeltas_RemoveNonRepeated
1057 merge_add = self._Issue(780, 6)
1058 self.services.issue.TestAddIssue(merge_add)
1059
1060 api_issue = issue_objects_pb2.Issue(
1061 name='projects/proj-780/issues/1',
1062 blocked_on_issue_refs=[
1063 issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/2'),
1064 issue_objects_pb2.IssueRef(ext_identifier='b/1')
1065 ],
1066 blocking_issue_refs=[
1067 issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/3'),
1068 issue_objects_pb2.IssueRef(ext_identifier='b/2')
1069 ],
1070 merged_into_issue_ref=issue_objects_pb2.IssueRef(
1071 issue='projects/proj-780/issues/6'))
1072
1073 api_delta = issues_pb2.IssueDelta(
1074 issue=api_issue,
1075 update_mask=field_mask_pb2.FieldMask(
1076 paths=[
1077 'blocked_on_issue_refs', 'blocking_issue_refs',
1078 'merged_into_issue_ref'
1079 ]),
1080 blocked_on_issues_remove=[
1081 issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/4'),
1082 issue_objects_pb2.IssueRef(ext_identifier='b/3')
1083 ],
1084 blocking_issues_remove=[
1085 issue_objects_pb2.IssueRef(issue='projects/proj-780/issues/5'),
1086 issue_objects_pb2.IssueRef(ext_identifier='b/4')
1087 ])
1088
1089 expected_delta = tracker_pb2.IssueDelta(
1090 blocked_on_add=[bo_add.issue_id],
1091 blocked_on_remove=[bo_remove.issue_id],
1092 blocking_add=[b_add.issue_id],
1093 blocking_remove=[b_remove.issue_id],
1094 ext_blocked_on_add=['b/1'],
1095 ext_blocked_on_remove=['b/3'],
1096 ext_blocking_add=['b/2'],
1097 ext_blocking_remove=['b/4'],
1098 merged_into=merge_add.issue_id)
1099
1100 # Test adding an external merged_into_issue.
1101 api_issue_ext_merged = issue_objects_pb2.Issue(
1102 name='projects/proj-780/issues/2',
1103 merged_into_issue_ref=issue_objects_pb2.IssueRef(ext_identifier='b/1'))
1104 api_delta_ext_merged = issues_pb2.IssueDelta(
1105 issue=api_issue_ext_merged,
1106 update_mask=field_mask_pb2.FieldMask(paths=['merged_into_issue_ref']))
1107 expected_delta_ext_merged = tracker_pb2.IssueDelta(
1108 merged_into_external='b/1')
1109
1110 # Test issue with empty mask.
1111 issue_all_masked = self._Issue(780, 11)
1112 self.services.issue.TestAddIssue(issue_all_masked)
1113
1114 api_issue_all_masked = copy.deepcopy(api_issue)
1115 api_issue_all_masked.name = 'projects/proj-780/issues/11'
1116 api_delta_all_masked = issues_pb2.IssueDelta(
1117 issue=api_issue_all_masked, update_mask=field_mask_pb2.FieldMask())
1118 expected_all_masked_delta = tracker_pb2.IssueDelta()
1119
1120 # Check results.
1121 actual = self.converter.IngestIssueDeltas(
1122 [api_delta, api_delta_ext_merged, api_delta_all_masked])
1123
1124 expected = [
1125 (78001, expected_delta), (78002, expected_delta_ext_merged),
1126 (78011, expected_all_masked_delta)
1127 ]
1128 self.assertEqual(actual, expected)
1129
1130 def testIngestIssueDeltas_OwnerAndOwnerDotUser(self):
1131 # Set up.
1132 self.services.project.TestAddProject('proj-780', project_id=780)
1133 issue = self._Issue(780, 1)
1134 self.services.issue.TestAddIssue(issue)
1135
1136 api_issue = issue_objects_pb2.Issue(
1137 name='projects/proj-780/issues/1',
1138 owner=issue_objects_pb2.Issue.UserValue(user='users/111')
1139 )
1140
1141 # Expect ingest to work when update_mask has just 'owner'.
1142 api_delta = issues_pb2.IssueDelta(
1143 issue=api_issue,
1144 update_mask=field_mask_pb2.FieldMask(paths=['owner'])
1145 )
1146 expected_delta = tracker_pb2.IssueDelta(owner_id=111)
1147 expected = [(78001, expected_delta)]
1148 actual = self.converter.IngestIssueDeltas([api_delta])
1149 self.assertEqual(actual, expected)
1150
1151 # Expect ingest to also work when update_mask uses 'owner.user' instead.
1152 api_delta = issues_pb2.IssueDelta(
1153 issue=api_issue,
1154 update_mask=field_mask_pb2.FieldMask(paths=['owner.user'])
1155 )
1156 actual = self.converter.IngestIssueDeltas([api_delta])
1157 self.assertEqual(actual, expected)
1158
1159 def testIngestIssueDeltas_StatusAndStatusDotStatus(self):
1160 # Set up.
1161 self.services.project.TestAddProject('proj-780', project_id=780)
1162 issue = self._Issue(780, 1)
1163 self.services.issue.TestAddIssue(issue)
1164
1165 api_issue = issue_objects_pb2.Issue(
1166 name='projects/proj-780/issues/1',
1167 owner=issue_objects_pb2.Issue.UserValue(user='users/111'),
1168 status=issue_objects_pb2.Issue.StatusValue(status='New')
1169 )
1170
1171 # Expect ingest to work when update_mask has just 'status'.
1172 api_delta = issues_pb2.IssueDelta(
1173 issue=api_issue,
1174 update_mask=field_mask_pb2.FieldMask(paths=['status'])
1175 )
1176 expected_delta = tracker_pb2.IssueDelta(status='New')
1177 expected = [(78001, expected_delta)]
1178 actual = self.converter.IngestIssueDeltas([api_delta])
1179 self.assertEqual(actual, expected)
1180
1181 # Expect ingest to also work when update_mask uses 'status.status' instead.
1182 api_delta = issues_pb2.IssueDelta(
1183 issue=api_issue,
1184 update_mask=field_mask_pb2.FieldMask(paths=['status.status'])
1185 )
1186 actual = self.converter.IngestIssueDeltas([api_delta])
1187 self.assertEqual(actual, expected)
1188
1189 def testIngestIssueDeltas_RemoveNonRepeated(self):
1190 # Set up.
1191 self.services.project.TestAddProject('proj-780', project_id=780)
1192 issue_1 = self._Issue(780, 1)
1193 self.services.issue.TestAddIssue(issue_1)
1194 issue_2 = self._Issue(780, 2)
1195 self.services.issue.TestAddIssue(issue_2)
1196
1197 # Check we can remove fields without specifying them in the
1198 # issue, as long as they're specified in the FieldMask.
1199 api_issue = issue_objects_pb2.Issue(
1200 name='projects/proj-780/issues/1')
1201 api_delta = issues_pb2.IssueDelta(
1202 issue=api_issue,
1203 update_mask=field_mask_pb2.FieldMask(
1204 paths=[
1205 'owner.user', 'status.status', 'summary',
1206 'merged_into_issue_ref.issue'
1207 ]))
1208
1209 # Check thet setting fields to '' result in same behavior as not
1210 # explicitly setting the values at all.
1211 api_issue_set = issue_objects_pb2.Issue(
1212 name='projects/proj-780/issues/2',
1213 summary='',
1214 status=issue_objects_pb2.Issue.StatusValue(status=''),
1215 owner=issue_objects_pb2.Issue.UserValue(user=''),
1216 merged_into_issue_ref=issue_objects_pb2.IssueRef(issue=''))
1217 api_delta_set = issues_pb2.IssueDelta(
1218 issue=api_issue_set,
1219 update_mask=field_mask_pb2.FieldMask(
1220 paths=[
1221 'owner.user', 'status.status', 'summary',
1222 'merged_into_issue_ref.issue'
1223 ]))
1224
1225 expected_delta = tracker_pb2.IssueDelta(
1226 owner_id=framework_constants.NO_USER_SPECIFIED,
1227 status='',
1228 summary='',
1229 merged_into=0)
1230
1231 actual = self.converter.IngestIssueDeltas([api_delta, api_delta_set])
1232 expected = [(78001, expected_delta), (78002, expected_delta)]
1233 self.assertEqual(actual, expected)
1234
1235 def testIngestIssueDeltas_InvalidMask(self):
1236 self.services.project.TestAddProject('proj-780', project_id=780)
1237 issue_1 = self._Issue(780, 1)
1238 self.services.issue.TestAddIssue(issue_1)
1239 issue_2 = self._Issue(780, 2)
1240 self.services.issue.TestAddIssue(issue_2)
1241 issue_3 = self._Issue(780, 3)
1242 self.services.issue.TestAddIssue(issue_3)
1243 api_deltas = []
1244 err_msgs = []
1245
1246 api_issue_1 = issue_objects_pb2.Issue(name='projects/proj-780/issues/1')
1247 api_delta_1 = issues_pb2.IssueDelta(issue=api_issue_1)
1248 api_deltas.append(api_delta_1)
1249 err_msgs.append(
1250 '`update_mask` must be set for projects/proj-780/issues/1 delta.')
1251
1252 api_issue_2 = issue_objects_pb2.Issue(name='projects/proj-780/issues/2')
1253 api_delta_2 = issues_pb2.IssueDelta(
1254 issue=api_issue_2,
1255 update_mask=field_mask_pb2.FieldMask()) # Empty but set is fine.
1256 api_deltas.append(api_delta_2)
1257
1258 api_issue_3 = issue_objects_pb2.Issue(name='projects/proj-780/issues/3')
1259 api_delta_3 = issues_pb2.IssueDelta(
1260 issue=api_issue_3,
1261 update_mask=field_mask_pb2.FieldMask(paths=['chicken']))
1262 api_deltas.append(api_delta_3)
1263 err_msgs.append(
1264 'Invalid `update_mask` for projects/proj-780/issues/3 delta.')
1265
1266 with self.assertRaisesRegexp(exceptions.InputException,
1267 '\n'.join(err_msgs)):
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)
1386 with self.assertRaisesRegexp(exceptions.InputException, error_messages_re):
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
1453 with self.assertRaisesRegexp(exceptions.InputException, expected_err):
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 )
1486 with self.assertRaisesRegexp(exceptions.InputException,
1487 'Field .* does not belong to approval .*'):
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 )
1514 with self.assertRaisesRegexp(
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
1742 with self.assertRaisesRegexp(
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)
2031 with self.assertRaisesRegexp(exceptions.InputException, error_messages_re):
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)