Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/v3/test/projects_servicer_test.py b/api/v3/test/projects_servicer_test.py
new file mode 100644
index 0000000..83aa8ab
--- /dev/null
+++ b/api/v3/test/projects_servicer_test.py
@@ -0,0 +1,245 @@
+# Copyright 2020 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 hotlists servicer."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import unittest
+import mock
+import logging
+
+from google.protobuf import timestamp_pb2
+from google.protobuf import empty_pb2
+
+from api import resource_name_converters as rnc
+from api.v3 import projects_servicer
+from api.v3 import converters
+from api.v3.api_proto import projects_pb2
+from api.v3.api_proto import project_objects_pb2
+from api.v3.api_proto import issue_objects_pb2
+from framework import exceptions
+from framework import monorailcontext
+from framework import permissions
+from testing import fake
+from services import service_manager
+
+from google.appengine.ext import testbed
+
+class ProjectsServicerTest(unittest.TestCase):
+
+  def setUp(self):
+    # memcache and datastore needed for generating page tokens.
+    self.testbed = testbed.Testbed()
+    self.testbed.activate()
+    self.testbed.init_memcache_stub()
+    self.testbed.init_datastore_v3_stub()
+
+    self.cnxn = fake.MonorailConnection()
+    self.services = service_manager.Services(
+        features=fake.FeaturesService(),
+        issue=fake.IssueService(),
+        project=fake.ProjectService(),
+        config=fake.ConfigService(),
+        user=fake.UserService(),
+        template=fake.TemplateService(),
+        usergroup=fake.UserGroupService())
+    self.projects_svcr = projects_servicer.ProjectsServicer(
+        self.services, make_rate_limiter=False)
+
+    self.user_1 = self.services.user.TestAddUser('user_111@example.com', 111)
+
+    self.project_1 = self.services.project.TestAddProject(
+        'proj', project_id=789)
+    self.template_1 = self.services.template.TestAddIssueTemplateDef(
+        123, 789, 'template_1_name', content='foo bar', summary='foo')
+    self.project_1_resource_name = 'projects/proj'
+    self.converter = None
+
+  def CallWrapped(self, wrapped_handler, mc, *args, **kwargs):
+    self.converter = converters.Converter(mc, self.services)
+    self.projects_svcr.converter = self.converter
+    return wrapped_handler.wrapped(self.projects_svcr, mc, *args, **kwargs)
+
+  def testListIssueTemplates(self):
+    request = projects_pb2.ListIssueTemplatesRequest(
+        parent=self.project_1_resource_name)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    response = self.CallWrapped(
+        self.projects_svcr.ListIssueTemplates, mc, request)
+
+    expected_issue = issue_objects_pb2.Issue(
+        summary=self.template_1.summary,
+        state=issue_objects_pb2.IssueContentState.Value('ACTIVE'),
+        status=issue_objects_pb2.Issue.StatusValue(
+            status=self.template_1.status,
+            derivation=issue_objects_pb2.Derivation.Value('EXPLICIT')))
+    expected_template = project_objects_pb2.IssueTemplate(
+        name='projects/{}/templates/{}'.format(
+            self.project_1.project_name, self.template_1.template_id),
+        display_name=self.template_1.name,
+        issue=expected_issue,
+        summary_must_be_edited=False,
+        template_privacy=project_objects_pb2.IssueTemplate.TemplatePrivacy
+        .Value('PUBLIC'),
+        default_owner=project_objects_pb2.IssueTemplate.DefaultOwner.Value(
+            'DEFAULT_OWNER_UNSPECIFIED'),
+        component_required=False)
+
+    self.assertEqual(
+        response,
+        projects_pb2.ListIssueTemplatesResponse(templates=[expected_template]))
+
+  @mock.patch('api.v3.api_constants.MAX_COMPONENTS_PER_PAGE', 3)
+  def testListComponentDefs(self):
+    project = self.services.project.TestAddProject(
+        'greece', project_id=987, owner_ids=[self.user_1.user_id])
+    config = fake.MakeTestConfig(project.project_id, [], [])
+    cd_1 = fake.MakeTestComponentDef(project.project_id, 1, path='Circe')
+    cd_2 = fake.MakeTestComponentDef(project.project_id, 2, path='Achilles')
+    cd_3 = fake.MakeTestComponentDef(project.project_id, 3, path='Patroclus')
+    cd_4 = fake.MakeTestComponentDef(project.project_id, 3, path='Galatea')
+    config.component_defs = [cd_1, cd_2, cd_3, cd_4]
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+
+    request = projects_pb2.ListComponentDefsRequest(parent='projects/greece')
+    response_1 = self.CallWrapped(
+        self.projects_svcr.ListComponentDefs, mc, request)
+    expected_cds_1 = self.converter.ConvertComponentDefs(
+        [cd_1, cd_2, cd_3], project.project_id)
+    self.assertEqual(list(response_1.component_defs), expected_cds_1)
+
+    request = projects_pb2.ListComponentDefsRequest(
+        parent='projects/greece', page_token=response_1.next_page_token)
+    response_2 = self.CallWrapped(
+        self.projects_svcr.ListComponentDefs, mc, request)
+    expected_cds_2 = self.converter.ConvertComponentDefs(
+        [cd_4], project.project_id)
+    self.assertEqual(list(response_2.component_defs), expected_cds_2)
+
+  @mock.patch('api.v3.api_constants.MAX_COMPONENTS_PER_PAGE', 2)
+  def testListComponentDefs_PaginateAndMaxSizeCap(self):
+    project = self.services.project.TestAddProject(
+        'greece', project_id=987, owner_ids=[self.user_1.user_id])
+    config = fake.MakeTestConfig(project.project_id, [], [])
+    cd_1 = fake.MakeTestComponentDef(project.project_id, 1, path='Circe')
+    cd_2 = fake.MakeTestComponentDef(project.project_id, 2, path='Achilles')
+    cd_3 = fake.MakeTestComponentDef(project.project_id, 3, path='Patroclus')
+    cd_4 = fake.MakeTestComponentDef(project.project_id, 4, path='Galatea')
+    cd_5 = fake.MakeTestComponentDef(project.project_id, 5, path='Briseis')
+    config.component_defs = [cd_1, cd_2, cd_3, cd_4, cd_5]
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+
+    request = projects_pb2.ListComponentDefsRequest(
+        parent='projects/greece', page_size=3)
+    response_1 = self.CallWrapped(
+        self.projects_svcr.ListComponentDefs, mc, request)
+    expected_cds_1 = self.converter.ConvertComponentDefs(
+        [cd_1, cd_2], project.project_id)
+    self.assertEqual(list(response_1.component_defs), expected_cds_1)
+
+    request = projects_pb2.ListComponentDefsRequest(
+        parent='projects/greece', page_size=3,
+        page_token=response_1.next_page_token)
+    response_2 = self.CallWrapped(
+        self.projects_svcr.ListComponentDefs, mc, request)
+    expected_cds_2 = self.converter.ConvertComponentDefs(
+        [cd_3, cd_4], project.project_id)
+    self.assertEqual(list(response_2.component_defs), expected_cds_2)
+
+    request = projects_pb2.ListComponentDefsRequest(
+        parent='projects/greece', page_size=3,
+        page_token=response_2.next_page_token)
+    response_3 = self.CallWrapped(
+        self.projects_svcr.ListComponentDefs, mc, request)
+    expected_cds_3 = self.converter.ConvertComponentDefs(
+        [cd_5], project.project_id)
+    self.assertEqual(response_3, projects_pb2.ListComponentDefsResponse(
+        component_defs=expected_cds_3))
+
+  @mock.patch('time.time')
+  def testCreateComponentDef(self, mockTime):
+    now = 123
+    mockTime.return_value = now
+
+    user_1 = self.services.user.TestAddUser('achilles@test.com', 981)
+    self.services.user.TestAddUser('patroclus@test.com', 982)
+    self.services.user.TestAddUser('circe@test.com', 983)
+
+    project = self.services.project.TestAddProject(
+        'chicken', project_id=987, owner_ids=[user_1.user_id])
+    config = fake.MakeTestConfig(project.project_id, [], [])
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    expected = project_objects_pb2.ComponentDef(
+        value='circe',
+        docstring='You threw me to the crows',
+        admins=['users/983'],
+        ccs=['users/981', 'users/982'],
+        labels=['more-soup', 'beach-day'],
+    )
+    request = projects_pb2.CreateComponentDefRequest(
+        parent='projects/chicken', component_def=expected)
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=user_1.email)
+    response = self.CallWrapped(
+        self.projects_svcr.CreateComponentDef, mc, request)
+
+    self.assertEqual(1, len(config.component_defs))
+    expected.name = 'projects/chicken/componentDefs/%d' % config.component_defs[
+        0].component_id
+    expected.state = project_objects_pb2.ComponentDef.ComponentDefState.Value(
+        'ACTIVE')
+    expected.creator = 'users/981'
+    expected.create_time.FromSeconds(now)
+    expected.modify_time.FromSeconds(0)
+    self.assertEqual(response, expected)
+
+  def testDeleteComponentDef(self):
+    user_1 = self.services.user.TestAddUser('achilles@test.com', 981)
+    project = self.services.project.TestAddProject(
+        'chicken', project_id=987, owner_ids=[user_1.user_id])
+    config = fake.MakeTestConfig(project.project_id, [], [])
+    component_def = fake.MakeTestComponentDef(
+        project.project_id, 1, path='Chickens>Dickens')
+    config.component_defs = [component_def]
+    self.services.config.StoreConfig(self.cnxn, config)
+
+    request = projects_pb2.DeleteComponentDefRequest(
+        name='projects/chicken/componentDefs/1')
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=user_1.email)
+    actual = self.CallWrapped(
+        self.projects_svcr.DeleteComponentDef, mc, request)
+    self.assertEqual(actual, empty_pb2.Empty())
+
+    self.assertEqual(config.component_defs, [])
+
+  @mock.patch('project.project_helpers.GetThumbnailUrl')
+  def testListProjects(self, mock_GetThumbnailUrl):
+    mock_GetThumbnailUrl.return_value = 'xyz'
+
+    request = projects_pb2.ListProjectsRequest()
+
+    mc = monorailcontext.MonorailContext(
+        self.services, cnxn=self.cnxn, requester=self.user_1.email)
+    response = self.CallWrapped(self.projects_svcr.ListProjects, mc, request)
+
+    expected_project = project_objects_pb2.Project(
+        name=self.project_1_resource_name,
+        display_name=self.project_1.project_name,
+        summary=self.project_1.summary,
+        thumbnail_url='xyz')
+
+    self.assertEqual(
+        response,
+        projects_pb2.ListProjectsResponse(projects=[expected_project]))