diff --git a/moto/s3/responses.py b/moto/s3/responses.py index eeb48388a..bed8a423b 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -261,7 +261,7 @@ class S3Response(BaseResponse): return status_code, headers, response_content def _bucket_response(self, request, full_url): - querystring = self._get_querystring(full_url) + querystring = self._get_querystring(request, full_url) method = request.method region_name = parse_region_from_url(full_url, use_default_region=False) if region_name is None: @@ -297,7 +297,17 @@ class S3Response(BaseResponse): ) @staticmethod - def _get_querystring(full_url): + def _get_querystring(request, full_url): + # Flask's Request has the querystring already parsed + # In ServerMode, we can use this, instead of manually parsing this + if hasattr(request, "args"): + query_dict = dict() + for key, val in dict(request.args).items(): + # The parse_qs-method returns List[str, List[Any]] + # Ensure that we confirm to the same response-type here + query_dict[key] = val if isinstance(val, list) else [val] + return query_dict + parsed_url = urlparse(full_url) # full_url can be one of two formats, depending on the version of werkzeug used: # http://foobaz.localhost:5000/?prefix=bar%2Bbaz diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 35226208b..2c41430ad 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -3285,7 +3285,9 @@ if settings.TEST_SERVER_MODE: @mock_s3 -@pytest.mark.parametrize("prefix", ["file", "file+else", "file&another"]) +@pytest.mark.parametrize( + "prefix", ["file", "file+else", "file&another", "file another"] +) def test_get_object_versions_with_prefix(prefix): bucket_name = "testbucket-3113" s3_resource = boto3.resource("s3", region_name=DEFAULT_REGION_NAME) diff --git a/tests/test_s3/test_server.py b/tests/test_s3/test_server.py index 70ca0faeb..222ced7b9 100644 --- a/tests/test_s3/test_server.py +++ b/tests/test_s3/test_server.py @@ -1,6 +1,8 @@ import io from urllib.parse import urlparse, parse_qs import sure # noqa # pylint: disable=unused-import +import pytest +import xmltodict from flask.testing import FlaskClient import moto.server as server @@ -32,7 +34,8 @@ def test_s3_server_get(): res.data.should.contain(b"ListAllMyBucketsResult") -def test_s3_server_bucket_create(): +@pytest.mark.parametrize("key_name", ["bar_baz", "bar+baz", "baz bar"]) +def test_s3_server_bucket_create(key_name): test_client = authenticated_client() res = test_client.put("/", "http://foobaz.localhost:5000/") @@ -45,22 +48,25 @@ def test_s3_server_bucket_create(): res.status_code.should.equal(200) res.data.should.contain(b"ListBucketResult") - for key_name in ("bar_baz", "bar+baz"): - res = test_client.put( - f"/{key_name}", "http://foobaz.localhost:5000/", data="test value" - ) - res.status_code.should.equal(200) - assert "ETag" in dict(res.headers) + res = test_client.put( + f"/{key_name}", "http://foobaz.localhost:5000/", data="test value" + ) + res.status_code.should.equal(200) + assert "ETag" in dict(res.headers) - res = test_client.get( - "/", "http://foobaz.localhost:5000/", query_string={"prefix": key_name} - ) - res.status_code.should.equal(200) - res.data.should.contain(b"Contents") + res = test_client.get( + "/", "http://foobaz.localhost:5000/", query_string={"prefix": key_name} + ) + res.status_code.should.equal(200) + content = xmltodict.parse(res.data)["ListBucketResult"]["Contents"] + # If we receive a dict, we only received one result + # If content is of type list, our call returned multiple results - which is not correct + content.should.be.a(dict) + content["Key"].should.equal(key_name) - res = test_client.get(f"/{key_name}", "http://foobaz.localhost:5000/") - res.status_code.should.equal(200) - res.data.should.equal(b"test value") + res = test_client.get(f"/{key_name}", "http://foobaz.localhost:5000/") + res.status_code.should.equal(200) + res.data.should.equal(b"test value") def test_s3_server_ignore_subdomain_for_bucketnames():