blob: b3084c31902934e3dca9c364cf853ffe0dbdc8f9 [file] [log] [blame]
Copybara854996b2021-09-07 19:36:02 +00001# Copyright 2018 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 projects servicer."""
7from __future__ import print_function
8from __future__ import division
9from __future__ import absolute_import
10
11import unittest
12from mock import patch
13
14from components.prpc import codes
15from components.prpc import context
16from components.prpc import server
17
18from api import projects_servicer
19from api.api_proto import common_pb2
20from api.api_proto import issue_objects_pb2
21from api.api_proto import project_objects_pb2
22from api.api_proto import projects_pb2
23from framework import authdata
24from framework import exceptions
25from framework import framework_constants
26from framework import monorailcontext
27from framework import permissions
28from proto import tracker_pb2
29from proto import project_pb2
30from tracker import tracker_bizobj
31from tracker import tracker_constants
32from testing import fake
33from testing import testing_helpers
34from services import service_manager
35
36
37class ProjectsServicerTest(unittest.TestCase):
38
39 def setUp(self):
40 self.cnxn = fake.MonorailConnection()
41 self.services = service_manager.Services(
42 config=fake.ConfigService(),
43 issue=fake.IssueService(),
44 user=fake.UserService(),
45 usergroup=fake.UserGroupService(),
46 project=fake.ProjectService(),
47 project_star=fake.ProjectStarService(),
48 features=fake.FeaturesService())
49
50 self.admin = self.services.user.TestAddUser('admin@example.com', 123)
51 self.admin.is_site_admin = True
52 self.owner = self.services.user.TestAddUser('owner@example.com', 111)
53 self.services.user.TestAddUser('user_222@example.com', 222)
54 self.services.user.TestAddUser('user_333@example.com', 333)
55 self.services.user.TestAddUser('user_444@example.com', 444)
56 self.services.user.TestAddUser('user_666@example.com', 666)
57
58 # User group 888 has members: user_555 and proj@monorail.com
59 self.services.user.TestAddUser('group888@googlegroups.com', 888)
60 self.services.usergroup.TestAddGroupSettings(
61 888, 'group888@googlegroups.com')
62 self.services.usergroup.TestAddMembers(888, [555, 1001])
63
64 # User group 999 has members: user_111 and user_444
65 self.services.user.TestAddUser('group999@googlegroups.com', 999)
66 self.services.usergroup.TestAddGroupSettings(
67 999, 'group999@googlegroups.com')
68 self.services.usergroup.TestAddMembers(999, [111, 444])
69
70 # User group 777 has members: user_666 and group 999.
71 self.services.user.TestAddUser('group777@googlegroups.com', 777)
72 self.services.usergroup.TestAddGroupSettings(
73 777, 'group777@googlegroups.com')
74 self.services.usergroup.TestAddMembers(777, [666, 999])
75
76 self.project = self.services.project.TestAddProject(
77 'proj',
78 project_id=789,
79 owner_ids=[111],
80 committer_ids=[222],
81 contrib_ids=[333])
82 self.projects_svcr = projects_servicer.ProjectsServicer(
83 self.services, make_rate_limiter=False)
84 self.prpc_context = context.ServicerContext()
85 self.prpc_context.set_code(codes.StatusCode.OK)
86
87 def CallWrapped(self, wrapped_handler, *args, **kwargs):
88 return wrapped_handler.wrapped(self.projects_svcr, *args, **kwargs)
89
90 def testListProjects_Normal(self):
91 """We can get a list of all projects on the site."""
92 request = projects_pb2.ListProjectsRequest()
93 mc = monorailcontext.MonorailContext(
94 self.services, cnxn=self.cnxn, requester='owner@example.com')
95 response = self.CallWrapped(self.projects_svcr.ListProjects, mc, request)
96 self.assertEqual(2, len(response.projects))
97
98 def testGetConfig_Normal(self):
99 """We can get a project config."""
100 request = projects_pb2.GetConfigRequest(project_name='proj')
101 mc = monorailcontext.MonorailContext(
102 self.services, cnxn=self.cnxn, requester='owner@example.com')
103 response = self.CallWrapped(self.projects_svcr.GetConfig, mc, request)
104 self.assertEqual('proj', response.project_name)
105
106 def testGetConfig_NoSuchProject(self):
107 """We reject a request to get a config for a non-existent project."""
108 request = projects_pb2.GetConfigRequest(project_name='unknown-proj')
109 mc = monorailcontext.MonorailContext(
110 self.services, cnxn=self.cnxn, requester='owner@example.com')
111 with self.assertRaises(exceptions.NoSuchProjectException):
112 self.CallWrapped(self.projects_svcr.GetConfig, mc, request)
113
114 def testGetConfig_PermissionDenied(self):
115 """We reject a request to get a config for a non-viewable project."""
116 self.project.access = project_pb2.ProjectAccess.MEMBERS_ONLY
117 request = projects_pb2.GetConfigRequest(project_name='proj')
118
119 # User is a member of the members-only project.
120 mc = monorailcontext.MonorailContext(
121 self.services, cnxn=self.cnxn, requester='owner@example.com')
122 response = self.CallWrapped(self.projects_svcr.GetConfig, mc, request)
123 self.assertEqual('proj', response.project_name)
124
125 # User is not a member of the members-only project.
126 mc = monorailcontext.MonorailContext(
127 self.services, cnxn=self.cnxn, requester='nonmember@example.com')
128 with self.assertRaises(permissions.PermissionException):
129 self.CallWrapped(self.projects_svcr.GetConfig, mc, request)
130
131 @patch('businesslogic.work_env.WorkEnv.ListProjectTemplates')
132 def testListProjectTemplates_Normal(self, mockListProjectTemplates):
133 fd_1 = tracker_pb2.FieldDef(
134 field_name='FirstField', field_id=1,
135 field_type=tracker_pb2.FieldTypes.STR_TYPE)
136 fd_2 = tracker_pb2.FieldDef(
137 field_name='LegalApproval', field_id=2,
138 field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE)
139 component = tracker_pb2.ComponentDef(component_id=1, path='dude')
140 status_def = tracker_pb2.StatusDef(status='New', means_open=True)
141 config = tracker_pb2.ProjectIssueConfig(
142 project_id=789, field_defs=[fd_1, fd_2], component_defs=[component],
143 well_known_statuses=[status_def])
144 self.services.config.StoreConfig(self.cnxn, config)
145 admin1 = self.services.user.TestAddUser('admin@example.com', 222)
146 appr1 = self.services.user.TestAddUser('approver@example.com', 333)
147 setter = self.services.user.TestAddUser('setter@example.com', 444)
148 template = tracker_pb2.TemplateDef(
149 name='Chicken', content='description', summary='summary',
150 status='New', admin_ids=[admin1.user_id],
151 field_values=[tracker_bizobj.MakeFieldValue(
152 fd_1.field_id, None, 'Cow', None, None, None, False)],
153 component_ids=[component.component_id],
154 approval_values=[tracker_pb2.ApprovalValue(
155 approval_id=2, approver_ids=[appr1.user_id],
156 setter_id=setter.user_id)])
157 mockListProjectTemplates.return_value = [template]
158
159 mc = monorailcontext.MonorailContext(
160 self.services, cnxn=self.cnxn, requester='owner@example.com')
161 request = projects_pb2.ListProjectTemplatesRequest(project_name='proj')
162 response = self.CallWrapped(
163 self.projects_svcr.ListProjectTemplates, mc, request)
164 self.assertEqual(
165 response,
166 projects_pb2.ListProjectTemplatesResponse(
167 templates=[project_objects_pb2.TemplateDef(
168 template_name='Chicken',
169 content='description',
170 summary='summary',
171 status_ref=common_pb2.StatusRef(
172 status='New',
173 is_derived=False,
174 means_open=True),
175 owner_defaults_to_member=True,
176 admin_refs=[
177 common_pb2.UserRef(
178 user_id=admin1.user_id,
179 display_name=testing_helpers.ObscuredEmail(admin1.email),
180 is_derived=False)],
181 field_values=[
182 issue_objects_pb2.FieldValue(
183 field_ref=common_pb2.FieldRef(
184 field_id=fd_1.field_id,
185 field_name=fd_1.field_name,
186 type=common_pb2.STR_TYPE),
187 value='Cow')],
188 component_refs=[
189 common_pb2.ComponentRef(
190 path=component.path, is_derived=False)],
191 approval_values=[
192 issue_objects_pb2.Approval(
193 field_ref=common_pb2.FieldRef(
194 field_id=fd_2.field_id,
195 field_name=fd_2.field_name,
196 type=common_pb2.APPROVAL_TYPE),
197 setter_ref=common_pb2.UserRef(
198 user_id=setter.user_id,
199 display_name=testing_helpers.ObscuredEmail(
200 setter.email)),
201 phase_ref=issue_objects_pb2.PhaseRef(),
202 approver_refs=[common_pb2.UserRef(
203 user_id=appr1.user_id,
204 display_name=testing_helpers.ObscuredEmail(appr1.email),
205 is_derived=False)])],
206 )]))
207
208 def testListProjectTemplates_NoProjectName(self):
209 mc = monorailcontext.MonorailContext(
210 self.services, cnxn=self.cnxn, requester='owner@example.com')
211 request = projects_pb2.ListProjectTemplatesRequest()
212 with self.assertRaises(exceptions.InputException):
213 self.CallWrapped(self.projects_svcr.ListProjectTemplates, mc, request)
214
215 def testListProjectTemplates_NoSuchProject(self):
216 mc = monorailcontext.MonorailContext(
217 self.services, cnxn=self.cnxn, requester='owner@example.com')
218 request = projects_pb2.ListProjectTemplatesRequest(project_name='ghost')
219 with self.assertRaises(exceptions.NoSuchProjectException):
220 self.CallWrapped(self.projects_svcr.ListProjectTemplates, mc, request)
221
222 def testListProjectTemplates_PermissionDenied(self):
223 self.project.access = project_pb2.ProjectAccess.MEMBERS_ONLY
224 mc = monorailcontext.MonorailContext(
225 self.services, cnxn=self.cnxn, requester='nonmember@example.com')
226 request = projects_pb2.GetConfigRequest(project_name='proj')
227 with self.assertRaises(permissions.PermissionException):
228 self.CallWrapped(self.projects_svcr.ListProjectTemplates, mc, request)
229
230 def testGetPresentationConfig_Normal(self):
231 """Test getting project summary, thumbnail url, custom issue entry, etc."""
232 config = tracker_pb2.ProjectIssueConfig(project_id=789)
233 self.project.summary = 'project summary'
234 config.custom_issue_entry_url = 'issue entry url'
235 config.member_default_query = 'default query'
236 config.default_col_spec = 'ID Summary'
237 config.default_sort_spec = 'Priority Status'
238 config.default_x_attr = 'Priority'
239 config.default_y_attr = 'Status'
240 self.project.revision_url_format = 'revision url format'
241 self.services.config.StoreConfig(self.cnxn, config)
242
243 mc = monorailcontext.MonorailContext(
244 self.services, cnxn=self.cnxn, requester='owner@example.com')
245
246 request = projects_pb2.GetPresentationConfigRequest(project_name='proj')
247 response = self.CallWrapped(
248 self.projects_svcr.GetPresentationConfig, mc, request)
249
250 self.assertEqual('project summary', response.project_summary)
251 self.assertEqual('issue entry url', response.custom_issue_entry_url)
252 self.assertEqual('default query', response.default_query)
253 self.assertEqual('ID Summary', response.default_col_spec)
254 self.assertEqual('Priority Status', response.default_sort_spec)
255 self.assertEqual('Priority', response.default_x_attr)
256 self.assertEqual('Status', response.default_y_attr)
257 self.assertEqual('revision url format', response.revision_url_format)
258
259 def testGetPresentationConfig_SavedQueriesAllowed(self):
260 """Only project members or higher can see project saved queries."""
261 self.services.features.UpdateCannedQueries(self.cnxn, 789, [
262 tracker_pb2.SavedQuery(query_id=101, name='test', query='owner:me'),
263 tracker_pb2.SavedQuery(query_id=202, name='hello', query='world')
264 ])
265
266 # User 333 is a contributor.
267 mc = monorailcontext.MonorailContext(
268 self.services, cnxn=self.cnxn, requester='user_333@example.com')
269
270 request = projects_pb2.GetPresentationConfigRequest(project_name='proj')
271 response = self.CallWrapped(self.projects_svcr.GetPresentationConfig, mc,
272 request)
273
274 self.assertEqual(2, len(response.saved_queries))
275
276 self.assertEqual(101, response.saved_queries[0].query_id)
277 self.assertEqual('test', response.saved_queries[0].name)
278 self.assertEqual('owner:me', response.saved_queries[0].query)
279
280 self.assertEqual(202, response.saved_queries[1].query_id)
281 self.assertEqual('hello', response.saved_queries[1].name)
282 self.assertEqual('world', response.saved_queries[1].query)
283
284 def testGetPresentationConfig_SavedQueriesDenied(self):
285 """Only project members or higher can see project saved queries."""
286 self.services.features.UpdateCannedQueries(self.cnxn, 789, [
287 tracker_pb2.SavedQuery(query_id=101, name='test', query='owner:me'),
288 tracker_pb2.SavedQuery(query_id=202, name='hello', query='world')
289 ])
290
291 mc = monorailcontext.MonorailContext(
292 self.services, cnxn=self.cnxn, requester='nonmember@example.com')
293
294 request = projects_pb2.GetPresentationConfigRequest(project_name='proj')
295 response = self.CallWrapped(self.projects_svcr.GetPresentationConfig, mc,
296 request)
297
298 self.assertEqual(0, len(response.saved_queries))
299
300 def testGetCustomPermissions_Normal(self):
301 self.project.extra_perms = [
302 project_pb2.Project.ExtraPerms(
303 member_id=111,
304 perms=['FooPerm', 'BarPerm'])]
305
306 request = projects_pb2.GetConfigRequest(project_name='proj')
307 mc = monorailcontext.MonorailContext(
308 self.services, cnxn=self.cnxn, requester='foo@example.org')
309 response = self.CallWrapped(
310 self.projects_svcr.GetCustomPermissions, mc, request)
311 self.assertEqual(['BarPerm', 'FooPerm'], response.permissions)
312
313 def testGetCustomPermissions_PermissionsAreDedupped(self):
314 self.project.extra_perms = [
315 project_pb2.Project.ExtraPerms(
316 member_id=111,
317 perms=['FooPerm', 'FooPerm']),
318 project_pb2.Project.ExtraPerms(
319 member_id=222,
320 perms=['FooPerm'])]
321
322 request = projects_pb2.GetConfigRequest(project_name='proj')
323 mc = monorailcontext.MonorailContext(
324 self.services, cnxn=self.cnxn, requester='foo@example.org')
325 response = self.CallWrapped(
326 self.projects_svcr.GetCustomPermissions, mc, request)
327 self.assertEqual(['FooPerm'], response.permissions)
328
329 def testGetCustomPermissions_PermissionsAreSorted(self):
330 self.project.extra_perms = [
331 project_pb2.Project.ExtraPerms(
332 member_id=111,
333 perms=['FooPerm', 'BarPerm']),
334 project_pb2.Project.ExtraPerms(
335 member_id=222,
336 perms=['BazPerm'])]
337
338 request = projects_pb2.GetConfigRequest(project_name='proj')
339 mc = monorailcontext.MonorailContext(
340 self.services, cnxn=self.cnxn, requester='foo@example.org')
341 response = self.CallWrapped(
342 self.projects_svcr.GetCustomPermissions, mc, request)
343 self.assertEqual(['BarPerm', 'BazPerm', 'FooPerm'], response.permissions)
344
345 def testGetCustomPermissions_IgnoreStandardPermissions(self):
346 self.project.extra_perms = [
347 project_pb2.Project.ExtraPerms(
348 member_id=111,
349 perms=permissions.STANDARD_PERMISSIONS + ['FooPerm'])]
350
351 request = projects_pb2.GetConfigRequest(project_name='proj')
352 mc = monorailcontext.MonorailContext(
353 self.services, cnxn=self.cnxn, requester='foo@example.org')
354 response = self.CallWrapped(
355 self.projects_svcr.GetCustomPermissions, mc, request)
356 self.assertEqual(['FooPerm'], response.permissions)
357
358 def testGetCustomPermissions_NoCustomPermissions(self):
359 self.project.extra_perms = []
360 request = projects_pb2.GetConfigRequest(project_name='proj')
361 mc = monorailcontext.MonorailContext(
362 self.services, cnxn=self.cnxn, requester='foo@example.org')
363 response = self.CallWrapped(
364 self.projects_svcr.GetCustomPermissions, mc, request)
365 self.assertEqual([], response.permissions)
366
367 def assertVisibleMembers(self, expected_user_ids, expected_group_ids,
368 requester=None):
369 request = projects_pb2.GetVisibleMembersRequest(project_name='proj')
370 mc = monorailcontext.MonorailContext(
371 self.services, cnxn=self.cnxn, requester=requester)
372 mc.LookupLoggedInUserPerms(self.project)
373 response = self.CallWrapped(
374 self.projects_svcr.GetVisibleMembers, mc, request)
375 self.assertEqual(
376 expected_user_ids,
377 [user_ref.user_id for user_ref in response.user_refs])
378 # Assert that we get the full email address.
379 self.assertEqual(
380 [self.services.user.LookupUserEmail(self.cnxn, user_id)
381 for user_id in expected_user_ids],
382 [user_ref.display_name for user_ref in response.user_refs])
383 self.assertEqual(
384 expected_group_ids,
385 [group_ref.user_id for group_ref in response.group_refs])
386 # Assert that we get the full email address.
387 self.assertEqual(
388 [self.services.user.LookupUserEmail(self.cnxn, user_id)
389 for user_id in expected_group_ids],
390 [group_ref.display_name for group_ref in response.group_refs])
391 return response
392
393 def testGetVisibleMembers_Normal(self):
394 # Not logged in - Test users have their email addresses obscured to
395 # non-project members by default.
396 self.assertVisibleMembers([], [])
397 # Logged in as non project member
398 self.assertVisibleMembers([], [], requester='foo@example.com')
399 # Logged in as owner
400 self.assertVisibleMembers([111, 222, 333], [],
401 requester='owner@example.com')
402 # Logged in as committer
403 self.assertVisibleMembers([111, 222, 333], [],
404 requester='user_222@example.com')
405 # Logged in as contributor
406 self.assertVisibleMembers([111, 222, 333], [],
407 requester='user_333@example.com')
408
409 def testGetVisibleMembers_OnlyOwnersSeeContributors(self):
410 self.project.only_owners_see_contributors = True
411 # Not logged in
412 with self.assertRaises(permissions.PermissionException):
413 self.assertVisibleMembers([111, 222], [])
414 # Logged in with a non-member
415 with self.assertRaises(permissions.PermissionException):
416 self.assertVisibleMembers([111, 222], [], requester='foo@example.com')
417 # Logged in as owner
418 self.assertVisibleMembers([111, 222, 333], [],
419 requester='owner@example.com')
420 # Logged in as committer
421 self.assertVisibleMembers([111, 222, 333], [],
422 requester='user_222@example.com')
423 # Logged in as contributor
424 with self.assertRaises(permissions.PermissionException):
425 self.assertVisibleMembers(
426 [111, 222], [], requester='user_333@example.com')
427
428 def testGetVisibleMembers_MemberIsGroup(self):
429 self.project.contributor_ids.extend([999])
430 self.assertVisibleMembers([999, 111, 222, 333, 444], [999],
431 requester='owner@example.com')
432
433 def testGetVisibleMembers_AcExclusion(self):
434 self.services.project.ac_exclusion_ids[self.project.project_id] = [333]
435 self.assertVisibleMembers([111, 222], [], requester='owner@example.com')
436
437 def testGetVisibleMembers_NoExpand(self):
438 self.services.project.no_expand_ids[self.project.project_id] = [999]
439 self.project.contributor_ids.extend([999])
440 self.assertVisibleMembers([999, 111, 222, 333], [999],
441 requester='owner@example.com')
442
443 def testGetVisibleMembers_ObscuredEmails(self):
444 # Unobscure the owner's email. Non-project members can see.
445 self.services.user.UpdateUserSettings(
446 self.cnxn, 111, self.owner, obscure_email=False)
447
448 # Not logged in
449 self.assertVisibleMembers([111], [])
450 # Logged in as not a project member
451 self.assertVisibleMembers([111], [], requester='foo@example.com')
452 # Logged in as owner
453 self.assertVisibleMembers(
454 [111, 222, 333], [], requester='owner@example.com')
455 # Logged in as committer
456 self.assertVisibleMembers(
457 [111, 222, 333], [], requester='user_222@example.com')
458 # Logged in as contributor
459 self.assertVisibleMembers(
460 [111, 222, 333], [], requester='user_333@example.com')
461
462 def testListStatuses(self):
463 request = projects_pb2.ListStatusesRequest(project_name='proj')
464 mc = monorailcontext.MonorailContext(
465 self.services, cnxn=self.cnxn, requester='owner@example.com')
466 response = self.CallWrapped(
467 self.projects_svcr.ListStatuses, mc, request)
468 self.assertFalse(response.restrict_to_known)
469 self.assertEqual(
470 [('New', True),
471 ('Accepted', True),
472 ('Started', True),
473 ('Fixed', False),
474 ('Verified', False),
475 ('Invalid', False),
476 ('Duplicate', False),
477 ('WontFix', False),
478 ('Done', False)],
479 [(status_def.status, status_def.means_open)
480 for status_def in response.status_defs])
481 self.assertEqual(
482 [('Duplicate', False)],
483 [(status_def.status, status_def.means_open)
484 for status_def in response.statuses_offer_merge])
485
486 def testListComponents(self):
487 self.services.config.CreateComponentDef(
488 self.cnxn, self.project.project_id, 'Foo', 'Foo Component', True, [],
489 [], True, 111, [])
490 self.services.config.CreateComponentDef(
491 self.cnxn, self.project.project_id, 'Bar', 'Bar Component', False, [],
492 [], True, 111, [])
493 self.services.config.CreateComponentDef(
494 self.cnxn, self.project.project_id, 'Bar>Baz', 'Baz Component',
495 False, [], [], True, 111, [])
496
497 request = projects_pb2.ListComponentsRequest(project_name='proj')
498 mc = monorailcontext.MonorailContext(
499 self.services, cnxn=self.cnxn, requester='owner@example.com')
500 response = self.CallWrapped(
501 self.projects_svcr.ListComponents, mc, request)
502
503 self.assertEqual(
504 [project_objects_pb2.ComponentDef(
505 path='Foo',
506 docstring='Foo Component',
507 deprecated=True),
508 project_objects_pb2.ComponentDef(
509 path='Bar',
510 docstring='Bar Component',
511 deprecated=False),
512 project_objects_pb2.ComponentDef(
513 path='Bar>Baz',
514 docstring='Baz Component',
515 deprecated=False)],
516 list(response.component_defs))
517
518 def testListComponents_IncludeAdminInfo(self):
519 self.services.config.CreateComponentDef(
520 self.cnxn, self.project.project_id, 'Foo', 'Foo Component', True, [],
521 [], 1234567, 111, [])
522 self.services.config.CreateComponentDef(
523 self.cnxn, self.project.project_id, 'Bar', 'Bar Component', False, [],
524 [], 1234568, 111, [])
525 self.services.config.CreateComponentDef(
526 self.cnxn, self.project.project_id, 'Bar>Baz', 'Baz Component',
527 False, [], [], 1234569, 111, [])
528 creator_ref = common_pb2.UserRef(
529 user_id=111,
530 display_name='owner@example.com')
531
532 request = projects_pb2.ListComponentsRequest(
533 project_name='proj', include_admin_info=True)
534 mc = monorailcontext.MonorailContext(
535 self.services, cnxn=self.cnxn, requester='owner@example.com')
536 response = self.CallWrapped(
537 self.projects_svcr.ListComponents, mc, request)
538
539 self.assertEqual(
540 [project_objects_pb2.ComponentDef(
541 path='Foo',
542 docstring='Foo Component',
543 deprecated=True,
544 created=1234567,
545 creator_ref=creator_ref),
546 project_objects_pb2.ComponentDef(
547 path='Bar',
548 docstring='Bar Component',
549 deprecated=False,
550 created=1234568,
551 creator_ref=creator_ref),
552 project_objects_pb2.ComponentDef(
553 path='Bar>Baz',
554 docstring='Baz Component',
555 deprecated=False,
556 created=1234569,
557 creator_ref=creator_ref),
558 ],
559 list(response.component_defs))
560
561 def AddField(self, name, **kwargs):
562 if kwargs.get('needs_perm'):
563 kwargs['needs_member'] = True
564 kwargs.setdefault('cnxn', self.cnxn)
565 kwargs.setdefault('project_id', self.project.project_id)
566 kwargs.setdefault('field_name', name)
567 kwargs.setdefault('field_type_str', 'USER_TYPE')
568 for arg in ('applic_type', 'applic_pred', 'is_required', 'is_niche',
569 'is_multivalued', 'min_value', 'max_value', 'regex',
570 'needs_member', 'needs_perm', 'grants_perm', 'notify_on',
571 'date_action_str', 'docstring'):
572 kwargs.setdefault(arg, None)
573 for arg in ('admin_ids', 'editor_ids'):
574 kwargs.setdefault(arg, [])
575
576 self.services.config.CreateFieldDef(**kwargs)
577
578 def testListFields_Normal(self):
579 self.AddField('Foo Field', needs_perm=permissions.EDIT_ISSUE)
580
581 request = projects_pb2.ListFieldsRequest(
582 project_name='proj', include_user_choices=True)
583 mc = monorailcontext.MonorailContext(
584 self.services, cnxn=self.cnxn, requester='owner@example.com')
585 response = self.CallWrapped(
586 self.projects_svcr.ListFields, mc, request)
587
588 self.assertEqual(1, len(response.field_defs))
589 field = response.field_defs[0]
590 self.assertEqual('Foo Field', field.field_ref.field_name)
591 self.assertEqual(
592 [111, 222],
593 sorted([user_ref.user_id for user_ref in field.user_choices]))
594 self.assertEqual(
595 ['owner@example.com', 'user_222@example.com'],
596 sorted([user_ref.display_name for user_ref in field.user_choices]))
597
598 def testListFields_DontIncludeUserChoices(self):
599 self.AddField('Foo Field', needs_perm=permissions.EDIT_ISSUE)
600
601 request = projects_pb2.ListFieldsRequest(project_name='proj')
602 mc = monorailcontext.MonorailContext(
603 self.services, cnxn=self.cnxn, requester='owner@example.com')
604 response = self.CallWrapped(
605 self.projects_svcr.ListFields, mc, request)
606
607 self.assertEqual(1, len(response.field_defs))
608 field = response.field_defs[0]
609 self.assertEqual(0, len(field.user_choices))
610
611 def testListFields_IncludeAdminInfo(self):
612 self.AddField('Foo Field', needs_perm=permissions.EDIT_ISSUE, is_niche=True,
613 applic_type='Foo Applic Type')
614
615 request = projects_pb2.ListFieldsRequest(
616 project_name='proj', include_admin_info=True)
617 mc = monorailcontext.MonorailContext(
618 self.services, cnxn=self.cnxn, requester='owner@example.com')
619 response = self.CallWrapped(
620 self.projects_svcr.ListFields, mc, request)
621
622 self.assertEqual(1, len(response.field_defs))
623 field = response.field_defs[0]
624 self.assertEqual('Foo Field', field.field_ref.field_name)
625 self.assertEqual(True, field.is_niche)
626 self.assertEqual('Foo Applic Type', field.applicable_type)
627
628 def testListFields_EnumFieldChoices(self):
629 self.AddField('Type', field_type_str='ENUM_TYPE')
630
631 request = projects_pb2.ListFieldsRequest(project_name='proj')
632 mc = monorailcontext.MonorailContext(
633 self.services, cnxn=self.cnxn, requester='owner@example.com')
634 response = self.CallWrapped(
635 self.projects_svcr.ListFields, mc, request)
636
637 self.assertEqual(1, len(response.field_defs))
638 field = response.field_defs[0]
639 self.assertEqual('Type', field.field_ref.field_name)
640 self.assertEqual(
641 ['Defect', 'Enhancement', 'Task', 'Other'],
642 [label.label for label in field.enum_choices])
643
644 def testListFields_CustomPermission(self):
645 self.AddField('Foo Field', needs_perm='FooPerm')
646 self.project.extra_perms = [
647 project_pb2.Project.ExtraPerms(
648 member_id=111,
649 perms=['UnrelatedPerm']),
650 project_pb2.Project.ExtraPerms(
651 member_id=222,
652 perms=['FooPerm'])]
653
654 request = projects_pb2.ListFieldsRequest(
655 project_name='proj', include_user_choices=True)
656 mc = monorailcontext.MonorailContext(
657 self.services, cnxn=self.cnxn, requester='owner@example.com')
658 response = self.CallWrapped(
659 self.projects_svcr.ListFields, mc, request)
660
661 self.assertEqual(1, len(response.field_defs))
662 field = response.field_defs[0]
663 self.assertEqual('Foo Field', field.field_ref.field_name)
664 self.assertEqual(
665 [222],
666 sorted([user_ref.user_id for user_ref in field.user_choices]))
667 self.assertEqual(
668 ['user_222@example.com'],
669 sorted([user_ref.display_name for user_ref in field.user_choices]))
670
671 def testListFields_IndirectPermission(self):
672 """Test that the permissions of effective ids are also considered."""
673 self.AddField('Foo Field', needs_perm='FooPerm')
674 self.project.contributor_ids.extend([999])
675 self.project.extra_perms = [
676 project_pb2.Project.ExtraPerms(
677 member_id=999,
678 perms=['FooPerm', 'BarPerm'])]
679
680 request = projects_pb2.ListFieldsRequest(
681 project_name='proj', include_user_choices=True)
682 mc = monorailcontext.MonorailContext(
683 self.services, cnxn=self.cnxn, requester='owner@example.com')
684 mc.LookupLoggedInUserPerms(self.project)
685 response = self.CallWrapped(
686 self.projects_svcr.ListFields, mc, request)
687
688 self.assertEqual(1, len(response.field_defs))
689 field = response.field_defs[0]
690 self.assertEqual('Foo Field', field.field_ref.field_name)
691 # Users 111 and 444 are members of group 999, which has the needed
692 # permission.
693 self.assertEqual(
694 [111, 444, 999],
695 sorted([user_ref.user_id for user_ref in field.user_choices]))
696 self.assertEqual(
697 ['group999@googlegroups.com', 'owner@example.com',
698 'user_444@example.com'],
699 sorted([user_ref.display_name for user_ref in field.user_choices]))
700
701 def testListFields_TwiceIndirectPermission(self):
702 """Test that only direct memberships are considered."""
703 self.AddField('Foo Field', needs_perm='FooPerm')
704 # User group 777 has members: user_666 and group 999.
705 self.project.contributor_ids.extend([777])
706 self.project.contributor_ids.extend([999])
707 self.project.extra_perms = [
708 project_pb2.Project.ExtraPerms(
709 member_id=777, perms=['FooPerm', 'BarPerm'])
710 ]
711
712 request = projects_pb2.ListFieldsRequest(
713 project_name='proj', include_user_choices=True)
714 mc = monorailcontext.MonorailContext(
715 self.services, cnxn=self.cnxn, requester='owner@example.com')
716 mc.LookupLoggedInUserPerms(self.project)
717 response = self.CallWrapped(self.projects_svcr.ListFields, mc, request)
718
719 self.assertEqual(1, len(response.field_defs))
720 field = response.field_defs[0]
721 self.assertEqual('Foo Field', field.field_ref.field_name)
722 self.assertEqual(
723 [666, 777, 999],
724 sorted([user_ref.user_id for user_ref in field.user_choices]))
725 self.assertEqual(
726 [
727 'group777@googlegroups.com', 'group999@googlegroups.com',
728 'user_666@example.com'
729 ], sorted([user_ref.display_name for user_ref in field.user_choices]))
730
731 def testListFields_NoPermissionsNeeded(self):
732 self.AddField('Foo Field')
733
734 request = projects_pb2.ListFieldsRequest(project_name='proj')
735 mc = monorailcontext.MonorailContext(
736 self.services, cnxn=self.cnxn, requester='owner@example.com')
737 response = self.CallWrapped(
738 self.projects_svcr.ListFields, mc, request)
739
740 self.assertEqual(1, len(response.field_defs))
741 field = response.field_defs[0]
742 self.assertEqual('Foo Field', field.field_ref.field_name)
743
744 def testListFields_MultipleFields(self):
745 self.AddField('Bar Field', needs_perm=permissions.VIEW)
746 self.AddField('Foo Field', needs_perm=permissions.EDIT_ISSUE)
747
748 request = projects_pb2.ListFieldsRequest(
749 project_name='proj', include_user_choices=True)
750 mc = monorailcontext.MonorailContext(
751 self.services, cnxn=self.cnxn, requester='owner@example.com')
752 response = self.CallWrapped(
753 self.projects_svcr.ListFields, mc, request)
754
755 self.assertEqual(2, len(response.field_defs))
756 field_defs = sorted(
757 response.field_defs, key=lambda field: field.field_ref.field_name)
758
759 self.assertEqual(
760 ['Bar Field', 'Foo Field'],
761 [field.field_ref.field_name for field in field_defs])
762 self.assertEqual(
763 [[111, 222, 333],
764 [111, 222]],
765 [sorted(user_ref.user_id for user_ref in field.user_choices)
766 for field in field_defs])
767 self.assertEqual(
768 [['owner@example.com', 'user_222@example.com', 'user_333@example.com'],
769 ['owner@example.com', 'user_222@example.com']],
770 [sorted(user_ref.display_name for user_ref in field.user_choices)
771 for field in field_defs])
772
773 def testListFields_NoFields(self):
774 request = projects_pb2.ListFieldsRequest(project_name='proj')
775 mc = monorailcontext.MonorailContext(
776 self.services, cnxn=self.cnxn, requester='owner@example.com')
777 response = self.CallWrapped(
778 self.projects_svcr.ListFields, mc, request)
779
780 self.assertEqual(0, len(response.field_defs))
781
782 def testGetLabelOptions_Normal(self):
783 request = projects_pb2.GetLabelOptionsRequest(project_name='proj')
784 mc = monorailcontext.MonorailContext(
785 self.services, cnxn=self.cnxn, requester='owner@example.com')
786 response = self.CallWrapped(
787 self.projects_svcr.GetLabelOptions, mc, request)
788
789 expected_label_names = [
790 label[0] for label in tracker_constants.DEFAULT_WELL_KNOWN_LABELS]
791 expected_label_names += [
792 'Restrict-View-EditIssue', 'Restrict-AddIssueComment-EditIssue',
793 'Restrict-View-CoreTeam']
794 self.assertEqual(
795 sorted(expected_label_names),
796 sorted(label.label for label in response.label_options))
797
798 def testGetLabelOptions_CustomPermissions(self):
799 self.project.extra_perms = [
800 project_pb2.Project.ExtraPerms(
801 member_id=222,
802 perms=['FooPerm', 'BarPerm'])]
803
804 request = projects_pb2.GetLabelOptionsRequest(project_name='proj')
805 mc = monorailcontext.MonorailContext(
806 self.services, cnxn=self.cnxn, requester='owner@example.com')
807 response = self.CallWrapped(
808 self.projects_svcr.GetLabelOptions, mc, request)
809
810 expected_label_names = [
811 label[0] for label in tracker_constants.DEFAULT_WELL_KNOWN_LABELS]
812 expected_label_names += [
813 'Restrict-View-EditIssue', 'Restrict-AddIssueComment-EditIssue']
814 expected_label_names += [
815 'Restrict-%s-%s' % (std_perm, custom_perm)
816 for std_perm in permissions.STANDARD_ISSUE_PERMISSIONS
817 for custom_perm in ('BarPerm', 'FooPerm')]
818
819 self.assertEqual(
820 sorted(expected_label_names),
821 sorted(label.label for label in response.label_options))
822
823 def testGetLabelOptions_FieldMasksLabel(self):
824 self.AddField('Type', field_type_str='ENUM_TYPE')
825
826 request = projects_pb2.GetLabelOptionsRequest(project_name='proj')
827 mc = monorailcontext.MonorailContext(
828 self.services, cnxn=self.cnxn, requester='owner@example.com')
829 response = self.CallWrapped(
830 self.projects_svcr.GetLabelOptions, mc, request)
831
832 expected_label_names = [
833 label[0] for label in tracker_constants.DEFAULT_WELL_KNOWN_LABELS
834 if not label[0].startswith('Type-')
835 ]
836 expected_label_names += [
837 'Restrict-View-EditIssue', 'Restrict-AddIssueComment-EditIssue',
838 'Restrict-View-CoreTeam']
839 self.assertEqual(
840 sorted(expected_label_names),
841 sorted(label.label for label in response.label_options))
842
843 def CallGetStarCount(self):
844 request = projects_pb2.GetProjectStarCountRequest(project_name='proj')
845 mc = monorailcontext.MonorailContext(
846 self.services, cnxn=self.cnxn, requester='owner@example.com')
847 response = self.CallWrapped(
848 self.projects_svcr.GetProjectStarCount, mc, request)
849 return response.star_count
850
851 def CallStar(self, requester='owner@example.com', starred=True):
852 request = projects_pb2.StarProjectRequest(
853 project_name='proj', starred=starred)
854 mc = monorailcontext.MonorailContext(
855 self.services, cnxn=self.cnxn, requester=requester)
856 mc.LookupLoggedInUserPerms(self.project)
857 response = self.CallWrapped(
858 self.projects_svcr.StarProject, mc, request)
859 return response.star_count
860
861 def testStarCount_Normal(self):
862 self.assertEqual(0, self.CallGetStarCount())
863 self.assertEqual(1, self.CallStar())
864 self.assertEqual(1, self.CallGetStarCount())
865
866 def testStarCount_StarTwiceSameUser(self):
867 self.assertEqual(1, self.CallStar())
868 self.assertEqual(1, self.CallStar())
869 self.assertEqual(1, self.CallGetStarCount())
870
871 def testStarCount_StarTwiceDifferentUser(self):
872 self.assertEqual(1, self.CallStar())
873 self.assertEqual(2, self.CallStar(requester='user_222@example.com'))
874 self.assertEqual(2, self.CallGetStarCount())
875
876 def testStarCount_RemoveStarTwiceSameUser(self):
877 self.assertEqual(1, self.CallStar())
878 self.assertEqual(1, self.CallGetStarCount())
879
880 self.assertEqual(0, self.CallStar(starred=False))
881 self.assertEqual(0, self.CallStar(starred=False))
882 self.assertEqual(0, self.CallGetStarCount())
883
884 def testStarCount_RemoveStarTwiceDifferentUser(self):
885 self.assertEqual(1, self.CallStar())
886 self.assertEqual(2, self.CallStar(requester='user_222@example.com'))
887 self.assertEqual(2, self.CallGetStarCount())
888
889 self.assertEqual(1, self.CallStar(starred=False))
890 self.assertEqual(
891 0, self.CallStar(requester='user_222@example.com', starred=False))
892 self.assertEqual(0, self.CallGetStarCount())
893
894 def testCheckProjectName_OK(self):
895 """We can check a project name."""
896 request = projects_pb2.CheckProjectNameRequest(project_name='foo')
897 mc = monorailcontext.MonorailContext(
898 self.services, cnxn=self.cnxn, requester='admin@example.com')
899 mc.LookupLoggedInUserPerms(self.project)
900 response = self.CallWrapped(
901 self.projects_svcr.CheckProjectName, mc, request)
902
903 self.assertEqual('', response.error)
904
905 def testCheckProjectName_InvalidProjectName(self):
906 """We reject an invalid project name."""
907 request = projects_pb2.CheckProjectNameRequest(project_name='Foo')
908 mc = monorailcontext.MonorailContext(
909 self.services, cnxn=self.cnxn, requester='admin@example.com')
910 mc.LookupLoggedInUserPerms(self.project)
911 response = self.CallWrapped(
912 self.projects_svcr.CheckProjectName, mc, request)
913
914 self.assertNotEqual('', response.error)
915
916 def testCheckProjectName_NotAllowed(self):
917 """Users that can't create a project shouldn't get any information."""
918 request = projects_pb2.CheckProjectNameRequest(project_name='Foo')
919 mc = monorailcontext.MonorailContext(
920 self.services, cnxn=self.cnxn, requester='owner@example.com')
921 mc.LookupLoggedInUserPerms(self.project)
922 with self.assertRaises(permissions.PermissionException):
923 self.CallWrapped(self.projects_svcr.CheckProjectName, mc, request)
924
925 def testCheckProjectName_ProjectAlreadyExists(self):
926 """There is already a project with that name."""
927 request = projects_pb2.CheckProjectNameRequest(project_name='proj')
928 mc = monorailcontext.MonorailContext(
929 self.services, cnxn=self.cnxn, requester='admin@example.com')
930 mc.LookupLoggedInUserPerms(self.project)
931 response = self.CallWrapped(
932 self.projects_svcr.CheckProjectName, mc, request)
933
934 self.assertNotEqual('', response.error)
935
936 def testCheckComponentName_OK(self):
937 request = projects_pb2.CheckComponentNameRequest(
938 project_name='proj',
939 component_name='Component')
940 mc = monorailcontext.MonorailContext(
941 self.services, cnxn=self.cnxn, requester='admin@example.com')
942 mc.LookupLoggedInUserPerms(self.project)
943 response = self.CallWrapped(
944 self.projects_svcr.CheckComponentName, mc, request)
945
946 self.assertEqual('', response.error)
947
948 def testCheckComponentName_ParentComponentOK(self):
949 self.services.config.CreateComponentDef(
950 self.cnxn, self.project.project_id, 'Component', 'Docstring',
951 False, [], [], 0, 111, [])
952 request = projects_pb2.CheckComponentNameRequest(
953 project_name='proj',
954 parent_path='Component',
955 component_name='Path')
956 mc = monorailcontext.MonorailContext(
957 self.services, cnxn=self.cnxn, requester='admin@example.com')
958 mc.LookupLoggedInUserPerms(self.project)
959 response = self.CallWrapped(
960 self.projects_svcr.CheckComponentName, mc, request)
961
962 self.assertEqual('', response.error)
963
964 def testCheckComponentName_InvalidComponentName(self):
965 request = projects_pb2.CheckComponentNameRequest(
966 project_name='proj',
967 component_name='Component-')
968 mc = monorailcontext.MonorailContext(
969 self.services, cnxn=self.cnxn, requester='admin@example.com')
970 mc.LookupLoggedInUserPerms(self.project)
971 response = self.CallWrapped(
972 self.projects_svcr.CheckComponentName, mc, request)
973
974 self.assertNotEqual('', response.error)
975
976 def testCheckComponentName_ComponentAlreadyExists(self):
977 self.services.config.CreateComponentDef(
978 self.cnxn, self.project.project_id, 'Component', 'Docstring',
979 False, [], [], 0, 111, [])
980 request = projects_pb2.CheckComponentNameRequest(
981 project_name='proj',
982 component_name='Component')
983 mc = monorailcontext.MonorailContext(
984 self.services, cnxn=self.cnxn, requester='admin@example.com')
985 mc.LookupLoggedInUserPerms(self.project)
986 response = self.CallWrapped(
987 self.projects_svcr.CheckComponentName, mc, request)
988
989 self.assertNotEqual('', response.error)
990
991 def testCheckComponentName_NotAllowedToViewProject(self):
992 self.project.access = project_pb2.ProjectAccess.MEMBERS_ONLY
993 request = projects_pb2.CheckComponentNameRequest(
994 project_name='proj',
995 parent_path='Component',
996 component_name='Path')
997 mc = monorailcontext.MonorailContext(
998 self.services, cnxn=self.cnxn, requester='user_444@example.com')
999 mc.LookupLoggedInUserPerms(self.project)
1000 with self.assertRaises(permissions.PermissionException):
1001 self.CallWrapped(self.projects_svcr.CheckComponentName, mc, request)
1002
1003 def testCheckComponentName_ParentComponentDoesntExist(self):
1004 request = projects_pb2.CheckComponentNameRequest(
1005 project_name='proj',
1006 parent_path='Component',
1007 component_name='Path')
1008 mc = monorailcontext.MonorailContext(
1009 self.services, cnxn=self.cnxn, requester='admin@example.com')
1010 mc.LookupLoggedInUserPerms(self.project)
1011 with self.assertRaises(exceptions.NoSuchComponentException):
1012 self.CallWrapped(self.projects_svcr.CheckComponentName, mc, request)
1013
1014 def testCheckFieldName_OK(self):
1015 request = projects_pb2.CheckFieldNameRequest(
1016 project_name='proj',
1017 field_name='Foo')
1018 mc = monorailcontext.MonorailContext(
1019 self.services, cnxn=self.cnxn, requester='admin@example.com')
1020 mc.LookupLoggedInUserPerms(self.project)
1021 response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
1022 self.assertEqual('', response.error)
1023
1024 def testCheckFieldName_InvalidFieldName(self):
1025 request = projects_pb2.CheckFieldNameRequest(
1026 project_name='proj',
1027 field_name='**Foo**')
1028 mc = monorailcontext.MonorailContext(
1029 self.services, cnxn=self.cnxn, requester='admin@example.com')
1030 mc.LookupLoggedInUserPerms(self.project)
1031 response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
1032 self.assertNotEqual('', response.error)
1033
1034 def testCheckFieldName_InvalidFieldName_ApproverSuffix(self):
1035 request = projects_pb2.CheckFieldNameRequest(
1036 project_name='proj',
1037 field_name='Foo-aPprOver')
1038 mc = monorailcontext.MonorailContext(
1039 self.services, cnxn=self.cnxn, requester='admin@example.com')
1040 mc.LookupLoggedInUserPerms(self.project)
1041 response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
1042 self.assertNotEqual('', response.error)
1043
1044 def testCheckFieldName_FieldAlreadyExists(self):
1045 self.AddField('Foo')
1046 request = projects_pb2.CheckFieldNameRequest(
1047 project_name='proj',
1048 field_name='Foo')
1049 mc = monorailcontext.MonorailContext(
1050 self.services, cnxn=self.cnxn, requester='admin@example.com')
1051 mc.LookupLoggedInUserPerms(self.project)
1052 response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
1053 self.assertNotEqual('', response.error)
1054
1055 def testCheckFieldName_FieldIsPrefixOfAnother(self):
1056 self.AddField('Foo-Bar')
1057 request = projects_pb2.CheckFieldNameRequest(
1058 project_name='proj',
1059 field_name='Foo')
1060 mc = monorailcontext.MonorailContext(
1061 self.services, cnxn=self.cnxn, requester='admin@example.com')
1062 mc.LookupLoggedInUserPerms(self.project)
1063 response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
1064 self.assertNotEqual('', response.error)
1065
1066 def testCheckFieldName_AnotherFieldIsPrefix(self):
1067 self.AddField('Foo')
1068 request = projects_pb2.CheckFieldNameRequest(
1069 project_name='proj',
1070 field_name='Foo-Bar')
1071 mc = monorailcontext.MonorailContext(
1072 self.services, cnxn=self.cnxn, requester='admin@example.com')
1073 mc.LookupLoggedInUserPerms(self.project)
1074 response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
1075 self.assertNotEqual('', response.error)
1076
1077 def testCheckFieldName_NotAllowedToViewProject(self):
1078 self.project.access = project_pb2.ProjectAccess.MEMBERS_ONLY
1079 request = projects_pb2.CheckFieldNameRequest(
1080 project_name='proj',
1081 field_name='Foo')
1082 mc = monorailcontext.MonorailContext(
1083 self.services, cnxn=self.cnxn, requester='user_444@example.com')
1084 mc.LookupLoggedInUserPerms(self.project)
1085 with self.assertRaises(permissions.PermissionException):
1086 self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)