Project import generated by Copybara.
GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/test/projects_servicer_test.py b/api/test/projects_servicer_test.py
new file mode 100644
index 0000000..b3084c3
--- /dev/null
+++ b/api/test/projects_servicer_test.py
@@ -0,0 +1,1086 @@
+# Copyright 2018 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Tests for the projects servicer."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import unittest
+from mock import patch
+
+from components.prpc import codes
+from components.prpc import context
+from components.prpc import server
+
+from api import projects_servicer
+from api.api_proto import common_pb2
+from api.api_proto import issue_objects_pb2
+from api.api_proto import project_objects_pb2
+from api.api_proto import projects_pb2
+from framework import authdata
+from framework import exceptions
+from framework import framework_constants
+from framework import monorailcontext
+from framework import permissions
+from proto import tracker_pb2
+from proto import project_pb2
+from tracker import tracker_bizobj
+from tracker import tracker_constants
+from testing import fake
+from testing import testing_helpers
+from services import service_manager
+
+
+class ProjectsServicerTest(unittest.TestCase):
+
+ def setUp(self):
+ self.cnxn = fake.MonorailConnection()
+ self.services = service_manager.Services(
+ config=fake.ConfigService(),
+ issue=fake.IssueService(),
+ user=fake.UserService(),
+ usergroup=fake.UserGroupService(),
+ project=fake.ProjectService(),
+ project_star=fake.ProjectStarService(),
+ features=fake.FeaturesService())
+
+ self.admin = self.services.user.TestAddUser('admin@example.com', 123)
+ self.admin.is_site_admin = True
+ self.owner = self.services.user.TestAddUser('owner@example.com', 111)
+ self.services.user.TestAddUser('user_222@example.com', 222)
+ self.services.user.TestAddUser('user_333@example.com', 333)
+ self.services.user.TestAddUser('user_444@example.com', 444)
+ self.services.user.TestAddUser('user_666@example.com', 666)
+
+ # User group 888 has members: user_555 and proj@monorail.com
+ self.services.user.TestAddUser('group888@googlegroups.com', 888)
+ self.services.usergroup.TestAddGroupSettings(
+ 888, 'group888@googlegroups.com')
+ self.services.usergroup.TestAddMembers(888, [555, 1001])
+
+ # User group 999 has members: user_111 and user_444
+ self.services.user.TestAddUser('group999@googlegroups.com', 999)
+ self.services.usergroup.TestAddGroupSettings(
+ 999, 'group999@googlegroups.com')
+ self.services.usergroup.TestAddMembers(999, [111, 444])
+
+ # User group 777 has members: user_666 and group 999.
+ self.services.user.TestAddUser('group777@googlegroups.com', 777)
+ self.services.usergroup.TestAddGroupSettings(
+ 777, 'group777@googlegroups.com')
+ self.services.usergroup.TestAddMembers(777, [666, 999])
+
+ self.project = self.services.project.TestAddProject(
+ 'proj',
+ project_id=789,
+ owner_ids=[111],
+ committer_ids=[222],
+ contrib_ids=[333])
+ self.projects_svcr = projects_servicer.ProjectsServicer(
+ self.services, make_rate_limiter=False)
+ self.prpc_context = context.ServicerContext()
+ self.prpc_context.set_code(codes.StatusCode.OK)
+
+ def CallWrapped(self, wrapped_handler, *args, **kwargs):
+ return wrapped_handler.wrapped(self.projects_svcr, *args, **kwargs)
+
+ def testListProjects_Normal(self):
+ """We can get a list of all projects on the site."""
+ request = projects_pb2.ListProjectsRequest()
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(self.projects_svcr.ListProjects, mc, request)
+ self.assertEqual(2, len(response.projects))
+
+ def testGetConfig_Normal(self):
+ """We can get a project config."""
+ request = projects_pb2.GetConfigRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(self.projects_svcr.GetConfig, mc, request)
+ self.assertEqual('proj', response.project_name)
+
+ def testGetConfig_NoSuchProject(self):
+ """We reject a request to get a config for a non-existent project."""
+ request = projects_pb2.GetConfigRequest(project_name='unknown-proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ with self.assertRaises(exceptions.NoSuchProjectException):
+ self.CallWrapped(self.projects_svcr.GetConfig, mc, request)
+
+ def testGetConfig_PermissionDenied(self):
+ """We reject a request to get a config for a non-viewable project."""
+ self.project.access = project_pb2.ProjectAccess.MEMBERS_ONLY
+ request = projects_pb2.GetConfigRequest(project_name='proj')
+
+ # User is a member of the members-only project.
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(self.projects_svcr.GetConfig, mc, request)
+ self.assertEqual('proj', response.project_name)
+
+ # User is not a member of the members-only project.
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='nonmember@example.com')
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.projects_svcr.GetConfig, mc, request)
+
+ @patch('businesslogic.work_env.WorkEnv.ListProjectTemplates')
+ def testListProjectTemplates_Normal(self, mockListProjectTemplates):
+ fd_1 = tracker_pb2.FieldDef(
+ field_name='FirstField', field_id=1,
+ field_type=tracker_pb2.FieldTypes.STR_TYPE)
+ fd_2 = tracker_pb2.FieldDef(
+ field_name='LegalApproval', field_id=2,
+ field_type=tracker_pb2.FieldTypes.APPROVAL_TYPE)
+ component = tracker_pb2.ComponentDef(component_id=1, path='dude')
+ status_def = tracker_pb2.StatusDef(status='New', means_open=True)
+ config = tracker_pb2.ProjectIssueConfig(
+ project_id=789, field_defs=[fd_1, fd_2], component_defs=[component],
+ well_known_statuses=[status_def])
+ self.services.config.StoreConfig(self.cnxn, config)
+ admin1 = self.services.user.TestAddUser('admin@example.com', 222)
+ appr1 = self.services.user.TestAddUser('approver@example.com', 333)
+ setter = self.services.user.TestAddUser('setter@example.com', 444)
+ template = tracker_pb2.TemplateDef(
+ name='Chicken', content='description', summary='summary',
+ status='New', admin_ids=[admin1.user_id],
+ field_values=[tracker_bizobj.MakeFieldValue(
+ fd_1.field_id, None, 'Cow', None, None, None, False)],
+ component_ids=[component.component_id],
+ approval_values=[tracker_pb2.ApprovalValue(
+ approval_id=2, approver_ids=[appr1.user_id],
+ setter_id=setter.user_id)])
+ mockListProjectTemplates.return_value = [template]
+
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ request = projects_pb2.ListProjectTemplatesRequest(project_name='proj')
+ response = self.CallWrapped(
+ self.projects_svcr.ListProjectTemplates, mc, request)
+ self.assertEqual(
+ response,
+ projects_pb2.ListProjectTemplatesResponse(
+ templates=[project_objects_pb2.TemplateDef(
+ template_name='Chicken',
+ content='description',
+ summary='summary',
+ status_ref=common_pb2.StatusRef(
+ status='New',
+ is_derived=False,
+ means_open=True),
+ owner_defaults_to_member=True,
+ admin_refs=[
+ common_pb2.UserRef(
+ user_id=admin1.user_id,
+ display_name=testing_helpers.ObscuredEmail(admin1.email),
+ is_derived=False)],
+ field_values=[
+ issue_objects_pb2.FieldValue(
+ field_ref=common_pb2.FieldRef(
+ field_id=fd_1.field_id,
+ field_name=fd_1.field_name,
+ type=common_pb2.STR_TYPE),
+ value='Cow')],
+ component_refs=[
+ common_pb2.ComponentRef(
+ path=component.path, is_derived=False)],
+ approval_values=[
+ issue_objects_pb2.Approval(
+ field_ref=common_pb2.FieldRef(
+ field_id=fd_2.field_id,
+ field_name=fd_2.field_name,
+ type=common_pb2.APPROVAL_TYPE),
+ setter_ref=common_pb2.UserRef(
+ user_id=setter.user_id,
+ display_name=testing_helpers.ObscuredEmail(
+ setter.email)),
+ phase_ref=issue_objects_pb2.PhaseRef(),
+ approver_refs=[common_pb2.UserRef(
+ user_id=appr1.user_id,
+ display_name=testing_helpers.ObscuredEmail(appr1.email),
+ is_derived=False)])],
+ )]))
+
+ def testListProjectTemplates_NoProjectName(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ request = projects_pb2.ListProjectTemplatesRequest()
+ with self.assertRaises(exceptions.InputException):
+ self.CallWrapped(self.projects_svcr.ListProjectTemplates, mc, request)
+
+ def testListProjectTemplates_NoSuchProject(self):
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ request = projects_pb2.ListProjectTemplatesRequest(project_name='ghost')
+ with self.assertRaises(exceptions.NoSuchProjectException):
+ self.CallWrapped(self.projects_svcr.ListProjectTemplates, mc, request)
+
+ def testListProjectTemplates_PermissionDenied(self):
+ self.project.access = project_pb2.ProjectAccess.MEMBERS_ONLY
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='nonmember@example.com')
+ request = projects_pb2.GetConfigRequest(project_name='proj')
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.projects_svcr.ListProjectTemplates, mc, request)
+
+ def testGetPresentationConfig_Normal(self):
+ """Test getting project summary, thumbnail url, custom issue entry, etc."""
+ config = tracker_pb2.ProjectIssueConfig(project_id=789)
+ self.project.summary = 'project summary'
+ config.custom_issue_entry_url = 'issue entry url'
+ config.member_default_query = 'default query'
+ config.default_col_spec = 'ID Summary'
+ config.default_sort_spec = 'Priority Status'
+ config.default_x_attr = 'Priority'
+ config.default_y_attr = 'Status'
+ self.project.revision_url_format = 'revision url format'
+ self.services.config.StoreConfig(self.cnxn, config)
+
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+
+ request = projects_pb2.GetPresentationConfigRequest(project_name='proj')
+ response = self.CallWrapped(
+ self.projects_svcr.GetPresentationConfig, mc, request)
+
+ self.assertEqual('project summary', response.project_summary)
+ self.assertEqual('issue entry url', response.custom_issue_entry_url)
+ self.assertEqual('default query', response.default_query)
+ self.assertEqual('ID Summary', response.default_col_spec)
+ self.assertEqual('Priority Status', response.default_sort_spec)
+ self.assertEqual('Priority', response.default_x_attr)
+ self.assertEqual('Status', response.default_y_attr)
+ self.assertEqual('revision url format', response.revision_url_format)
+
+ def testGetPresentationConfig_SavedQueriesAllowed(self):
+ """Only project members or higher can see project saved queries."""
+ self.services.features.UpdateCannedQueries(self.cnxn, 789, [
+ tracker_pb2.SavedQuery(query_id=101, name='test', query='owner:me'),
+ tracker_pb2.SavedQuery(query_id=202, name='hello', query='world')
+ ])
+
+ # User 333 is a contributor.
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='user_333@example.com')
+
+ request = projects_pb2.GetPresentationConfigRequest(project_name='proj')
+ response = self.CallWrapped(self.projects_svcr.GetPresentationConfig, mc,
+ request)
+
+ self.assertEqual(2, len(response.saved_queries))
+
+ self.assertEqual(101, response.saved_queries[0].query_id)
+ self.assertEqual('test', response.saved_queries[0].name)
+ self.assertEqual('owner:me', response.saved_queries[0].query)
+
+ self.assertEqual(202, response.saved_queries[1].query_id)
+ self.assertEqual('hello', response.saved_queries[1].name)
+ self.assertEqual('world', response.saved_queries[1].query)
+
+ def testGetPresentationConfig_SavedQueriesDenied(self):
+ """Only project members or higher can see project saved queries."""
+ self.services.features.UpdateCannedQueries(self.cnxn, 789, [
+ tracker_pb2.SavedQuery(query_id=101, name='test', query='owner:me'),
+ tracker_pb2.SavedQuery(query_id=202, name='hello', query='world')
+ ])
+
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='nonmember@example.com')
+
+ request = projects_pb2.GetPresentationConfigRequest(project_name='proj')
+ response = self.CallWrapped(self.projects_svcr.GetPresentationConfig, mc,
+ request)
+
+ self.assertEqual(0, len(response.saved_queries))
+
+ def testGetCustomPermissions_Normal(self):
+ self.project.extra_perms = [
+ project_pb2.Project.ExtraPerms(
+ member_id=111,
+ perms=['FooPerm', 'BarPerm'])]
+
+ request = projects_pb2.GetConfigRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='foo@example.org')
+ response = self.CallWrapped(
+ self.projects_svcr.GetCustomPermissions, mc, request)
+ self.assertEqual(['BarPerm', 'FooPerm'], response.permissions)
+
+ def testGetCustomPermissions_PermissionsAreDedupped(self):
+ self.project.extra_perms = [
+ project_pb2.Project.ExtraPerms(
+ member_id=111,
+ perms=['FooPerm', 'FooPerm']),
+ project_pb2.Project.ExtraPerms(
+ member_id=222,
+ perms=['FooPerm'])]
+
+ request = projects_pb2.GetConfigRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='foo@example.org')
+ response = self.CallWrapped(
+ self.projects_svcr.GetCustomPermissions, mc, request)
+ self.assertEqual(['FooPerm'], response.permissions)
+
+ def testGetCustomPermissions_PermissionsAreSorted(self):
+ self.project.extra_perms = [
+ project_pb2.Project.ExtraPerms(
+ member_id=111,
+ perms=['FooPerm', 'BarPerm']),
+ project_pb2.Project.ExtraPerms(
+ member_id=222,
+ perms=['BazPerm'])]
+
+ request = projects_pb2.GetConfigRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='foo@example.org')
+ response = self.CallWrapped(
+ self.projects_svcr.GetCustomPermissions, mc, request)
+ self.assertEqual(['BarPerm', 'BazPerm', 'FooPerm'], response.permissions)
+
+ def testGetCustomPermissions_IgnoreStandardPermissions(self):
+ self.project.extra_perms = [
+ project_pb2.Project.ExtraPerms(
+ member_id=111,
+ perms=permissions.STANDARD_PERMISSIONS + ['FooPerm'])]
+
+ request = projects_pb2.GetConfigRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='foo@example.org')
+ response = self.CallWrapped(
+ self.projects_svcr.GetCustomPermissions, mc, request)
+ self.assertEqual(['FooPerm'], response.permissions)
+
+ def testGetCustomPermissions_NoCustomPermissions(self):
+ self.project.extra_perms = []
+ request = projects_pb2.GetConfigRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='foo@example.org')
+ response = self.CallWrapped(
+ self.projects_svcr.GetCustomPermissions, mc, request)
+ self.assertEqual([], response.permissions)
+
+ def assertVisibleMembers(self, expected_user_ids, expected_group_ids,
+ requester=None):
+ request = projects_pb2.GetVisibleMembersRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester=requester)
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(
+ self.projects_svcr.GetVisibleMembers, mc, request)
+ self.assertEqual(
+ expected_user_ids,
+ [user_ref.user_id for user_ref in response.user_refs])
+ # Assert that we get the full email address.
+ self.assertEqual(
+ [self.services.user.LookupUserEmail(self.cnxn, user_id)
+ for user_id in expected_user_ids],
+ [user_ref.display_name for user_ref in response.user_refs])
+ self.assertEqual(
+ expected_group_ids,
+ [group_ref.user_id for group_ref in response.group_refs])
+ # Assert that we get the full email address.
+ self.assertEqual(
+ [self.services.user.LookupUserEmail(self.cnxn, user_id)
+ for user_id in expected_group_ids],
+ [group_ref.display_name for group_ref in response.group_refs])
+ return response
+
+ def testGetVisibleMembers_Normal(self):
+ # Not logged in - Test users have their email addresses obscured to
+ # non-project members by default.
+ self.assertVisibleMembers([], [])
+ # Logged in as non project member
+ self.assertVisibleMembers([], [], requester='foo@example.com')
+ # Logged in as owner
+ self.assertVisibleMembers([111, 222, 333], [],
+ requester='owner@example.com')
+ # Logged in as committer
+ self.assertVisibleMembers([111, 222, 333], [],
+ requester='user_222@example.com')
+ # Logged in as contributor
+ self.assertVisibleMembers([111, 222, 333], [],
+ requester='user_333@example.com')
+
+ def testGetVisibleMembers_OnlyOwnersSeeContributors(self):
+ self.project.only_owners_see_contributors = True
+ # Not logged in
+ with self.assertRaises(permissions.PermissionException):
+ self.assertVisibleMembers([111, 222], [])
+ # Logged in with a non-member
+ with self.assertRaises(permissions.PermissionException):
+ self.assertVisibleMembers([111, 222], [], requester='foo@example.com')
+ # Logged in as owner
+ self.assertVisibleMembers([111, 222, 333], [],
+ requester='owner@example.com')
+ # Logged in as committer
+ self.assertVisibleMembers([111, 222, 333], [],
+ requester='user_222@example.com')
+ # Logged in as contributor
+ with self.assertRaises(permissions.PermissionException):
+ self.assertVisibleMembers(
+ [111, 222], [], requester='user_333@example.com')
+
+ def testGetVisibleMembers_MemberIsGroup(self):
+ self.project.contributor_ids.extend([999])
+ self.assertVisibleMembers([999, 111, 222, 333, 444], [999],
+ requester='owner@example.com')
+
+ def testGetVisibleMembers_AcExclusion(self):
+ self.services.project.ac_exclusion_ids[self.project.project_id] = [333]
+ self.assertVisibleMembers([111, 222], [], requester='owner@example.com')
+
+ def testGetVisibleMembers_NoExpand(self):
+ self.services.project.no_expand_ids[self.project.project_id] = [999]
+ self.project.contributor_ids.extend([999])
+ self.assertVisibleMembers([999, 111, 222, 333], [999],
+ requester='owner@example.com')
+
+ def testGetVisibleMembers_ObscuredEmails(self):
+ # Unobscure the owner's email. Non-project members can see.
+ self.services.user.UpdateUserSettings(
+ self.cnxn, 111, self.owner, obscure_email=False)
+
+ # Not logged in
+ self.assertVisibleMembers([111], [])
+ # Logged in as not a project member
+ self.assertVisibleMembers([111], [], requester='foo@example.com')
+ # Logged in as owner
+ self.assertVisibleMembers(
+ [111, 222, 333], [], requester='owner@example.com')
+ # Logged in as committer
+ self.assertVisibleMembers(
+ [111, 222, 333], [], requester='user_222@example.com')
+ # Logged in as contributor
+ self.assertVisibleMembers(
+ [111, 222, 333], [], requester='user_333@example.com')
+
+ def testListStatuses(self):
+ request = projects_pb2.ListStatusesRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListStatuses, mc, request)
+ self.assertFalse(response.restrict_to_known)
+ self.assertEqual(
+ [('New', True),
+ ('Accepted', True),
+ ('Started', True),
+ ('Fixed', False),
+ ('Verified', False),
+ ('Invalid', False),
+ ('Duplicate', False),
+ ('WontFix', False),
+ ('Done', False)],
+ [(status_def.status, status_def.means_open)
+ for status_def in response.status_defs])
+ self.assertEqual(
+ [('Duplicate', False)],
+ [(status_def.status, status_def.means_open)
+ for status_def in response.statuses_offer_merge])
+
+ def testListComponents(self):
+ self.services.config.CreateComponentDef(
+ self.cnxn, self.project.project_id, 'Foo', 'Foo Component', True, [],
+ [], True, 111, [])
+ self.services.config.CreateComponentDef(
+ self.cnxn, self.project.project_id, 'Bar', 'Bar Component', False, [],
+ [], True, 111, [])
+ self.services.config.CreateComponentDef(
+ self.cnxn, self.project.project_id, 'Bar>Baz', 'Baz Component',
+ False, [], [], True, 111, [])
+
+ request = projects_pb2.ListComponentsRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListComponents, mc, request)
+
+ self.assertEqual(
+ [project_objects_pb2.ComponentDef(
+ path='Foo',
+ docstring='Foo Component',
+ deprecated=True),
+ project_objects_pb2.ComponentDef(
+ path='Bar',
+ docstring='Bar Component',
+ deprecated=False),
+ project_objects_pb2.ComponentDef(
+ path='Bar>Baz',
+ docstring='Baz Component',
+ deprecated=False)],
+ list(response.component_defs))
+
+ def testListComponents_IncludeAdminInfo(self):
+ self.services.config.CreateComponentDef(
+ self.cnxn, self.project.project_id, 'Foo', 'Foo Component', True, [],
+ [], 1234567, 111, [])
+ self.services.config.CreateComponentDef(
+ self.cnxn, self.project.project_id, 'Bar', 'Bar Component', False, [],
+ [], 1234568, 111, [])
+ self.services.config.CreateComponentDef(
+ self.cnxn, self.project.project_id, 'Bar>Baz', 'Baz Component',
+ False, [], [], 1234569, 111, [])
+ creator_ref = common_pb2.UserRef(
+ user_id=111,
+ display_name='owner@example.com')
+
+ request = projects_pb2.ListComponentsRequest(
+ project_name='proj', include_admin_info=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListComponents, mc, request)
+
+ self.assertEqual(
+ [project_objects_pb2.ComponentDef(
+ path='Foo',
+ docstring='Foo Component',
+ deprecated=True,
+ created=1234567,
+ creator_ref=creator_ref),
+ project_objects_pb2.ComponentDef(
+ path='Bar',
+ docstring='Bar Component',
+ deprecated=False,
+ created=1234568,
+ creator_ref=creator_ref),
+ project_objects_pb2.ComponentDef(
+ path='Bar>Baz',
+ docstring='Baz Component',
+ deprecated=False,
+ created=1234569,
+ creator_ref=creator_ref),
+ ],
+ list(response.component_defs))
+
+ def AddField(self, name, **kwargs):
+ if kwargs.get('needs_perm'):
+ kwargs['needs_member'] = True
+ kwargs.setdefault('cnxn', self.cnxn)
+ kwargs.setdefault('project_id', self.project.project_id)
+ kwargs.setdefault('field_name', name)
+ kwargs.setdefault('field_type_str', 'USER_TYPE')
+ for arg in ('applic_type', 'applic_pred', 'is_required', 'is_niche',
+ 'is_multivalued', 'min_value', 'max_value', 'regex',
+ 'needs_member', 'needs_perm', 'grants_perm', 'notify_on',
+ 'date_action_str', 'docstring'):
+ kwargs.setdefault(arg, None)
+ for arg in ('admin_ids', 'editor_ids'):
+ kwargs.setdefault(arg, [])
+
+ self.services.config.CreateFieldDef(**kwargs)
+
+ def testListFields_Normal(self):
+ self.AddField('Foo Field', needs_perm=permissions.EDIT_ISSUE)
+
+ request = projects_pb2.ListFieldsRequest(
+ project_name='proj', include_user_choices=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListFields, mc, request)
+
+ self.assertEqual(1, len(response.field_defs))
+ field = response.field_defs[0]
+ self.assertEqual('Foo Field', field.field_ref.field_name)
+ self.assertEqual(
+ [111, 222],
+ sorted([user_ref.user_id for user_ref in field.user_choices]))
+ self.assertEqual(
+ ['owner@example.com', 'user_222@example.com'],
+ sorted([user_ref.display_name for user_ref in field.user_choices]))
+
+ def testListFields_DontIncludeUserChoices(self):
+ self.AddField('Foo Field', needs_perm=permissions.EDIT_ISSUE)
+
+ request = projects_pb2.ListFieldsRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListFields, mc, request)
+
+ self.assertEqual(1, len(response.field_defs))
+ field = response.field_defs[0]
+ self.assertEqual(0, len(field.user_choices))
+
+ def testListFields_IncludeAdminInfo(self):
+ self.AddField('Foo Field', needs_perm=permissions.EDIT_ISSUE, is_niche=True,
+ applic_type='Foo Applic Type')
+
+ request = projects_pb2.ListFieldsRequest(
+ project_name='proj', include_admin_info=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListFields, mc, request)
+
+ self.assertEqual(1, len(response.field_defs))
+ field = response.field_defs[0]
+ self.assertEqual('Foo Field', field.field_ref.field_name)
+ self.assertEqual(True, field.is_niche)
+ self.assertEqual('Foo Applic Type', field.applicable_type)
+
+ def testListFields_EnumFieldChoices(self):
+ self.AddField('Type', field_type_str='ENUM_TYPE')
+
+ request = projects_pb2.ListFieldsRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListFields, mc, request)
+
+ self.assertEqual(1, len(response.field_defs))
+ field = response.field_defs[0]
+ self.assertEqual('Type', field.field_ref.field_name)
+ self.assertEqual(
+ ['Defect', 'Enhancement', 'Task', 'Other'],
+ [label.label for label in field.enum_choices])
+
+ def testListFields_CustomPermission(self):
+ self.AddField('Foo Field', needs_perm='FooPerm')
+ self.project.extra_perms = [
+ project_pb2.Project.ExtraPerms(
+ member_id=111,
+ perms=['UnrelatedPerm']),
+ project_pb2.Project.ExtraPerms(
+ member_id=222,
+ perms=['FooPerm'])]
+
+ request = projects_pb2.ListFieldsRequest(
+ project_name='proj', include_user_choices=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListFields, mc, request)
+
+ self.assertEqual(1, len(response.field_defs))
+ field = response.field_defs[0]
+ self.assertEqual('Foo Field', field.field_ref.field_name)
+ self.assertEqual(
+ [222],
+ sorted([user_ref.user_id for user_ref in field.user_choices]))
+ self.assertEqual(
+ ['user_222@example.com'],
+ sorted([user_ref.display_name for user_ref in field.user_choices]))
+
+ def testListFields_IndirectPermission(self):
+ """Test that the permissions of effective ids are also considered."""
+ self.AddField('Foo Field', needs_perm='FooPerm')
+ self.project.contributor_ids.extend([999])
+ self.project.extra_perms = [
+ project_pb2.Project.ExtraPerms(
+ member_id=999,
+ perms=['FooPerm', 'BarPerm'])]
+
+ request = projects_pb2.ListFieldsRequest(
+ project_name='proj', include_user_choices=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(
+ self.projects_svcr.ListFields, mc, request)
+
+ self.assertEqual(1, len(response.field_defs))
+ field = response.field_defs[0]
+ self.assertEqual('Foo Field', field.field_ref.field_name)
+ # Users 111 and 444 are members of group 999, which has the needed
+ # permission.
+ self.assertEqual(
+ [111, 444, 999],
+ sorted([user_ref.user_id for user_ref in field.user_choices]))
+ self.assertEqual(
+ ['group999@googlegroups.com', 'owner@example.com',
+ 'user_444@example.com'],
+ sorted([user_ref.display_name for user_ref in field.user_choices]))
+
+ def testListFields_TwiceIndirectPermission(self):
+ """Test that only direct memberships are considered."""
+ self.AddField('Foo Field', needs_perm='FooPerm')
+ # User group 777 has members: user_666 and group 999.
+ self.project.contributor_ids.extend([777])
+ self.project.contributor_ids.extend([999])
+ self.project.extra_perms = [
+ project_pb2.Project.ExtraPerms(
+ member_id=777, perms=['FooPerm', 'BarPerm'])
+ ]
+
+ request = projects_pb2.ListFieldsRequest(
+ project_name='proj', include_user_choices=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.projects_svcr.ListFields, mc, request)
+
+ self.assertEqual(1, len(response.field_defs))
+ field = response.field_defs[0]
+ self.assertEqual('Foo Field', field.field_ref.field_name)
+ self.assertEqual(
+ [666, 777, 999],
+ sorted([user_ref.user_id for user_ref in field.user_choices]))
+ self.assertEqual(
+ [
+ 'group777@googlegroups.com', 'group999@googlegroups.com',
+ 'user_666@example.com'
+ ], sorted([user_ref.display_name for user_ref in field.user_choices]))
+
+ def testListFields_NoPermissionsNeeded(self):
+ self.AddField('Foo Field')
+
+ request = projects_pb2.ListFieldsRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListFields, mc, request)
+
+ self.assertEqual(1, len(response.field_defs))
+ field = response.field_defs[0]
+ self.assertEqual('Foo Field', field.field_ref.field_name)
+
+ def testListFields_MultipleFields(self):
+ self.AddField('Bar Field', needs_perm=permissions.VIEW)
+ self.AddField('Foo Field', needs_perm=permissions.EDIT_ISSUE)
+
+ request = projects_pb2.ListFieldsRequest(
+ project_name='proj', include_user_choices=True)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListFields, mc, request)
+
+ self.assertEqual(2, len(response.field_defs))
+ field_defs = sorted(
+ response.field_defs, key=lambda field: field.field_ref.field_name)
+
+ self.assertEqual(
+ ['Bar Field', 'Foo Field'],
+ [field.field_ref.field_name for field in field_defs])
+ self.assertEqual(
+ [[111, 222, 333],
+ [111, 222]],
+ [sorted(user_ref.user_id for user_ref in field.user_choices)
+ for field in field_defs])
+ self.assertEqual(
+ [['owner@example.com', 'user_222@example.com', 'user_333@example.com'],
+ ['owner@example.com', 'user_222@example.com']],
+ [sorted(user_ref.display_name for user_ref in field.user_choices)
+ for field in field_defs])
+
+ def testListFields_NoFields(self):
+ request = projects_pb2.ListFieldsRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.ListFields, mc, request)
+
+ self.assertEqual(0, len(response.field_defs))
+
+ def testGetLabelOptions_Normal(self):
+ request = projects_pb2.GetLabelOptionsRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.GetLabelOptions, mc, request)
+
+ expected_label_names = [
+ label[0] for label in tracker_constants.DEFAULT_WELL_KNOWN_LABELS]
+ expected_label_names += [
+ 'Restrict-View-EditIssue', 'Restrict-AddIssueComment-EditIssue',
+ 'Restrict-View-CoreTeam']
+ self.assertEqual(
+ sorted(expected_label_names),
+ sorted(label.label for label in response.label_options))
+
+ def testGetLabelOptions_CustomPermissions(self):
+ self.project.extra_perms = [
+ project_pb2.Project.ExtraPerms(
+ member_id=222,
+ perms=['FooPerm', 'BarPerm'])]
+
+ request = projects_pb2.GetLabelOptionsRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.GetLabelOptions, mc, request)
+
+ expected_label_names = [
+ label[0] for label in tracker_constants.DEFAULT_WELL_KNOWN_LABELS]
+ expected_label_names += [
+ 'Restrict-View-EditIssue', 'Restrict-AddIssueComment-EditIssue']
+ expected_label_names += [
+ 'Restrict-%s-%s' % (std_perm, custom_perm)
+ for std_perm in permissions.STANDARD_ISSUE_PERMISSIONS
+ for custom_perm in ('BarPerm', 'FooPerm')]
+
+ self.assertEqual(
+ sorted(expected_label_names),
+ sorted(label.label for label in response.label_options))
+
+ def testGetLabelOptions_FieldMasksLabel(self):
+ self.AddField('Type', field_type_str='ENUM_TYPE')
+
+ request = projects_pb2.GetLabelOptionsRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.GetLabelOptions, mc, request)
+
+ expected_label_names = [
+ label[0] for label in tracker_constants.DEFAULT_WELL_KNOWN_LABELS
+ if not label[0].startswith('Type-')
+ ]
+ expected_label_names += [
+ 'Restrict-View-EditIssue', 'Restrict-AddIssueComment-EditIssue',
+ 'Restrict-View-CoreTeam']
+ self.assertEqual(
+ sorted(expected_label_names),
+ sorted(label.label for label in response.label_options))
+
+ def CallGetStarCount(self):
+ request = projects_pb2.GetProjectStarCountRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ response = self.CallWrapped(
+ self.projects_svcr.GetProjectStarCount, mc, request)
+ return response.star_count
+
+ def CallStar(self, requester='owner@example.com', starred=True):
+ request = projects_pb2.StarProjectRequest(
+ project_name='proj', starred=starred)
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester=requester)
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(
+ self.projects_svcr.StarProject, mc, request)
+ return response.star_count
+
+ def testStarCount_Normal(self):
+ self.assertEqual(0, self.CallGetStarCount())
+ self.assertEqual(1, self.CallStar())
+ self.assertEqual(1, self.CallGetStarCount())
+
+ def testStarCount_StarTwiceSameUser(self):
+ self.assertEqual(1, self.CallStar())
+ self.assertEqual(1, self.CallStar())
+ self.assertEqual(1, self.CallGetStarCount())
+
+ def testStarCount_StarTwiceDifferentUser(self):
+ self.assertEqual(1, self.CallStar())
+ self.assertEqual(2, self.CallStar(requester='user_222@example.com'))
+ self.assertEqual(2, self.CallGetStarCount())
+
+ def testStarCount_RemoveStarTwiceSameUser(self):
+ self.assertEqual(1, self.CallStar())
+ self.assertEqual(1, self.CallGetStarCount())
+
+ self.assertEqual(0, self.CallStar(starred=False))
+ self.assertEqual(0, self.CallStar(starred=False))
+ self.assertEqual(0, self.CallGetStarCount())
+
+ def testStarCount_RemoveStarTwiceDifferentUser(self):
+ self.assertEqual(1, self.CallStar())
+ self.assertEqual(2, self.CallStar(requester='user_222@example.com'))
+ self.assertEqual(2, self.CallGetStarCount())
+
+ self.assertEqual(1, self.CallStar(starred=False))
+ self.assertEqual(
+ 0, self.CallStar(requester='user_222@example.com', starred=False))
+ self.assertEqual(0, self.CallGetStarCount())
+
+ def testCheckProjectName_OK(self):
+ """We can check a project name."""
+ request = projects_pb2.CheckProjectNameRequest(project_name='foo')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(
+ self.projects_svcr.CheckProjectName, mc, request)
+
+ self.assertEqual('', response.error)
+
+ def testCheckProjectName_InvalidProjectName(self):
+ """We reject an invalid project name."""
+ request = projects_pb2.CheckProjectNameRequest(project_name='Foo')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(
+ self.projects_svcr.CheckProjectName, mc, request)
+
+ self.assertNotEqual('', response.error)
+
+ def testCheckProjectName_NotAllowed(self):
+ """Users that can't create a project shouldn't get any information."""
+ request = projects_pb2.CheckProjectNameRequest(project_name='Foo')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='owner@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.projects_svcr.CheckProjectName, mc, request)
+
+ def testCheckProjectName_ProjectAlreadyExists(self):
+ """There is already a project with that name."""
+ request = projects_pb2.CheckProjectNameRequest(project_name='proj')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(
+ self.projects_svcr.CheckProjectName, mc, request)
+
+ self.assertNotEqual('', response.error)
+
+ def testCheckComponentName_OK(self):
+ request = projects_pb2.CheckComponentNameRequest(
+ project_name='proj',
+ component_name='Component')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(
+ self.projects_svcr.CheckComponentName, mc, request)
+
+ self.assertEqual('', response.error)
+
+ def testCheckComponentName_ParentComponentOK(self):
+ self.services.config.CreateComponentDef(
+ self.cnxn, self.project.project_id, 'Component', 'Docstring',
+ False, [], [], 0, 111, [])
+ request = projects_pb2.CheckComponentNameRequest(
+ project_name='proj',
+ parent_path='Component',
+ component_name='Path')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(
+ self.projects_svcr.CheckComponentName, mc, request)
+
+ self.assertEqual('', response.error)
+
+ def testCheckComponentName_InvalidComponentName(self):
+ request = projects_pb2.CheckComponentNameRequest(
+ project_name='proj',
+ component_name='Component-')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(
+ self.projects_svcr.CheckComponentName, mc, request)
+
+ self.assertNotEqual('', response.error)
+
+ def testCheckComponentName_ComponentAlreadyExists(self):
+ self.services.config.CreateComponentDef(
+ self.cnxn, self.project.project_id, 'Component', 'Docstring',
+ False, [], [], 0, 111, [])
+ request = projects_pb2.CheckComponentNameRequest(
+ project_name='proj',
+ component_name='Component')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(
+ self.projects_svcr.CheckComponentName, mc, request)
+
+ self.assertNotEqual('', response.error)
+
+ def testCheckComponentName_NotAllowedToViewProject(self):
+ self.project.access = project_pb2.ProjectAccess.MEMBERS_ONLY
+ request = projects_pb2.CheckComponentNameRequest(
+ project_name='proj',
+ parent_path='Component',
+ component_name='Path')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='user_444@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.projects_svcr.CheckComponentName, mc, request)
+
+ def testCheckComponentName_ParentComponentDoesntExist(self):
+ request = projects_pb2.CheckComponentNameRequest(
+ project_name='proj',
+ parent_path='Component',
+ component_name='Path')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ with self.assertRaises(exceptions.NoSuchComponentException):
+ self.CallWrapped(self.projects_svcr.CheckComponentName, mc, request)
+
+ def testCheckFieldName_OK(self):
+ request = projects_pb2.CheckFieldNameRequest(
+ project_name='proj',
+ field_name='Foo')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
+ self.assertEqual('', response.error)
+
+ def testCheckFieldName_InvalidFieldName(self):
+ request = projects_pb2.CheckFieldNameRequest(
+ project_name='proj',
+ field_name='**Foo**')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
+ self.assertNotEqual('', response.error)
+
+ def testCheckFieldName_InvalidFieldName_ApproverSuffix(self):
+ request = projects_pb2.CheckFieldNameRequest(
+ project_name='proj',
+ field_name='Foo-aPprOver')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
+ self.assertNotEqual('', response.error)
+
+ def testCheckFieldName_FieldAlreadyExists(self):
+ self.AddField('Foo')
+ request = projects_pb2.CheckFieldNameRequest(
+ project_name='proj',
+ field_name='Foo')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
+ self.assertNotEqual('', response.error)
+
+ def testCheckFieldName_FieldIsPrefixOfAnother(self):
+ self.AddField('Foo-Bar')
+ request = projects_pb2.CheckFieldNameRequest(
+ project_name='proj',
+ field_name='Foo')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
+ self.assertNotEqual('', response.error)
+
+ def testCheckFieldName_AnotherFieldIsPrefix(self):
+ self.AddField('Foo')
+ request = projects_pb2.CheckFieldNameRequest(
+ project_name='proj',
+ field_name='Foo-Bar')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='admin@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ response = self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)
+ self.assertNotEqual('', response.error)
+
+ def testCheckFieldName_NotAllowedToViewProject(self):
+ self.project.access = project_pb2.ProjectAccess.MEMBERS_ONLY
+ request = projects_pb2.CheckFieldNameRequest(
+ project_name='proj',
+ field_name='Foo')
+ mc = monorailcontext.MonorailContext(
+ self.services, cnxn=self.cnxn, requester='user_444@example.com')
+ mc.LookupLoggedInUserPerms(self.project)
+ with self.assertRaises(permissions.PermissionException):
+ self.CallWrapped(self.projects_svcr.CheckFieldName, mc, request)