blob: 72f7aee573f546ee494d553ebb6f992833d30e08 [file] [log] [blame]
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001# Copyright 2016 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"""Tests for the API v1."""
6from __future__ import print_function
7from __future__ import division
8from __future__ import absolute_import
9
10import datetime
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010011from unittest import mock
Copybara854996b2021-09-07 19:36:02 +000012import endpoints
13import logging
14from mock import Mock, patch, ANY
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010015import six
Copybara854996b2021-09-07 19:36:02 +000016import time
17import unittest
18import webtest
19
20from google.appengine.api import oauth
21from protorpc import messages
22from protorpc import message_types
23
24from features import send_notifications
25from framework import authdata
26from framework import exceptions
27from framework import framework_constants
28from framework import permissions
29from framework import profiler
30from framework import template_helpers
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010031from mrproto import api_pb2_v1
32from mrproto import project_pb2
33from mrproto import tracker_pb2
Copybara854996b2021-09-07 19:36:02 +000034from search import frontendsearchpipeline
35from services import api_svc_v1
36from services import service_manager
37from services import template_svc
38from services import tracker_fulltext
39from testing import fake
40from testing import testing_helpers
41from testing_utils import testing
42from tracker import tracker_bizobj
43from tracker import tracker_constants
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +010044from redirect import redirect_utils
Copybara854996b2021-09-07 19:36:02 +000045
46
47def MakeFakeServiceManager():
48 return service_manager.Services(
49 user=fake.UserService(),
50 usergroup=fake.UserGroupService(),
51 project=fake.ProjectService(),
52 config=fake.ConfigService(),
53 issue=fake.IssueService(),
54 issue_star=fake.IssueStarService(),
55 features=fake.FeaturesService(),
56 template=Mock(spec=template_svc.TemplateService),
57 cache_manager=fake.CacheManager())
58
59
60class FakeMonorailApiRequest(object):
61
62 def __init__(self, request, services, perms=None):
63 self.profiler = profiler.Profiler()
64 self.cnxn = None
65 self.auth = authdata.AuthData.FromEmail(
66 self.cnxn, request['requester'], services)
67 self.me_user_id = self.auth.user_id
68 self.project_name = None
69 self.project = None
70 self.viewed_username = None
71 self.viewed_user_auth = None
72 self.config = None
73 if 'userId' in request:
74 self.viewed_username = request['userId']
75 self.viewed_user_auth = authdata.AuthData.FromEmail(
76 self.cnxn, self.viewed_username, services)
77 else:
78 assert 'groupName' in request
79 self.viewed_username = request['groupName']
80 try:
81 self.viewed_user_auth = authdata.AuthData.FromEmail(
82 self.cnxn, self.viewed_username, services)
83 except exceptions.NoSuchUserException:
84 self.viewed_user_auth = None
85 if 'projectId' in request:
86 self.project_name = request['projectId']
87 self.project = services.project.GetProjectByName(
88 self.cnxn, self.project_name)
89 self.config = services.config.GetProjectConfig(
90 self.cnxn, self.project_id)
91 self.perms = perms or permissions.GetPermissions(
92 self.auth.user_pb, self.auth.effective_ids, self.project)
93 self.granted_perms = set()
94
95 self.params = {
96 'can': request.get('can', 1),
97 'start': request.get('startIndex', 0),
98 'num': request.get('maxResults', 100),
99 'q': request.get('q', ''),
100 'sort': request.get('sort', ''),
101 'groupby': '',
102 'projects': request.get('additionalProject', []) + [self.project_name]}
103 self.use_cached_searches = True
104 self.errors = template_helpers.EZTError()
105 self.mode = None
106
107 self.query_project_names = self.GetParam('projects')
108 self.group_by_spec = self.GetParam('groupby')
109 self.sort_spec = self.GetParam('sort')
110 self.query = self.GetParam('q')
111 self.can = self.GetParam('can')
112 self.start = self.GetParam('start')
113 self.num = self.GetParam('num')
114 self.warnings = []
115
116 def CleanUp(self):
117 self.cnxn = None
118
119 @property
120 def project_id(self):
121 return self.project.project_id if self.project else None
122
123 def GetParam(self, query_param_name, default_value=None,
124 _antitamper_re=None):
125 return self.params.get(query_param_name, default_value)
126
127
128class FakeFrontendSearchPipeline(object):
129
130 def __init__(self):
131 issue1 = fake.MakeTestIssue(
132 project_id=12345, local_id=1, owner_id=222, status='New', summary='sum')
133 issue2 = fake.MakeTestIssue(
134 project_id=12345, local_id=2, owner_id=222, status='New', summary='sum')
135 self.allowed_results = [issue1, issue2]
136 self.visible_results = [issue1]
137 self.total_count = len(self.allowed_results)
138 self.config = None
139 self.projectId = 0
140
141 def SearchForIIDs(self):
142 pass
143
144 def MergeAndSortIssues(self):
145 pass
146
147 def Paginate(self):
148 pass
149
150
151class MonorailApiBadAuthTest(testing.EndpointsTestCase):
152
153 api_service_cls = api_svc_v1.MonorailApi
154
155 def setUp(self):
156 super(MonorailApiBadAuthTest, self).setUp()
157 self.requester = RequesterMock(email='requester@example.com')
158 self.mock(endpoints, 'get_current_user', lambda: None)
159 self.request = {'userId': 'user@example.com'}
160
161 def testUsersGet_BadOAuth(self):
162 """The requester's token is invalid, e.g., because it expired."""
163 oauth.get_current_user = Mock(
164 return_value=RequesterMock(email='test@example.com'))
165 oauth.get_current_user.side_effect = oauth.Error()
166 with self.assertRaises(webtest.AppError) as cm:
167 self.call_api('users_get', self.request)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100168 self.assertTrue(str(cm.exception).startswith('Bad response: 401'))
Copybara854996b2021-09-07 19:36:02 +0000169
170
171class MonorailApiTest(testing.EndpointsTestCase):
172
173 api_service_cls = api_svc_v1.MonorailApi
174
175 def setUp(self):
176 super(MonorailApiTest, self).setUp()
177 # Load queue.yaml.
178 self.requester = RequesterMock(email='requester@example.com')
179 self.mock(endpoints, 'get_current_user', lambda: self.requester)
180 self.config = None
181 self.services = MakeFakeServiceManager()
182 self.mock(api_svc_v1.MonorailApi, '_services', self.services)
183 self.services.user.TestAddUser('requester@example.com', 111)
184 self.services.user.TestAddUser('user@example.com', 222)
185 self.services.user.TestAddUser('group@example.com', 123)
186 self.services.usergroup.TestAddGroupSettings(123, 'group@example.com')
187 self.request = {
188 'userId': 'user@example.com',
189 'ownerProjectsOnly': False,
190 'requester': 'requester@example.com',
191 'projectId': 'test-project',
192 'issueId': 1}
193 self.mock(api_svc_v1.MonorailApi, 'mar_factory',
194 lambda x, y, z: FakeMonorailApiRequest(
195 self.request, self.services))
196
197 # api_base_checks is tested in AllBaseChecksTest,
198 # so mock it to reduce noise.
199 self.mock(api_svc_v1, 'api_base_checks',
200 lambda x, y, z, u, v, w: ('id', 'email'))
201
202 self.mock(tracker_fulltext, 'IndexIssues', lambda x, y, z, u, v: None)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100203 self.mock(tracker_fulltext, 'UnindexIssues', lambda _: None)
Copybara854996b2021-09-07 19:36:02 +0000204
205 def SetUpComponents(
206 self, project_id, component_id, component_name, component_doc='doc',
207 deprecated=False, admin_ids=None, cc_ids=None, created=100000,
208 creator=111):
209 admin_ids = admin_ids or []
210 cc_ids = cc_ids or []
211 self.config = self.services.config.GetProjectConfig(
212 'fake cnxn', project_id)
213 self.services.config.StoreConfig('fake cnxn', self.config)
214 cd = tracker_bizobj.MakeComponentDef(
215 component_id, project_id, component_name, component_doc, deprecated,
216 admin_ids, cc_ids, created, creator, modifier_id=creator)
217 self.config.component_defs.append(cd)
218
219 def SetUpFieldDefs(
220 self, field_id, project_id, field_name, field_type_int,
221 min_value=0, max_value=100, needs_member=False, docstring='doc',
222 approval_id=None, is_phase_field=False):
223 self.config = self.services.config.GetProjectConfig(
224 'fake cnxn', project_id)
225 self.services.config.StoreConfig('fake cnxn', self.config)
226 fd = tracker_bizobj.MakeFieldDef(
227 field_id, project_id, field_name, field_type_int, '',
228 '', False, False, False, min_value, max_value, None, needs_member,
229 None, '', tracker_pb2.NotifyTriggers.NEVER, 'no_action', docstring,
230 False, approval_id=approval_id, is_phase_field=is_phase_field)
231 self.config.field_defs.append(fd)
232
233 def testUsersGet_NoProject(self):
234 """The viewed user has no projects."""
235
236 self.services.project.TestAddProject(
237 'public-project', owner_ids=[111])
238 resp = self.call_api('users_get', self.request).json_body
239 expected = {
240 'id': '222',
241 'kind': 'monorail#user'}
242 self.assertEqual(expected, resp)
243
244 def testUsersGet_PublicProject(self):
245 """The viewed user has one public project."""
246 self.services.template.GetProjectTemplates.return_value = \
247 testing_helpers.DefaultTemplates()
248 self.services.project.TestAddProject(
249 'public-project', owner_ids=[222])
250 resp = self.call_api('users_get', self.request).json_body
251
252 self.assertEqual(1, len(resp['projects']))
253 self.assertEqual('public-project', resp['projects'][0]['name'])
254
255 def testUsersGet_PrivateProject(self):
256 """The viewed user has one project but the requester cannot view."""
257
258 self.services.project.TestAddProject(
259 'private-project', owner_ids=[222],
260 access=project_pb2.ProjectAccess.MEMBERS_ONLY)
261 resp = self.call_api('users_get', self.request).json_body
262 self.assertNotIn('projects', resp)
263
264 def testUsersGet_OwnerProjectOnly(self):
265 """The viewed user has different roles of projects."""
266 self.services.template.GetProjectTemplates.return_value = \
267 testing_helpers.DefaultTemplates()
268 self.services.project.TestAddProject(
269 'owner-project', owner_ids=[222])
270 self.services.project.TestAddProject(
271 'member-project', owner_ids=[111], committer_ids=[222])
272 resp = self.call_api('users_get', self.request).json_body
273 self.assertEqual(2, len(resp['projects']))
274
275 self.request['ownerProjectsOnly'] = True
276 resp = self.call_api('users_get', self.request).json_body
277 self.assertEqual(1, len(resp['projects']))
278 self.assertEqual('owner-project', resp['projects'][0]['name'])
279
280 def testIssuesGet_GetIssue(self):
281 """Get the requested issue."""
282
283 self.services.project.TestAddProject(
284 'test-project', owner_ids=[222],
285 project_id=12345)
286 self.SetUpComponents(12345, 1, 'API')
287 self.SetUpFieldDefs(1, 12345, 'Field1', tracker_pb2.FieldTypes.INT_TYPE)
288
289 fv = tracker_pb2.FieldValue(
290 field_id=1,
291 int_value=11)
292 issue1 = fake.MakeTestIssue(
293 project_id=12345, local_id=1, owner_id=222, reporter_id=111,
294 status='New', summary='sum', component_ids=[1], field_values=[fv])
295 self.services.issue.TestAddIssue(issue1)
296
297 resp = self.call_api('issues_get', self.request).json_body
298 self.assertEqual(1, resp['id'])
299 self.assertEqual('New', resp['status'])
300 self.assertEqual('open', resp['state'])
301 self.assertFalse(resp['canEdit'])
302 self.assertTrue(resp['canComment'])
303 self.assertEqual('requester@example.com', resp['author']['name'])
304 self.assertEqual('user@example.com', resp['owner']['name'])
305 self.assertEqual('API', resp['components'][0])
306 self.assertEqual('Field1', resp['fieldValues'][0]['fieldName'])
307 self.assertEqual('11', resp['fieldValues'][0]['fieldValue'])
308
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100309 @mock.patch('businesslogic.work_env.WorkEnv.GetIssueMigratedID')
310 def testIssuesGet_GetIssue_MigratedId(self, mockGetIssueMigratedId):
311 """Get the requested issue."""
312 mockGetIssueMigratedId.return_value = '23456'
313
314 self.services.project.TestAddProject(
315 'test-project', owner_ids=[222], project_id=12345)
316 self.SetUpComponents(12345, 1, 'API')
317 self.SetUpFieldDefs(1, 12345, 'Field1', tracker_pb2.FieldTypes.INT_TYPE)
318
319 fv = tracker_pb2.FieldValue(field_id=1, int_value=11)
320 issue1 = fake.MakeTestIssue(
321 project_id=12345,
322 local_id=1,
323 owner_id=222,
324 reporter_id=111,
325 status='New',
326 summary='sum',
327 component_ids=[1],
328 field_values=[fv])
329 self.services.issue.TestAddIssue(issue1)
330
331 resp = self.call_api('issues_get', self.request).json_body
332 self.assertEqual(1, resp['id'])
333 self.assertEqual('New', resp['status'])
334 self.assertEqual('open', resp['state'])
335 self.assertFalse(resp['canEdit'])
336 self.assertTrue(resp['canComment'])
337 self.assertEqual('requester@example.com', resp['author']['name'])
338 self.assertEqual('user@example.com', resp['owner']['name'])
339 self.assertEqual('API', resp['components'][0])
340 self.assertEqual('Field1', resp['fieldValues'][0]['fieldName'])
341 self.assertEqual('11', resp['fieldValues'][0]['fieldValue'])
342 self.assertEqual('23456', resp['migrated_id'])
343
344 @patch('framework.cloud_tasks_helpers.create_task')
345 def testIssuesInsert_FreezeLabels(self, _create_task_mock):
346 """Attempts to add new labels are blocked"""
347 self.services.project.TestAddProject(
348 'test-project', owner_ids=[222], committer_ids=[111], project_id=999)
349 self.SetUpFieldDefs(1, 999, 'Field1', tracker_pb2.FieldTypes.INT_TYPE)
350
351 issue1 = fake.MakeTestIssue(
352 project_id=999,
353 local_id=1,
354 owner_id=222,
355 reporter_id=111,
356 status='New',
357 summary='Test issue')
358 self.services.issue.TestAddIssue(issue1)
359
360 issue_dict = {
361 'blockedOn': [{
362 'issueId': 1
363 }],
364 'cc': [{
365 'name': 'user@example.com'
366 }, {
367 'name': ''
368 }, {
369 'name': ' '
370 }],
371 'description': 'description',
372 'labels': ['freeze_new_label', 'label1'],
373 'owner': {
374 'name': 'requester@example.com'
375 },
376 'status': 'New',
377 'summary': 'Test issue',
378 'fieldValues': [{
379 'fieldName': 'Field1',
380 'fieldValue': '11'
381 }]
382 }
383 self.request.update(issue_dict)
384
385 with self.call_should_fail(400):
386 self.call_api('issues_insert', self.request)
387
Copybara854996b2021-09-07 19:36:02 +0000388 def testIssuesInsert_BadRequest(self):
389 """The request does not specify summary or status."""
390
391 with self.assertRaises(webtest.AppError):
392 self.call_api('issues_insert', self.request)
393
394 issue_dict = {
395 'status': 'New',
396 'summary': 'Test issue',
397 'owner': {'name': 'notexist@example.com'}}
398 self.request.update(issue_dict)
399 self.services.project.TestAddProject(
400 'test-project', owner_ids=[222],
401 project_id=12345)
402 with self.call_should_fail(400):
403 self.call_api('issues_insert', self.request)
404
405 # Invalid field value
406 self.SetUpFieldDefs(1, 12345, 'Field1', tracker_pb2.FieldTypes.INT_TYPE)
407 issue_dict = {
408 'status': 'New',
409 'summary': 'Test issue',
410 'owner': {'name': 'requester@example.com'},
411 'fieldValues': [{'fieldName': 'Field1', 'fieldValue': '111'}]}
412 self.request.update(issue_dict)
413 with self.call_should_fail(400):
414 self.call_api('issues_insert', self.request)
415
416 def testIssuesInsert_NoPermission(self):
417 """The requester has no permission to create issues."""
418
419 issue_dict = {
420 'status': 'New',
421 'summary': 'Test issue'}
422 self.request.update(issue_dict)
423
424 self.services.project.TestAddProject(
425 'test-project', owner_ids=[222],
426 access=project_pb2.ProjectAccess.MEMBERS_ONLY,
427 project_id=12345)
428 with self.call_should_fail(403):
429 self.call_api('issues_insert', self.request)
430
431 @patch('framework.cloud_tasks_helpers.create_task')
432 def testIssuesInsert_CreateIssue(self, _create_task_mock):
433 """Create an issue as requested."""
434
435 self.services.project.TestAddProject(
436 'test-project', owner_ids=[222], committer_ids=[111], project_id=12345)
437 self.SetUpFieldDefs(1, 12345, 'Field1', tracker_pb2.FieldTypes.INT_TYPE)
438
439 issue1 = fake.MakeTestIssue(
440 project_id=12345, local_id=1, owner_id=222, reporter_id=111,
441 status='New', summary='Test issue')
442 self.services.issue.TestAddIssue(issue1)
443
444 issue_dict = {
445 'blockedOn': [{'issueId': 1}],
446 'cc': [{'name': 'user@example.com'}, {'name': ''}, {'name': ' '}],
447 'description': 'description',
448 'labels': ['label1', 'label2'],
449 'owner': {'name': 'requester@example.com'},
450 'status': 'New',
451 'summary': 'Test issue',
452 'fieldValues': [{'fieldName': 'Field1', 'fieldValue': '11'}]}
453 self.request.update(issue_dict)
454
455 resp = self.call_api('issues_insert', self.request).json_body
456 self.assertEqual('New', resp['status'])
457 self.assertEqual('requester@example.com', resp['author']['name'])
458 self.assertEqual('requester@example.com', resp['owner']['name'])
459 self.assertEqual('user@example.com', resp['cc'][0]['name'])
460 self.assertEqual(1, resp['blockedOn'][0]['issueId'])
461 self.assertEqual([u'label1', u'label2'], resp['labels'])
462 self.assertEqual('Test issue', resp['summary'])
463 self.assertEqual('Field1', resp['fieldValues'][0]['fieldName'])
464 self.assertEqual('11', resp['fieldValues'][0]['fieldValue'])
465
466 new_issue = self.services.issue.GetIssueByLocalID(
467 'fake cnxn', 12345, resp['id'])
468
469 starrers = self.services.issue_star.LookupItemStarrers(
470 'fake cnxn', new_issue.issue_id)
471 self.assertIn(111, starrers)
472
473 @patch('framework.cloud_tasks_helpers.create_task')
474 def testIssuesInsert_EmptyOwnerCcNames(self, _create_task_mock):
475 """Create an issue as requested."""
476
477 self.services.project.TestAddProject(
478 'test-project', owner_ids=[222],
479 project_id=12345)
480 self.SetUpFieldDefs(1, 12345, 'Field1', tracker_pb2.FieldTypes.INT_TYPE)
481
482 issue_dict = {
483 'cc': [{'name': 'user@example.com'}, {'name': ''}],
484 'description': 'description',
485 'owner': {'name': ''},
486 'status': 'New',
487 'summary': 'Test issue'}
488 self.request.update(issue_dict)
489
490 resp = self.call_api('issues_insert', self.request).json_body
491 self.assertEqual('New', resp['status'])
492 self.assertEqual('requester@example.com', resp['author']['name'])
493 self.assertTrue('owner' not in resp)
494 self.assertEqual('user@example.com', resp['cc'][0]['name'])
495 self.assertEqual(len(resp['cc']), 1)
496 self.assertEqual('Test issue', resp['summary'])
497
498 new_issue = self.services.issue.GetIssueByLocalID(
499 'fake cnxn', 12345, resp['id'])
500 self.assertEqual(new_issue.owner_id, 0)
501
502 def testIssuesList_NoPermission(self):
503 """No permission for additional projects."""
504 self.services.project.TestAddProject(
505 'test-project', owner_ids=[222],
506 project_id=12345)
507
508 self.services.project.TestAddProject(
509 'test-project2', owner_ids=[222],
510 access=project_pb2.ProjectAccess.MEMBERS_ONLY,
511 project_id=123456)
512 self.request['additionalProject'] = ['test-project2']
513 with self.call_should_fail(403):
514 self.call_api('issues_list', self.request)
515
516 def testIssuesList_SearchIssues(self):
517 """Find issues of one project."""
518
519 self.mock(
520 frontendsearchpipeline,
521 'FrontendSearchPipeline', lambda cnxn, serv, auth, me, q, q_proj_names,
522 num, start, can, group_spec, sort_spec, warnings, errors, use_cache,
523 profiler, project: FakeFrontendSearchPipeline())
524
525 self.services.project.TestAddProject(
526 'test-project', owner_ids=[111], # requester
527 access=project_pb2.ProjectAccess.MEMBERS_ONLY,
528 project_id=12345)
529 resp = self.call_api('issues_list', self.request).json_body
530 self.assertEqual(2, int(resp['totalResults']))
531 self.assertEqual(1, len(resp['items']))
532 self.assertEqual(1, resp['items'][0]['id'])
533
534 def testIssuesCommentsList_GetComments(self):
535 """Get comments of requested issue."""
536
537 self.services.project.TestAddProject(
538 'test-project', owner_ids=[222],
539 project_id=12345)
540
541 issue1 = fake.MakeTestIssue(
542 project_id=12345, local_id=1, summary='test summary', status='New',
543 issue_id=10001, owner_id=222, reporter_id=111)
544 self.services.issue.TestAddIssue(issue1)
545
546 comment = tracker_pb2.IssueComment(
547 id=123, issue_id=10001,
548 project_id=12345, user_id=222,
549 content='this is a comment',
550 timestamp=1437700000)
551 self.services.issue.TestAddComment(comment, 1)
552
553 resp = self.call_api('issues_comments_list', self.request).json_body
554 self.assertEqual(2, resp['totalResults'])
555 comment1 = resp['items'][0]
556 comment2 = resp['items'][1]
557 self.assertEqual('requester@example.com', comment1['author']['name'])
558 self.assertEqual('test summary', comment1['content'])
559 self.assertEqual('user@example.com', comment2['author']['name'])
560 self.assertEqual('this is a comment', comment2['content'])
561
562 def testParseImportedReporter_Normal(self):
563 """Normal attempt to post a comment under the requester's name."""
564 mar = FakeMonorailApiRequest(self.request, self.services)
565 container = api_pb2_v1.ISSUES_COMMENTS_INSERT_REQUEST_RESOURCE_CONTAINER
566 request = container.body_message_class()
567
568 monorail_api = self.api_service_cls()
569 monorail_api._set_services(self.services)
570 reporter_id, timestamp = monorail_api.parse_imported_reporter(mar, request)
571 self.assertEqual(111, reporter_id)
572 self.assertIsNone(timestamp)
573
574 # API users should not need to specify anything for author when posting
575 # as the signed-in user, but it is OK if they specify their own email.
576 request.author = api_pb2_v1.AtomPerson(name='requester@example.com')
577 request.published = datetime.datetime.now() # Ignored
578 monorail_api = self.api_service_cls()
579 monorail_api._set_services(self.services)
580 reporter_id, timestamp = monorail_api.parse_imported_reporter(mar, request)
581 self.assertEqual(111, reporter_id)
582 self.assertIsNone(timestamp)
583
584 def testParseImportedReporter_Import_Allowed(self):
585 """User is importing a comment posted by a different user."""
586 project = self.services.project.TestAddProject(
587 'test-project', owner_ids=[222], contrib_ids=[111],
588 project_id=12345)
589 project.extra_perms = [project_pb2.Project.ExtraPerms(
590 member_id=111, perms=['ImportComment'])]
591 mar = FakeMonorailApiRequest(self.request, self.services)
592 container = api_pb2_v1.ISSUES_COMMENTS_INSERT_REQUEST_RESOURCE_CONTAINER
593 request = container.body_message_class()
594 request.author = api_pb2_v1.AtomPerson(name='user@example.com')
595 NOW = 1234567890
596 request.published = datetime.datetime.utcfromtimestamp(NOW)
597 monorail_api = self.api_service_cls()
598 monorail_api._set_services(self.services)
599
600 reporter_id, timestamp = monorail_api.parse_imported_reporter(mar, request)
601
602 self.assertEqual(222, reporter_id) # that is user@
603 self.assertEqual(NOW, timestamp)
604
605 def testParseImportedReporter_Import_NotAllowed(self):
606 """User is importing a comment posted by a different user without perm."""
607 mar = FakeMonorailApiRequest(self.request, self.services)
608 container = api_pb2_v1.ISSUES_COMMENTS_INSERT_REQUEST_RESOURCE_CONTAINER
609 request = container.body_message_class()
610 request.author = api_pb2_v1.AtomPerson(name='user@example.com')
611 NOW = 1234567890
612 request.published = datetime.datetime.fromtimestamp(NOW)
613 monorail_api = self.api_service_cls()
614 monorail_api._set_services(self.services)
615
616 with self.assertRaises(permissions.PermissionException):
617 monorail_api.parse_imported_reporter(mar, request)
618
619 def testIssuesCommentsInsert_ApprovalFields(self):
620 """Attempts to update approval field values are blocked."""
621 self.services.project.TestAddProject(
622 'test-project', owner_ids=[222],
623 access=project_pb2.ProjectAccess.MEMBERS_ONLY,
624 project_id=12345)
625
626 issue1 = fake.MakeTestIssue(
627 12345, 1, 'Issue 1', 'New', 2, issue_id=1234501)
628 self.services.issue.TestAddIssue(issue1)
629
630 self.SetUpFieldDefs(
631 1, 12345, 'Field_int', tracker_pb2.FieldTypes.INT_TYPE)
632 self.SetUpFieldDefs(
633 2, 12345, 'ApprovalChild', tracker_pb2.FieldTypes.STR_TYPE,
634 approval_id=1)
635
636 self.request['updates'] = {
637 'fieldValues': [{'fieldName': 'Field_int', 'fieldValue': '11'},
638 {'fieldName': 'ApprovalChild', 'fieldValue': 'str'}]}
639
640 with self.call_should_fail(403):
641 self.call_api('issues_comments_insert', self.request)
642
643 def testIssuesCommentsInsert_NoCommentPermission(self):
644 """No permission to comment an issue."""
645
646 self.services.project.TestAddProject(
647 'test-project', owner_ids=[222],
648 access=project_pb2.ProjectAccess.MEMBERS_ONLY,
649 project_id=12345)
650
651 issue1 = fake.MakeTestIssue(
652 12345, 1, 'Issue 1', 'New', 2)
653 self.services.issue.TestAddIssue(issue1)
654
655 with self.call_should_fail(403):
656 self.call_api('issues_comments_insert', self.request)
657
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100658 def testIssuesCommentsInsert_ArchivedProject(self):
659 """No permission to comment in an archived project."""
660 self.services.project.TestAddProject(
661 'test-project',
662 owner_ids=[111],
663 state=project_pb2.ProjectState.ARCHIVED,
664 project_id=12345)
665 issue1 = fake.MakeTestIssue(12345, 1, 'Issue 1', 'New', 2)
666 self.services.issue.TestAddIssue(issue1)
667
668 self.services.project.TestAddProject(
669 'archived-project', owner_ids=[222], project_id=6789)
670 issue2 = fake.MakeTestIssue(
671 6789, 2, 'Issue 2', 'New', 222, project_name='archived-project')
672 self.services.issue.TestAddIssue(issue2)
673
674 self.request['updates'] = {
675 'blockedOn': ['archived-project:2'],
676 'mergedInto': '',
677 }
678 with self.call_should_fail(403):
679 self.call_api('issues_comments_insert', self.request)
680
681 self.request['updates'] = {
682 'blockedOn': [],
683 'mergedInto': 'archived-project:2',
684 }
685 with self.call_should_fail(403):
686 self.call_api('issues_comments_insert', self.request)
687
Copybara854996b2021-09-07 19:36:02 +0000688 def testIssuesCommentsInsert_CommentPermissionOnly(self):
689 """User has permission to comment, even though they cannot edit."""
690 self.services.project.TestAddProject(
691 'test-project', owner_ids=[], project_id=12345)
692
693 issue1 = fake.MakeTestIssue(
694 12345, 1, 'Issue 1', 'New', 222)
695 self.services.issue.TestAddIssue(issue1)
696
697 self.request['content'] = 'This is just a comment'
698 resp = self.call_api('issues_comments_insert', self.request).json_body
699 self.assertEqual('requester@example.com', resp['author']['name'])
700 self.assertEqual('This is just a comment', resp['content'])
701
702 def testIssuesCommentsInsert_TooLongComment(self):
703 """Too long of a comment to add."""
704 self.services.project.TestAddProject(
705 'test-project', owner_ids=[], project_id=12345)
706
707 issue1 = fake.MakeTestIssue(12345, 1, 'Issue 1', 'New', 222)
708 self.services.issue.TestAddIssue(issue1)
709
710 long_comment = ' ' + 'c' * tracker_constants.MAX_COMMENT_CHARS + ' '
711 self.request['content'] = long_comment
712 with self.call_should_fail(400):
713 self.call_api('issues_comments_insert', self.request)
714
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100715 def testIssuesCommentsInsert_FreezeLabels(self):
716 """Attempts to add new labels are blocked"""
717 self.services.project.TestAddProject(
718 'test-project', owner_ids=[111], project_id=999)
719
720 issue1 = fake.MakeTestIssue(
721 999, 1, 'Issue 1', 'New', 222, project_name='test-project')
722 self.services.issue.TestAddIssue(issue1)
723
724 self.request['updates'] = {
725 'summary': 'new summary',
726 'status': 'Started',
727 'owner': 'requester@example.com',
728 'cc': ['user@example.com'],
729 'labels': ['freeze_new_label', '-remove_label'],
730 'blockedOn': ['2'],
731 'blocking': ['3'],
732 }
733
734 with self.call_should_fail(400):
735 self.call_api('issues_comments_insert', self.request)
736
Copybara854996b2021-09-07 19:36:02 +0000737 def testIssuesCommentsInsert_Amendments_Normal(self):
738 """Insert comments with amendments."""
739
740 self.services.project.TestAddProject(
741 'test-project', owner_ids=[111],
742 project_id=12345)
743
744 issue1 = fake.MakeTestIssue(
745 12345, 1, 'Issue 1', 'New', 222, project_name='test-project')
746 issue2 = fake.MakeTestIssue(
747 12345, 2, 'Issue 2', 'New', 222, project_name='test-project')
748 issue3 = fake.MakeTestIssue(
749 12345, 3, 'Issue 3', 'New', 222, project_name='test-project')
750 self.services.issue.TestAddIssue(issue1)
751 self.services.issue.TestAddIssue(issue2)
752 self.services.issue.TestAddIssue(issue3)
753
754 self.request['updates'] = {
755 'summary': 'new summary',
756 'status': 'Started',
757 'owner': 'requester@example.com',
758 'cc': ['user@example.com'],
759 'labels': ['add_label', '-remove_label'],
760 'blockedOn': ['2'],
761 'blocking': ['3'],
762 }
763 resp = self.call_api('issues_comments_insert', self.request).json_body
764 self.assertEqual('requester@example.com', resp['author']['name'])
765 self.assertEqual('Started', resp['updates']['status'])
766 self.assertEqual(0, issue1.merged_into)
767
768 def testIssuesCommentsInsert_Amendments_NoPerms(self):
769 """Can't insert comments using account that lacks permissions."""
770
771 project1 = self.services.project.TestAddProject(
772 'test-project', owner_ids=[], project_id=12345)
773
774 issue1 = fake.MakeTestIssue(
775 12345, 1, 'Issue 1', 'New', 222, project_name='test-project')
776 self.services.issue.TestAddIssue(issue1)
777
778 self.request['updates'] = {
779 'summary': 'new summary',
780 }
781 with self.call_should_fail(403):
782 self.call_api('issues_comments_insert', self.request)
783
784 project1.contributor_ids = [1] # Does not grant edit perm.
785 with self.call_should_fail(403):
786 self.call_api('issues_comments_insert', self.request)
787
788 def testIssuesCommentsInsert_Amendments_BadOwner(self):
789 """Can't set owner to someone who is not a project member."""
790
791 _project1 = self.services.project.TestAddProject(
792 'test-project', owner_ids=[111], project_id=12345)
793
794 issue1 = fake.MakeTestIssue(
795 12345, 1, 'Issue 1', 'New', 222, project_name='test-project')
796 self.services.issue.TestAddIssue(issue1)
797
798 self.request['updates'] = {
799 'owner': 'user@example.com',
800 }
801 with self.call_should_fail(400):
802 self.call_api('issues_comments_insert', self.request)
803
804 @patch('framework.cloud_tasks_helpers.create_task')
805 def testIssuesCommentsInsert_MergeInto(self, _create_task_mock):
806 """Insert comment that merges an issue into another issue."""
807
808 self.services.project.TestAddProject(
809 'test-project', owner_ids=[222], committer_ids=[111],
810 project_id=12345)
811
812 issue1 = fake.MakeTestIssue(
813 12345, 1, 'Issue 1', 'New', 222, project_name='test-project')
814 issue2 = fake.MakeTestIssue(
815 12345, 2, 'Issue 2', 'New', 222, project_name='test-project')
816 self.services.issue.TestAddIssue(issue1)
817 self.services.issue.TestAddIssue(issue2)
818 self.services.issue_star.SetStarsBatch(
819 'cnxn', 'service', 'config', issue1.issue_id, [111, 222, 333], True)
820 self.services.issue_star.SetStarsBatch(
821 'cnxn', 'service', 'config', issue2.issue_id, [555], True)
822
823 self.request['updates'] = {
824 'summary': 'new summary',
825 'status': 'Duplicate',
826 'owner': 'requester@example.com',
827 'cc': ['user@example.com'],
828 'labels': ['add_label', '-remove_label'],
829 'mergedInto': '2',
830 }
831 resp = self.call_api('issues_comments_insert', self.request).json_body
832 self.assertEqual('requester@example.com', resp['author']['name'])
833 self.assertEqual('Duplicate', resp['updates']['status'])
834 self.assertEqual(issue2.issue_id, issue1.merged_into)
835 issue2_comments = self.services.issue.GetCommentsForIssue(
836 'cnxn', issue2.issue_id)
837 self.assertEqual(2, len(issue2_comments)) # description and merge
838 source_starrers = self.services.issue_star.LookupItemStarrers(
839 'cnxn', issue1.issue_id)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100840 six.assertCountEqual(self, [111, 222, 333], source_starrers)
Copybara854996b2021-09-07 19:36:02 +0000841 target_starrers = self.services.issue_star.LookupItemStarrers(
842 'cnxn', issue2.issue_id)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +0100843 six.assertCountEqual(self, [111, 222, 333, 555], target_starrers)
Copybara854996b2021-09-07 19:36:02 +0000844
845 def testIssuesCommentsInsert_CustomFields(self):
846 """Update custom field values."""
847 self.services.project.TestAddProject(
848 'test-project', owner_ids=[111],
849 project_id=12345)
850 issue1 = fake.MakeTestIssue(
851 12345, 1, 'Issue 1', 'New', 222,
852 project_name='test-project')
853 self.services.issue.TestAddIssue(issue1)
854 self.SetUpFieldDefs(
855 1, 12345, 'Field_int', tracker_pb2.FieldTypes.INT_TYPE)
856 self.SetUpFieldDefs(
857 2, 12345, 'Field_enum', tracker_pb2.FieldTypes.ENUM_TYPE)
858
859 self.request['updates'] = {
860 'fieldValues': [{'fieldName': 'Field_int', 'fieldValue': '11'},
861 {'fieldName': 'Field_enum', 'fieldValue': 'str'}]}
862 resp = self.call_api('issues_comments_insert', self.request).json_body
863 self.assertEqual(
864 {'fieldName': 'Field_int', 'fieldValue': '11'},
865 resp['updates']['fieldValues'][0])
866
867 def testIssuesCommentsInsert_IsDescription(self):
868 """Add a new issue description."""
869 self.services.project.TestAddProject(
870 'test-project', owner_ids=[111], project_id=12345)
871 issue1 = fake.MakeTestIssue(
872 12345, 1, 'Issue 1', 'New', 222, project_name='test-project')
873 self.services.issue.TestAddIssue(issue1)
874 # Note: the initially issue description will be "Issue 1".
875
876 self.request['content'] = 'new desc'
877 self.request['updates'] = {'is_description': True}
878 resp = self.call_api('issues_comments_insert', self.request).json_body
879 self.assertEqual('new desc', resp['content'])
880 comments = self.services.issue.GetCommentsForIssue('cnxn', issue1.issue_id)
881 self.assertEqual(2, len(comments))
882 self.assertTrue(comments[1].is_description)
883 self.assertEqual('new desc', comments[1].content)
884
885 def testIssuesCommentsInsert_MoveToProject_NoPermsSrc(self):
886 """Don't move issue when user has no perms to edit issue."""
887 self.services.project.TestAddProject(
888 'test-project', owner_ids=[], project_id=12345)
889 issue1 = fake.MakeTestIssue(
890 12345, 1, 'Issue 1', 'New', 222, labels=[],
891 project_name='test-project')
892 self.services.issue.TestAddIssue(issue1)
893 self.services.project.TestAddProject(
894 'test-project2', owner_ids=[111], project_id=12346)
895
896 # The user has no permission in test-project.
897 self.request['projectId'] = 'test-project'
898 self.request['updates'] = {
899 'moveToProject': 'test-project2'}
900 with self.call_should_fail(403):
901 self.call_api('issues_comments_insert', self.request)
902
903 def testIssuesCommentsInsert_MoveToProject_NoPermsDest(self):
904 """Don't move issue to a different project where user has no perms."""
905 self.services.project.TestAddProject(
906 'test-project', owner_ids=[111], project_id=12345)
907 issue1 = fake.MakeTestIssue(
908 12345, 1, 'Issue 1', 'New', 222, labels=[],
909 project_name='test-project')
910 self.services.issue.TestAddIssue(issue1)
911 self.services.project.TestAddProject(
912 'test-project2', owner_ids=[], project_id=12346)
913
914 # The user has no permission in test-project2.
915 self.request['projectId'] = 'test-project'
916 self.request['updates'] = {
917 'moveToProject': 'test-project2'}
918 with self.call_should_fail(400):
919 self.call_api('issues_comments_insert', self.request)
920
921 def testIssuesCommentsInsert_MoveToProject_NoSuchProject(self):
922 """Don't move issue to a different project that does not exist."""
923 project1 = self.services.project.TestAddProject(
924 'test-project', owner_ids=[222], project_id=12345)
925 issue1 = fake.MakeTestIssue(
926 12345, 1, 'Issue 1', 'New', 222, labels=[],
927 project_name='test-project')
928 self.services.issue.TestAddIssue(issue1)
929
930 # Project doesn't exist.
931 project1.owner_ids = [111, 222]
932 self.request['updates'] = {
933 'moveToProject': 'not exist'}
934 with self.call_should_fail(400):
935 self.call_api('issues_comments_insert', self.request)
936
937 def testIssuesCommentsInsert_MoveToProject_SameProject(self):
938 """Don't move issue to the project it is already in."""
939 self.services.project.TestAddProject(
940 'test-project', owner_ids=[111], project_id=12345)
941 issue1 = fake.MakeTestIssue(
942 12345, 1, 'Issue 1', 'New', 222, labels=[],
943 project_name='test-project')
944 self.services.issue.TestAddIssue(issue1)
945
946 # The issue is already in destination
947 self.request['updates'] = {
948 'moveToProject': 'test-project'}
949 with self.call_should_fail(400):
950 self.call_api('issues_comments_insert', self.request)
951
952 def testIssuesCommentsInsert_MoveToProject_Restricted(self):
953 """Don't move restricted issue to a different project."""
954 self.services.project.TestAddProject(
955 'test-project', owner_ids=[111], project_id=12345)
956 issue1 = fake.MakeTestIssue(
957 12345, 1, 'Issue 1', 'New', 222, labels=['Restrict-View-Google'],
958 project_name='test-project')
959 self.services.issue.TestAddIssue(issue1)
960 self.services.project.TestAddProject(
961 'test-project2', owner_ids=[111],
962 project_id=12346)
963
964 # Issue has restrict labels, so it cannot move.
965 self.request['projectId'] = 'test-project'
966 self.request['updates'] = {
967 'moveToProject': 'test-project2'}
968 with self.call_should_fail(400):
969 self.call_api('issues_comments_insert', self.request)
970
971 def testIssuesCommentsInsert_MoveToProject_Normal(self):
972 """Move issue."""
973 self.services.project.TestAddProject(
974 'test-project', owner_ids=[111, 222],
975 project_id=12345)
976 self.services.project.TestAddProject(
977 'test-project2', owner_ids=[111, 222],
978 project_id=12346)
979 issue1 = fake.MakeTestIssue(
980 12345, 1, 'Issue 1', 'New', 222, project_name='test-project')
981 self.services.issue.TestAddIssue(issue1)
982 issue2 = fake.MakeTestIssue(
983 12346, 1, 'Issue 1', 'New', 222, project_name='test-project2')
984 self.services.issue.TestAddIssue(issue2)
985
986 self.request['updates'] = {
987 'moveToProject': 'test-project2'}
988 resp = self.call_api('issues_comments_insert', self.request).json_body
989
990 self.assertEqual(
991 'Moved issue test-project:1 to now be issue test-project2:2.',
992 resp['content'])
993
994 def testIssuesCommentsInsert_Import_Allowed(self):
995 """Post a comment attributed to another user, with permission."""
996 project = self.services.project.TestAddProject(
997 'test-project', committer_ids=[111, 222], project_id=12345)
998 project.extra_perms = [project_pb2.Project.ExtraPerms(
999 member_id=111, perms=['ImportComment'])]
1000 issue1 = fake.MakeTestIssue(
1001 12345, 1, 'Issue 1', 'New', 222, project_name='test-project')
1002 self.services.issue.TestAddIssue(issue1)
1003
1004 self.request['author'] = {'name': 'user@example.com'} # 222
1005 self.request['content'] = 'a comment'
1006 self.request['updates'] = {
1007 'owner': 'user@example.com',
1008 }
1009
1010 resp = self.call_api('issues_comments_insert', self.request).json_body
1011
1012 self.assertEqual('a comment', resp['content'])
1013 comments = self.services.issue.GetCommentsForIssue('cnxn', issue1.issue_id)
1014 self.assertEqual(2, len(comments))
1015 self.assertEqual(222, comments[1].user_id)
1016 self.assertEqual('a comment', comments[1].content)
1017
1018
1019 def testIssuesCommentsInsert_Import_Self(self):
1020 """Specifying the comment author is OK if it is the requester."""
1021 self.services.project.TestAddProject(
1022 'test-project', committer_ids=[111, 222], project_id=12345)
1023 # Note: No ImportComment permission has been granted.
1024 issue1 = fake.MakeTestIssue(
1025 12345, 1, 'Issue 1', 'New', 222, project_name='test-project')
1026 self.services.issue.TestAddIssue(issue1)
1027
1028 self.request['author'] = {'name': 'requester@example.com'} # 111
1029 self.request['content'] = 'a comment'
1030 self.request['updates'] = {
1031 'owner': 'user@example.com',
1032 }
1033
1034 resp = self.call_api('issues_comments_insert', self.request).json_body
1035
1036 self.assertEqual('a comment', resp['content'])
1037 comments = self.services.issue.GetCommentsForIssue('cnxn', issue1.issue_id)
1038 self.assertEqual(2, len(comments))
1039 self.assertEqual(111, comments[1].user_id)
1040 self.assertEqual('a comment', comments[1].content)
1041
1042 def testIssuesCommentsInsert_Import_Denied(self):
1043 """Cannot post a comment attributed to another user without permission."""
1044 self.services.project.TestAddProject(
1045 'test-project', committer_ids=[111, 222], project_id=12345)
1046 # Note: No ImportComment permission has been granted.
1047 issue1 = fake.MakeTestIssue(
1048 12345, 1, 'Issue 1', 'New', 222, project_name='test-project')
1049 self.services.issue.TestAddIssue(issue1)
1050
1051 self.request['author'] = {'name': 'user@example.com'} # 222
1052 self.request['content'] = 'a comment'
1053 self.request['updates'] = {
1054 'owner': 'user@example.com',
1055 }
1056
1057 with self.call_should_fail(403):
1058 self.call_api('issues_comments_insert', self.request)
1059
1060 def testIssuesCommentsDelete_NoComment(self):
1061 self.services.project.TestAddProject(
1062 'test-project', owner_ids=[222],
1063 project_id=12345)
1064 issue1 = fake.MakeTestIssue(
1065 project_id=12345, local_id=1, summary='test summary',
1066 issue_id=10001, status='New', owner_id=222, reporter_id=222)
1067 self.services.issue.TestAddIssue(issue1)
1068 self.request['commentId'] = 1
1069 with self.call_should_fail(404):
1070 self.call_api('issues_comments_delete', self.request)
1071
1072 def testIssuesCommentsDelete_NoDeletePermission(self):
1073 self.services.project.TestAddProject(
1074 'test-project', owner_ids=[222],
1075 project_id=12345)
1076 issue1 = fake.MakeTestIssue(
1077 project_id=12345, local_id=1, summary='test summary',
1078 issue_id=10001, status='New', owner_id=222, reporter_id=222)
1079 self.services.issue.TestAddIssue(issue1)
1080 self.request['commentId'] = 0
1081 with self.call_should_fail(403):
1082 self.call_api('issues_comments_delete', self.request)
1083
1084 def testIssuesCommentsDelete_DeleteUndelete(self):
1085 self.services.project.TestAddProject(
1086 'test-project', owner_ids=[222],
1087 project_id=12345)
1088 issue1 = fake.MakeTestIssue(
1089 project_id=12345, local_id=1, summary='test summary',
1090 issue_id=10001, status='New', owner_id=222, reporter_id=111)
1091 self.services.issue.TestAddIssue(issue1)
1092 comment = tracker_pb2.IssueComment(
1093 id=123, issue_id=10001,
1094 project_id=12345, user_id=111,
1095 content='this is a comment',
1096 timestamp=1437700000)
1097 self.services.issue.TestAddComment(comment, 1)
1098 self.request['commentId'] = 1
1099
1100 comments = self.services.issue.GetCommentsForIssue(None, 10001)
1101
1102 self.call_api('issues_comments_delete', self.request)
1103 self.assertEqual(111, comments[1].deleted_by)
1104
1105 self.call_api('issues_comments_undelete', self.request)
1106 self.assertIsNone(comments[1].deleted_by)
1107
1108 def approvalRequest(self, approval, request_fields=None, comment=None,
1109 issue_labels=None):
1110 request = {'userId': 'user@example.com',
1111 'requester': 'requester@example.com',
1112 'projectId': 'test-project',
1113 'issueId': 1,
1114 'approvalName': 'Legal-Review',
1115 'sendEmail': False,
1116 }
1117 if request_fields:
1118 request.update(request_fields)
1119
1120 self.SetUpFieldDefs(
1121 1, 12345, 'Legal-Review', tracker_pb2.FieldTypes.APPROVAL_TYPE)
1122
1123 issue1 = fake.MakeTestIssue(
1124 12345, 1, 'Issue 1', 'New', 222, approval_values=[approval],
1125 labels=issue_labels)
1126 self.services.issue.TestAddIssue(issue1)
1127
1128 self.services.issue.DeltaUpdateIssueApproval = Mock(return_value=comment)
1129
1130 self.mock(api_svc_v1.MonorailApi, 'mar_factory',
1131 lambda x, y, z: FakeMonorailApiRequest(
1132 request, self.services))
1133 return request, issue1
1134
1135 def getFakeComments(self):
1136 return [
1137 tracker_pb2.IssueComment(
1138 id=123, issue_id=1234501, project_id=12345, user_id=111,
1139 content='1st comment', timestamp=1437700000, approval_id=1),
1140 tracker_pb2.IssueComment(
1141 id=223, issue_id=1234501, project_id=12345, user_id=111,
1142 content='2nd comment', timestamp=1437700000, approval_id=2),
1143 tracker_pb2.IssueComment(
1144 id=323, issue_id=1234501, project_id=12345, user_id=111,
1145 content='3rd comment', timestamp=1437700000, approval_id=1,
1146 is_description=True),
1147 tracker_pb2.IssueComment(
1148 id=423, issue_id=1234501, project_id=12345, user_id=111,
1149 content='4th comment', timestamp=1437700000)]
1150
1151 def testApprovalsCommentsList_NoViewPermission(self):
1152 self.services.project.TestAddProject(
1153 'test-project', owner_ids=[222],
1154 project_id=12345)
1155
1156 approval = tracker_pb2.ApprovalValue(approval_id=1)
1157 request, _issue = self.approvalRequest(
1158 approval, issue_labels=['Restrict-View-Google'])
1159
1160 with self.call_should_fail(403):
1161 self.call_api('approvals_comments_list', request)
1162
1163 def testApprovalsCommentsList_NoApprovalFound(self):
1164 self.services.project.TestAddProject(
1165 'test-project', owner_ids=[222],
1166 project_id=12345)
1167
1168 approval = tracker_pb2.ApprovalValue(approval_id=1)
1169 request, _issue = self.approvalRequest(approval)
1170 self.config.field_defs = [] # empty field_defs of approval fd
1171
1172 with self.call_should_fail(400):
1173 self.call_api('approvals_comments_list', request)
1174
1175 def testApprovalsCommentsList(self):
1176 """Get comments of requested issue approval."""
1177 self.services.project.TestAddProject(
1178 'test-project', owner_ids=[222], project_id=12345)
1179 self.services.issue.GetCommentsForIssue = Mock(
1180 return_value=self.getFakeComments())
1181
1182 approval = tracker_pb2.ApprovalValue(approval_id=1)
1183 request, _issue = self.approvalRequest(approval)
1184
1185 response = self.call_api('approvals_comments_list', request).json_body
1186 self.assertEqual(response['kind'], 'monorail#approvalCommentList')
1187 self.assertEqual(response['totalResults'], 2)
1188 self.assertEqual(len(response['items']), 2)
1189
1190 def testApprovalsCommentsList_MaxResults(self):
1191 """get comments of requested issue approval with maxResults."""
1192 self.services.project.TestAddProject(
1193 'test-project', owner_ids=[222], project_id=12345)
1194 self.services.issue.GetCommentsForIssue = Mock(
1195 return_value=self.getFakeComments())
1196
1197 approval = tracker_pb2.ApprovalValue(approval_id=1)
1198 request, _issue = self.approvalRequest(
1199 approval, request_fields={'maxResults': 1})
1200
1201 response = self.call_api('approvals_comments_list', request).json_body
1202 self.assertEqual(response['kind'], 'monorail#approvalCommentList')
1203 self.assertEqual(response['totalResults'], 2)
1204 self.assertEqual(len(response['items']), 1)
1205 self.assertEqual(response['items'][0]['content'], '1st comment')
1206
1207 @patch('testing.fake.IssueService.GetCommentsForIssue')
1208 def testApprovalsCommentsList_StartIndex(self, mockGetComments):
1209 """get comments of requested issue approval with maxResults."""
1210 self.services.project.TestAddProject(
1211 'test-project', owner_ids=[222], project_id=12345)
1212 mockGetComments.return_value = self.getFakeComments()
1213
1214 approval = tracker_pb2.ApprovalValue(approval_id=1)
1215 request, _issue = self.approvalRequest(
1216 approval, request_fields={'startIndex': 1})
1217
1218 response = self.call_api('approvals_comments_list', request).json_body
1219 self.assertEqual(response['kind'], 'monorail#approvalCommentList')
1220 self.assertEqual(response['totalResults'], 2)
1221 self.assertEqual(len(response['items']), 1)
1222 self.assertEqual(response['items'][0]['content'], '3rd comment')
1223
1224 def testApprovalsCommentsInsert_NoCommentPermission(self):
1225 """No permission to comment on an issue, including approvals."""
1226
1227 self.services.project.TestAddProject(
1228 'test-project', owner_ids=[222],
1229 access=project_pb2.ProjectAccess.MEMBERS_ONLY,
1230 project_id=12345)
1231
1232 approval = tracker_pb2.ApprovalValue(approval_id=1)
1233 request, _issue = self.approvalRequest(approval)
1234
1235 with self.call_should_fail(403):
1236 self.call_api('approvals_comments_insert', request)
1237
1238 def testApprovalsCommentsInsert_TooLongComment(self):
1239 """Too long of a comment when comments on approvals."""
1240 self.services.project.TestAddProject(
1241 'test-project', owner_ids=[222], project_id=12345)
1242
1243 approval = tracker_pb2.ApprovalValue(approval_id=1)
1244 request, _issue = self.approvalRequest(approval)
1245
1246 long_comment = ' ' + 'c' * tracker_constants.MAX_COMMENT_CHARS + ' '
1247 request['content'] = long_comment
1248 with self.call_should_fail(400):
1249 self.call_api('approvals_comments_insert', request)
1250
1251 def testApprovalsCommentsInsert_NoApprovalDefFound(self):
1252 """No approval with approvalName found."""
1253 self.services.project.TestAddProject(
1254 'test-project', owner_ids=[222],
1255 project_id=12345)
1256
1257 approval = tracker_pb2.ApprovalValue(approval_id=1)
1258 request, _issue = self.approvalRequest(approval)
1259 self.config.field_defs = []
1260
1261 with self.call_should_fail(400):
1262 self.call_api('approvals_comments_insert', request)
1263
1264 # Test wrong field_type is also caught.
1265 self.SetUpFieldDefs(
1266 1, 12345, 'Legal-Review', tracker_pb2.FieldTypes.STR_TYPE)
1267 with self.call_should_fail(400):
1268 self.call_api('approvals_comments_insert', request)
1269
1270 def testApprovalscommentsInsert_NoIssueFound(self):
1271 """No issue found in project."""
1272 request = {'userId': 'user@example.com',
1273 'requester': 'requester@example.com',
1274 'projectId': 'test-project',
1275 'issueId': 1,
1276 'approvalName': 'Legal-Review',
1277 }
1278 # No issue created.
1279 with self.call_should_fail(400):
1280 self.call_api('approvals_comments_insert', request)
1281
1282 def testApprovalsCommentsInsert_NoIssueApprovalFound(self):
1283 """No approval with the given name found in the issue."""
1284
1285 request = {'userId': 'user@example.com',
1286 'requester': 'requester@example.com',
1287 'projectId': 'test-project',
1288 'issueId': 1,
1289 'approvalName': 'Legal-Review',
1290 'sendEmail': False,
1291 }
1292
1293 self.SetUpFieldDefs(
1294 1, 12345, 'Legal-Review', tracker_pb2.FieldTypes.APPROVAL_TYPE)
1295
1296 # issue 1 does not contain the Legal-Review approval.
1297 issue1 = fake.MakeTestIssue(12345, 1, 'Issue 1', 'New', 222)
1298 self.services.issue.TestAddIssue(issue1)
1299
1300 with self.call_should_fail(400):
1301 self.call_api('approvals_comments_insert', request)
1302
1303 def testApprovalsCommentsInsert_FieldValueChanges_NotFound(self):
1304 """Approval's subfield value not found."""
1305 self.services.project.TestAddProject(
1306 'test-project', owner_ids=[222],
1307 project_id=12345)
1308 approval = tracker_pb2.ApprovalValue(approval_id=1)
1309
1310 request, _issue = self.approvalRequest(
1311 approval,
1312 request_fields={
1313 'approvalUpdates': {
1314 'fieldValues': [
1315 {'fieldName': 'DoesNotExist', 'fieldValue': 'cow'}]
1316 },
1317 })
1318 with self.call_should_fail(400):
1319 self.call_api('approvals_comments_insert', request)
1320
1321 # Test field belongs to another approval
1322 self.config.field_defs.append(
1323 tracker_bizobj.MakeFieldDef(
1324 2, 12345, 'DoesNotExist', tracker_pb2.FieldTypes.STR_TYPE,
1325 '', '', False, False, False, None, None, None, False,
1326 None, '', tracker_pb2.NotifyTriggers.NEVER, 'no_action',
1327 'parent approval is wrong', False, approval_id=4))
1328 with self.call_should_fail(400):
1329 self.call_api('approvals_comments_insert', request)
1330
1331 @patch('time.time')
1332 def testApprovalCommentsInsert_FieldValueChanges(self, mock_time):
1333 """Field value changes are properly processed."""
1334 test_time = 6789
1335 mock_time.return_value = test_time
1336 comment = tracker_pb2.IssueComment(
1337 id=123, issue_id=10001,
1338 project_id=12345, user_id=111,
1339 content='cows moo',
1340 timestamp=143770000)
1341 self.services.project.TestAddProject(
1342 'test-project', owner_ids=[222], project_id=12345)
1343 approval = tracker_pb2.ApprovalValue(
1344 approval_id=1, approver_ids=[444])
1345
1346 request, issue = self.approvalRequest(
1347 approval,
1348 request_fields={'approvalUpdates': {
1349 'fieldValues': [
1350 {'fieldName': 'CowLayerName', 'fieldValue': 'cow'},
1351 {'fieldName': 'CowType', 'fieldValue': 'skim'},
1352 {'fieldName': 'CowType', 'fieldValue': 'milk'},
1353 {'fieldName': 'CowType', 'fieldValue': 'chocolate',
1354 'operator': 'remove'}]
1355 }},
1356 comment=comment)
1357 self.config.field_defs.extend(
1358 [tracker_bizobj.MakeFieldDef(
1359 2, 12345, 'CowLayerName', tracker_pb2.FieldTypes.STR_TYPE,
1360 '', '', False, False, False, None, None, None, False,
1361 None, '', tracker_pb2.NotifyTriggers.NEVER, 'no_action',
1362 'sub field value of approval 1', False, approval_id=1),
1363 tracker_bizobj.MakeFieldDef(
1364 3, 12345, 'CowType', tracker_pb2.FieldTypes.ENUM_TYPE,
1365 '', '', False, False, True, None, None, None, False,
1366 None, '', tracker_pb2.NotifyTriggers.NEVER, 'no_action',
1367 'enum sub field value of approval 1', False, approval_id=1)])
1368
1369 response = self.call_api('approvals_comments_insert', request).json_body
1370 fvs_add = [tracker_bizobj.MakeFieldValue(
1371 2, None, 'cow', None, None, None, False)]
1372 labels_add = ['CowType-skim', 'CowType-milk']
1373 labels_remove = ['CowType-chocolate']
1374 approval_delta = tracker_bizobj.MakeApprovalDelta(
1375 None, 111, [], [], fvs_add, [], [],
1376 labels_add, labels_remove, set_on=test_time)
1377 self.services.issue.DeltaUpdateIssueApproval.assert_called_with(
1378 None, 111, self.config, issue, approval, approval_delta,
1379 comment_content=None, is_description=None)
1380 self.assertEqual(response['content'], comment.content)
1381
1382 @patch('time.time')
1383 def testApprovalsCommentsInsert_StatusChanges_Normal(self, mock_time):
1384 test_time = 6789
1385 mock_time.return_value = test_time
1386 comment = tracker_pb2.IssueComment(
1387 id=123, issue_id=10001,
1388 project_id=12345, user_id=111, # requester
1389 content='this is a comment',
1390 timestamp=1437700000,
1391 amendments=[tracker_bizobj.MakeApprovalStatusAmendment(
1392 tracker_pb2.ApprovalStatus.REVIEW_REQUESTED)])
1393 self.services.project.TestAddProject(
1394 'test-project', owner_ids=[222], project_id=12345)
1395 approval = tracker_pb2.ApprovalValue(
1396 approval_id=1, approver_ids=[444],
1397 status=tracker_pb2.ApprovalStatus.NOT_SET)
1398
1399 request, issue = self.approvalRequest(
1400 approval,
1401 request_fields={'approvalUpdates': {'status': 'reviewRequested'}},
1402 comment=comment)
1403 response = self.call_api('approvals_comments_insert', request).json_body
1404 approval_delta = tracker_bizobj.MakeApprovalDelta(
1405 tracker_pb2.ApprovalStatus.REVIEW_REQUESTED, 111, [], [], [], [], [],
1406 [], [], set_on=test_time)
1407 self.services.issue.DeltaUpdateIssueApproval.assert_called_with(
1408 None, 111, self.config, issue, approval, approval_delta,
1409 comment_content=None, is_description=None)
1410
1411 self.assertEqual(response['author']['name'], 'requester@example.com')
1412 self.assertEqual(response['content'], comment.content)
1413 self.assertTrue(response['canDelete'])
1414 self.assertEqual(response['approvalUpdates'],
1415 {'kind': 'monorail#approvalCommentUpdate',
1416 'status': 'reviewRequested'})
1417
1418 def testApprovalsCommentsInsert_StatusChanges_NoPerms(self):
1419 self.services.project.TestAddProject(
1420 'test-project', owner_ids=[222],
1421 project_id=12345)
1422 approval = tracker_pb2.ApprovalValue(
1423 approval_id=1, approver_ids=[444],
1424 status=tracker_pb2.ApprovalStatus.NOT_SET)
1425 request, _issue = self.approvalRequest(
1426 approval,
1427 request_fields={'approvalUpdates': {'status': 'approved'}})
1428 with self.call_should_fail(403):
1429 self.call_api('approvals_comments_insert', request)
1430
1431 @patch('time.time')
1432 def testApprovalsCommentsInsert_StatusChanges_ApproverPerms(self, mock_time):
1433 test_time = 6789
1434 mock_time.return_value = test_time
1435 comment = tracker_pb2.IssueComment(
1436 id=123, issue_id=1234501,
1437 project_id=12345, user_id=111,
1438 content='this is a comment',
1439 timestamp=1437700000,
1440 amendments=[tracker_bizobj.MakeApprovalStatusAmendment(
1441 tracker_pb2.ApprovalStatus.NOT_APPROVED)])
1442 self.services.project.TestAddProject(
1443 'test-project', owner_ids=[222],
1444 project_id=12345)
1445 approval = tracker_pb2.ApprovalValue(
1446 approval_id=1, approver_ids=[111], # requester
1447 status=tracker_pb2.ApprovalStatus.NOT_SET)
1448 request, issue = self.approvalRequest(
1449 approval,
1450 request_fields={'approvalUpdates': {'status': 'notApproved'}},
1451 comment=comment)
1452 response = self.call_api('approvals_comments_insert', request).json_body
1453
1454 approval_delta = tracker_bizobj.MakeApprovalDelta(
1455 tracker_pb2.ApprovalStatus.NOT_APPROVED, 111, [], [], [], [], [],
1456 [], [], set_on=test_time)
1457 self.services.issue.DeltaUpdateIssueApproval.assert_called_with(
1458 None, 111, self.config, issue, approval, approval_delta,
1459 comment_content=None, is_description=None)
1460 self.assertEqual(response['author']['name'], 'requester@example.com')
1461 self.assertEqual(response['content'], comment.content)
1462 self.assertTrue(response['canDelete'])
1463 self.assertEqual(response['approvalUpdates'],
1464 {'kind': 'monorail#approvalCommentUpdate',
1465 'status': 'notApproved'})
1466
1467 def testApprovalsCommentsInsert_ApproverChanges_NoPerms(self):
1468 self.services.project.TestAddProject(
1469 'test-project', owner_ids=[222],
1470 project_id=12345)
1471
1472 approval = tracker_pb2.ApprovalValue(
1473 approval_id=1, approver_ids=[444],
1474 status=tracker_pb2.ApprovalStatus.NOT_SET)
1475 request, _issue = self.approvalRequest(
1476 approval,
1477 request_fields={'approvalUpdates': {'approvers': 'someone@test.com'}})
1478 with self.call_should_fail(403):
1479 self.call_api('approvals_comments_insert', request)
1480
1481 @patch('time.time')
1482 def testApprovalsCommentsInsert_ApproverChanges_ApproverPerms(
1483 self, mock_time):
1484 test_time = 6789
1485 mock_time.return_value = test_time
1486 comment = tracker_pb2.IssueComment(
1487 id=123, issue_id=1234501,
1488 project_id=12345, user_id=111,
1489 content='this is a comment',
1490 timestamp=1437700000,
1491 amendments=[tracker_bizobj.MakeApprovalApproversAmendment(
1492 [222], [123])])
1493 self.services.project.TestAddProject(
1494 'test-project', owner_ids=[222],
1495 project_id=12345)
1496
1497 approval = tracker_pb2.ApprovalValue(
1498 approval_id=1, approver_ids=[111], # requester
1499 status=tracker_pb2.ApprovalStatus.NOT_SET)
1500 request, issue = self.approvalRequest(
1501 approval,
1502 request_fields={
1503 'approvalUpdates':
1504 {'approvers': ['user@example.com', '-group@example.com']}},
1505 comment=comment)
1506 response = self.call_api('approvals_comments_insert', request).json_body
1507
1508 approval_delta = tracker_bizobj.MakeApprovalDelta(
1509 None, 111, [222], [123], [], [], [], [], [], set_on=test_time)
1510 self.services.issue.DeltaUpdateIssueApproval.assert_called_with(
1511 None, 111, self.config, issue, approval, approval_delta,
1512 comment_content=None, is_description=None)
1513 self.assertEqual(response['author']['name'], 'requester@example.com')
1514 self.assertEqual(response['content'], comment.content)
1515 self.assertTrue(response['canDelete'])
1516 self.assertEqual(response['approvalUpdates'],
1517 {'kind': 'monorail#approvalCommentUpdate',
1518 'approvers': ['user@example.com', '-group@example.com']})
1519
1520 @patch('time.time')
1521 def testApprovalsCommentsInsert_IsSurvey(self, mock_time):
1522 test_time = 6789
1523 mock_time.return_value = test_time
1524 comment = tracker_pb2.IssueComment(
1525 id=123, issue_id=10001,
1526 project_id=12345, user_id=111,
1527 content='this is a comment',
1528 timestamp=1437700000)
1529 self.services.project.TestAddProject(
1530 'test-project', owner_ids=[222],
1531 project_id=12345)
1532
1533 approval = tracker_pb2.ApprovalValue(
1534 approval_id=1, approver_ids=[111], # requester
1535 status=tracker_pb2.ApprovalStatus.NOT_SET)
1536 request, issue = self.approvalRequest(
1537 approval,
1538 request_fields={'content': 'updated survey', 'is_description': True},
1539 comment=comment)
1540 response = self.call_api('approvals_comments_insert', request).json_body
1541
1542 approval_delta = tracker_bizobj.MakeApprovalDelta(
1543 None, 111, [], [], [], [], [], [], [], set_on=test_time)
1544 self.services.issue.DeltaUpdateIssueApproval.assert_called_with(
1545 None, 111, self.config, issue, approval, approval_delta,
1546 comment_content='updated survey', is_description=True)
1547 self.assertEqual(response['author']['name'], 'requester@example.com')
1548 self.assertTrue(response['canDelete'])
1549
1550 @patch('time.time')
1551 @patch('features.send_notifications.PrepareAndSendApprovalChangeNotification')
1552 def testApprovalsCommentsInsert_SendEmail(
1553 self, mockPrepareAndSend, mock_time,):
1554 test_time = 6789
1555 mock_time.return_value = test_time
1556 comment = tracker_pb2.IssueComment(
1557 id=123, issue_id=10001,
1558 project_id=12345, user_id=111,
1559 content='this is a comment',
1560 timestamp=1437700000)
1561 self.services.project.TestAddProject(
1562 'test-project', owner_ids=[222],
1563 project_id=12345)
1564
1565 approval = tracker_pb2.ApprovalValue(
1566 approval_id=1, approver_ids=[111], # requester
1567 status=tracker_pb2.ApprovalStatus.NOT_SET)
1568 request, issue = self.approvalRequest(
1569 approval,
1570 request_fields={'content': comment.content, 'sendEmail': True},
1571 comment=comment)
1572
1573 response = self.call_api('approvals_comments_insert', request).json_body
1574
1575 mockPrepareAndSend.assert_called_with(
1576 issue.issue_id, approval.approval_id, ANY, comment.id, send_email=True)
1577
1578 approval_delta = tracker_bizobj.MakeApprovalDelta(
1579 None, 111, [], [], [], [], [], [], [], set_on=test_time)
1580 self.services.issue.DeltaUpdateIssueApproval.assert_called_with(
1581 None, 111, self.config, issue, approval, approval_delta,
1582 comment_content=comment.content, is_description=None)
1583 self.assertEqual(response['author']['name'], 'requester@example.com')
1584 self.assertTrue(response['canDelete'])
1585
1586 def testGroupsSettingsList_AllSettings(self):
1587 resp = self.call_api('groups_settings_list', self.request).json_body
1588 all_settings = resp['groupSettings']
1589 self.assertEqual(1, len(all_settings))
1590 self.assertEqual('group@example.com', all_settings[0]['groupName'])
1591
1592 def testGroupsSettingsList_ImportedSettings(self):
1593 self.services.user.TestAddUser('imported@example.com', 234)
1594 self.services.usergroup.TestAddGroupSettings(
1595 234, 'imported@example.com', external_group_type='mdb')
1596 self.request['importedGroupsOnly'] = True
1597 resp = self.call_api('groups_settings_list', self.request).json_body
1598 all_settings = resp['groupSettings']
1599 self.assertEqual(1, len(all_settings))
1600 self.assertEqual('imported@example.com', all_settings[0]['groupName'])
1601
1602 def testGroupsCreate_NoPermission(self):
1603 self.request['groupName'] = 'group'
1604 with self.call_should_fail(403):
1605 self.call_api('groups_create', self.request)
1606
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001607 def SetUpGroupRequest(
1608 self,
1609 group_name,
1610 who_can_view_members='MEMBERS',
1611 ext_group_type='CHROME_INFRA_AUTH',
1612 perms=None,
1613 requester='requester@example.com'):
Copybara854996b2021-09-07 19:36:02 +00001614 request = {
1615 'groupName': group_name,
1616 'requester': requester,
1617 'who_can_view_members': who_can_view_members,
1618 'ext_group_type': ext_group_type}
1619 self.request.pop("userId", None)
1620 self.mock(api_svc_v1.MonorailApi, 'mar_factory',
1621 lambda x, y, z: FakeMonorailApiRequest(
1622 request, self.services, perms=perms))
1623 return request
1624
1625 def testGroupsCreate_Normal(self):
1626 request = self.SetUpGroupRequest('newgroup@example.com', 'MEMBERS',
1627 'MDB', permissions.ADMIN_PERMISSIONSET)
1628
1629 resp = self.call_api('groups_create', request).json_body
1630 self.assertIn('groupID', resp)
1631
1632 def testGroupsGet_NoPermission(self):
1633 request = self.SetUpGroupRequest('group@example.com')
1634 with self.call_should_fail(403):
1635 self.call_api('groups_get', request)
1636
1637 def testGroupsGet_Normal(self):
1638 request = self.SetUpGroupRequest('group@example.com',
1639 perms=permissions.ADMIN_PERMISSIONSET)
1640 self.services.usergroup.TestAddMembers(123, [111], 'member')
1641 self.services.usergroup.TestAddMembers(123, [222], 'owner')
1642 resp = self.call_api('groups_get', request).json_body
1643 self.assertEqual(123, resp['groupID'])
1644 self.assertEqual(['requester@example.com'], resp['groupMembers'])
1645 self.assertEqual(['user@example.com'], resp['groupOwners'])
1646 self.assertEqual('group@example.com', resp['groupSettings']['groupName'])
1647
1648 def testGroupsUpdate_NoPermission(self):
1649 request = self.SetUpGroupRequest('group@example.com')
1650 with self.call_should_fail(403):
1651 self.call_api('groups_update', request)
1652
1653 def testGroupsUpdate_Normal(self):
1654 request = self.SetUpGroupRequest('group@example.com')
1655 request = self.SetUpGroupRequest('group@example.com',
1656 perms=permissions.ADMIN_PERMISSIONSET)
1657 request['last_sync_time'] = 123456789
1658 request['groupOwners'] = ['requester@example.com']
1659 request['groupMembers'] = ['user@example.com']
1660 resp = self.call_api('groups_update', request).json_body
1661 self.assertFalse(resp.get('error'))
1662
1663 def testComponentsList(self):
1664 """Get components for a project."""
1665 self.services.project.TestAddProject(
1666 'test-project', owner_ids=[222],
1667 project_id=12345)
1668 self.SetUpComponents(12345, 1, 'API')
1669 resp = self.call_api('components_list', self.request).json_body
1670
1671 self.assertEqual(1, len(resp['components']))
1672 cd = resp['components'][0]
1673 self.assertEqual(1, cd['componentId'])
1674 self.assertEqual('API', cd['componentPath'])
1675 self.assertEqual(1, cd['componentId'])
1676 self.assertEqual('test-project', cd['projectName'])
1677
1678 def testComponentsCreate_NoPermission(self):
1679 self.services.project.TestAddProject(
1680 'test-project', owner_ids=[222],
1681 project_id=12345)
1682 self.SetUpComponents(12345, 1, 'API')
1683
1684 cd_dict = {
1685 'componentName': 'Test'}
1686 self.request.update(cd_dict)
1687
1688 with self.call_should_fail(403):
1689 self.call_api('components_create', self.request)
1690
1691 def testComponentsCreate_Invalid(self):
1692 self.services.project.TestAddProject(
1693 'test-project', owner_ids=[111],
1694 project_id=12345)
1695 self.SetUpComponents(12345, 1, 'API')
1696
1697 # Component with invalid name
1698 cd_dict = {
1699 'componentName': 'c>d>e'}
1700 self.request.update(cd_dict)
1701 with self.call_should_fail(400):
1702 self.call_api('components_create', self.request)
1703
1704 # Name already in use
1705 cd_dict = {
1706 'componentName': 'API'}
1707 self.request.update(cd_dict)
1708 with self.call_should_fail(400):
1709 self.call_api('components_create', self.request)
1710
1711 # Parent component does not exist
1712 cd_dict = {
1713 'componentName': 'test',
1714 'parentPath': 'NotExist'}
1715 self.request.update(cd_dict)
1716 with self.call_should_fail(404):
1717 self.call_api('components_create', self.request)
1718
1719
1720 def testComponentsCreate_Normal(self):
1721 self.services.project.TestAddProject(
1722 'test-project', owner_ids=[111],
1723 project_id=12345)
1724 self.SetUpComponents(12345, 1, 'API')
1725
1726 cd_dict = {
1727 'componentName': 'Test',
1728 'description': 'test comp',
1729 'cc': ['requester@example.com', '']
1730 }
1731 self.request.update(cd_dict)
1732
1733 resp = self.call_api('components_create', self.request).json_body
1734 self.assertEqual('test comp', resp['description'])
1735 self.assertEqual('requester@example.com', resp['creator'])
1736 self.assertEqual([u'requester@example.com'], resp['cc'])
1737 self.assertEqual('Test', resp['componentPath'])
1738
1739 cd_dict = {
1740 'componentName': 'TestChild',
1741 'parentPath': 'API'}
1742 self.request.update(cd_dict)
1743 resp = self.call_api('components_create', self.request).json_body
1744
1745 self.assertEqual('API>TestChild', resp['componentPath'])
1746
1747 def testComponentsDelete_Invalid(self):
1748 self.services.project.TestAddProject(
1749 'test-project', owner_ids=[222],
1750 project_id=12345)
1751 self.SetUpComponents(12345, 1, 'API')
1752
1753 # Fail to delete a non-existent component
1754 cd_dict = {
1755 'componentPath': 'NotExist'}
1756 self.request.update(cd_dict)
1757 with self.call_should_fail(404):
1758 self.call_api('components_delete', self.request)
1759
1760 # The user has no permission to delete component
1761 cd_dict = {
1762 'componentPath': 'API'}
1763 self.request.update(cd_dict)
1764 with self.call_should_fail(403):
1765 self.call_api('components_delete', self.request)
1766
1767 # The user tries to delete component that had subcomponents
1768 self.services.project.TestAddProject(
1769 'test-project2', owner_ids=[111],
1770 project_id=123456)
1771 self.SetUpComponents(123456, 1, 'Parent')
1772 self.SetUpComponents(123456, 2, 'Parent>Child')
1773 cd_dict = {
1774 'componentPath': 'Parent',
1775 'projectId': 'test-project2',}
1776 self.request.update(cd_dict)
1777 with self.call_should_fail(403):
1778 self.call_api('components_delete', self.request)
1779
1780 def testComponentsDelete_Normal(self):
1781 self.services.project.TestAddProject(
1782 'test-project', owner_ids=[111],
1783 project_id=12345)
1784 self.SetUpComponents(12345, 1, 'API')
1785
1786 cd_dict = {
1787 'componentPath': 'API'}
1788 self.request.update(cd_dict)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001789 with self.assertWarns(webtest.lint.WSGIWarning):
1790 _ = self.call_api('components_delete', self.request)
Copybara854996b2021-09-07 19:36:02 +00001791 self.assertEqual(0, len(self.config.component_defs))
1792
1793 def testComponentsUpdate_Invalid(self):
1794 self.services.project.TestAddProject(
1795 'test-project', owner_ids=[222],
1796 project_id=12345)
1797 self.SetUpComponents(12345, 1, 'API')
1798 self.SetUpComponents(12345, 2, 'Test', admin_ids=[111])
1799
1800 # Fail to update a non-existent component
1801 cd_dict = {
1802 'componentPath': 'NotExist'}
1803 self.request.update(cd_dict)
1804 with self.call_should_fail(404):
1805 self.call_api('components_update', self.request)
1806
1807 # The user has no permission to edit component
1808 cd_dict = {
1809 'componentPath': 'API'}
1810 self.request.update(cd_dict)
1811 with self.call_should_fail(403):
1812 self.call_api('components_update', self.request)
1813
1814 # The user tries an invalid component name
1815 cd_dict = {
1816 'componentPath': 'Test',
1817 'updates': [{'field': 'LEAF_NAME', 'leafName': 'c>e'}]}
1818 self.request.update(cd_dict)
1819 with self.call_should_fail(400):
1820 self.call_api('components_update', self.request)
1821
1822 # The user tries a name already in use
1823 cd_dict = {
1824 'componentPath': 'Test',
1825 'updates': [{'field': 'LEAF_NAME', 'leafName': 'API'}]}
1826 self.request.update(cd_dict)
1827 with self.call_should_fail(400):
1828 self.call_api('components_update', self.request)
1829
1830 def testComponentsUpdate_Normal(self):
1831 self.services.project.TestAddProject(
1832 'test-project', owner_ids=[111],
1833 project_id=12345)
1834 self.SetUpComponents(12345, 1, 'API')
1835 self.SetUpComponents(12345, 2, 'Parent')
1836 self.SetUpComponents(12345, 3, 'Parent>Child')
1837
1838 cd_dict = {
1839 'componentPath': 'API',
1840 'updates': [
1841 {'field': 'DESCRIPTION', 'description': ''},
1842 {'field': 'CC', 'cc': [
1843 'requester@example.com', 'user@example.com', '', ' ']},
1844 {'field': 'DEPRECATED', 'deprecated': True}]}
1845 self.request.update(cd_dict)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001846 with self.assertWarns(webtest.lint.WSGIWarning):
1847 _ = self.call_api('components_update', self.request)
Copybara854996b2021-09-07 19:36:02 +00001848 component_def = tracker_bizobj.FindComponentDef(
1849 'API', self.config)
1850 self.assertIsNotNone(component_def)
1851 self.assertEqual('', component_def.docstring)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001852 six.assertCountEqual(self, [111, 222], component_def.cc_ids)
Copybara854996b2021-09-07 19:36:02 +00001853 self.assertTrue(component_def.deprecated)
1854
1855 cd_dict = {
1856 'componentPath': 'Parent',
1857 'updates': [
1858 {'field': 'LEAF_NAME', 'leafName': 'NewParent'}]}
1859 self.request.update(cd_dict)
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001860 with self.assertWarns(webtest.lint.WSGIWarning):
1861 _ = self.call_api('components_update', self.request)
Copybara854996b2021-09-07 19:36:02 +00001862 cd_parent = tracker_bizobj.FindComponentDef(
1863 'NewParent', self.config)
1864 cd_child = tracker_bizobj.FindComponentDef(
1865 'NewParent>Child', self.config)
1866 self.assertIsNotNone(cd_parent)
1867 self.assertIsNotNone(cd_child)
1868
1869
1870class RequestMock(object):
1871
1872 def __init__(self):
1873 self.projectId = None
1874 self.issueId = None
1875
1876
1877class RequesterMock(object):
1878
1879 def __init__(self, email=None):
1880 self._email = email
1881
1882 def email(self):
1883 return self._email
1884
1885
1886class AllBaseChecksTest(unittest.TestCase):
1887
1888 def setUp(self):
1889 self.services = MakeFakeServiceManager()
1890 self.services.user.TestAddUser('test@example.com', 111)
1891 self.user_2 = self.services.user.TestAddUser('test@google.com', 222)
1892 self.services.project.TestAddProject(
1893 'test-project', owner_ids=[111], project_id=123,
1894 access=project_pb2.ProjectAccess.MEMBERS_ONLY)
1895 self.auth_client_ids = ['123456789.apps.googleusercontent.com']
1896 oauth.get_client_id = Mock(return_value=self.auth_client_ids[0])
1897 oauth.get_current_user = Mock(
1898 return_value=RequesterMock(email='test@example.com'))
1899 oauth.get_authorized_scopes = Mock()
1900
1901 def testUnauthorizedRequester(self):
1902 with self.assertRaises(endpoints.UnauthorizedException):
1903 api_svc_v1.api_base_checks(None, None, None, None, [], [])
1904
1905 def testNoUser(self):
1906 requester = RequesterMock(email='notexist@example.com')
1907 with self.assertRaises(exceptions.NoSuchUserException):
1908 api_svc_v1.api_base_checks(
1909 None, requester, self.services, None, self.auth_client_ids, [])
1910
1911 def testAllowedDomain_MonorailScope(self):
1912 oauth.get_authorized_scopes.return_value = [
1913 framework_constants.MONORAIL_SCOPE]
1914 oauth.get_current_user.return_value = RequesterMock(
1915 email=self.user_2.email)
1916 allowlisted_client_ids = []
1917 allowlisted_emails = []
1918 client_id, email = api_svc_v1.api_base_checks(
1919 None, None, self.services, None, allowlisted_client_ids,
1920 allowlisted_emails)
1921 self.assertEqual(client_id, self.auth_client_ids[0])
1922 self.assertEqual(email, self.user_2.email)
1923
1924 def testAllowedDomain_NoMonorailScope(self):
1925 oauth.get_authorized_scopes.return_value = []
1926 oauth.get_current_user.return_value = RequesterMock(
1927 email=self.user_2.email)
1928 allowlisted_client_ids = []
1929 allowlisted_emails = []
1930 with self.assertRaises(endpoints.UnauthorizedException):
1931 api_svc_v1.api_base_checks(
1932 None, None, self.services, None, allowlisted_client_ids,
1933 allowlisted_emails)
1934
1935 def testAllowedDomain_BadEmail(self):
1936 oauth.get_authorized_scopes.return_value = [
1937 framework_constants.MONORAIL_SCOPE]
1938 oauth.get_current_user.return_value = RequesterMock(
1939 email='chicken@chicken.test')
1940 allowlisted_client_ids = []
1941 allowlisted_emails = []
1942 self.services.user.TestAddUser('chicken@chicken.test', 333)
1943 with self.assertRaises(endpoints.UnauthorizedException):
1944 api_svc_v1.api_base_checks(
1945 None, None, self.services, None, allowlisted_client_ids,
1946 allowlisted_emails)
1947
1948 def testNoOauthUser(self):
1949 oauth.get_current_user.side_effect = oauth.Error()
1950 with self.assertRaises(endpoints.UnauthorizedException):
1951 api_svc_v1.api_base_checks(
1952 None, None, self.services, None, [], [])
1953
1954 def testBannedUser(self):
1955 banned_email = 'banned@example.com'
1956 self.services.user.TestAddUser(banned_email, 222, banned=True)
1957 requester = RequesterMock(email=banned_email)
1958 with self.assertRaises(permissions.BannedUserException):
1959 api_svc_v1.api_base_checks(
1960 None, requester, self.services, None, self.auth_client_ids, [])
1961
1962 def testNoProject(self):
1963 request = RequestMock()
1964 request.projectId = 'notexist-project'
1965 requester = RequesterMock(email='test@example.com')
1966 with self.assertRaises(exceptions.NoSuchProjectException):
1967 api_svc_v1.api_base_checks(
1968 request, requester, self.services, None, self.auth_client_ids, [])
1969
1970 def testNonLiveProject(self):
1971 archived_project = 'archived-project'
1972 self.services.project.TestAddProject(
1973 archived_project, owner_ids=[111],
1974 state=project_pb2.ProjectState.ARCHIVED)
1975 request = RequestMock()
1976 request.projectId = archived_project
1977 requester = RequesterMock(email='test@example.com')
1978 with self.assertRaises(permissions.PermissionException):
1979 api_svc_v1.api_base_checks(
1980 request, requester, self.services, None, self.auth_client_ids, [])
1981
Adrià Vilanova Martínezf19ea432024-01-23 20:20:52 +01001982 def testNonLiveMigratedProject(self):
1983 archived_project = 'archived-migrated-project'
1984 redirect_utils.PROJECT_REDIRECT_MAP = {
1985 'archived-migrated-project': 'https://example.dev'
1986 }
1987 self.services.project.TestAddProject(
1988 archived_project,
1989 owner_ids=[111],
1990 state=project_pb2.ProjectState.ARCHIVED)
1991 request = RequestMock()
1992 request.projectId = archived_project
1993 requester = RequesterMock(email='test@example.com')
1994 api_svc_v1.api_base_checks(
1995 request, requester, self.services, None, self.auth_client_ids, [])
1996
Copybara854996b2021-09-07 19:36:02 +00001997 def testNoViewProjectPermission(self):
1998 nonmember_email = 'nonmember@example.com'
1999 self.services.user.TestAddUser(nonmember_email, 222)
2000 requester = RequesterMock(email=nonmember_email)
2001 request = RequestMock()
2002 request.projectId = 'test-project'
2003 with self.assertRaises(permissions.PermissionException):
2004 api_svc_v1.api_base_checks(
2005 request, requester, self.services, None, self.auth_client_ids, [])
2006
2007 def testAllPass(self):
2008 requester = RequesterMock(email='test@example.com')
2009 request = RequestMock()
2010 request.projectId = 'test-project'
2011 api_svc_v1.api_base_checks(
2012 request, requester, self.services, None, self.auth_client_ids, [])
2013
2014 def testNoIssue(self):
2015 requester = RequesterMock(email='test@example.com')
2016 request = RequestMock()
2017 request.projectId = 'test-project'
2018 request.issueId = 12345
2019 with self.assertRaises(exceptions.NoSuchIssueException):
2020 api_svc_v1.api_base_checks(
2021 request, requester, self.services, None, self.auth_client_ids, [])
2022
2023 def testNoViewIssuePermission(self):
2024 requester = RequesterMock(email='test@example.com')
2025 request = RequestMock()
2026 request.projectId = 'test-project'
2027 request.issueId = 1
2028 issue1 = fake.MakeTestIssue(
2029 project_id=123, local_id=1, summary='test summary',
2030 status='New', owner_id=111, reporter_id=111)
2031 issue1.deleted = True
2032 self.services.issue.TestAddIssue(issue1)
2033 with self.assertRaises(permissions.PermissionException):
2034 api_svc_v1.api_base_checks(
2035 request, requester, self.services, None, self.auth_client_ids, [])
2036
2037 def testAnonymousClients(self):
2038 # Some clients specifically pass "anonymous" as the client ID.
2039 oauth.get_client_id = Mock(return_value='anonymous')
2040 requester = RequesterMock(email='test@example.com')
2041 request = RequestMock()
2042 request.projectId = 'test-project'
2043 api_svc_v1.api_base_checks(
2044 request, requester, self.services, None, [], ['test@example.com'])
2045
2046 # Any client_id is OK if the email is allowlisted.
2047 oauth.get_client_id = Mock(return_value='anything')
2048 api_svc_v1.api_base_checks(
2049 request, requester, self.services, None, [], ['test@example.com'])
2050
2051 # Reject request when neither client ID nor email is allowlisted.
2052 with self.assertRaises(endpoints.UnauthorizedException):
2053 api_svc_v1.api_base_checks(
2054 request, requester, self.services, None, [], [])