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