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