diff --git a/moto/core/responses.py b/moto/core/responses.py index 508bd8c5..c52e8989 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -16,7 +16,7 @@ from moto.core.exceptions import DryRunClientError from jinja2 import Environment, DictLoader, TemplateNotFound import six -from six.moves.urllib.parse import parse_qs, urlparse +from six.moves.urllib.parse import parse_qs, parse_qsl, urlparse import xmltodict from werkzeug.exceptions import HTTPException @@ -30,7 +30,7 @@ log = logging.getLogger(__name__) def _decode_dict(d): - decoded = {} + decoded = OrderedDict() for key, value in d.items(): if isinstance(key, six.binary_type): newkey = key.decode("utf-8") @@ -199,7 +199,7 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): return cls()._dispatch(*args, **kwargs) def setup_class(self, request, full_url, headers): - querystring = {} + querystring = OrderedDict() if hasattr(request, "body"): # Boto self.body = request.body @@ -211,7 +211,7 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): # definition for back-compatibility self.body = request.data - querystring = {} + querystring = OrderedDict() for key, value in request.form.items(): querystring[key] = [value] @@ -240,7 +240,14 @@ class BaseResponse(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): querystring[key] = [value] elif self.body: try: - querystring.update(parse_qs(raw_body, keep_blank_values=True)) + querystring.update( + OrderedDict( + (key, [value]) + for key, value in parse_qsl( + raw_body, keep_blank_values=True + ) + ) + ) except UnicodeEncodeError: pass # ignore encoding errors, as the body may not contain a legitimate querystring if not querystring: diff --git a/tests/test_cloudformation/test_validate.py b/tests/test_cloudformation/test_validate.py index 4dd4d7e0..a4278b55 100644 --- a/tests/test_cloudformation/test_validate.py +++ b/tests/test_cloudformation/test_validate.py @@ -62,10 +62,9 @@ def test_boto3_json_invalid_missing_resource(): cf_conn.validate_template(TemplateBody=dummy_bad_template_json) assert False except botocore.exceptions.ClientError as e: - assert ( - str(e) - == "An error occurred (ValidationError) when calling the ValidateTemplate operation: Stack" - " with id Missing top level item Resources to file module does not exist" + str(e).should.contain( + "An error occurred (ValidationError) when calling the ValidateTemplate operation: Stack" + " with id Missing top level" ) assert True @@ -103,9 +102,8 @@ def test_boto3_yaml_invalid_missing_resource(): cf_conn.validate_template(TemplateBody=yaml_bad_template) assert False except botocore.exceptions.ClientError as e: - assert ( - str(e) - == "An error occurred (ValidationError) when calling the ValidateTemplate operation: Stack" - " with id Missing top level item Resources to file module does not exist" + str(e).should.contain( + "An error occurred (ValidationError) when calling the ValidateTemplate operation: Stack" + " with id Missing top level" ) assert True