Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/services/test/template_svc_test.py b/services/test/template_svc_test.py
new file mode 100644
index 0000000..964722d
--- /dev/null
+++ b/services/test/template_svc_test.py
@@ -0,0 +1,471 @@
+# 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
+
+"""Unit tests for services.template_svc module."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import mock
+import unittest
+
+from mock import Mock, patch
+
+from proto import tracker_pb2
+from services import template_svc
+from testing import fake
+from testing import testing_helpers
+from tracker import tracker_bizobj
+from tracker import tracker_constants
+
+
+class TemplateSetTwoLevelCacheTest(unittest.TestCase):
+
+  def setUp(self):
+    self.ts2lc = template_svc.TemplateSetTwoLevelCache(
+        cache_manager=fake.CacheManager(),
+        template_service=Mock(spec=template_svc.TemplateService))
+    self.ts2lc.template_service.template_tbl = Mock()
+
+  def testFetchItems_Empty(self):
+    self.ts2lc.template_service.template_tbl.Select .return_value = []
+    actual = self.ts2lc.FetchItems(cnxn=None, keys=[1, 2])
+    self.assertEqual({1: [], 2: []}, actual)
+
+  def testFetchItems_Normal(self):
+    # pylint: disable=unused-argument
+    def mockSelect(cnxn, cols, project_id, order_by):
+      assert project_id in (1, 2)
+      if project_id == 1:
+        return [
+          (8, 1, 'template-8', 'content', 'summary', False, 111, 'status',
+              False, False, False),
+          (9, 1, 'template-9', 'content', 'summary', False, 111, 'status',
+              True, False, False)]
+      else:
+        return [
+          (7, 2, 'template-7', 'content', 'summary', False, 111, 'status',
+              False, False, False)]
+
+    self.ts2lc.template_service.template_tbl.Select.side_effect = mockSelect
+    actual = self.ts2lc.FetchItems(cnxn=None, keys=[1, 2])
+    expected = {
+      1: [(8, 'template-8', False), (9, 'template-9', True)],
+      2: [(7, 'template-7', False)],
+    }
+    self.assertEqual(expected, actual)
+
+
+class TemplateDefTwoLevelCacheTest(unittest.TestCase):
+
+  def setUp(self):
+    self.template_def_2lc = template_svc.TemplateDefTwoLevelCache(
+        cache_manager=fake.CacheManager(),
+        template_service=Mock(spec=template_svc.TemplateService))
+    self.template_def_2lc.template_service.template_tbl = Mock()
+    self.template_def_2lc.template_service.template2label_tbl = Mock()
+    self.template_def_2lc.template_service.template2component_tbl = Mock()
+    self.template_def_2lc.template_service.template2admin_tbl = Mock()
+    self.template_def_2lc.template_service.template2fieldvalue_tbl = Mock()
+    self.template_def_2lc.template_service.issuephasedef_tbl = Mock()
+    self.template_def_2lc.template_service.template2approvalvalue_tbl = Mock()
+
+  def testFetchItems_Empty(self):
+    self.template_def_2lc.template_service.template_tbl.Select\
+        .return_value = []
+    self.template_def_2lc.template_service.template2label_tbl.Select\
+        .return_value = []
+    self.template_def_2lc.template_service.template2component_tbl.Select\
+        .return_value = []
+    self.template_def_2lc.template_service.template2admin_tbl.Select\
+        .return_value = []
+    self.template_def_2lc.template_service.template2fieldvalue_tbl.Select\
+        .return_value = []
+    self.template_def_2lc.template_service.template2approvalvalue_tbl.Select\
+        .return_value = []
+
+    actual = self.template_def_2lc.FetchItems(cnxn=None, keys=[1, 2])
+    self.assertEqual({}, actual)
+
+  def testFetchItems_Normal(self):
+    template_9_row = (9, 1, 'template-9', 'content', 'summary',
+        False, 111, 'status',
+        False, False, False)
+    template_8_row = (8, 1, 'template-8', 'content', 'summary',
+        False, 111, 'status',
+        False, False, False)
+    template_7_row = (7, 2, 'template-7', 'content', 'summary',
+        False, 111, 'status',
+        False, False, False)
+
+    self.template_def_2lc.template_service.template_tbl.Select\
+        .return_value = [template_7_row, template_8_row,
+            template_9_row]
+    self.template_def_2lc.template_service.template2label_tbl.Select\
+        .return_value = [(9, 'label-1'), (7, 'label-2')]
+    self.template_def_2lc.template_service.template2component_tbl.Select\
+        .return_value = [(9, 13), (7, 14)]
+    self.template_def_2lc.template_service.template2admin_tbl.Select\
+        .return_value = [(9, 111), (7, 222)]
+
+    fv1_row = (15, None, 'fv-1', None, None, None, False)
+    fv2_row = (16, None, 'fv-2', None, None, None, False)
+    fv1 = tracker_bizobj.MakeFieldValue(*fv1_row)
+    fv2 = tracker_bizobj.MakeFieldValue(*fv2_row)
+    self.template_def_2lc.template_service.template2fieldvalue_tbl.Select\
+        .return_value = [((9,) + fv1_row[:-1]), ((7,) + fv2_row[:-1])]
+
+    av1_row = (17, 9, 19, 'na')
+    av2_row = (18, 7, 20, 'not_set')
+    av1 = tracker_pb2.ApprovalValue(approval_id=17, phase_id=19,
+                                    status=tracker_pb2.ApprovalStatus('NA'))
+    av2 = tracker_pb2.ApprovalValue(approval_id=18, phase_id=20,
+                                    status=tracker_pb2.ApprovalStatus(
+                                        'NOT_SET'))
+    phase1_row = (19, 'phase-1', 1)
+    phase2_row = (20, 'phase-2', 2)
+    phase1 = tracker_pb2.Phase(phase_id=19, name='phase-1', rank=1)
+    phase2 = tracker_pb2.Phase(phase_id=20, name='phase-2', rank=2)
+
+    self.template_def_2lc.template_service.template2approvalvalue_tbl.Select\
+        .return_value = [av1_row, av2_row]
+    self.template_def_2lc.template_service.issuephasedef_tbl.Select\
+        .return_value = [phase1_row, phase2_row]
+
+    actual = self.template_def_2lc.FetchItems(cnxn=None, keys=[7, 8, 9])
+    self.assertEqual(3, len(list(actual.keys())))
+    self.assertTrue(isinstance(actual[7], tracker_pb2.TemplateDef))
+    self.assertTrue(isinstance(actual[8], tracker_pb2.TemplateDef))
+    self.assertTrue(isinstance(actual[9], tracker_pb2.TemplateDef))
+
+    self.assertEqual(7, actual[7].template_id)
+    self.assertEqual(8, actual[8].template_id)
+    self.assertEqual(9, actual[9].template_id)
+
+    self.assertEqual(['label-2'], actual[7].labels)
+    self.assertEqual([], actual[8].labels)
+    self.assertEqual(['label-1'], actual[9].labels)
+
+    self.assertEqual([14], actual[7].component_ids)
+    self.assertEqual([], actual[8].component_ids)
+    self.assertEqual([13], actual[9].component_ids)
+
+    self.assertEqual([222], actual[7].admin_ids)
+    self.assertEqual([], actual[8].admin_ids)
+    self.assertEqual([111], actual[9].admin_ids)
+
+    self.assertEqual([fv2], actual[7].field_values)
+    self.assertEqual([], actual[8].field_values)
+    self.assertEqual([fv1], actual[9].field_values)
+
+    self.assertEqual([phase2], actual[7].phases)
+    self.assertEqual([], actual[8].phases)
+    self.assertEqual([phase1], actual[9].phases)
+
+    self.assertEqual([av2], actual[7].approval_values)
+    self.assertEqual([], actual[8].approval_values)
+    self.assertEqual([av1], actual[9].approval_values)
+
+
+class TemplateServiceTest(unittest.TestCase):
+
+  def setUp(self):
+    self.cnxn = Mock()
+    self.template_service = template_svc.TemplateService(fake.CacheManager())
+    self.template_service.template_set_2lc = Mock()
+    self.template_service.template_def_2lc = Mock()
+
+  def testCreateDefaultProjectTemplates_Normal(self):
+    self.template_service.CreateIssueTemplateDef = Mock()
+    self.template_service.CreateDefaultProjectTemplates(self.cnxn, 789)
+
+    expected_calls = [
+        mock.call(self.cnxn, 789, tpl['name'], tpl['content'], tpl['summary'],
+          tpl['summary_must_be_edited'], tpl['status'],
+          tpl.get('members_only', False), True, False, None, tpl['labels'],
+          [], [], [], [])
+        for tpl in tracker_constants.DEFAULT_TEMPLATES]
+    self.template_service.CreateIssueTemplateDef.assert_has_calls(
+        expected_calls, any_order=True)
+
+  def testGetTemplateByName_Normal(self):
+    """GetTemplateByName returns a template that exists."""
+    result_dict = {789: [(1, 'one', 0)]}
+    template = tracker_pb2.TemplateDef(name='one')
+    self.template_service.template_set_2lc.GetAll.return_value = (
+        result_dict, None)
+    self.template_service.template_def_2lc.GetAll.return_value = (
+        {1: template}, None)
+    actual = self.template_service.GetTemplateByName(self.cnxn, 'one', 789)
+    self.assertEqual(actual.template_id, template.template_id)
+
+  def testGetTemplateByName_NotFound(self):
+    """When GetTemplateByName is given the name of a template that does not
+    exist."""
+    result_dict = {789: [(1, 'one', 0)]}
+    template = tracker_pb2.TemplateDef(name='one')
+    self.template_service.template_set_2lc.GetAll.return_value = (
+        result_dict, None)
+    self.template_service.template_def_2lc.GetAll.return_value = (
+        {1: template}, None)
+    actual = self.template_service.GetTemplateByName(self.cnxn, 'two', 789)
+    self.assertEqual(actual, None)
+
+  def testGetTemplateById_Normal(self):
+    """GetTemplateById_Normal returns a template that exists."""
+    template = tracker_pb2.TemplateDef(template_id=1, name='one')
+    self.template_service.template_def_2lc.GetAll.return_value = (
+        {1: template}, None)
+    actual = self.template_service.GetTemplateById(self.cnxn, 1)
+    self.assertEqual(actual.template_id, template.template_id)
+
+  def testGetTemplateById_NotFound(self):
+    """When GetTemplateById is given the ID of a template that does not
+    exist."""
+    self.template_service.template_def_2lc.GetAll.return_value = (
+        {}, None)
+    actual = self.template_service.GetTemplateById(self.cnxn, 1)
+    self.assertEqual(actual, None)
+
+  def testGetTemplatesById_Normal(self):
+    """GetTemplatesById_Normal returns a template that exists."""
+    template = tracker_pb2.TemplateDef(template_id=1, name='one')
+    self.template_service.template_def_2lc.GetAll.return_value = (
+        {1: template}, None)
+    actual = self.template_service.GetTemplatesById(self.cnxn, 1)
+    self.assertEqual(actual[0].template_id, template.template_id)
+
+  def testGetTemplatesById_NotFound(self):
+    """When GetTemplatesById is given the ID of a template that does not
+    exist."""
+    self.template_service.template_def_2lc.GetAll.return_value = (
+        {}, None)
+    actual = self.template_service.GetTemplatesById(self.cnxn, 1)
+    self.assertEqual(actual, [])
+
+  def testGetProjectTemplates_Normal(self):
+    template_set = [(1, 'one', 0), (2, 'two', 1)]
+    result_dict = {789: template_set}
+    self.template_service.template_set_2lc.GetAll.return_value = (
+        result_dict, None)
+    self.template_service.template_def_2lc.GetAll.return_value = (
+        {1: tracker_pb2.TemplateDef()}, None)
+
+    self.assertEqual([tracker_pb2.TemplateDef()],
+        self.template_service.GetProjectTemplates(self.cnxn, 789))
+    self.template_service.template_set_2lc.GetAll.assert_called_once_with(
+        self.cnxn, [789])
+
+  def testExpungeProjectTemplates(self):
+    template_id_rows = [(1,), (2,)]
+    self.template_service.template_tbl.Select = Mock(
+        return_value=template_id_rows)
+    self.template_service.template2label_tbl.Delete = Mock()
+    self.template_service.template2component_tbl.Delete = Mock()
+    self.template_service.template_tbl.Delete = Mock()
+
+    self.template_service.ExpungeProjectTemplates(self.cnxn, 789)
+
+    self.template_service.template_tbl.Select\
+        .assert_called_once_with(self.cnxn, project_id=789, cols=['id'])
+    self.template_service.template2label_tbl.Delete\
+        .assert_called_once_with(self.cnxn, template_id=[1, 2])
+    self.template_service.template2component_tbl.Delete\
+        .assert_called_once_with(self.cnxn, template_id=[1, 2])
+    self.template_service.template_tbl.Delete\
+        .assert_called_once_with(self.cnxn, project_id=789)
+
+
+class CreateIssueTemplateDefTest(TemplateServiceTest):
+
+  def setUp(self):
+    super(CreateIssueTemplateDefTest, self).setUp()
+
+    self.template_service.template_tbl.InsertRow = Mock(return_value=1)
+    self.template_service.template2label_tbl.InsertRows = Mock()
+    self.template_service.template2component_tbl.InsertRows = Mock()
+    self.template_service.template2admin_tbl.InsertRows = Mock()
+    self.template_service.template2fieldvalue_tbl.InsertRows = Mock()
+    self.template_service.issuephasedef_tbl.InsertRow = Mock(return_value=81)
+    self.template_service.template2approvalvalue_tbl.InsertRows = Mock()
+    self.template_service.template_set_2lc._StrToKey = Mock(return_value=789)
+
+  def testCreateIssueTemplateDef(self):
+    fv = tracker_bizobj.MakeFieldValue(
+        1, None, 'somestring', None, None, None, False)
+    av_23 = tracker_pb2.ApprovalValue(
+        approval_id=23, phase_id=11,
+        status=tracker_pb2.ApprovalStatus.NEEDS_REVIEW)
+    av_24 = tracker_pb2.ApprovalValue(approval_id=24, phase_id=11)
+    approval_values = [av_23, av_24]
+    phases = [tracker_pb2.Phase(
+        name='Canary', rank=11, phase_id=11)]
+
+    actual_template_id = self.template_service.CreateIssueTemplateDef(
+        self.cnxn, 789, 'template', 'content', 'summary', True, 'Available',
+        True, True, True, owner_id=111, labels=['label'], component_ids=[3],
+        admin_ids=[222], field_values=[fv], phases=phases,
+        approval_values=approval_values)
+
+    self.assertEqual(1, actual_template_id)
+
+    self.template_service.template_tbl.InsertRow\
+        .assert_called_once_with(self.cnxn, project_id=789, name='template',
+            content='content', summary='summary', summary_must_be_edited=True,
+            owner_id=111, status='Available', members_only=True,
+            owner_defaults_to_member=True, component_required=True,
+            commit=False)
+    self.template_service.template2label_tbl.InsertRows\
+        .assert_called_once_with(self.cnxn, template_svc.TEMPLATE2LABEL_COLS,
+            [(1, 'label')], commit=False)
+    self.template_service.template2component_tbl.InsertRows\
+        .assert_called_once_with(self.cnxn,
+            template_svc.TEMPLATE2COMPONENT_COLS,
+            [(1, 3)], commit=False)
+    self.template_service.template2admin_tbl.InsertRows\
+        .assert_called_once_with(self.cnxn, template_svc.TEMPLATE2ADMIN_COLS,
+            [(1, 222)], commit=False)
+    self.template_service.template2fieldvalue_tbl.InsertRows\
+        .assert_called_once_with(self.cnxn,
+            template_svc.TEMPLATE2FIELDVALUE_COLS,
+            [(1, 1, None, 'somestring', None, None, None)], commit=False)
+    self.template_service.issuephasedef_tbl.InsertRow\
+        .assert_called_once_with(self.cnxn, name='Canary',
+            rank=11, commit=False)
+    self.template_service.template2approvalvalue_tbl.InsertRows\
+        .assert_called_once_with(self.cnxn,
+            template_svc.TEMPLATE2APPROVALVALUE_COLS,
+            [(23, 1, 81, 'needs_review'), (24, 1, 81, 'not_set')], commit=False)
+    self.cnxn.Commit.assert_called_once_with()
+    self.template_service.template_set_2lc.InvalidateKeys\
+        .assert_called_once_with(self.cnxn, [789])
+
+
+class UpdateIssueTemplateDefTest(TemplateServiceTest):
+
+  def setUp(self):
+    super(UpdateIssueTemplateDefTest, self).setUp()
+
+    self.template_service.template_tbl.Update = Mock()
+    self.template_service.template2label_tbl.Delete = Mock()
+    self.template_service.template2label_tbl.InsertRows = Mock()
+    self.template_service.template2admin_tbl.Delete = Mock()
+    self.template_service.template2admin_tbl.InsertRows = Mock()
+    self.template_service.template2approvalvalue_tbl.Delete = Mock()
+    self.template_service.issuephasedef_tbl.InsertRow = Mock(return_value=1)
+    self.template_service.template2approvalvalue_tbl.InsertRows = Mock()
+    self.template_service.template_set_2lc._StrToKey = Mock(return_value=789)
+
+  def testUpdateIssueTemplateDef(self):
+    av_20 = tracker_pb2.ApprovalValue(approval_id=20, phase_id=11)
+    av_21 = tracker_pb2.ApprovalValue(approval_id=21, phase_id=11)
+    approval_values = [av_20, av_21]
+    phases = [tracker_pb2.Phase(
+        name='Canary', phase_id=11, rank=11)]
+    self.template_service.UpdateIssueTemplateDef(
+        self.cnxn, 789, 1, content='content', summary='summary',
+        component_required=True, labels=[], admin_ids=[111],
+        phases=phases, approval_values=approval_values)
+
+    new_values = dict(
+        content='content', summary='summary', component_required=True)
+    self.template_service.template_tbl.Update\
+        .assert_called_once_with(self.cnxn, new_values, id=1, commit=False)
+    self.template_service.template2label_tbl.Delete\
+        .assert_called_once_with(self.cnxn, template_id=1, commit=False)
+    self.template_service.template2label_tbl.InsertRows\
+        .assert_called_once_with(self.cnxn, template_svc.TEMPLATE2LABEL_COLS,
+            [], commit=False)
+    self.template_service.template2admin_tbl.Delete\
+        .assert_called_once_with(self.cnxn, template_id=1, commit=False)
+    self.template_service.template2admin_tbl.InsertRows\
+        .assert_called_once_with(self.cnxn, template_svc.TEMPLATE2ADMIN_COLS,
+            [(1, 111)], commit=False)
+    self.template_service.template2approvalvalue_tbl.Delete\
+        .assert_called_once_with(self.cnxn, template_id=1, commit=False)
+    self.template_service.issuephasedef_tbl.InsertRow\
+        .assert_called_once_with(self.cnxn, name='Canary',
+            rank=11, commit=False)
+    self.template_service.template2approvalvalue_tbl.InsertRows\
+        .assert_called_once_with(self.cnxn,
+            template_svc.TEMPLATE2APPROVALVALUE_COLS,
+            [(20, 1, 1, 'not_set'), (21, 1, 1, 'not_set')], commit=False)
+    self.cnxn.Commit.assert_called_once_with()
+    self.template_service.template_set_2lc.InvalidateKeys\
+        .assert_called_once_with(self.cnxn, [789])
+    self.template_service.template_def_2lc.InvalidateKeys\
+        .assert_called_once_with(self.cnxn, [1])
+
+
+class DeleteTemplateTest(TemplateServiceTest):
+
+  def testDeleteIssueTemplateDef(self):
+    self.template_service.template2label_tbl.Delete = Mock()
+    self.template_service.template2component_tbl.Delete = Mock()
+    self.template_service.template2admin_tbl.Delete = Mock()
+    self.template_service.template2fieldvalue_tbl.Delete = Mock()
+    self.template_service.template2approvalvalue_tbl.Delete = Mock()
+    self.template_service.template_tbl.Delete = Mock()
+    self.template_service.template_set_2lc._StrToKey = Mock(return_value=789)
+
+    self.template_service.DeleteIssueTemplateDef(self.cnxn, 789, 1)
+
+    self.template_service.template2label_tbl.Delete\
+        .assert_called_once_with(self.cnxn, template_id=1, commit=False)
+    self.template_service.template2component_tbl.Delete\
+        .assert_called_once_with(self.cnxn, template_id=1, commit=False)
+    self.template_service.template2admin_tbl.Delete\
+        .assert_called_once_with(self.cnxn, template_id=1, commit=False)
+    self.template_service.template2fieldvalue_tbl.Delete\
+        .assert_called_once_with(self.cnxn, template_id=1, commit=False)
+    self.template_service.template2approvalvalue_tbl.Delete\
+        .assert_called_once_with(self.cnxn, template_id=1, commit=False)
+    self.template_service.template_tbl.Delete\
+        .assert_called_once_with(self.cnxn, id=1, commit=False)
+    self.cnxn.Commit.assert_called_once_with()
+    self.template_service.template_set_2lc.InvalidateKeys\
+        .assert_called_once_with(self.cnxn, [789])
+    self.template_service.template_def_2lc.InvalidateKeys\
+        .assert_called_once_with(self.cnxn, [1])
+
+
+class ExpungeUsersInTemplatesTest(TemplateServiceTest):
+
+  def setUp(self):
+    super(ExpungeUsersInTemplatesTest, self).setUp()
+
+    self.template_service.template2admin_tbl.Delete = Mock()
+    self.template_service.template2fieldvalue_tbl.Delete = Mock()
+    self.template_service.template_tbl.Update = Mock()
+
+  def testExpungeUsersInTemplates(self):
+    user_ids = [111, 222]
+    self.template_service.ExpungeUsersInTemplates(self.cnxn, user_ids, limit=60)
+
+    self.template_service.template2admin_tbl.Delete.assert_called_once_with(
+            self.cnxn, admin_id=user_ids, commit=False, limit=60)
+    self.template_service.template2fieldvalue_tbl\
+        .Delete.assert_called_once_with(
+            self.cnxn, user_id=user_ids, commit=False, limit=60)
+    self.template_service.template_tbl.Update.assert_called_once_with(
+        self.cnxn, {'owner_id': None}, owner_id=user_ids, commit=False)
+
+
+class UnpackTemplateTest(unittest.TestCase):
+
+  def testEmpty(self):
+    with self.assertRaises(ValueError):
+      template_svc.UnpackTemplate(())
+
+  def testNormal(self):
+    row = (1, 2, 'name', 'content', 'summary', False, 3, 'status', False,
+        False, False)
+    self.assertEqual(
+        tracker_pb2.TemplateDef(template_id=1, name='name',
+          content='content', summary='summary', summary_must_be_edited=False,
+          owner_id=3, status='status', members_only=False,
+          owner_defaults_to_member=False,
+          component_required=False),
+        template_svc.UnpackTemplate(row))