Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/api/test/resource_name_converters_test.py b/api/test/resource_name_converters_test.py
new file mode 100644
index 0000000..e9ca437
--- /dev/null
+++ b/api/test/resource_name_converters_test.py
@@ -0,0 +1,773 @@
+# 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 converting between resource names and external ids."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+from mock import Mock, patch
+import unittest
+import re
+
+from api import resource_name_converters as rnc
+from framework import exceptions
+from testing import fake
+from services import service_manager
+from proto import tracker_pb2
+
+class ResourceNameConverterTest(unittest.TestCase):
+
+  def setUp(self):
+    self.services = service_manager.Services(
+        issue=fake.IssueService(),
+        project=fake.ProjectService(),
+        user=fake.UserService(),
+        features=fake.FeaturesService(),
+        template=fake.TemplateService(),
+        config=fake.ConfigService())
+    self.cnxn = fake.MonorailConnection()
+    self.PAST_TIME = 12345
+    self.project_1 = self.services.project.TestAddProject(
+        'proj', project_id=789)
+    self.project_2 = self.services.project.TestAddProject(
+        'goose', project_id=788)
+    self.dne_project_id = 1999
+
+    self.issue_1 = fake.MakeTestIssue(
+        self.project_1.project_id, 1, 'sum', 'New', 111,
+        project_name=self.project_1.project_name)
+    self.issue_2 = fake.MakeTestIssue(
+        self.project_2.project_id, 2, 'sum', 'New', 111,
+        project_name=self.project_2.project_name)
+    self.services.issue.TestAddIssue(self.issue_1)
+    self.services.issue.TestAddIssue(self.issue_2)
+
+    self.user_1 = self.services.user.TestAddUser('user_111@example.com', 111)
+    self.user_2 = self.services.user.TestAddUser('user_222@example.com', 222)
+    self.user_3 = self.services.user.TestAddUser('user_333@example.com', 333)
+
+    hotlist_items = [
+        (self.issue_1.issue_id, 9, self.user_2.user_id, self.PAST_TIME, 'note'),
+        (self.issue_2.issue_id, 1, self.user_1.user_id, self.PAST_TIME, 'note')]
+    self.hotlist_1 = self.services.features.TestAddHotlist(
+        'HotlistName', owner_ids=[], editor_ids=[],
+        hotlist_item_fields=hotlist_items)
+
+    self.template_1 = self.services.template.TestAddIssueTemplateDef(
+        1, self.project_1.project_id, 'template_1_name')
+    self.template_2 = self.services.template.TestAddIssueTemplateDef(
+        2, self.project_2.project_id, 'template_2_name')
+    self.dne_template_id = 3
+
+    self.field_def_1_name = 'test_field'
+    self.field_def_1 = self.services.config.CreateFieldDef(
+        self.cnxn, self.project_1.project_id, self.field_def_1_name, 'STR_TYPE',
+        None, None, None, None, None, None, None, None, None, None, None, None,
+        None, None, [], [])
+    self.approval_def_1_name = 'approval_field_1'
+    self.approval_def_1_id = self.services.config.CreateFieldDef(
+        self.cnxn, self.project_1.project_id, self.approval_def_1_name,
+        'APPROVAL_TYPE', None, None, None, None, None, None, None, None, None,
+        None, None, None, None, None, [], [])
+    self.component_def_1_path = 'Foo'
+    self.component_def_1_id = self.services.config.CreateComponentDef(
+        self.cnxn, self.project_1.project_id, self.component_def_1_path, '',
+        False, [], [], None, 111, [])
+    self.component_def_2_path = 'Foo>Bar>Hey123_I-am-valid'
+    self.component_def_2_id = self.services.config.CreateComponentDef(
+        self.cnxn, self.project_1.project_id, self.component_def_2_path, '',
+        False, [], [], None, 111, [])
+    self.component_def_3_path = 'Fizz'
+    self.component_def_3_id = self.services.config.CreateComponentDef(
+        self.cnxn, self.project_2.project_id, self.component_def_3_path, '',
+        False, [], [], None, 111, [])
+    self.dne_component_def_id = 999
+    self.dne_field_def_id = 999999
+    self.psq_1 = tracker_pb2.SavedQuery(
+        query_id=2, name='psq1 name', base_query_id=1, query='foo=bar')
+    self.psq_2 = tracker_pb2.SavedQuery(
+        query_id=3, name='psq2 name', base_query_id=1, query='fizz=buzz')
+    self.dne_psq_id = 987
+    self.services.features.UpdateCannedQueries(
+        self.cnxn, self.project_1.project_id, [self.psq_1, self.psq_2])
+
+  def testGetResourceNameMatch(self):
+    """We can get a resource name match."""
+    regex = re.compile(r'name\/(?P<group_name>[a-z]+)$')
+    match = rnc._GetResourceNameMatch('name/honque', regex)
+    self.assertEqual(match.group('group_name'), 'honque')
+
+  def testGetResouceNameMatch_InvalidName(self):
+    """An exception is raised if there is not match."""
+    regex = re.compile(r'name\/(?P<group_name>[a-z]+)$')
+    with self.assertRaises(exceptions.InputException):
+      rnc._GetResourceNameMatch('honque/honque', regex)
+
+  def testIngestApprovalDefName(self):
+    approval_id = rnc.IngestApprovalDefName(
+        self.cnxn, 'projects/proj/approvalDefs/123', self.services)
+    self.assertEqual(approval_id, 123)
+
+  def testIngestApprovalDefName_InvalidName(self):
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestApprovalDefName(
+          self.cnxn, 'projects/proj/approvalDefs/123d', self.services)
+
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.IngestApprovalDefName(
+          self.cnxn, 'projects/garbage/approvalDefs/123', self.services)
+
+  def testIngestFieldDefName(self):
+    """We can get a FieldDef's resource name match."""
+    self.assertEqual(
+        rnc.IngestFieldDefName(
+            self.cnxn, 'projects/proj/fieldDefs/123', self.services),
+        (789, 123))
+
+  def testIngestFieldDefName_InvalidName(self):
+    """An exception is raised if the FieldDef's resource name is invalid"""
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestFieldDefName(
+          self.cnxn, 'projects/proj/fieldDefs/7Dog', self.services)
+
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestFieldDefName(
+          self.cnxn, 'garbage/proj/fieldDefs/123', self.services)
+
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.IngestFieldDefName(
+          self.cnxn, 'projects/garbage/fieldDefs/123', self.services)
+
+  def testIngestHotlistName(self):
+    """We can get a Hotlist's resource name match."""
+    self.assertEqual(rnc.IngestHotlistName('hotlists/78909'), 78909)
+
+  def testIngestHotlistName_InvalidName(self):
+    """An exception is raised if the Hotlist's resource name is invalid"""
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestHotlistName('hotlists/789honk789')
+
+  def testIngestHotlistItemNames(self):
+    """We can get Issue IDs from HotlistItems resource names."""
+    names = [
+        'hotlists/78909/items/proj.1',
+        'hotlists/78909/items/goose.2']
+    self.assertEqual(
+        rnc.IngestHotlistItemNames(self.cnxn, names, self.services),
+        [self.issue_1.issue_id, self.issue_2.issue_id])
+
+  def testIngestHotlistItemNames_ProjectNotFound(self):
+    """Exception is raised if a project is not found."""
+    names = [
+        'hotlists/78909/items/proj.1',
+        'hotlists/78909/items/chicken.2']
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.IngestHotlistItemNames(self.cnxn, names, self.services)
+
+  def testIngestHotlistItemNames_MultipleProjectsNotFound(self):
+    """Aggregated exceptions raised if projects are not found."""
+    names = [
+        'hotlists/78909/items/proj.1', 'hotlists/78909/items/chicken.2',
+        'hotlists/78909/items/cow.3'
+    ]
+    with self.assertRaisesRegexp(exceptions.NoSuchProjectException,
+                                 'Project chicken not found.\n' +
+                                 'Project cow not found.'):
+      rnc.IngestHotlistItemNames(self.cnxn, names, self.services)
+
+  def testIngestHotlistItems_IssueNotFound(self):
+    """Exception is raised if an Issue is not found."""
+    names = [
+        'hotlists/78909/items/proj.1',
+        'hotlists/78909/items/goose.5']
+    with self.assertRaisesRegexp(exceptions.NoSuchIssueException, '%r' % names):
+      rnc.IngestHotlistItemNames(self.cnxn, names, self.services)
+
+  def testConvertHotlistName(self):
+    """We can get a Hotlist's resource name."""
+    self.assertEqual(rnc.ConvertHotlistName(10), 'hotlists/10')
+
+  def testConvertHotlistItemNames(self):
+    """We can get Hotlist items' resource names."""
+    expected_dict = {
+        self.hotlist_1.items[0].issue_id: 'hotlists/7739/items/proj.1',
+        self.hotlist_1.items[1].issue_id: 'hotlists/7739/items/goose.2',
+    }
+    self.assertEqual(
+        rnc.ConvertHotlistItemNames(
+            self.cnxn, self.hotlist_1.hotlist_id, expected_dict.keys(),
+            self.services), expected_dict)
+
+  def testIngestApprovalValueName(self):
+    project_id, issue_id, approval_def_id = rnc.IngestApprovalValueName(
+        self.cnxn, 'projects/proj/issues/1/approvalValues/404', self.services)
+    self.assertEqual(project_id, self.project_1.project_id)
+    self.assertEqual(issue_id, self.issue_1.issue_id)
+    self.assertEqual(404, approval_def_id)  # We don't verify it exists.
+
+  def testIngestApprovalValueName_ProjectDoesNotExist(self):
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.IngestApprovalValueName(
+          self.cnxn, 'projects/noproj/issues/1/approvalValues/1', self.services)
+
+  def testIngestApprovalValueName_IssueDoesNotExist(self):
+    with self.assertRaisesRegexp(exceptions.NoSuchIssueException,
+                                 'projects/proj/issues/404'):
+      rnc.IngestApprovalValueName(
+          self.cnxn, 'projects/proj/issues/404/approvalValues/1', self.services)
+
+  def testIngestApprovalValueName_InvalidStart(self):
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestApprovalValueName(
+          self.cnxn, 'zprojects/proj/issues/1/approvalValues/1', self.services)
+
+  def testIngestApprovalValueName_InvalidEnd(self):
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestApprovalValueName(
+          self.cnxn, 'projects/proj/issues/1/approvalValues/1z', self.services)
+
+  def testIngestApprovalValueName_InvalidCollection(self):
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestApprovalValueName(
+          self.cnxn, 'projects/proj/issues/1/approvalValue/1', self.services)
+
+  def testIngestIssueName(self):
+    """We can get an Issue global id from its resource name."""
+    self.assertEqual(
+        rnc.IngestIssueName(self.cnxn, 'projects/proj/issues/1', self.services),
+        self.issue_1.issue_id)
+
+  def testIngestIssueName_ProjectDoesNotExist(self):
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.IngestIssueName(self.cnxn, 'projects/noproj/issues/1', self.services)
+
+  def testIngestIssueName_IssueDoesNotExist(self):
+    with self.assertRaises(exceptions.NoSuchIssueException):
+      rnc.IngestIssueName(self.cnxn, 'projects/proj/issues/2', self.services)
+
+  def testIngestIssueName_InvalidLocalId(self):
+    """Issue resource name Local IDs are digits."""
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestIssueName(self.cnxn, 'projects/proj/issues/x', self.services)
+
+  def testIngestIssueName_InvalidProjectId(self):
+    """Project names are more than 1 character."""
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestIssueName(self.cnxn, 'projects/p/issues/1', self.services)
+
+  def testIngestIssueName_InvalidFormat(self):
+    """Issue resource names must begin with the project resource name."""
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestIssueName(self.cnxn, 'issues/1', self.services)
+
+  def testIngestIssueName_Moved(self):
+    """We can get a moved issue."""
+    moved_to_project_id = 987
+    self.services.project.TestAddProject(
+        'other', project_id=moved_to_project_id)
+    new_issue_id = 1010
+    issue = fake.MakeTestIssue(
+        moved_to_project_id, 200, 'sum', 'New', 111, issue_id=new_issue_id)
+    self.services.issue.TestAddIssue(issue)
+    self.services.issue.TestAddMovedIssueRef(
+        self.project_1.project_id, 404, moved_to_project_id, 200)
+
+    self.assertEqual(
+        rnc.IngestIssueName(
+            self.cnxn, 'projects/proj/issues/404', self.services), new_issue_id)
+
+  def testIngestIssueNames(self):
+    """We can get an Issue global ids from resource names."""
+    self.assertEqual(
+        rnc.IngestIssueNames(
+            self.cnxn, ['projects/proj/issues/1', 'projects/goose/issues/2'],
+            self.services), [self.issue_1.issue_id, self.issue_2.issue_id])
+
+  def testIngestIssueNames_EmptyList(self):
+    """We get an empty list when providing an empty list of issue names."""
+    self.assertEqual(rnc.IngestIssueNames(self.cnxn, [], self.services), [])
+
+  def testIngestIssueNames_WithBadInputs(self):
+    """We aggregate input exceptions."""
+    with self.assertRaisesRegexp(
+        exceptions.InputException,
+        'Invalid resource name: projects/proj/badformat/1.\n' +
+        'Invalid resource name: badformat/proj/issues/1.'):
+      rnc.IngestIssueNames(
+          self.cnxn, [
+              'projects/proj/badformat/1', 'badformat/proj/issues/1',
+              'projects/proj/issues/1'
+          ], self.services)
+
+  def testIngestIssueNames_OneDoesNotExist(self):
+    """We get an exception if one issue name provided does not exist."""
+    with self.assertRaises(exceptions.NoSuchIssueException):
+      rnc.IngestIssueNames(
+          self.cnxn, ['projects/proj/issues/1', 'projects/proj/issues/2'],
+          self.services)
+
+  def testIngestIssueNames_ManyDoNotExist(self):
+    """We get an exception if one issue name provided does not exist."""
+    dne_issues = ['projects/proj/issues/2', 'projects/proj/issues/3']
+    with self.assertRaisesRegexp(exceptions.NoSuchIssueException,
+                                 '%r' % dne_issues):
+      rnc.IngestIssueNames(self.cnxn, dne_issues, self.services)
+
+  def testIngestIssueNames_ProjectsNotExist(self):
+    """Aggregated exceptions raised if projects are not found."""
+    with self.assertRaisesRegexp(exceptions.NoSuchProjectException,
+                                 'Project chicken not found.\n' +
+                                 'Project cow not found.'):
+      rnc.IngestIssueNames(
+          self.cnxn, [
+              'projects/chicken/issues/2', 'projects/cow/issues/3',
+              'projects/proj/issues/1'
+          ], self.services)
+
+  def testIngestProjectFromIssue(self):
+    self.assertEqual(rnc.IngestProjectFromIssue('projects/xyz/issues/1'), 'xyz')
+
+  def testIngestProjectFromIssue_InvalidName(self):
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestProjectFromIssue('projects/xyz')
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestProjectFromIssue('garbage')
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestProjectFromIssue('projects/xyz/issues/garbage')
+
+  def testIngestCommentName(self):
+    name = 'projects/proj/issues/1/comments/0'
+    actual = rnc.IngestCommentName(self.cnxn, name, self.services)
+    self.assertEqual(
+        actual, (self.project_1.project_id, self.issue_1.issue_id, 0))
+
+  def testIngestCommentName_InputException(self):
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestCommentName(self.cnxn, 'misspelled name', self.services)
+
+  def testIngestCommentName_NoSuchProject(self):
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.IngestCommentName(
+          self.cnxn, 'projects/doesnotexist/issues/1/comments/0', self.services)
+
+  def testIngestCommentName_NoSuchIssue(self):
+    with self.assertRaisesRegexp(exceptions.NoSuchIssueException,
+                                 "['projects/proj/issues/404']"):
+      rnc.IngestCommentName(
+          self.cnxn, 'projects/proj/issues/404/comments/0', self.services)
+
+  def testConvertCommentNames(self):
+    """We can create comment names."""
+    expected = {
+        0: 'projects/proj/issues/1/comments/0',
+        1: 'projects/proj/issues/1/comments/1'
+    }
+    self.assertEqual(rnc.CreateCommentNames(1, 'proj', [0, 1]), expected)
+
+  def testConvertCommentNames_Empty(self):
+    """Converting an empty list of comments returns an empty dict."""
+    self.assertEqual(rnc.CreateCommentNames(1, 'proj', []), {})
+
+  def testConvertIssueName(self):
+    """We can create an Issue resource name from an issue_id."""
+    self.assertEqual(
+        rnc.ConvertIssueName(self.cnxn, self.issue_1.issue_id, self.services),
+        'projects/proj/issues/1')
+
+  def testConvertIssueName_NotFound(self):
+    """Exception is raised if the issue is not found."""
+    with self.assertRaises(exceptions.NoSuchIssueException):
+      rnc.ConvertIssueName(self.cnxn, 3279, self.services)
+
+  def testConvertIssueNames(self):
+    """We can create Issue resource names from issue_ids."""
+    self.assertEqual(
+        rnc.ConvertIssueNames(
+            self.cnxn, [self.issue_1.issue_id, 3279], self.services),
+        {self.issue_1.issue_id: 'projects/proj/issues/1'})
+
+  def testConvertApprovalValueNames(self):
+    """We can create ApprovalValue resource names."""
+    self.issue_1.approval_values = [tracker_pb2.ApprovalValue(
+        approval_id=self.approval_def_1_id)]
+
+    expected_name = (
+        'projects/proj/issues/1/approvalValues/%d' % self.approval_def_1_id)
+    self.assertEqual(
+        {self.approval_def_1_id: expected_name},
+        rnc.ConvertApprovalValueNames(
+            self.cnxn, self.issue_1.issue_id, self.services))
+
+  def testIngestUserName(self):
+    """We can get a User ID from User resource name."""
+    name = 'users/111'
+    self.assertEqual(rnc.IngestUserName(self.cnxn, name, self.services), 111)
+
+  def testIngestUserName_DisplayName(self):
+    """We can get a User ID from User resource name with a display name set."""
+    name = 'users/%s' % self.user_3.email
+    self.assertEqual(rnc.IngestUserName(self.cnxn, name, self.services), 333)
+
+  def testIngestUserName_NoSuchUser(self):
+    """When autocreate=False, we raise an exception if a user is not found."""
+    name = 'users/chicken@test.com'
+    with self.assertRaises(exceptions.NoSuchUserException):
+      rnc.IngestUserName(self.cnxn, name, self.services)
+
+  def testIngestUserName_InvalidEmail(self):
+    """We raise an exception if a given resource name's email is invalid."""
+    name = 'users/chickentest.com'
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestUserName(self.cnxn, name, self.services)
+
+  def testIngestUserName_Autocreate(self):
+    """When autocreate=True create a new User if they don't already exist."""
+    new_email = 'chicken@test.com'
+    name = 'users/%s' % new_email
+    user_id = rnc.IngestUserName(
+        self.cnxn, name, self.services, autocreate=True)
+
+    new_id = self.services.user.LookupUserID(
+        self.cnxn, new_email, autocreate=False)
+    self.assertEqual(new_id, user_id)
+
+  def testIngestUserNames(self):
+    """We can get User IDs from User resource names."""
+    names = ['users/111', 'users/222', 'users/%s' % self.user_3.email]
+    expected_ids = [111, 222, 333]
+    self.assertEqual(
+        rnc.IngestUserNames(self.cnxn, names, self.services), expected_ids)
+
+  def testIngestUserNames_NoSuchUser(self):
+    """When autocreate=False, we raise an exception if a user is not found."""
+    names = [
+        'users/111', 'users/chicken@test.com',
+        'users/%s' % self.user_3.email
+    ]
+    with self.assertRaises(exceptions.NoSuchUserException):
+      rnc.IngestUserNames(self.cnxn, names, self.services)
+
+  def testIngestUserNames_InvalidEmail(self):
+    """We raise an exception if a given resource name's email is invalid."""
+    names = [
+        'users/111', 'users/chickentest.com',
+        'users/%s' % self.user_3.email
+    ]
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestUserNames(self.cnxn, names, self.services)
+
+  def testIngestUserNames_Autocreate(self):
+    """When autocreate=True we create new Users if they don't already exist."""
+    new_email = 'user_444@example.com'
+    names = [
+        'users/111',
+        'users/%s' % new_email,
+        'users/%s' % self.user_3.email
+    ]
+    ids = rnc.IngestUserNames(self.cnxn, names, self.services, autocreate=True)
+
+    new_id = self.services.user.LookupUserID(
+        self.cnxn, new_email, autocreate=False)
+    expected_ids = [111, new_id, 333]
+    self.assertEqual(expected_ids, ids)
+
+  def testConvertUserName(self):
+    """We can convert a single User ID to resource name."""
+    self.assertEqual(rnc.ConvertUserName(111), 'users/111')
+
+  def testConvertUserNames(self):
+    """We can get User resource names."""
+    expected_dict = {111: 'users/111', 222: 'users/222', 333: 'users/333'}
+    self.assertEqual(rnc.ConvertUserNames(expected_dict.keys()), expected_dict)
+
+  def testConvertUserNames_Empty(self):
+    """We can process an empty Users list."""
+    self.assertEqual(rnc.ConvertUserNames([]), {})
+
+  def testConvertProjectStarName(self):
+    """We can convert a User ID and Project ID to resource name."""
+    name = rnc.ConvertProjectStarName(
+        self.cnxn, 111, self.project_1.project_id, self.services)
+    expected = 'users/111/projectStars/{}'.format(self.project_1.project_name)
+    self.assertEqual(name, expected)
+
+  def testConvertProjectStarName_NoSuchProjectException(self):
+    """Throws an exception when Project ID is invalid."""
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertProjectStarName(self.cnxn, 111, 123455, self.services)
+
+  def testIngestProjectName(self):
+    """We can get project name from Project resource names."""
+    name = 'projects/{}'.format(self.project_1.project_name)
+    expected = self.project_1.project_id
+    self.assertEqual(
+        rnc.IngestProjectName(self.cnxn, name, self.services), expected)
+
+  def testIngestProjectName_InvalidName(self):
+    """An exception is raised if the Hotlist's resource name is invalid"""
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestProjectName(self.cnxn, 'projects/', self.services)
+
+  def testConvertTemplateNames(self):
+    """We can get IssueTemplate resource names."""
+    expected_resource_name = 'projects/{}/templates/{}'.format(
+        self.project_1.project_name, self.template_1.template_id)
+    expected = {self.template_1.template_id: expected_resource_name}
+
+    self.assertEqual(
+        rnc.ConvertTemplateNames(
+            self.cnxn, self.project_1.project_id, [self.template_1.template_id],
+            self.services), expected)
+
+  def testConvertTemplateNames_NoSuchProjectException(self):
+    """We get an exception if project with id does not exist."""
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertTemplateNames(
+          self.cnxn, self.dne_project_id, [self.template_1.template_id],
+          self.services)
+
+  def testConvertTemplateNames_NonExistentTemplate(self):
+    """We only return templates that exist."""
+    self.assertEqual(
+        rnc.ConvertTemplateNames(
+            self.cnxn, self.project_1.project_id, [self.dne_template_id],
+            self.services), {})
+
+  def testConvertTemplateNames_TemplateInProject(self):
+    """We only return templates in the project."""
+    expected_resource_name = 'projects/{}/templates/{}'.format(
+        self.project_2.project_name, self.template_2.template_id)
+    expected = {self.template_2.template_id: expected_resource_name}
+
+    self.assertEqual(
+        rnc.ConvertTemplateNames(
+            self.cnxn, self.project_2.project_id,
+            [self.template_1.template_id, self.template_2.template_id],
+            self.services), expected)
+
+  def testIngestTemplateName(self):
+    name = 'projects/{}/templates/{}'.format(
+        self.project_1.project_name, self.template_1.template_id)
+    actual = rnc.IngestTemplateName(self.cnxn, name, self.services)
+    expected = (self.template_1.template_id, self.project_1.project_id)
+    self.assertEqual(actual, expected)
+
+  def testIngestTemplateName_DoesNotExist(self):
+    """We will ingest templates that don't exist."""
+    name = 'projects/{}/templates/{}'.format(
+        self.project_1.project_name, self.dne_template_id)
+    actual = rnc.IngestTemplateName(self.cnxn, name, self.services)
+    expected = (self.dne_template_id, self.project_1.project_id)
+    self.assertEqual(actual, expected)
+
+  def testIngestTemplateName_InvalidName(self):
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestTemplateName(
+          self.cnxn, 'projects/asdf/misspelled_template/123', self.services)
+
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.IngestTemplateName(
+          self.cnxn, 'projects/asdf/templates/123', self.services)
+
+  def testConvertStatusDefNames(self):
+    """We can get Status resource name."""
+    expected_resource_name = 'projects/{}/statusDefs/{}'.format(
+        self.project_1.project_name, self.issue_1.status)
+
+    actual = rnc.ConvertStatusDefNames(
+        self.cnxn, [self.issue_1.status], self.project_1.project_id,
+        self.services)
+    self.assertEqual(actual[self.issue_1.status], expected_resource_name)
+
+  def testConvertStatusDefNames_NoSuchProjectException(self):
+    """We can get an exception if project with id does not exist."""
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertStatusDefNames(
+          self.cnxn, [self.issue_1.status], self.dne_project_id, self.services)
+
+  def testConvertLabelDefNames(self):
+    """We can get Label resource names."""
+    expected_label = 'some label'
+    expected_resource_name = 'projects/{}/labelDefs/{}'.format(
+        self.project_1.project_name, expected_label)
+
+    self.assertEqual(
+        rnc.ConvertLabelDefNames(
+            self.cnxn, [expected_label], self.project_1.project_id,
+            self.services), {expected_label: expected_resource_name})
+
+  def testConvertLabelDefNames_NoSuchProjectException(self):
+    """We can get an exception if project with id does not exist."""
+    some_label = 'some label'
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertLabelDefNames(
+          self.cnxn, [some_label], self.dne_project_id, self.services)
+
+  def testConvertComponentDefNames(self):
+    """We can get Component resource names."""
+    expected_id = 123456
+    expected_resource_name = 'projects/{}/componentDefs/{}'.format(
+        self.project_1.project_name, expected_id)
+
+    self.assertEqual(
+        rnc.ConvertComponentDefNames(
+            self.cnxn, [expected_id], self.project_1.project_id, self.services),
+        {expected_id: expected_resource_name})
+
+  def testConvertComponentDefNames_NoSuchProjectException(self):
+    """We can get an exception if project with id does not exist."""
+    component_id = 123456
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertComponentDefNames(
+          self.cnxn, [component_id], self.dne_project_id, self.services)
+
+  def testIngestComponentDefNames(self):
+    names = [
+        'projects/proj/componentDefs/%d' % self.component_def_1_id,
+        'projects/proj/componentDefs/%s' % self.component_def_2_path
+    ]
+    actual = rnc.IngestComponentDefNames(self.cnxn, names, self.services)
+    self.assertEqual(actual, [
+        (self.project_1.project_id, self.component_def_1_id),
+        (self.project_1.project_id, self.component_def_2_id)])
+
+  def testIngestComponentDefNames_NoSuchProjectException(self):
+    names = ['projects/xyz/componentDefs/%d' % self.component_def_1_id]
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.IngestComponentDefNames(self.cnxn, names, self.services)
+
+    names = ['projects/xyz/componentDefs/1', 'projects/zyz/componentDefs/1']
+    expected_error = 'Project not found: xyz.\nProject not found: zyz.'
+    with self.assertRaisesRegexp(exceptions.NoSuchProjectException,
+                                 expected_error):
+      rnc.IngestComponentDefNames(self.cnxn, names, self.services)
+
+  def testIngestComponentDefNames_NoSuchComponentException(self):
+    names = ['projects/proj/componentDefs/%d' % self.dne_component_def_id]
+    with self.assertRaises(exceptions.NoSuchComponentException):
+      rnc.IngestComponentDefNames(self.cnxn, names, self.services)
+
+    names = [
+        'projects/proj/componentDefs/999', 'projects/proj/componentDefs/cow'
+    ]
+    expected_error = 'Component not found: 999.\nComponent not found: \'cow\'.'
+    with self.assertRaisesRegexp(exceptions.NoSuchComponentException,
+                                 expected_error):
+      rnc.IngestComponentDefNames(self.cnxn, names, self.services)
+
+  def testIngestComponentDefNames_InvalidNames(self):
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestComponentDefNames(
+          self.cnxn, ['projects/proj/componentDefs/not.path.or.id'],
+          self.services)
+
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestComponentDefNames(
+          self.cnxn, ['projects/proj/componentDefs/Foo>'], self.services)
+
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestComponentDefNames(
+          self.cnxn, ['projects/proj/componentDefs/>Bar'], self.services)
+
+    with self.assertRaises(exceptions.InputException):
+      rnc.IngestComponentDefNames(
+          self.cnxn, ['projects/proj/componentDefs/Foo>123Bar'], self.services)
+
+  def testIngestComponentDefNames_CrossProject(self):
+    names = [
+        'projects/proj/componentDefs/%d' % self.component_def_1_id,
+        'projects/goose/componentDefs/%s' % self.component_def_3_path
+    ]
+    actual = rnc.IngestComponentDefNames(self.cnxn, names, self.services)
+    self.assertEqual(actual, [
+        (self.project_1.project_id, self.component_def_1_id),
+        (self.project_2.project_id, self.component_def_3_id)])
+
+  def testConvertFieldDefNames(self):
+    """Returns resource names for fields that exist and ignores others."""
+    expected_key = self.field_def_1
+    expected_value = 'projects/{}/fieldDefs/{}'.format(
+        self.project_1.project_name, self.field_def_1)
+
+    field_ids = [self.field_def_1, self.dne_field_def_id]
+    self.assertEqual(
+        rnc.ConvertFieldDefNames(
+            self.cnxn, field_ids, self.project_1.project_id, self.services),
+        {expected_key: expected_value})
+
+  def testConvertFieldDefNames_NoSuchProjectException(self):
+    field_ids = [self.field_def_1, self.dne_field_def_id]
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertFieldDefNames(
+          self.cnxn, field_ids, self.dne_project_id, self.services)
+
+  def testConvertApprovalDefNames(self):
+    outcome = rnc.ConvertApprovalDefNames(
+        self.cnxn, [self.approval_def_1_id], self.project_1.project_id,
+        self.services)
+
+    expected_key = self.approval_def_1_id
+    expected_value = 'projects/{}/approvalDefs/{}'.format(
+        self.project_1.project_name, self.approval_def_1_id)
+    self.assertEqual(outcome, {expected_key: expected_value})
+
+  def testConvertApprovalDefNames_NoSuchProjectException(self):
+    approval_ids = [self.approval_def_1_id]
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertApprovalDefNames(
+          self.cnxn, approval_ids, self.dne_project_id, self.services)
+
+  def testConvertProjectName(self):
+    self.assertEqual(
+        rnc.ConvertProjectName(
+            self.cnxn, self.project_1.project_id, self.services),
+        'projects/{}'.format(self.project_1.project_name))
+
+  def testConvertProjectName_NoSuchProjectException(self):
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertProjectName(self.cnxn, self.dne_project_id, self.services)
+
+  def testConvertProjectConfigName(self):
+    self.assertEqual(
+        rnc.ConvertProjectConfigName(
+            self.cnxn, self.project_1.project_id, self.services),
+        'projects/{}/config'.format(self.project_1.project_name))
+
+  def testConvertProjectConfigName_NoSuchProjectException(self):
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertProjectConfigName(
+          self.cnxn, self.dne_project_id, self.services)
+
+  def testConvertProjectMemberName(self):
+    self.assertEqual(
+        rnc.ConvertProjectMemberName(
+            self.cnxn, self.project_1.project_id, 111, self.services),
+        'projects/{}/members/{}'.format(self.project_1.project_name, 111))
+
+  def testConvertProjectMemberName_NoSuchProjectException(self):
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertProjectMemberName(
+          self.cnxn, self.dne_project_id, 111, self.services)
+
+  def testConvertProjectSavedQueryNames(self):
+    query_ids = [self.psq_1.query_id, self.psq_2.query_id, self.dne_psq_id]
+    outcome = rnc.ConvertProjectSavedQueryNames(
+        self.cnxn, query_ids, self.project_1.project_id, self.services)
+
+    expected_value_1 = 'projects/{}/savedQueries/{}'.format(
+        self.project_1.project_name, self.psq_1.name)
+    expected_value_2 = 'projects/{}/savedQueries/{}'.format(
+        self.project_1.project_name, self.psq_2.name)
+    self.assertEqual(
+        outcome, {
+            self.psq_1.query_id: expected_value_1,
+            self.psq_2.query_id: expected_value_2
+        })
+
+  def testConvertProjectSavedQueryNames_NoSuchProjectException(self):
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      rnc.ConvertProjectSavedQueryNames(
+          self.cnxn, [self.psq_1.query_id], self.dne_project_id, self.services)