224 lines
6.8 KiB
Python
224 lines
6.8 KiB
Python
|
# Copyright 2014 Google Inc. All rights reserved.
|
||
|
#
|
||
|
# 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 unittest2
|
||
|
|
||
|
|
||
|
class Test_PropertyMixin(unittest2.TestCase):
|
||
|
|
||
|
def _getTargetClass(self):
|
||
|
from gcloud.storage._helpers import _PropertyMixin
|
||
|
return _PropertyMixin
|
||
|
|
||
|
def _makeOne(self, *args, **kw):
|
||
|
return self._getTargetClass()(*args, **kw)
|
||
|
|
||
|
def _derivedClass(self, path=None):
|
||
|
|
||
|
class Derived(self._getTargetClass()):
|
||
|
|
||
|
client = None
|
||
|
|
||
|
@property
|
||
|
def path(self):
|
||
|
return path
|
||
|
|
||
|
return Derived
|
||
|
|
||
|
def test_path_is_abstract(self):
|
||
|
mixin = self._makeOne()
|
||
|
self.assertRaises(NotImplementedError, lambda: mixin.path)
|
||
|
|
||
|
def test_client_is_abstract(self):
|
||
|
mixin = self._makeOne()
|
||
|
self.assertRaises(NotImplementedError, lambda: mixin.client)
|
||
|
|
||
|
def test_reload(self):
|
||
|
connection = _Connection({'foo': 'Foo'})
|
||
|
client = _Client(connection)
|
||
|
derived = self._derivedClass('/path')()
|
||
|
# Make sure changes is not a set, so we can observe a change.
|
||
|
derived._changes = object()
|
||
|
derived.reload(client=client)
|
||
|
self.assertEqual(derived._properties, {'foo': 'Foo'})
|
||
|
kw = connection._requested
|
||
|
self.assertEqual(len(kw), 1)
|
||
|
self.assertEqual(kw[0]['method'], 'GET')
|
||
|
self.assertEqual(kw[0]['path'], '/path')
|
||
|
self.assertEqual(kw[0]['query_params'], {'projection': 'noAcl'})
|
||
|
# Make sure changes get reset by reload.
|
||
|
self.assertEqual(derived._changes, set())
|
||
|
|
||
|
def test__set_properties(self):
|
||
|
mixin = self._makeOne()
|
||
|
self.assertEqual(mixin._properties, {})
|
||
|
VALUE = object()
|
||
|
mixin._set_properties(VALUE)
|
||
|
self.assertEqual(mixin._properties, VALUE)
|
||
|
|
||
|
def test__patch_property(self):
|
||
|
derived = self._derivedClass()()
|
||
|
derived._patch_property('foo', 'Foo')
|
||
|
self.assertEqual(derived._properties, {'foo': 'Foo'})
|
||
|
|
||
|
def test_patch(self):
|
||
|
connection = _Connection({'foo': 'Foo'})
|
||
|
client = _Client(connection)
|
||
|
derived = self._derivedClass('/path')()
|
||
|
# Make sure changes is non-empty, so we can observe a change.
|
||
|
BAR = object()
|
||
|
BAZ = object()
|
||
|
derived._properties = {'bar': BAR, 'baz': BAZ}
|
||
|
derived._changes = set(['bar']) # Ignore baz.
|
||
|
derived.patch(client=client)
|
||
|
self.assertEqual(derived._properties, {'foo': 'Foo'})
|
||
|
kw = connection._requested
|
||
|
self.assertEqual(len(kw), 1)
|
||
|
self.assertEqual(kw[0]['method'], 'PATCH')
|
||
|
self.assertEqual(kw[0]['path'], '/path')
|
||
|
self.assertEqual(kw[0]['query_params'], {'projection': 'full'})
|
||
|
# Since changes does not include `baz`, we don't see it sent.
|
||
|
self.assertEqual(kw[0]['data'], {'bar': BAR})
|
||
|
# Make sure changes get reset by patch().
|
||
|
self.assertEqual(derived._changes, set())
|
||
|
|
||
|
|
||
|
class Test__scalar_property(unittest2.TestCase):
|
||
|
|
||
|
def _callFUT(self, fieldName):
|
||
|
from gcloud.storage._helpers import _scalar_property
|
||
|
return _scalar_property(fieldName)
|
||
|
|
||
|
def test_getter(self):
|
||
|
|
||
|
class Test(object):
|
||
|
def __init__(self, **kw):
|
||
|
self._properties = kw.copy()
|
||
|
do_re_mi = self._callFUT('solfege')
|
||
|
|
||
|
test = Test(solfege='Latido')
|
||
|
self.assertEqual(test.do_re_mi, 'Latido')
|
||
|
|
||
|
def test_setter(self):
|
||
|
|
||
|
class Test(object):
|
||
|
def _patch_property(self, name, value):
|
||
|
self._patched = (name, value)
|
||
|
do_re_mi = self._callFUT('solfege')
|
||
|
|
||
|
test = Test()
|
||
|
test.do_re_mi = 'Latido'
|
||
|
self.assertEqual(test._patched, ('solfege', 'Latido'))
|
||
|
|
||
|
|
||
|
class Test__base64_md5hash(unittest2.TestCase):
|
||
|
|
||
|
def _callFUT(self, bytes_to_sign):
|
||
|
from gcloud.storage._helpers import _base64_md5hash
|
||
|
return _base64_md5hash(bytes_to_sign)
|
||
|
|
||
|
def test_it(self):
|
||
|
from io import BytesIO
|
||
|
BYTES_TO_SIGN = b'FOO'
|
||
|
BUFFER = BytesIO()
|
||
|
BUFFER.write(BYTES_TO_SIGN)
|
||
|
BUFFER.seek(0)
|
||
|
|
||
|
SIGNED_CONTENT = self._callFUT(BUFFER)
|
||
|
self.assertEqual(SIGNED_CONTENT, b'kBiQqOnIz21aGlQrIp/r/w==')
|
||
|
|
||
|
def test_it_with_stubs(self):
|
||
|
from gcloud._testing import _Monkey
|
||
|
from gcloud.storage import _helpers as MUT
|
||
|
|
||
|
class _Buffer(object):
|
||
|
|
||
|
def __init__(self, return_vals):
|
||
|
self.return_vals = return_vals
|
||
|
self._block_sizes = []
|
||
|
|
||
|
def read(self, block_size):
|
||
|
self._block_sizes.append(block_size)
|
||
|
return self.return_vals.pop()
|
||
|
|
||
|
BASE64 = _Base64()
|
||
|
DIGEST_VAL = object()
|
||
|
BYTES_TO_SIGN = b'BYTES_TO_SIGN'
|
||
|
BUFFER = _Buffer([b'', BYTES_TO_SIGN])
|
||
|
MD5 = _MD5(DIGEST_VAL)
|
||
|
|
||
|
with _Monkey(MUT, base64=BASE64, md5=MD5):
|
||
|
SIGNED_CONTENT = self._callFUT(BUFFER)
|
||
|
|
||
|
self.assertEqual(BUFFER._block_sizes, [8192, 8192])
|
||
|
self.assertTrue(SIGNED_CONTENT is DIGEST_VAL)
|
||
|
self.assertEqual(BASE64._called_b64encode, [DIGEST_VAL])
|
||
|
self.assertEqual(MD5._called, [None])
|
||
|
self.assertEqual(MD5.hash_obj.num_digest_calls, 1)
|
||
|
self.assertEqual(MD5.hash_obj._blocks, [BYTES_TO_SIGN])
|
||
|
|
||
|
|
||
|
class _Connection(object):
|
||
|
|
||
|
def __init__(self, *responses):
|
||
|
self._responses = responses
|
||
|
self._requested = []
|
||
|
|
||
|
def api_request(self, **kw):
|
||
|
self._requested.append(kw)
|
||
|
response, self._responses = self._responses[0], self._responses[1:]
|
||
|
return response
|
||
|
|
||
|
|
||
|
class _MD5Hash(object):
|
||
|
|
||
|
def __init__(self, digest_val):
|
||
|
self.digest_val = digest_val
|
||
|
self.num_digest_calls = 0
|
||
|
self._blocks = []
|
||
|
|
||
|
def update(self, block):
|
||
|
self._blocks.append(block)
|
||
|
|
||
|
def digest(self):
|
||
|
self.num_digest_calls += 1
|
||
|
return self.digest_val
|
||
|
|
||
|
|
||
|
class _MD5(object):
|
||
|
|
||
|
def __init__(self, digest_val):
|
||
|
self.hash_obj = _MD5Hash(digest_val)
|
||
|
self._called = []
|
||
|
|
||
|
def __call__(self, data=None):
|
||
|
self._called.append(data)
|
||
|
return self.hash_obj
|
||
|
|
||
|
|
||
|
class _Base64(object):
|
||
|
|
||
|
def __init__(self):
|
||
|
self._called_b64encode = []
|
||
|
|
||
|
def b64encode(self, value):
|
||
|
self._called_b64encode.append(value)
|
||
|
return value
|
||
|
|
||
|
|
||
|
class _Client(object):
|
||
|
|
||
|
def __init__(self, connection):
|
||
|
self.connection = connection
|