python-botocore/tests/unit/test_serialize.py

544 lines
19 KiB
Python
Raw Normal View History

"""Additional tests for request serialization.
While there are compliance tests in tests/unit/protocols where
the majority of the request serialization/response parsing is tested,
this test module contains additional tests that go above and beyond the
spec. This can happen for a number of reasons:
* We are testing python specific behavior that doesn't make sense as a
compliance test.
* We are testing behavior that is not strictly part of the spec. These
may result in a a coverage gap that would otherwise be untested.
"""
import base64
import datetime
2022-05-26 00:10:07 +02:00
import json
import dateutil.tz
from botocore import serialize
from botocore.compat import six
from botocore.exceptions import ParamValidationError
2022-05-26 00:10:07 +02:00
from botocore.model import ServiceModel
from tests import unittest
class BaseModelWithBlob(unittest.TestCase):
def setUp(self):
self.model = {
'metadata': {'protocol': 'query', 'apiVersion': '2014-01-01'},
'documentation': '',
'operations': {
'TestOperation': {
'name': 'TestOperation',
'http': {
'method': 'POST',
'requestUri': '/',
},
'input': {'shape': 'InputShape'},
}
},
'shapes': {
'InputShape': {
'type': 'structure',
'members': {
'Blob': {'shape': 'BlobType'},
2022-05-26 00:10:07 +02:00
},
},
'BlobType': {
'type': 'blob',
2022-05-26 00:10:07 +02:00
},
},
}
def serialize_to_request(self, input_params):
service_model = ServiceModel(self.model)
request_serializer = serialize.create_serializer(
2022-05-26 00:10:07 +02:00
service_model.metadata['protocol']
)
return request_serializer.serialize_to_request(
2022-05-26 00:10:07 +02:00
input_params, service_model.operation_model('TestOperation')
)
def assert_serialized_blob_equals(self, request, blob_bytes):
# This method handles all the details of the base64 decoding.
encoded = base64.b64encode(blob_bytes)
# Now the serializers actually have the base64 encoded contents
# as str types so we need to decode back. We know that this is
# ascii so it's safe to use the ascii encoding.
expected = encoded.decode('ascii')
self.assertEqual(request['body']['Blob'], expected)
class TestBinaryTypes(BaseModelWithBlob):
def test_blob_accepts_bytes_type(self):
body = b'bytes body'
request = self.serialize_to_request(input_params={'Blob': body})
2022-05-26 00:10:07 +02:00
self.assert_serialized_blob_equals(request, blob_bytes=body)
def test_blob_accepts_str_type(self):
2022-05-26 00:10:07 +02:00
body = 'ascii text'
request = self.serialize_to_request(input_params={'Blob': body})
self.assert_serialized_blob_equals(
2022-05-26 00:10:07 +02:00
request, blob_bytes=body.encode('ascii')
)
def test_blob_handles_unicode_chars(self):
2022-05-26 00:10:07 +02:00
body = '\u2713'
request = self.serialize_to_request(input_params={'Blob': body})
self.assert_serialized_blob_equals(
2022-05-26 00:10:07 +02:00
request, blob_bytes=body.encode('utf-8')
)
class TestBinaryTypesJSON(BaseModelWithBlob):
def setUp(self):
2022-05-26 00:10:07 +02:00
super().setUp()
self.model['metadata'] = {
'protocol': 'json',
'apiVersion': '2014-01-01',
'jsonVersion': '1.1',
'targetPrefix': 'foo',
}
def test_blob_accepts_bytes_type(self):
body = b'bytes body'
request = self.serialize_to_request(input_params={'Blob': body})
serialized_blob = json.loads(request['body'].decode('utf-8'))['Blob']
self.assertEqual(
2022-05-26 00:10:07 +02:00
base64.b64encode(body).decode('ascii'), serialized_blob
)
class TestBinaryTypesWithRestXML(BaseModelWithBlob):
def setUp(self):
2022-05-26 00:10:07 +02:00
super().setUp()
self.model['metadata'] = {
'protocol': 'rest-xml',
'apiVersion': '2014-01-01',
}
self.model['operations']['TestOperation']['input'] = {
'shape': 'InputShape',
'locationName': 'OperationRequest',
'payload': 'Blob',
}
def test_blob_serialization_with_file_like_object(self):
body = six.BytesIO(b'foobar')
request = self.serialize_to_request(input_params={'Blob': body})
self.assertEqual(request['body'], body)
def test_blob_serialization_when_payload_is_unicode(self):
# When the body is a text type, we should encode the
# text to bytes.
2022-05-26 00:10:07 +02:00
body = '\u2713'
request = self.serialize_to_request(input_params={'Blob': body})
self.assertEqual(request['body'], body.encode('utf-8'))
def test_blob_serialization_when_payload_is_bytes(self):
body = b'bytes body'
request = self.serialize_to_request(input_params={'Blob': body})
self.assertEqual(request['body'], body)
class TestTimestampHeadersWithRestXML(unittest.TestCase):
def setUp(self):
self.model = {
'metadata': {'protocol': 'rest-xml', 'apiVersion': '2014-01-01'},
'documentation': '',
'operations': {
'TestOperation': {
'name': 'TestOperation',
'http': {
'method': 'POST',
'requestUri': '/',
},
'input': {'shape': 'InputShape'},
}
},
'shapes': {
'InputShape': {
'type': 'structure',
'members': {
'TimestampHeader': {
'shape': 'TimestampType',
'location': 'header',
2022-05-26 00:10:07 +02:00
'locationName': 'x-timestamp',
},
2022-05-26 00:10:07 +02:00
},
},
'TimestampType': {
'type': 'timestamp',
2022-05-26 00:10:07 +02:00
},
},
}
self.service_model = ServiceModel(self.model)
def serialize_to_request(self, input_params):
request_serializer = serialize.create_serializer(
2022-05-26 00:10:07 +02:00
self.service_model.metadata['protocol']
)
return request_serializer.serialize_to_request(
2022-05-26 00:10:07 +02:00
input_params, self.service_model.operation_model('TestOperation')
)
def test_accepts_datetime_object(self):
request = self.serialize_to_request(
2022-05-26 00:10:07 +02:00
{
'TimestampHeader': datetime.datetime(
2014, 1, 1, 12, 12, 12, tzinfo=dateutil.tz.tzutc()
)
}
)
self.assertEqual(
request['headers']['x-timestamp'], 'Wed, 01 Jan 2014 12:12:12 GMT'
)
def test_accepts_iso_8601_format(self):
request = self.serialize_to_request(
2022-05-26 00:10:07 +02:00
{'TimestampHeader': '2014-01-01T12:12:12+00:00'}
)
self.assertEqual(
request['headers']['x-timestamp'], 'Wed, 01 Jan 2014 12:12:12 GMT'
)
def test_accepts_iso_8601_format_non_utc(self):
request = self.serialize_to_request(
2022-05-26 00:10:07 +02:00
{'TimestampHeader': '2014-01-01T07:12:12-05:00'}
)
self.assertEqual(
request['headers']['x-timestamp'], 'Wed, 01 Jan 2014 12:12:12 GMT'
)
def test_accepts_rfc_822_format(self):
request = self.serialize_to_request(
2022-05-26 00:10:07 +02:00
{'TimestampHeader': 'Wed, 01 Jan 2014 12:12:12 GMT'}
)
self.assertEqual(
request['headers']['x-timestamp'], 'Wed, 01 Jan 2014 12:12:12 GMT'
)
def test_accepts_unix_timestamp_integer(self):
2022-05-26 00:10:07 +02:00
request = self.serialize_to_request({'TimestampHeader': 1388578332})
self.assertEqual(
request['headers']['x-timestamp'], 'Wed, 01 Jan 2014 12:12:12 GMT'
)
class TestTimestamps(unittest.TestCase):
def setUp(self):
self.model = {
'metadata': {'protocol': 'query', 'apiVersion': '2014-01-01'},
'documentation': '',
'operations': {
'TestOperation': {
'name': 'TestOperation',
'http': {
'method': 'POST',
'requestUri': '/',
},
'input': {'shape': 'InputShape'},
}
},
'shapes': {
'InputShape': {
'type': 'structure',
'members': {
'Timestamp': {'shape': 'TimestampType'},
2022-05-26 00:10:07 +02:00
},
},
'TimestampType': {
'type': 'timestamp',
2022-05-26 00:10:07 +02:00
},
},
}
self.service_model = ServiceModel(self.model)
def serialize_to_request(self, input_params):
request_serializer = serialize.create_serializer(
2022-05-26 00:10:07 +02:00
self.service_model.metadata['protocol']
)
return request_serializer.serialize_to_request(
2022-05-26 00:10:07 +02:00
input_params, self.service_model.operation_model('TestOperation')
)
def test_accepts_datetime_object(self):
request = self.serialize_to_request(
2022-05-26 00:10:07 +02:00
{
'Timestamp': datetime.datetime(
2014, 1, 1, 12, 12, 12, tzinfo=dateutil.tz.tzutc()
)
}
)
self.assertEqual(request['body']['Timestamp'], '2014-01-01T12:12:12Z')
def test_accepts_naive_datetime_object(self):
request = self.serialize_to_request(
2022-05-26 00:10:07 +02:00
{'Timestamp': datetime.datetime(2014, 1, 1, 12, 12, 12)}
)
self.assertEqual(request['body']['Timestamp'], '2014-01-01T12:12:12Z')
def test_accepts_iso_8601_format(self):
request = self.serialize_to_request(
2022-05-26 00:10:07 +02:00
{'Timestamp': '2014-01-01T12:12:12Z'}
)
self.assertEqual(request['body']['Timestamp'], '2014-01-01T12:12:12Z')
def test_accepts_timestamp_without_tz_info(self):
# If a timezone/utc is not specified, assume they meant
# UTC. This is also the previous behavior from older versions
# of botocore so we want to make sure we preserve this behavior.
request = self.serialize_to_request(
2022-05-26 00:10:07 +02:00
{'Timestamp': '2014-01-01T12:12:12'}
)
self.assertEqual(request['body']['Timestamp'], '2014-01-01T12:12:12Z')
def test_microsecond_timestamp_without_tz_info(self):
request = self.serialize_to_request(
2022-05-26 00:10:07 +02:00
{'Timestamp': '2014-01-01T12:12:12.123456'}
)
self.assertEqual(
request['body']['Timestamp'], '2014-01-01T12:12:12.123456Z'
)
class TestJSONTimestampSerialization(unittest.TestCase):
def setUp(self):
self.model = {
2022-05-26 00:10:07 +02:00
'metadata': {
'protocol': 'json',
'apiVersion': '2014-01-01',
'jsonVersion': '1.1',
'targetPrefix': 'foo',
},
'documentation': '',
'operations': {
'TestOperation': {
'name': 'TestOperation',
'http': {
'method': 'POST',
'requestUri': '/',
},
'input': {'shape': 'InputShape'},
}
},
'shapes': {
'InputShape': {
'type': 'structure',
'members': {
'Timestamp': {'shape': 'TimestampType'},
2022-05-26 00:10:07 +02:00
},
},
'TimestampType': {
'type': 'timestamp',
2022-05-26 00:10:07 +02:00
},
},
}
self.service_model = ServiceModel(self.model)
def serialize_to_request(self, input_params):
request_serializer = serialize.create_serializer(
2022-05-26 00:10:07 +02:00
self.service_model.metadata['protocol']
)
return request_serializer.serialize_to_request(
2022-05-26 00:10:07 +02:00
input_params, self.service_model.operation_model('TestOperation')
)
def test_accepts_iso_8601_format(self):
2022-05-26 00:10:07 +02:00
body = json.loads(
self.serialize_to_request({'Timestamp': '1970-01-01T00:00:00'})[
'body'
].decode('utf-8')
)
self.assertEqual(body['Timestamp'], 0)
def test_accepts_epoch(self):
2022-05-26 00:10:07 +02:00
body = json.loads(
self.serialize_to_request({'Timestamp': '0'})['body'].decode(
'utf-8'
)
)
self.assertEqual(body['Timestamp'], 0)
# Can also be an integer 0.
2022-05-26 00:10:07 +02:00
body = json.loads(
self.serialize_to_request({'Timestamp': 0})['body'].decode('utf-8')
)
self.assertEqual(body['Timestamp'], 0)
def test_accepts_partial_iso_format(self):
2022-05-26 00:10:07 +02:00
body = json.loads(
self.serialize_to_request({'Timestamp': '1970-01-01'})[
'body'
].decode('utf-8')
)
self.assertEqual(body['Timestamp'], 0)
class TestInstanceCreation(unittest.TestCase):
def setUp(self):
self.model = {
'metadata': {'protocol': 'query', 'apiVersion': '2014-01-01'},
'documentation': '',
'operations': {
'TestOperation': {
'name': 'TestOperation',
'http': {
'method': 'POST',
'requestUri': '/',
},
'input': {'shape': 'InputShape'},
}
},
'shapes': {
'InputShape': {
'type': 'structure',
'members': {
'Timestamp': {'shape': 'StringTestType'},
2022-05-26 00:10:07 +02:00
},
},
2022-05-26 00:10:07 +02:00
'StringTestType': {'type': 'string', 'min': 15},
},
}
self.service_model = ServiceModel(self.model)
def assert_serialize_valid_parameter(self, request_serializer):
valid_string = 'valid_string_with_min_15_chars'
request = request_serializer.serialize_to_request(
{'Timestamp': valid_string},
2022-05-26 00:10:07 +02:00
self.service_model.operation_model('TestOperation'),
)
self.assertEqual(request['body']['Timestamp'], valid_string)
def assert_serialize_invalid_parameter(self, request_serializer):
invalid_string = 'short string'
request = request_serializer.serialize_to_request(
{'Timestamp': invalid_string},
2022-05-26 00:10:07 +02:00
self.service_model.operation_model('TestOperation'),
)
self.assertEqual(request['body']['Timestamp'], invalid_string)
def test_instantiate_without_validation(self):
request_serializer = serialize.create_serializer(
2022-05-26 00:10:07 +02:00
self.service_model.metadata['protocol'], False
)
try:
self.assert_serialize_valid_parameter(request_serializer)
except ParamValidationError as e:
self.fail(
2021-11-03 18:14:15 +01:00
"Shouldn't fail serializing valid parameter without "
"validation: {}".format(e)
)
try:
self.assert_serialize_invalid_parameter(request_serializer)
except ParamValidationError as e:
self.fail(
2021-11-03 18:14:15 +01:00
"Shouldn't fail serializing invalid parameter without "
"validation: {}".format(e)
)
def test_instantiate_with_validation(self):
request_serializer = serialize.create_serializer(
2022-05-26 00:10:07 +02:00
self.service_model.metadata['protocol'], True
)
try:
self.assert_serialize_valid_parameter(request_serializer)
except ParamValidationError as e:
self.fail(
2021-11-03 18:14:15 +01:00
"Shouldn't fail serializing invalid parameter without "
"validation: {}".format(e)
)
with self.assertRaises(ParamValidationError):
self.assert_serialize_invalid_parameter(request_serializer)
class TestHeaderSerialization(BaseModelWithBlob):
def setUp(self):
self.model = {
'metadata': {'protocol': 'rest-xml', 'apiVersion': '2014-01-01'},
'documentation': '',
'operations': {
'TestOperation': {
'name': 'TestOperation',
'http': {
'method': 'POST',
'requestUri': '/',
},
'input': {'shape': 'InputShape'},
}
},
'shapes': {
'InputShape': {
'type': 'structure',
'members': {
'ContentLength': {
'shape': 'Integer',
'location': 'header',
2022-05-26 00:10:07 +02:00
'locationName': 'Content-Length',
},
2022-05-26 00:10:07 +02:00
},
},
2022-05-26 00:10:07 +02:00
'Integer': {'type': 'integer'},
},
}
self.service_model = ServiceModel(self.model)
def test_always_serialized_as_str(self):
request = self.serialize_to_request({'ContentLength': 100})
self.assertEqual(request['headers']['Content-Length'], '100')
class TestRestXMLUnicodeSerialization(unittest.TestCase):
def setUp(self):
self.model = {
'metadata': {'protocol': 'rest-xml', 'apiVersion': '2014-01-01'},
'documentation': '',
'operations': {
'TestOperation': {
'name': 'TestOperation',
'http': {
'method': 'POST',
'requestUri': '/',
},
'input': {'shape': 'InputShape'},
}
},
'shapes': {
'InputShape': {
'type': 'structure',
'members': {
2022-05-26 00:10:07 +02:00
'Foo': {'shape': 'FooShape', 'locationName': 'Foo'},
},
2022-05-26 00:10:07 +02:00
'payload': 'Foo',
},
'FooShape': {
'type': 'list',
2022-05-26 00:10:07 +02:00
'member': {'shape': 'StringShape'},
},
'StringShape': {
'type': 'string',
2022-05-26 00:10:07 +02:00
},
},
}
self.service_model = ServiceModel(self.model)
def serialize_to_request(self, input_params):
request_serializer = serialize.create_serializer(
2022-05-26 00:10:07 +02:00
self.service_model.metadata['protocol']
)
return request_serializer.serialize_to_request(
2022-05-26 00:10:07 +02:00
input_params, self.service_model.operation_model('TestOperation')
)
def test_restxml_serializes_unicode(self):
2022-05-26 00:10:07 +02:00
params = {'Foo': ['\u65e5\u672c\u8a9e\u3067\u304a\uff4b']}
try:
self.serialize_to_request(params)
except UnicodeEncodeError:
self.fail("RestXML serializer failed to serialize unicode text.")