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