# Copyright 2014 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import unittest


class TestKey(unittest.TestCase):

    _DEFAULT_PROJECT = 'PROJECT'
    # NOTE: This comes directly from a running (in the dev appserver)
    #       App Engine app. Created via:
    #
    #           from google.appengine.ext import ndb
    #           key = ndb.Key(
    #               'Parent', 59, 'Child', 'Feather',
    #               namespace='space', app='s~sample-app')
    #           urlsafe = key.urlsafe()
    _URLSAFE_EXAMPLE1 = (
        b'agxzfnNhbXBsZS1hcHByHgsSBlBhcmVudBg7DAsSBUNoaWxkIgdGZ'
        b'WF0aGVyDKIBBXNwYWNl')
    _URLSAFE_APP1 = 's~sample-app'
    _URLSAFE_NAMESPACE1 = 'space'
    _URLSAFE_FLAT_PATH1 = ('Parent', 59, 'Child', 'Feather')
    _URLSAFE_EXAMPLE2 = b'agZzfmZpcmVyDwsSBEtpbmQiBVRoaW5nDA'
    _URLSAFE_APP2 = 's~fire'
    _URLSAFE_FLAT_PATH2 = ('Kind', 'Thing')
    _URLSAFE_EXAMPLE3 = b'ahhzfnNhbXBsZS1hcHAtbm8tbG9jYXRpb25yCgsSBFpvcnAYWAw'
    _URLSAFE_APP3 = 'sample-app-no-location'
    _URLSAFE_FLAT_PATH3 = ('Zorp', 88)

    @staticmethod
    def _get_target_class():
        from google.cloud.datastore.key import Key

        return Key

    def _make_one(self, *args, **kwargs):
        return self._get_target_class()(*args, **kwargs)

    def test_ctor_empty(self):
        self.assertRaises(ValueError, self._make_one)

    def test_ctor_no_project(self):
        klass = self._get_target_class()
        self.assertRaises(ValueError, klass, 'KIND')

    def test_ctor_w_explicit_project_empty_path(self):
        _PROJECT = 'PROJECT'
        self.assertRaises(ValueError, self._make_one, project=_PROJECT)

    def test_ctor_parent(self):
        _PARENT_KIND = 'KIND1'
        _PARENT_ID = 1234
        _PARENT_PROJECT = 'PROJECT-ALT'
        _PARENT_NAMESPACE = 'NAMESPACE'
        _CHILD_KIND = 'KIND2'
        _CHILD_ID = 2345
        _PATH = [
            {'kind': _PARENT_KIND, 'id': _PARENT_ID},
            {'kind': _CHILD_KIND, 'id': _CHILD_ID},
        ]
        parent_key = self._make_one(_PARENT_KIND, _PARENT_ID,
                                    project=_PARENT_PROJECT,
                                    namespace=_PARENT_NAMESPACE)
        key = self._make_one(_CHILD_KIND, _CHILD_ID, parent=parent_key)
        self.assertEqual(key.project, parent_key.project)
        self.assertEqual(key.namespace, parent_key.namespace)
        self.assertEqual(key.kind, _CHILD_KIND)
        self.assertEqual(key.path, _PATH)
        self.assertIs(key.parent, parent_key)

    def test_ctor_partial_parent(self):
        parent_key = self._make_one('KIND', project=self._DEFAULT_PROJECT)
        with self.assertRaises(ValueError):
            self._make_one('KIND2', 1234, parent=parent_key)

    def test_ctor_parent_bad_type(self):
        with self.assertRaises(AttributeError):
            self._make_one('KIND2', 1234, parent=('KIND1', 1234),
                           project=self._DEFAULT_PROJECT)

    def test_ctor_parent_bad_namespace(self):
        parent_key = self._make_one('KIND', 1234, namespace='FOO',
                                    project=self._DEFAULT_PROJECT)
        with self.assertRaises(ValueError):
            self._make_one('KIND2', 1234, namespace='BAR', parent=parent_key,
                           PROJECT=self._DEFAULT_PROJECT)

    def test_ctor_parent_bad_project(self):
        parent_key = self._make_one('KIND', 1234, project='FOO')
        with self.assertRaises(ValueError):
            self._make_one('KIND2', 1234, parent=parent_key,
                           project='BAR')

    def test_ctor_parent_empty_path(self):
        parent_key = self._make_one('KIND', 1234,
                                    project=self._DEFAULT_PROJECT)
        with self.assertRaises(ValueError):
            self._make_one(parent=parent_key)

    def test_ctor_explicit(self):
        _PROJECT = 'PROJECT-ALT'
        _NAMESPACE = 'NAMESPACE'
        _KIND = 'KIND'
        _ID = 1234
        _PATH = [{'kind': _KIND, 'id': _ID}]
        key = self._make_one(_KIND, _ID, namespace=_NAMESPACE,
                             project=_PROJECT)
        self.assertEqual(key.project, _PROJECT)
        self.assertEqual(key.namespace, _NAMESPACE)
        self.assertEqual(key.kind, _KIND)
        self.assertEqual(key.path, _PATH)

    def test_ctor_bad_kind(self):
        self.assertRaises(ValueError, self._make_one, object(),
                          project=self._DEFAULT_PROJECT)

    def test_ctor_bad_id_or_name(self):
        self.assertRaises(ValueError, self._make_one, 'KIND', object(),
                          project=self._DEFAULT_PROJECT)
        self.assertRaises(ValueError, self._make_one, 'KIND', None,
                          project=self._DEFAULT_PROJECT)
        self.assertRaises(ValueError, self._make_one, 'KIND', 10, 'KIND2',
                          None, project=self._DEFAULT_PROJECT)

    def test__clone(self):
        _PROJECT = 'PROJECT-ALT'
        _NAMESPACE = 'NAMESPACE'
        _KIND = 'KIND'
        _ID = 1234
        _PATH = [{'kind': _KIND, 'id': _ID}]
        key = self._make_one(_KIND, _ID, namespace=_NAMESPACE,
                             project=_PROJECT)
        clone = key._clone()
        self.assertEqual(clone.project, _PROJECT)
        self.assertEqual(clone.namespace, _NAMESPACE)
        self.assertEqual(clone.kind, _KIND)
        self.assertEqual(clone.path, _PATH)

    def test__clone_with_parent(self):
        _PROJECT = 'PROJECT-ALT'
        _NAMESPACE = 'NAMESPACE'
        _KIND1 = 'PARENT'
        _KIND2 = 'KIND'
        _ID1 = 1234
        _ID2 = 2345
        _PATH = [{'kind': _KIND1, 'id': _ID1}, {'kind': _KIND2, 'id': _ID2}]

        parent = self._make_one(_KIND1, _ID1, namespace=_NAMESPACE,
                                project=_PROJECT)
        key = self._make_one(_KIND2, _ID2, parent=parent)
        self.assertIs(key.parent, parent)
        clone = key._clone()
        self.assertIs(clone.parent, key.parent)
        self.assertEqual(clone.project, _PROJECT)
        self.assertEqual(clone.namespace, _NAMESPACE)
        self.assertEqual(clone.path, _PATH)

    def test___eq_____ne___w_non_key(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        _NAME = 'one'
        key = self._make_one(_KIND, _NAME, project=_PROJECT)
        self.assertFalse(key == object())
        self.assertTrue(key != object())

    def test___eq_____ne___two_incomplete_keys_same_kind(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        key1 = self._make_one(_KIND, project=_PROJECT)
        key2 = self._make_one(_KIND, project=_PROJECT)
        self.assertFalse(key1 == key2)
        self.assertTrue(key1 != key2)

    def test___eq_____ne___incomplete_key_w_complete_key_same_kind(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        _ID = 1234
        key1 = self._make_one(_KIND, project=_PROJECT)
        key2 = self._make_one(_KIND, _ID, project=_PROJECT)
        self.assertFalse(key1 == key2)
        self.assertTrue(key1 != key2)

    def test___eq_____ne___complete_key_w_incomplete_key_same_kind(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        _ID = 1234
        key1 = self._make_one(_KIND, _ID, project=_PROJECT)
        key2 = self._make_one(_KIND, project=_PROJECT)
        self.assertFalse(key1 == key2)
        self.assertTrue(key1 != key2)

    def test___eq_____ne___same_kind_different_ids(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        _ID1 = 1234
        _ID2 = 2345
        key1 = self._make_one(_KIND, _ID1, project=_PROJECT)
        key2 = self._make_one(_KIND, _ID2, project=_PROJECT)
        self.assertFalse(key1 == key2)
        self.assertTrue(key1 != key2)

    def test___eq_____ne___same_kind_and_id(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        _ID = 1234
        key1 = self._make_one(_KIND, _ID, project=_PROJECT)
        key2 = self._make_one(_KIND, _ID, project=_PROJECT)
        self.assertTrue(key1 == key2)
        self.assertFalse(key1 != key2)

    def test___eq_____ne___same_kind_and_id_different_project(self):
        _PROJECT1 = 'PROJECT1'
        _PROJECT2 = 'PROJECT2'
        _KIND = 'KIND'
        _ID = 1234
        key1 = self._make_one(_KIND, _ID, project=_PROJECT1)
        key2 = self._make_one(_KIND, _ID, project=_PROJECT2)
        self.assertFalse(key1 == key2)
        self.assertTrue(key1 != key2)

    def test___eq_____ne___same_kind_and_id_different_namespace(self):
        _PROJECT = 'PROJECT'
        _NAMESPACE1 = 'NAMESPACE1'
        _NAMESPACE2 = 'NAMESPACE2'
        _KIND = 'KIND'
        _ID = 1234
        key1 = self._make_one(_KIND, _ID, project=_PROJECT,
                              namespace=_NAMESPACE1)
        key2 = self._make_one(_KIND, _ID, project=_PROJECT,
                              namespace=_NAMESPACE2)
        self.assertFalse(key1 == key2)
        self.assertTrue(key1 != key2)

    def test___eq_____ne___same_kind_different_names(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        _NAME1 = 'one'
        _NAME2 = 'two'
        key1 = self._make_one(_KIND, _NAME1, project=_PROJECT)
        key2 = self._make_one(_KIND, _NAME2, project=_PROJECT)
        self.assertFalse(key1 == key2)
        self.assertTrue(key1 != key2)

    def test___eq_____ne___same_kind_and_name(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        _NAME = 'one'
        key1 = self._make_one(_KIND, _NAME, project=_PROJECT)
        key2 = self._make_one(_KIND, _NAME, project=_PROJECT)
        self.assertTrue(key1 == key2)
        self.assertFalse(key1 != key2)

    def test___eq_____ne___same_kind_and_name_different_project(self):
        _PROJECT1 = 'PROJECT1'
        _PROJECT2 = 'PROJECT2'
        _KIND = 'KIND'
        _NAME = 'one'
        key1 = self._make_one(_KIND, _NAME, project=_PROJECT1)
        key2 = self._make_one(_KIND, _NAME, project=_PROJECT2)
        self.assertFalse(key1 == key2)
        self.assertTrue(key1 != key2)

    def test___eq_____ne___same_kind_and_name_different_namespace(self):
        _PROJECT = 'PROJECT'
        _NAMESPACE1 = 'NAMESPACE1'
        _NAMESPACE2 = 'NAMESPACE2'
        _KIND = 'KIND'
        _NAME = 'one'
        key1 = self._make_one(_KIND, _NAME, project=_PROJECT,
                              namespace=_NAMESPACE1)
        key2 = self._make_one(_KIND, _NAME, project=_PROJECT,
                              namespace=_NAMESPACE2)
        self.assertFalse(key1 == key2)
        self.assertTrue(key1 != key2)

    def test___hash___incomplete(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        key = self._make_one(_KIND, project=_PROJECT)
        self.assertNotEqual(hash(key),
                            hash(_KIND) + hash(_PROJECT) + hash(None))

    def test___hash___completed_w_id(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        _ID = 1234
        key = self._make_one(_KIND, _ID, project=_PROJECT)
        self.assertNotEqual(hash(key),
                            hash(_KIND) + hash(_ID) +
                            hash(_PROJECT) + hash(None))

    def test___hash___completed_w_name(self):
        _PROJECT = 'PROJECT'
        _KIND = 'KIND'
        _NAME = 'NAME'
        key = self._make_one(_KIND, _NAME, project=_PROJECT)
        self.assertNotEqual(hash(key),
                            hash(_KIND) + hash(_NAME) +
                            hash(_PROJECT) + hash(None))

    def test_completed_key_on_partial_w_id(self):
        key = self._make_one('KIND', project=self._DEFAULT_PROJECT)
        _ID = 1234
        new_key = key.completed_key(_ID)
        self.assertIsNot(key, new_key)
        self.assertEqual(new_key.id, _ID)
        self.assertIsNone(new_key.name)

    def test_completed_key_on_partial_w_name(self):
        key = self._make_one('KIND', project=self._DEFAULT_PROJECT)
        _NAME = 'NAME'
        new_key = key.completed_key(_NAME)
        self.assertIsNot(key, new_key)
        self.assertIsNone(new_key.id)
        self.assertEqual(new_key.name, _NAME)

    def test_completed_key_on_partial_w_invalid(self):
        key = self._make_one('KIND', project=self._DEFAULT_PROJECT)
        self.assertRaises(ValueError, key.completed_key, object())

    def test_completed_key_on_complete(self):
        key = self._make_one('KIND', 1234, project=self._DEFAULT_PROJECT)
        self.assertRaises(ValueError, key.completed_key, 5678)

    def test_to_protobuf_defaults(self):
        from google.cloud.datastore_v1.proto import entity_pb2

        _KIND = 'KIND'
        key = self._make_one(_KIND, project=self._DEFAULT_PROJECT)
        pb = key.to_protobuf()
        self.assertIsInstance(pb, entity_pb2.Key)

        # Check partition ID.
        self.assertEqual(pb.partition_id.project_id, self._DEFAULT_PROJECT)
        # Unset values are False-y.
        self.assertEqual(pb.partition_id.namespace_id, '')

        # Check the element PB matches the partial key and kind.
        elem, = list(pb.path)
        self.assertEqual(elem.kind, _KIND)
        # Unset values are False-y.
        self.assertEqual(elem.name, '')
        # Unset values are False-y.
        self.assertEqual(elem.id, 0)

    def test_to_protobuf_w_explicit_project(self):
        _PROJECT = 'PROJECT-ALT'
        key = self._make_one('KIND', project=_PROJECT)
        pb = key.to_protobuf()
        self.assertEqual(pb.partition_id.project_id, _PROJECT)

    def test_to_protobuf_w_explicit_namespace(self):
        _NAMESPACE = 'NAMESPACE'
        key = self._make_one('KIND', namespace=_NAMESPACE,
                             project=self._DEFAULT_PROJECT)
        pb = key.to_protobuf()
        self.assertEqual(pb.partition_id.namespace_id, _NAMESPACE)

    def test_to_protobuf_w_explicit_path(self):
        _PARENT = 'PARENT'
        _CHILD = 'CHILD'
        _ID = 1234
        _NAME = 'NAME'
        key = self._make_one(_PARENT, _NAME, _CHILD, _ID,
                             project=self._DEFAULT_PROJECT)
        pb = key.to_protobuf()
        elems = list(pb.path)
        self.assertEqual(len(elems), 2)
        self.assertEqual(elems[0].kind, _PARENT)
        self.assertEqual(elems[0].name, _NAME)
        self.assertEqual(elems[1].kind, _CHILD)
        self.assertEqual(elems[1].id, _ID)

    def test_to_protobuf_w_no_kind(self):
        key = self._make_one('KIND', project=self._DEFAULT_PROJECT)
        # Force the 'kind' to be unset. Maybe `to_protobuf` should fail
        # on this? The backend certainly will.
        key._path[-1].pop('kind')
        pb = key.to_protobuf()
        # Unset values are False-y.
        self.assertEqual(pb.path[0].kind, '')

    def test_to_legacy_urlsafe(self):
        key = self._make_one(
            *self._URLSAFE_FLAT_PATH1,
            project=self._URLSAFE_APP1,
            namespace=self._URLSAFE_NAMESPACE1)
        # NOTE: ``key.project`` is somewhat "invalid" but that is OK.
        urlsafe = key.to_legacy_urlsafe()
        self.assertEqual(urlsafe, self._URLSAFE_EXAMPLE1)

    def test_to_legacy_urlsafe_strip_padding(self):
        key = self._make_one(
            *self._URLSAFE_FLAT_PATH2,
            project=self._URLSAFE_APP2)
        # NOTE: ``key.project`` is somewhat "invalid" but that is OK.
        urlsafe = key.to_legacy_urlsafe()
        self.assertEqual(urlsafe, self._URLSAFE_EXAMPLE2)
        # Make sure it started with base64 padding.
        self.assertNotEqual(len(self._URLSAFE_EXAMPLE2) % 4, 0)

    def test_to_legacy_urlsafe_with_location_prefix(self):
        key = self._make_one(
            *self._URLSAFE_FLAT_PATH3,
            project=self._URLSAFE_APP3)
        urlsafe = key.to_legacy_urlsafe(location_prefix='s~')
        self.assertEqual(urlsafe, self._URLSAFE_EXAMPLE3)

    def test_from_legacy_urlsafe(self):
        klass = self._get_target_class()
        key = klass.from_legacy_urlsafe(self._URLSAFE_EXAMPLE1)

        self.assertEqual('s~' + key.project, self._URLSAFE_APP1)
        self.assertEqual(key.namespace, self._URLSAFE_NAMESPACE1)
        self.assertEqual(key.flat_path, self._URLSAFE_FLAT_PATH1)
        # Also make sure we didn't accidentally set the parent.
        self.assertIsNone(key._parent)
        self.assertIsNotNone(key.parent)
        self.assertIs(key._parent, key.parent)

    def test_from_legacy_urlsafe_needs_padding(self):
        klass = self._get_target_class()
        # Make sure it will have base64 padding added.
        self.assertNotEqual(len(self._URLSAFE_EXAMPLE2) % 4, 0)
        key = klass.from_legacy_urlsafe(self._URLSAFE_EXAMPLE2)

        self.assertEqual('s~' + key.project, self._URLSAFE_APP2)
        self.assertIsNone(key.namespace)
        self.assertEqual(key.flat_path, self._URLSAFE_FLAT_PATH2)

    def test_from_legacy_urlsafe_with_location_prefix(self):
        klass = self._get_target_class()
        # Make sure it will have base64 padding added.
        key = klass.from_legacy_urlsafe(self._URLSAFE_EXAMPLE3)

        self.assertEqual(key.project, self._URLSAFE_APP3)
        self.assertIsNone(key.namespace)
        self.assertEqual(key.flat_path, self._URLSAFE_FLAT_PATH3)

    def test_is_partial_no_name_or_id(self):
        key = self._make_one('KIND', project=self._DEFAULT_PROJECT)
        self.assertTrue(key.is_partial)

    def test_is_partial_w_id(self):
        _ID = 1234
        key = self._make_one('KIND', _ID, project=self._DEFAULT_PROJECT)
        self.assertFalse(key.is_partial)

    def test_is_partial_w_name(self):
        _NAME = 'NAME'
        key = self._make_one('KIND', _NAME, project=self._DEFAULT_PROJECT)
        self.assertFalse(key.is_partial)

    def test_id_or_name_no_name_or_id(self):
        key = self._make_one('KIND', project=self._DEFAULT_PROJECT)
        self.assertIsNone(key.id_or_name)

    def test_id_or_name_no_name_or_id_child(self):
        key = self._make_one('KIND1', 1234, 'KIND2',
                             project=self._DEFAULT_PROJECT)
        self.assertIsNone(key.id_or_name)

    def test_id_or_name_w_id_only(self):
        _ID = 1234
        key = self._make_one('KIND', _ID, project=self._DEFAULT_PROJECT)
        self.assertEqual(key.id_or_name, _ID)

    def test_id_or_name_w_name_only(self):
        _NAME = 'NAME'
        key = self._make_one('KIND', _NAME, project=self._DEFAULT_PROJECT)
        self.assertEqual(key.id_or_name, _NAME)

    def test_parent_default(self):
        key = self._make_one('KIND', project=self._DEFAULT_PROJECT)
        self.assertIsNone(key.parent)

    def test_parent_explicit_top_level(self):
        key = self._make_one('KIND', 1234, project=self._DEFAULT_PROJECT)
        self.assertIsNone(key.parent)

    def test_parent_explicit_nested(self):
        _PARENT_KIND = 'KIND1'
        _PARENT_ID = 1234
        _PARENT_PATH = [{'kind': _PARENT_KIND, 'id': _PARENT_ID}]
        key = self._make_one(_PARENT_KIND, _PARENT_ID, 'KIND2',
                             project=self._DEFAULT_PROJECT)
        self.assertEqual(key.parent.path, _PARENT_PATH)

    def test_parent_multiple_calls(self):
        _PARENT_KIND = 'KIND1'
        _PARENT_ID = 1234
        _PARENT_PATH = [{'kind': _PARENT_KIND, 'id': _PARENT_ID}]
        key = self._make_one(_PARENT_KIND, _PARENT_ID, 'KIND2',
                             project=self._DEFAULT_PROJECT)
        parent = key.parent
        self.assertEqual(parent.path, _PARENT_PATH)
        new_parent = key.parent
        self.assertIs(parent, new_parent)


class Test__clean_app(unittest.TestCase):

    PROJECT = 'my-prahjekt'

    @staticmethod
    def _call_fut(app_str):
        from google.cloud.datastore.key import _clean_app

        return _clean_app(app_str)

    def test_already_clean(self):
        app_str = self.PROJECT
        self.assertEqual(self._call_fut(app_str), self.PROJECT)

    def test_standard(self):
        app_str = 's~' + self.PROJECT
        self.assertEqual(self._call_fut(app_str), self.PROJECT)

    def test_european(self):
        app_str = 'e~' + self.PROJECT
        self.assertEqual(self._call_fut(app_str), self.PROJECT)

    def test_dev_server(self):
        app_str = 'dev~' + self.PROJECT
        self.assertEqual(self._call_fut(app_str), self.PROJECT)


class Test__get_empty(unittest.TestCase):

    @staticmethod
    def _call_fut(value, empty_value):
        from google.cloud.datastore.key import _get_empty

        return _get_empty(value, empty_value)

    def test_unset(self):
        for empty_value in (u'', 0, 0.0, []):
            ret_val = self._call_fut(empty_value, empty_value)
            self.assertIsNone(ret_val)

    def test_actually_set(self):
        value_pairs = (
            (u'hello', u''),
            (10, 0),
            (3.14, 0.0),
            (['stuff', 'here'], []),
        )
        for value, empty_value in value_pairs:
            ret_val = self._call_fut(value, empty_value)
            self.assertIs(ret_val, value)


class Test__check_database_id(unittest.TestCase):

    @staticmethod
    def _call_fut(database_id):
        from google.cloud.datastore.key import _check_database_id

        return _check_database_id(database_id)

    def test_empty_value(self):
        ret_val = self._call_fut(u'')
        # Really we are just happy there was no exception.
        self.assertIsNone(ret_val)

    def test_failure(self):
        with self.assertRaises(ValueError):
            self._call_fut(u'some-database-id')


class Test__add_id_or_name(unittest.TestCase):

    @staticmethod
    def _call_fut(flat_path, element_pb, empty_allowed):
        from google.cloud.datastore.key import _add_id_or_name

        return _add_id_or_name(flat_path, element_pb, empty_allowed)

    def test_add_id(self):
        flat_path = []
        id_ = 123
        element_pb = _make_element_pb(id=id_)

        ret_val = self._call_fut(flat_path, element_pb, False)
        self.assertIsNone(ret_val)
        self.assertEqual(flat_path, [id_])
        ret_val = self._call_fut(flat_path, element_pb, True)
        self.assertIsNone(ret_val)
        self.assertEqual(flat_path, [id_, id_])

    def test_add_name(self):
        flat_path = []
        name = 'moon-shadow'
        element_pb = _make_element_pb(name=name)

        ret_val = self._call_fut(flat_path, element_pb, False)
        self.assertIsNone(ret_val)
        self.assertEqual(flat_path, [name])
        ret_val = self._call_fut(flat_path, element_pb, True)
        self.assertIsNone(ret_val)
        self.assertEqual(flat_path, [name, name])

    def test_both_present(self):
        element_pb = _make_element_pb(id=17, name='seventeen')
        flat_path = []
        with self.assertRaises(ValueError):
            self._call_fut(flat_path, element_pb, False)
        with self.assertRaises(ValueError):
            self._call_fut(flat_path, element_pb, True)

        self.assertEqual(flat_path, [])

    def test_both_empty_failure(self):
        element_pb = _make_element_pb()
        flat_path = []
        with self.assertRaises(ValueError):
            self._call_fut(flat_path, element_pb, False)

        self.assertEqual(flat_path, [])

    def test_both_empty_allowed(self):
        element_pb = _make_element_pb()
        flat_path = []
        ret_val = self._call_fut(flat_path, element_pb, True)
        self.assertIsNone(ret_val)
        self.assertEqual(flat_path, [])


class Test__get_flat_path(unittest.TestCase):

    @staticmethod
    def _call_fut(path_pb):
        from google.cloud.datastore.key import _get_flat_path

        return _get_flat_path(path_pb)

    def test_one_pair(self):
        kind = 'Widget'
        name = 'Scooter'
        element_pb = _make_element_pb(type=kind, name=name)
        path_pb = _make_path_pb(element_pb)
        flat_path = self._call_fut(path_pb)
        self.assertEqual(flat_path, (kind, name))

    def test_two_pairs(self):
        kind1 = 'parent'
        id1 = 59
        element_pb1 = _make_element_pb(type=kind1, id=id1)

        kind2 = 'child'
        name2 = 'naem'
        element_pb2 = _make_element_pb(type=kind2, name=name2)

        path_pb = _make_path_pb(element_pb1, element_pb2)
        flat_path = self._call_fut(path_pb)
        self.assertEqual(flat_path, (kind1, id1, kind2, name2))

    def test_partial_key(self):
        kind1 = 'grandparent'
        name1 = 'cats'
        element_pb1 = _make_element_pb(type=kind1, name=name1)

        kind2 = 'parent'
        id2 = 1337
        element_pb2 = _make_element_pb(type=kind2, id=id2)

        kind3 = 'child'
        element_pb3 = _make_element_pb(type=kind3)

        path_pb = _make_path_pb(element_pb1, element_pb2, element_pb3)
        flat_path = self._call_fut(path_pb)
        self.assertEqual(flat_path, (kind1, name1, kind2, id2, kind3))


class Test__to_legacy_path(unittest.TestCase):

    @staticmethod
    def _call_fut(dict_path):
        from google.cloud.datastore.key import _to_legacy_path

        return _to_legacy_path(dict_path)

    def test_one_pair(self):
        kind = 'Widget'
        name = 'Scooter'
        dict_path = [{'kind': kind, 'name': name}]
        path_pb = self._call_fut(dict_path)

        element_pb = _make_element_pb(type=kind, name=name)
        expected_pb = _make_path_pb(element_pb)
        self.assertEqual(path_pb, expected_pb)

    def test_two_pairs(self):
        kind1 = 'parent'
        id1 = 59

        kind2 = 'child'
        name2 = 'naem'

        dict_path = [{'kind': kind1, 'id': id1}, {'kind': kind2, 'name': name2}]
        path_pb = self._call_fut(dict_path)

        element_pb1 = _make_element_pb(type=kind1, id=id1)
        element_pb2 = _make_element_pb(type=kind2, name=name2)
        expected_pb = _make_path_pb(element_pb1, element_pb2)
        self.assertEqual(path_pb, expected_pb)

    def test_partial_key(self):
        kind1 = 'grandparent'
        name1 = 'cats'

        kind2 = 'parent'
        id2 = 1337

        kind3 = 'child'

        dict_path = [
            {'kind': kind1, 'name': name1},
            {'kind': kind2, 'id': id2},
            {'kind': kind3},
        ]
        path_pb = self._call_fut(dict_path)

        element_pb1 = _make_element_pb(type=kind1, name=name1)
        element_pb2 = _make_element_pb(type=kind2, id=id2)
        element_pb3 = _make_element_pb(type=kind3)
        expected_pb = _make_path_pb(element_pb1, element_pb2, element_pb3)
        self.assertEqual(path_pb, expected_pb)


def _make_element_pb(**kwargs):
    from google.cloud.datastore import _app_engine_key_pb2

    return _app_engine_key_pb2.Path.Element(**kwargs)


def _make_path_pb(*element_pbs):
    from google.cloud.datastore import _app_engine_key_pb2

    return _app_engine_key_pb2.Path(element=element_pbs)
