blob: 2526fdcdb44bd099251bd5b426d7a117d176b962 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2018 The Chromium Authors
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
Copybara854996b2021-09-07 19:36:02 +00004
5"""Unittests for the flt launch issues conversion task."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9import copy
10import unittest
11import settings
12import mock
13
14from businesslogic import work_env
15from framework import exceptions
16from framework import permissions
17from services import service_manager
18from services import template_svc
19from tracker import fltconversion
20from tracker import tracker_bizobj
21from testing import fake
22from testing import testing_helpers
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010023from mrproto import tracker_pb2
Copybara854996b2021-09-07 19:36:02 +000024
25class FLTConvertTask(unittest.TestCase):
26
27 def setUp(self):
28 self.services = service_manager.Services(
29 issue=fake.IssueService(),
30 user=fake.UserService(),
31 project=fake.ProjectService(),
32 config=fake.ConfigService(),
33 template=mock.Mock(spec=template_svc.TemplateService),)
34 self.mr = testing_helpers.MakeMonorailRequest()
Adrià Vilanova Martínez9f9ade52022-10-10 23:20:11 +020035 self.task = fltconversion.FLTConvertTask(services=self.services)
Copybara854996b2021-09-07 19:36:02 +000036 self.task.mr = self.mr
37 self.issue = fake.MakeTestIssue(
38 789, 1, 'summary', 'New', 111, issue_id=78901)
39 self.config = tracker_bizobj.MakeDefaultProjectIssueConfig(789)
40 self.work_env = work_env.WorkEnv(
41 self.mr, self.services, 'Testing')
42 self.issue1 = fake.MakeTestIssue(
43 789, 1, 'sum', 'New', 111, issue_id=78901,
44 labels=[
45 'Launch-M-Approved-71-Stable', 'Launch-M-Target-70-Beta',
46 'Launch-UI-Yes', 'Launch-Privacy-NeedInfo',
47 'pm-jojwang', 'tl-annajo', 'ux-shiba', 'Type-Launch'])
48 self.issue2 = fake.MakeTestIssue(
49 789, 2, 'sum', 'New', 111, issue_id=78902,
50 labels=[
51 'Launch-M-Target-71-Stable', 'Launch-M-Approved-70-Beta',
52 'pm-jojwang', 'tl-annajo', 'OS-Chrome', 'OS-Android',
53 'Type-Launch'])
54 self.issue3 = fake.MakeTestIssue(
55 789, 3, 'sum', 'New', 111, issue_id=78903,
56 labels=['Launch-M-Approved-71-Stable',
57 'Launch-M-Approved-79-Stable-Exp', 'Launch-M-Target-70-Beta',
58 'Launch-M-Target-70-Stable', 'Launch-UI-Yes',
59 'Launch-Exp-Leadership-Yes', 'pm-annajo', 'tl-jojwang',
60 'OS-Chrome', 'Type-Launch'])
61 self.issue4 = fake.MakeTestIssue(
62 789, 4, 'sum', 'New', 111, issue_id=78904,
63 labels=['Launch-UI-Yes', 'OS-Chrome', 'Type-Launch'])
64 self.issue5 = fake.MakeTestIssue(
65 789, 5, 'sum', 'New', 111, issue_id=78905,
66 labels=['Launch-M-Approved-71-Stable',
67 'Launch-M-Approved-79-Stable-Exp', 'Launch-M-Target-70-Beta',
68 'Launch-M-Target-70-Stable', 'Launch-UI-Yes',
69 'Launch-Privacy-NeedInfo', 'Launch-Exp-Leadership-Yes',
70 'pm-annajo', 'tl-jojwang', 'OS-Chrome', 'Type-Launch'])
71
72 self.approval_values = [
73 tracker_pb2.ApprovalValue(
74 approval_id=7, status=tracker_pb2.ApprovalStatus.NOT_SET),
75 tracker_pb2.ApprovalValue(
76 approval_id=8, status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW)]
77 self.phases = [tracker_pb2.Phase(name='Stable', phase_id=88),
78 tracker_pb2.Phase(name='Beta', phase_id=89)]
79
80 def testAssertBasePermission(self):
81 self.mr.auth.user_pb.is_site_admin = True
82 settings.app_id = 'monorail-staging'
83 self.task.AssertBasePermission(self.mr)
84
85 settings.app_id = 'monorail-prod'
86 self.task.AssertBasePermission(self.mr)
87
88 self.mr.auth.user_pb.is_site_admin = False
89 self.assertRaises(permissions.PermissionException,
90 self.task.AssertBasePermission, self.mr)
91
92 def testHandleRequest(self):
93 # Set up Objects
94 project_info = fltconversion.ProjectInfo(
95 self.config, 'q=query', self.approval_values, self.phases,
96 11, 12, 13, 16, 14, 15, fltconversion.BROWSER_PHASE_MAP,
97 fltconversion.BROWSER_APPROVALS_TO_LABELS,
98 fltconversion.BROWSER_M_LABELS_RE)
99
100 self.config.field_defs = [
101 tracker_pb2.FieldDef(field_id=7, field_name='Chrome-UX',
102 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE),
103 tracker_pb2.FieldDef(field_id=8, field_name='Chrome-Privacy',
104 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE)
105 ]
106
107 # Set up mocks
108 patcher = mock.patch(
109 'search.frontendsearchpipeline.FrontendSearchPipeline',
110 spec=True, visible_results=[self.issue1, self.issue2])
111 mockPipeline = patcher.start()
112
113 self.task.services.issue.GetIssue = mock.Mock(
114 side_effect=[self.issue1, self.issue2])
115
116 self.task.FetchAndAssertProjectInfo = mock.Mock(return_value=project_info)
117
118 with self.work_env as we:
119 we.ListIssues = mock.Mock(return_value=mockPipeline)
120
121 def side_effect(_cnxn, email):
122 if email == 'jojwang@chromium.org':
123 return 111
124 if email == 'annajo@google.com':
125 return 222
126 if email == 'shiba@google.com':
127 return 333
128 raise exceptions.NoSuchUserException
129 self.task.services.user.LookupUserID = mock.Mock(side_effect=side_effect)
130
131 self.task.ExecuteIssueChanges = mock.Mock(return_value=[])
132
133 # Call
134 json = self.task.HandleRequest(self.mr)
135
136 # assert
137 self.assertEqual(json['converted_issues'], [1, 2])
138
139 new_approvals1 = [
140 tracker_pb2.ApprovalValue(
141 approval_id=7, status=tracker_pb2.ApprovalStatus.APPROVED),
142 tracker_pb2.ApprovalValue(
143 approval_id=8, status=tracker_pb2.ApprovalStatus.NEED_INFO)]
144 new_fvs1 = [
145 # M-Approved Stable
146 tracker_bizobj.MakeFieldValue(
147 15, 71, None, None, None, None, False, phase_id=88),
148 # M-Target Beta
149 tracker_bizobj.MakeFieldValue(
150 14, 70, None, None, None, None, False, phase_id=89),
151 # PM field
152 tracker_bizobj.MakeFieldValue(
153 11, None, None, 111, None, None, False),
154 # TL field
155 tracker_bizobj.MakeFieldValue(
156 12, None, None, 222, None, None, False),
157 # UX field
158 tracker_bizobj.MakeFieldValue(
159 16, None, None, 333, None, None, False)
160 ]
161
162
163 new_approvals2 = [
164 tracker_pb2.ApprovalValue(
165 approval_id=7, status=tracker_pb2.ApprovalStatus.NOT_SET),
166 tracker_pb2.ApprovalValue(
167 approval_id=8, status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW)
168 ]
169 new_fvs2 = [
170 tracker_bizobj.MakeFieldValue(
171 14, 71, None, None, None, None, False, phase_id=88),
172 tracker_bizobj.MakeFieldValue(
173 15, 70, None, None, None, None, False, phase_id=89),
174 # PM field
175 tracker_bizobj.MakeFieldValue(
176 11, None, None, 111, None, None, False),
177 # TL field
178 tracker_bizobj.MakeFieldValue(
179 12, None, None, 222, None, None, False)]
180
181 execute_calls = [
182 mock.call(
183 self.config, self.issue1, new_approvals1, self.phases, new_fvs1),
184 mock.call(
185 self.config, self.issue2, new_approvals2, self.phases, new_fvs2)]
186 self.task.ExecuteIssueChanges.assert_has_calls(execute_calls)
187
188 patcher.stop()
189
190 def testHandleRequest_UndoConversion(self):
191 # test Delete() is actually called
192 mr = testing_helpers.MakeMonorailRequest(path='url/url?launch=delete')
193 self.task.UndoConversion = mock.Mock(return_value={'deleteing': [1, 2]})
194 actualReturn = self.task.HandleRequest(mr)
195 self.assertEqual({'deleteing': [1, 2]}, actualReturn)
196
197 def testUndoConversion(self):
198 # Set up objects
199 self.issue1.field_values = [
200 # Test non phase and TL/PM/TE field_values are not deleted
201 tracker_bizobj.MakeFieldValue(
202 17, None, 'strvalue', None, None, None, False)]
203 issue1 = copy.deepcopy(self.issue1)
204 issue2 = copy.deepcopy(self.issue2)
205 fvs = [
206 tracker_bizobj.MakeFieldValue(
207 11, None, None, 222, None, None, False),
208 tracker_bizobj.MakeFieldValue(
209 12, None, None, 111, None, None, False),
210 tracker_bizobj.MakeFieldValue(
211 16, None, None, 111, None, None, False)]
212 self.config.field_defs = [
213 tracker_pb2.FieldDef(field_id=11, field_name='PM'),
214 tracker_pb2.FieldDef(field_id=12, field_name='TL'),
215 tracker_pb2.FieldDef(field_id=13, field_name='TE'),
216 tracker_pb2.FieldDef(field_id=16, field_name='UX')]
217 # Make element edits made during conversion that should be undone.
218 issue1.labels.extend(['Type-FLT-Launch', 'FLT-Conversion'])
219 issue1.labels.remove('Type-Launch')
220 issue2.labels.extend(['Type-FLT-Launch', 'FLT-Conversion'])
221 issue2.labels.remove('Type-Launch')
222 issue1.approval_values = self.approval_values
223 issue2.approval_values = self.approval_values
224 issue1.phases = self.phases
225 issue2.phases = self.phases
226 issue1.field_values.extend(fvs)
227
228 # Set up mocks
229 patcher = mock.patch(
230 'search.frontendsearchpipeline.FrontendSearchPipeline',
231 spec=True, visible_results=[issue1, issue2]) # converted issues
232 mockPipeline = patcher.start()
233 self.task.services.project.GetProjectByName = mock.Mock()
234 self.task.services.config.GetProjectConfig = mock.Mock(
235 return_value=self.config)
236 self.task.services.issue.GetIssue = mock.Mock(
237 side_effect=[issue1, issue2])
238 self.task.services.issue._UpdateIssuesApprovals = mock.Mock()
239 self.task.services.issue.UpdateIssue = mock.Mock()
240
241 with self.work_env as we:
242 we.ListIssues = mock.Mock(return_value=mockPipeline)
243
244 json = self.task.UndoConversion(self.mr)
245 self.assertEqual(json['deleting'], [1, 2])
246 # assert convert issue1 is back to the pre-conversion state, self.issue1.
247 self.assertEqual(issue1, self.issue1)
248 self.assertEqual(issue2, self.issue2)
249
250 # assert UpdateIssue calls were made with pre-conversion state issues.
251 update_calls = [
252 mock.call(self.mr.cnxn, self.issue1),
253 mock.call(self.mr.cnxn, self.issue2)]
254 self.task.services.issue._UpdateIssuesApprovals.assert_has_calls(
255 update_calls)
256 self.task.services.issue.UpdateIssue.assert_has_calls(update_calls)
257 patcher.stop()
258
259 def testVerifyConversion(self):
260 # Set up objects
261 self.issue1.labels.extend(
262 # Launch-M-Target-70-Stable-Exp should be ignored
263 ['Rollout-Type-Default', 'Launch-M-Target-70-Stable-Exp'])
264 self.issue1.phases = [tracker_pb2.Phase(name='Beta', phase_id=1),
265 tracker_pb2.Phase(name='Stable', phase_id=2)]
266 self.issue1.approval_values = [
267 tracker_pb2.ApprovalValue(
268 approval_id=1, status=tracker_pb2.ApprovalStatus.NOT_SET),
269 tracker_pb2.ApprovalValue(
270 approval_id=2, status=tracker_pb2.ApprovalStatus.APPROVED),
271 tracker_pb2.ApprovalValue(
272 approval_id=3, status=tracker_pb2.ApprovalStatus.NEED_INFO),
273 ]
274 self.issue1.field_values = [
275 # problem = expected field for TL
276 tracker_bizobj.MakeFieldValue(4, None, None, 111, None, None, False),
277 tracker_pb2.FieldValue(field_id=7, int_value=70, phase_id=1),
278 tracker_pb2.FieldValue(field_id=8, int_value=71, phase_id=2),
279 ]
280
281 self.issue2.labels.extend(['Rollout-Type-Finch'])
282 self.issue2.phases = [tracker_pb2.Phase(name='Beta', phase_id=1),
283 tracker_pb2.Phase(name='Stable-Full', phase_id=2),
284 tracker_pb2.Phase(name='Stable-Exp', phase_id=3)]
285 self.issue2.approval_values = [
286 tracker_pb2.ApprovalValue(
287 approval_id=1, status=tracker_pb2.ApprovalStatus.NOT_SET),
288 tracker_pb2.ApprovalValue(
289 approval_id=2, status=tracker_pb2.ApprovalStatus.NOT_SET),
290 tracker_pb2.ApprovalValue(
291 # problem = approval Chrome-Privacy has status approved for None
292 approval_id=3, status=tracker_pb2.ApprovalStatus.APPROVED),
293 ]
294 self.issue2.field_values = [
295 # problem = no phase field for label 'Launch-M-Approved-70-Beta'
296 tracker_pb2.FieldValue(field_id=7, int_value=71, phase_id=2),
297 tracker_bizobj.MakeFieldValue(4, None, None, 111, None, None, False),
298 tracker_bizobj.MakeFieldValue(5, None, None, 111, None, None, False),
299 ]
300
301 self.issue3.labels.extend(['Rollout-Type-Default'])
302 self.issue3.phases = [tracker_pb2.Phase(name='Feature Freeze', phase_id=4),
303 tracker_pb2.Phase(name='Branch', phase_id=5),
304 tracker_pb2.Phase(name='Stable', phase_id=6)]
305 self.issue3.approval_values = [
306 tracker_pb2.ApprovalValue(
307 approval_id=9, status=tracker_pb2.ApprovalStatus.APPROVED),
308 tracker_pb2.ApprovalValue(
309 approval_id=10, status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW)]
310 # problem = no phase field label Launch-M-Target-70-Stable
311 # problem = missing a field for TL
312 self.issue3.field_values = [
313 tracker_pb2.FieldValue(field_id=8, int_value=71, phase_id=6),
314 tracker_bizobj.MakeFieldValue(4, None, None, 111, None, None, False)
315 ]
316
317 self.issue4.labels.extend(['Rollout-Type-Default'])
318 # problem = incorrect phases for OS default launch
319 self.issue4.phases = [tracker_pb2.Phase(name='Branch', phase_id=5),
320 tracker_pb2.Phase(name='Stable-Exp', phase_id=7)]
321 # problem = approval ChromeOS-UX has status 'NEEDS_REVIEW'
322 # for label value Yes
323 self.issue4.approval_values = [
324 tracker_pb2.ApprovalValue(
325 approval_id=9, status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW)]
326
327 self.issue5.labels.extend(['Rollout-Type-Finch'])
328 self.issue5.phases = [tracker_pb2.Phase(name='Branch', phase_id=5),
329 tracker_pb2.Phase(name='Feature Freeze', phase_id=4),
330 tracker_pb2.Phase(name='Stable-Exp', phase_id=7),
331 tracker_pb2.Phase(name='Stable-Full', phase_id=8)]
332 self.issue5.approval_values = [
333 tracker_pb2.ApprovalValue(
334 approval_id=9, status=tracker_pb2.ApprovalStatus.APPROVED),
335 # problem = approval ChromeOS-Privacy has status 'REVIEW_REQUESTED'
336 # for label value NeedInfo
337 tracker_pb2.ApprovalValue(
338 approval_id=11, status=tracker_pb2.ApprovalStatus.REVIEW_REQUESTED),
339 # problem = approval ChromeOS-Leadership-Exp has status 'NA' for label
340 # value Yes.
341 tracker_pb2.ApprovalValue(
342 approval_id=13, status=tracker_pb2.ApprovalStatus.NA)
343 ]
344
345 # problem = no phase field for label Launch-M-Approved-79-Stable-Exp
346 # problem = no phase field for label Launch-M-Target-70-Stable
347 self.issue5.field_values = [
348 tracker_pb2.FieldValue(field_id=8, int_value=71, phase_id=8),
349 tracker_bizobj.MakeFieldValue(4, None, None, 111, None, None, False),
350 tracker_bizobj.MakeFieldValue(5, None, None, 111, None, None, False)]
351
352 self.config.field_defs = [
353 tracker_pb2.FieldDef(field_id=1, field_name='Chrome-Test'),
354 tracker_pb2.FieldDef(field_id=2, field_name='Chrome-UX'),
355 tracker_pb2.FieldDef(field_id=3, field_name='Chrome-Privacy'),
356 tracker_pb2.FieldDef(field_id=4, field_name='PM'),
357 tracker_pb2.FieldDef(field_id=5, field_name='TL'),
358 tracker_pb2.FieldDef(field_id=6, field_name='TE'),
359 tracker_pb2.FieldDef(field_id=12, field_name='UX'),
360 tracker_pb2.FieldDef(field_id=7, field_name='M-Target'),
361 tracker_pb2.FieldDef(field_id=8, field_name='M-Approved'),
362 tracker_pb2.FieldDef(field_id=9, field_name='ChromeOS-UX'),
363 tracker_pb2.FieldDef(field_id=10, field_name='ChromeOS-Enterprise'),
364 tracker_pb2.FieldDef(field_id=11, field_name='ChromeOS-Privacy'),
365 tracker_pb2.FieldDef(field_id=13, field_name='ChromeOS-Leadership-Exp')
366 ]
367
368 # Set up mocks
369 patcher = mock.patch(
370 'search.frontendsearchpipeline.FrontendSearchPipeline',
371 spec=True, allowed_results=[
372 self.issue1, self.issue2, self.issue3, self.issue4, self.issue5])
373 mockPipeline = patcher.start()
374 self.task.services.project.GetProjectByName = mock.Mock()
375 self.task.services.config.GetProjectConfig = mock.Mock(
376 return_value=self.config)
377 self.task.services.issue.GetIssue = mock.Mock(
378 side_effect=[
379 self.issue1, self.issue2, self.issue3, self.issue4, self.issue5])
380 self.task.services.user.LookupUserID = mock.Mock(return_value=111)
381 with self.work_env as we:
382 we.ListIssues = mock.Mock(return_value=mockPipeline)
383
384 # Assert
385 json = self.task.VerifyConversion(self.mr)
386 self.assertEqual(json['issues verified'],
387 ['issue 1', 'issue 2', 'issue 3', 'issue 4', 'issue 5'])
388 problems = json['problems found']
389 expected_problems = [
390 'issue 1: missing a field for TL',
391 'issue 1: missing a field for UX',
392 'issue 2: approval Chrome-Privacy has status \'APPROVED\' for '
393 'label value None',
394 'issue 2: no phase field for label Launch-M-Approved-70-Beta',
395 'issue 3: missing a field for TL',
396 'issue 3: no phase field for label Launch-M-Target-70-Stable',
397 'issue 4: incorrect phases for OS default launch.',
398 'issue 4: approval ChromeOS-UX has status \'NEEDS_REVIEW\' for '
399 'label value Yes',
400 'issue 5: approval ChromeOS-Privacy has status \'REVIEW_REQUESTED\' '
401 'for label value NeedInfo',
402 'issue 5: approval ChromeOS-Leadership-Exp has status \'NA\' for label '
403 'value Yes',
404 'issue 5: no phase field for label Launch-M-Approved-79-Stable-Exp',
405 'issue 5: no phase field for label Launch-M-Target-70-Stable',
406 ]
407 self.assertEqual(problems, expected_problems)
408 patcher.stop()
409
410 def testFetchAndAssertProjectInfo(self):
411
412 # test no 'launch' in request
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100413 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000414 AssertionError, r'bad launch type:',
415 self.task.FetchAndAssertProjectInfo, self.mr)
416
417 # test bad 'launch' in request
418 mr = testing_helpers.MakeMonorailRequest(path='url/url?launch=bad')
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100419 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000420 AssertionError, r'bad launch type: bad',
421 self.task.FetchAndAssertProjectInfo, mr)
422
423 self.task.services.project.GetProjectByName = mock.Mock()
424 self.task.services.config.GetProjectConfig = mock.Mock(
425 return_value=self.config)
426
427 mr = testing_helpers.MakeMonorailRequest(path='url/url?launch=default')
428 # test no template
429 self.task.services.template.GetTemplateByName = mock.Mock(
430 return_value=None)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100431 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000432 AssertionError, r'not found in chromium project',
433 self.task.FetchAndAssertProjectInfo, mr)
434
435 # test template has no phases/approvals
436 template = tracker_bizobj.MakeIssueTemplate(
437 'template', 'sum', 'New', 111, 'content', [], [], [], [])
438 self.task.services.template.GetTemplateByName = mock.Mock(
439 return_value=template)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100440 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000441 AssertionError, 'no approvals or phases in',
442 self.task.FetchAndAssertProjectInfo, mr)
443
444 # test phases not recognized
445 template.phases = [tracker_pb2.Phase(name='WeirdPhase')]
446 template.approval_values = [tracker_pb2.ApprovalValue()]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100447 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000448 AssertionError, 'one or more phases not recognized',
449 self.task.FetchAndAssertProjectInfo, mr)
450
451 template.phases = [tracker_pb2.Phase(name='Stable'),
452 tracker_pb2.Phase(name='Stable-Exp')]
453 template.approval_values = [
454 tracker_pb2.ApprovalValue(approval_id=1),
455 tracker_pb2.ApprovalValue(approval_id=2),
456 tracker_pb2.ApprovalValue(approval_id=3)]
457
458 # test approvals not recognized
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100459 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000460 AssertionError, 'one or more approvals not recognized',
461 self.task.FetchAndAssertProjectInfo, mr)
462
463 self.config.field_defs = [
464 tracker_pb2.FieldDef(field_id=1, field_name='ChromeOS-Enterprise',
465 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE),
466 tracker_pb2.FieldDef(field_id=2, field_name='Chrome-UX',
467 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE),
468 tracker_pb2.FieldDef(field_id=3, field_name='Chrome-Privacy',
469 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE)
470 ]
471
472 # test approvals not in config's approval_defs
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100473 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000474 AssertionError, 'one or more approvals not in config.approval_defs',
475 self.task.FetchAndAssertProjectInfo, mr)
476
477 self.config.approval_defs = [
478 tracker_pb2.ApprovalDef(approval_id=1),
479 tracker_pb2.ApprovalDef(approval_id=2),
480 tracker_pb2.ApprovalDef(approval_id=3)]
481
482 # test no pm field exists in project
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100483 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000484 AssertionError, 'project has no FieldDef %s' % fltconversion.PM_FIELD,
485 self.task.FetchAndAssertProjectInfo, mr)
486
487 self.config.field_defs.extend([
488 tracker_pb2.FieldDef(field_id=4, field_name='PM',
489 field_type=tracker_pb2.FieldTypes.USER_TYPE),
490 tracker_pb2.FieldDef(field_id=5, field_name='TL',
491 field_type=tracker_pb2.FieldTypes.USER_TYPE),
492 tracker_pb2.FieldDef(field_id=9, field_name='UX',
493 field_type=tracker_pb2.FieldTypes.USER_TYPE),
494 tracker_pb2.FieldDef(field_id=6, field_name='TE')
495 ])
496
497 # test no USER_TYPE te field exists in project
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100498 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000499 AssertionError, 'project has no FieldDef %s' % fltconversion.TE_FIELD,
500 self.task.FetchAndAssertProjectInfo, mr)
501
502 self.config.field_defs[-1].field_type = tracker_pb2.FieldTypes.USER_TYPE
503 self.config.field_defs.extend([
504 tracker_pb2.FieldDef(
505 field_id=7, field_name='M-Target', is_phase_field=True),
506 tracker_pb2.FieldDef(
507 field_id=8, field_name='M-Approved', is_multivalued=True,
508 field_type=tracker_pb2.FieldTypes.INT_TYPE)
509 ])
510
511 # test no M-Target INT_TYPE multivalued Phase FieldDefs
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100512 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000513 AssertionError,
514 'project has no FieldDef %s' % fltconversion.MTARGET_FIELD,
515 self.task.FetchAndAssertProjectInfo, mr)
516
517 self.config.field_defs[-2].field_type = tracker_pb2.FieldTypes.INT_TYPE
518 self.config.field_defs[-2].is_multivalued = True
519
520 # test no M-Approved INT_TYPE multivalued Phase FieldDefs
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100521 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000522 AssertionError,
523 'project has no FieldDef %s' % fltconversion.MAPPROVED_FIELD,
524 self.task.FetchAndAssertProjectInfo, mr)
525
526 self.config.field_defs[-1].is_phase_field = True
527
528 self.assertEqual(
529 self.task.FetchAndAssertProjectInfo(mr),
530 fltconversion.ProjectInfo(
531 self.config, fltconversion.QUERY_MAP['default'],
532 template.approval_values, template.phases, 4, 5, 6, 9, 7, 8,
533 fltconversion.BROWSER_PHASE_MAP,
534 fltconversion.BROWSER_APPROVALS_TO_LABELS,
535 fltconversion.BROWSER_M_LABELS_RE))
536
537 # FINCH special case
538 # test approvals for Finch not required
539 mr = testing_helpers.MakeMonorailRequest(path='url/url?launch=finch')
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100540 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000541 AssertionError, 'finch template not set up correctly',
542 self.task.FetchAndAssertProjectInfo, mr)
543
544 for av in template.approval_values:
545 av.status = tracker_pb2.ApprovalStatus.NEEDS_REVIEW
546
547 self.assertEqual(
548 self.task.FetchAndAssertProjectInfo(mr),
549 fltconversion.ProjectInfo(
550 self.config, fltconversion.QUERY_MAP['finch'],
551 template.approval_values, template.phases, 4, 5, 6, 9, 7, 8,
552 fltconversion.BROWSER_PHASE_MAP,
553 fltconversion.BROWSER_APPROVALS_TO_LABELS,
554 fltconversion.BROWSER_M_LABELS_RE))
555
556 def testFetchAndAssertProjectInfo_OS(self):
557 self.task.services.project.GetProjectByName = mock.Mock()
558 self.task.services.config.GetProjectConfig = mock.Mock(
559 return_value=self.config)
560
561 mr = testing_helpers.MakeMonorailRequest(path='url/url?launch=os')
562 template = tracker_bizobj.MakeIssueTemplate(
563 'template', 'sum', 'New', 111, 'content', [], [], [], [])
564 self.task.services.template.GetTemplateByName = mock.Mock(
565 return_value=template)
566
567 # test phases not recognized
568 template.phases = [tracker_pb2.Phase(name='Chrome-Test')]
569 template.approval_values = [tracker_pb2.ApprovalValue()]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100570 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000571 AssertionError, 'one or more phases not recognized',
572 self.task.FetchAndAssertProjectInfo, mr)
573
574 template.phases = [tracker_pb2.Phase(name='feature freeze'),
575 tracker_pb2.Phase(name='branch')]
576
577 # test template not set up correctly
578 template.approval_values = [
579 tracker_pb2.ApprovalValue(approval_id=1),
580 tracker_pb2.ApprovalValue(approval_id=2),
581 tracker_pb2.ApprovalValue(approval_id=3)]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100582 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000583 AssertionError, 'os template not set up correctly',
584 self.task.FetchAndAssertProjectInfo, mr)
585
586 for av in template.approval_values:
587 av.status = tracker_pb2.ApprovalStatus.NEEDS_REVIEW
588
589 # test approvals not recognized
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100590 self.assertRaisesRegex(
Copybara854996b2021-09-07 19:36:02 +0000591 AssertionError, 'one or more approvals not recognized',
592 self.task.FetchAndAssertProjectInfo, mr)
593
594 self.config.field_defs = [
595 tracker_pb2.FieldDef(field_id=1, field_name='ChromeOS-Enterprise',
596 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE),
597 tracker_pb2.FieldDef(field_id=2, field_name='ChromeOS-UX',
598 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE),
599 tracker_pb2.FieldDef(field_id=3, field_name='ChromeOS-Privacy',
600 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE)
601 ]
602
603 # Skip remaining checks. No different from Browser process.
604 self.config.approval_defs = [
605 tracker_pb2.ApprovalDef(approval_id=1),
606 tracker_pb2.ApprovalDef(approval_id=2),
607 tracker_pb2.ApprovalDef(approval_id=3)]
608
609 self.config.field_defs.extend([
610 tracker_pb2.FieldDef(field_id=4, field_name='PM',
611 field_type=tracker_pb2.FieldTypes.USER_TYPE),
612 tracker_pb2.FieldDef(field_id=5, field_name='TL',
613 field_type=tracker_pb2.FieldTypes.USER_TYPE),
614 tracker_pb2.FieldDef(field_id=6, field_name='TE',
615 field_type=tracker_pb2.FieldTypes.USER_TYPE),
616 tracker_pb2.FieldDef(field_id=9, field_name='UX',
617 field_type=tracker_pb2.FieldTypes.USER_TYPE)
618 ])
619 self.config.field_defs.extend([
620 tracker_pb2.FieldDef(
621 field_id=7, field_name='M-Target', is_phase_field=True,
622 is_multivalued=True, field_type=tracker_pb2.FieldTypes.INT_TYPE),
623 tracker_pb2.FieldDef(
624 field_id=8, field_name='M-Approved', is_phase_field=True,
625 is_multivalued=True, field_type=tracker_pb2.FieldTypes.INT_TYPE)
626 ])
627
628 self.assertEqual(
629 self.task.FetchAndAssertProjectInfo(mr),
630 fltconversion.ProjectInfo(
631 self.config, fltconversion.QUERY_MAP['os'],
632 template.approval_values, template.phases, 4, 5, 6, 9, 7, 8,
633 fltconversion.OS_PHASE_MAP, fltconversion.OS_APPROVALS_TO_LABELS,
634 fltconversion.OS_M_LABELS_RE))
635
636 @mock.patch('time.time')
637 def testExecuteIssueChanges(self, mockTime):
638 mockTime.return_value = 123
639 self.task.services.issue._UpdateIssuesApprovals = mock.Mock()
640 self.task.services.issue.DeltaUpdateIssue = mock.Mock(
641 return_value=([], None))
642 self.task.services.issue.InsertComment = mock.Mock()
643 self.config.approval_defs = [
644 tracker_pb2.ApprovalDef(
645 # test empty survey
646 approval_id=1, survey='', approver_ids=[111, 222]),
647 tracker_pb2.ApprovalDef(approval_id=2), # test missing survey
648 tracker_pb2.ApprovalDef(survey='Missing approval_id should not error.'),
649 tracker_pb2.ApprovalDef(approval_id=3, survey='Q1\nQ2\n\nQ3'),
650 tracker_pb2.ApprovalDef(approval_id=4, survey='Q1\nQ2\n\nQ3 two'),
651 tracker_pb2.ApprovalDef()]
652
653 new_avs = [tracker_pb2.ApprovalValue(
654 approval_id=1, status=tracker_pb2.ApprovalStatus.APPROVED),
655 tracker_pb2.ApprovalValue(approval_id=4),
656 tracker_pb2.ApprovalValue(approval_id=2),
657 tracker_pb2.ApprovalValue(approval_id=3)]
658
659 phases = [tracker_pb2.Phase(phase_id=1, name='Phase1', rank=1)]
660 new_fvs = [tracker_bizobj.MakeFieldValue(
661 11, 70, None, None, None, None, False, phase_id=1),
662 tracker_bizobj.MakeFieldValue(
663 12, None, 'strfield', None, None, None, False)]
664 _amendments = self.task.ExecuteIssueChanges(
665 self.config, self.issue, new_avs, phases, new_fvs)
666
667 # approver_ids set in ExecuteIssueChanges()
668 new_avs[0].approver_ids = [111, 222]
669 self.issue.approval_values = new_avs
670 self.issue.phases = phases
671 delta = tracker_pb2.IssueDelta(
672 labels_add=['Type-FLT-Launch', 'FLT-Conversion'],
673 labels_remove=['Type-Launch'], field_vals_add=new_fvs)
674 cmt_1 = tracker_pb2.IssueComment(
675 issue_id=78901, project_id=789, user_id=self.mr.auth.user_id,
676 content='', is_description=True, approval_id=1, timestamp=123)
677 cmt_2 = tracker_pb2.IssueComment(
678 issue_id=78901, project_id=789, user_id=self.mr.auth.user_id,
679 content='', is_description=True, approval_id=2, timestamp=123)
680 cmt_3 = tracker_pb2.IssueComment(
681 issue_id=78901, project_id=789, user_id=self.mr.auth.user_id,
682 content='<b>Q1</b>\n<b>Q2</b>\n<b></b>\n<b>Q3</b>',
683 is_description=True, approval_id=3, timestamp=123)
684 cmt_4 = tracker_pb2.IssueComment(
685 issue_id=78901, project_id=789, user_id=self.mr.auth.user_id,
686 content='<b>Q1</b>\n<b>Q2</b>\n<b></b>\n<b>Q3 two</b>',
687 is_description=True, approval_id=4, timestamp=123)
688
689
690 comment_calls = [mock.call(self.mr.cnxn, cmt_1),
691 mock.call(self.mr.cnxn, cmt_4),
692 mock.call(self.mr.cnxn, cmt_2),
693 mock.call(self.mr.cnxn, cmt_3)]
694 self.task.services.issue.InsertComment.assert_has_calls(comment_calls)
695
696 self.task.services.issue._UpdateIssuesApprovals.assert_called_once_with(
697 self.mr.cnxn, self.issue)
698 self.task.services.issue.DeltaUpdateIssue.assert_called_once_with(
699 self.mr.cnxn, self.task.services, self.mr.auth.user_id, 789,
700 self.config, self.issue, delta,
701 comment=fltconversion.CONVERSION_COMMENT)
702
703 def testConvertPeopleLabels(self):
704 self.task.services.user.LookupUserID = mock.Mock(
705 side_effect=[1, 2, 3, 4, 5, 6])
706 labels = [
707 'pm-u1', 'pm-u2', 'tl-u2', 'test-3', 'test-4', 'ux-u5', 'ux-6']
708 fvs = self.task.ConvertPeopleLabels(self.mr, labels, 11, 12, 13, 14)
709 expected = [
710 tracker_bizobj.MakeFieldValue(11, None, None, 1, None, None, False),
711 tracker_bizobj.MakeFieldValue(12, None, None, 2, None, None, False),
712 tracker_bizobj.MakeFieldValue(13, None, None, 3, None, None, False),
713 tracker_bizobj.MakeFieldValue(13, None, None, 4, None, None, False),
714 tracker_bizobj.MakeFieldValue(14, None, None, 5, None, None, False),
715 tracker_bizobj.MakeFieldValue(14, None, None, 6, None, None, False),
716 ]
717 self.assertEqual(fvs, expected)
718
719 def testConvertPeopleLabels_NoUsers(self):
720 def side_effect(_cnxn, _email):
721 raise exceptions.NoSuchUserException()
722 labels = []
723 self.task.services.user.LookupUserID = mock.Mock(side_effect=side_effect)
724 self.assertFalse(
725 len(self.task.ConvertPeopleLabels(self.mr, labels, 11, 12, 13, 14)))
726
727 def testCreateUserFieldValue_Chromium(self):
728 self.task.services.user.LookupUserID = mock.Mock(return_value=1)
729 actual = self.task.CreateUserFieldValue(self.mr, 'ldap', 11)
730 expected = tracker_bizobj.MakeFieldValue(
731 11, None, None, 1, None, None, False)
732 self.assertEqual(actual, expected)
733 self.task.services.user.LookupUserID.assert_called_once_with(
734 self.mr.cnxn, 'ldap@chromium.org')
735
736 def testCreateUserFieldValue_Goog(self):
737 def side_effect(_cnxn, email):
738 if email.endswith('chromium.org'):
739 raise exceptions.NoSuchUserException()
740 else:
741 return 2
742 self.task.services.user.LookupUserID = mock.Mock(side_effect=side_effect)
743 actual = self.task.CreateUserFieldValue(self.mr, 'ldap', 11)
744 expected = tracker_bizobj.MakeFieldValue(
745 11, None, None, 2, None, None, False)
746 self.assertEqual(actual, expected)
747 self.task.services.user.LookupUserID.assert_any_call(
748 self.mr.cnxn, 'ldap@chromium.org')
749 self.task.services.user.LookupUserID.assert_any_call(
750 self.mr.cnxn, 'ldap@google.com')
751
752 def testCreateUserFieldValue_NoUserFound(self):
753 def side_effect(_cnxn, _email):
754 raise exceptions.NoSuchUserException()
755 self.task.services.user.LookupUserID = mock.Mock(side_effect=side_effect)
756 self.assertIsNone(self.task.CreateUserFieldValue(self.mr, 'ldap', 11))
757
758
759class ConvertMLabels(unittest.TestCase):
760
761 def setUp(self):
762 self.target_id = 24
763 self.approved_id = 27
764 self.beta_phase = tracker_pb2.Phase(phase_id=1, name='bEtA')
765 self.stable_phase = tracker_pb2.Phase(phase_id=2, name='StAbLe')
766 self.stable_full_phase = tracker_pb2.Phase(phase_id=3, name='stable-FULL')
767 self.stable_exp_phase = tracker_pb2.Phase(phase_id=4, name='STABLE-exp')
768 self.feature_freeze_phase = tracker_pb2.Phase(
769 phase_id=5, name='FEATURE Freeze')
770 self.branch_phase = tracker_pb2.Phase(phase_id=6, name='bRANCH')
771
772 def testConvertMLabels_NormalFinch(self):
773
774 phases = [self.stable_exp_phase, self.beta_phase, self.stable_full_phase]
775 labels = [
776 'launch-m-approved-81-beta', # beta:M-Approved=81
777 'launch-m-target-80-stable-car', # ignore
778 'a-Launch-M-Target-80-Stable-car', # ignore
779 'launch-m-target-70-Stable', # stable-full:M-Target=70
780 'LAUNCH-M-TARGET-71-STABLE', # stable-full:M-Target=71
781 'launch-m-target-70-stable-exp', # stable-exp:M-Target=70
782 'launch-m-target-69-stable-exp', # stable-exp:M-Target=69
783 'launch-M-APPROVED-70-Stable-Exp', # stable-exp:M-Approved-70
784 'launch-m-approved-73-stable', # stable-full:M-Approved-73
785 'launch-m-error-73-stable', # ignore
786 'launch-m-approved-8-stable', #ignore
787 'irrelevant label-weird', # ignore
788 ]
789 actual_fvs = fltconversion.ConvertMLabels(
790 labels, phases, self.target_id, self.approved_id,
791 fltconversion.BROWSER_M_LABELS_RE, fltconversion.BROWSER_PHASE_MAP)
792
793 expected_fvs = [
794 tracker_pb2.FieldValue(
795 field_id=self.approved_id, int_value=81,
796 phase_id=self.beta_phase.phase_id, derived=False,),
797 tracker_pb2.FieldValue(
798 field_id=self.target_id, int_value=70,
799 phase_id=self.stable_full_phase.phase_id, derived=False),
800 tracker_pb2.FieldValue(
801 field_id=self.target_id, int_value=71,
802 phase_id=self.stable_full_phase.phase_id, derived=False),
803 tracker_pb2.FieldValue(
804 field_id=self.target_id, int_value=70,
805 phase_id=self.stable_exp_phase.phase_id, derived=False),
806 tracker_pb2.FieldValue(
807 field_id=self.target_id, int_value=69,
808 phase_id=self.stable_exp_phase.phase_id, derived=False),
809 tracker_pb2.FieldValue(
810 field_id=self.approved_id, int_value=70,
811 phase_id=self.stable_exp_phase.phase_id, derived=False),
812 tracker_pb2.FieldValue(
813 field_id=self.approved_id, int_value=73,
814 phase_id=self.stable_full_phase.phase_id, derived=False)
815 ]
816
817 self.assertEqual(actual_fvs, expected_fvs)
818
819 def testConvertMLabels_OS(self):
820 phases = [self.feature_freeze_phase, self.branch_phase, self.stable_phase]
821 labels = [
822 'launch-m-approved-81-beta', # ignore
823 'launch-m-target-80-stable-car', # ignore
824 'a-Launch-M-Target-80-Stable-car', # ignore
825 'launch-m-target-70-Stable', # stable:M-Target=70
826 'LAUNCH-M-TARGET-71-STABLE', # stable:M-Target=71
827 'launch-m-target-70-stable-exp', # ignore
828 'launch-M-APPROVED-70-Stable-Exp', # ignore
829 'launch-m-approved-73-stable', # stable:M-Approved-73
830 'launch-m-error-73-stable', # ignore
831 'launch-m-approved-8-stable', #ignore
832 'irrelevant label-weird', # ignore
833 ]
834 actual_fvs = fltconversion.ConvertMLabels(
835 labels, phases, self.target_id, self.approved_id,
836 fltconversion.OS_M_LABELS_RE, fltconversion.OS_PHASE_MAP)
837
838 expected_fvs = [
839 tracker_pb2.FieldValue(
840 field_id=self.target_id, int_value=70,
841 phase_id=self.stable_phase.phase_id, derived=False,),
842 tracker_pb2.FieldValue(
843 field_id=self.target_id, int_value=71,
844 phase_id=self.stable_phase.phase_id, derived=False),
845 tracker_pb2.FieldValue(
846 field_id=self.approved_id, int_value=73,
847 phase_id=self.stable_phase.phase_id, derived=False)
848 ]
849
850 self.assertEqual(actual_fvs, expected_fvs)
851
852
853class ConvertLaunchLabels(unittest.TestCase):
854
855 def setUp(self):
856 self.project_fds = [
857 tracker_pb2.FieldDef(
858 field_id=1, project_id=789, field_name='String',
859 field_type=tracker_pb2.FieldTypes.STR_TYPE),
860 tracker_pb2.FieldDef(
861 field_id=2, project_id=789, field_name='Chrome-UX',
862 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE),
863 tracker_pb2.FieldDef(
864 field_id=3, project_id=789, field_name='Chrome-Privacy',
865 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE)
866 ]
867 approvalUX = tracker_pb2.ApprovalValue(
868 approval_id=2, status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW)
869 approvalPrivacy = tracker_pb2.ApprovalValue(approval_id=3)
870 self.approvals = [approvalUX, approvalPrivacy]
871
872 def testConvertLaunchLabels_Normal(self):
873 labels = [
874 'Launch-UX-NotReviewed', 'Launch-Privacy-Yes', 'Launch-NotRelevant']
875 actual = fltconversion.ConvertLaunchLabels(
876 labels, self.approvals, self.project_fds,
877 fltconversion.BROWSER_APPROVALS_TO_LABELS)
878 expected = [
879 tracker_pb2.ApprovalValue(
880 approval_id=2, status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW),
881 tracker_pb2.ApprovalValue(
882 approval_id=3, status=tracker_pb2.ApprovalStatus.APPROVED)
883 ]
884 self.assertEqual(actual, expected)
885
886 def testConvertLaunchLabels_ExtraAndMissingLabels(self):
887 labels = [
888 'Blah-Launch-Privacy-Yes', # Missing, this is not a valid Label
889 'Launch-Security-Yes', # Extra, no matching approval in given approvals
890 'Launch-UI-Yes'] # Missing Launch-Privacy
891 actual = fltconversion.ConvertLaunchLabels(
892 labels, self.approvals, self.project_fds,
893 fltconversion.BROWSER_APPROVALS_TO_LABELS)
894 expected = [
895 tracker_pb2.ApprovalValue(
896 approval_id=2, status=tracker_pb2.ApprovalStatus.APPROVED),
897 tracker_pb2.ApprovalValue(
898 approval_id=3, status=tracker_pb2.ApprovalStatus.NOT_SET)
899 ]
900 self.assertEqual(actual, expected)
901
902class ExtractLabelLDAPs(unittest.TestCase):
903
904 def testExtractLabelLDAPs_Normal(self):
905 labels = [
906 'tl-USER1',
907 'pm-',
908 'tL-User2',
909 'test-user4',
910 'PM-USER3',
911 'pm',
912 'test-user5',
913 'test-',
914 'ux-user9']
915 (actual_pm, actual_tl, actual_tests,
916 actual_ux) = fltconversion.ExtractLabelLDAPs(labels)
917 self.assertEqual(actual_pm, 'user3')
918 self.assertEqual(actual_tl, 'user2')
919 self.assertEqual(actual_tests, ['user4', 'user5'])
920 self.assertEqual(actual_ux, ['user9'])
921
922 def testExtractLabelLDAPs_NoLabels(self):
923 (actual_pm, actual_tl, actual_tests,
924 actual_ux) = fltconversion.ExtractLabelLDAPs([])
925 self.assertIsNone(actual_pm)
926 self.assertIsNone(actual_tl)
927 self.assertFalse(len(actual_tests))
928 self.assertFalse(len(actual_ux))