diff --git a/moto/s3/responses.py b/moto/s3/responses.py index e0e65850c..a0dcbbaf4 100644 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -413,7 +413,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): if marker: result_keys = self._get_results_from_token(result_keys, marker) - result_keys, is_truncated, _ = self._truncate_result(result_keys, max_keys) + result_keys, is_truncated, next_marker = self._truncate_result(result_keys, max_keys) template = self.response_template(S3_BUCKET_GET_RESPONSE) return 200, {}, template.render( @@ -423,6 +423,7 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin): result_keys=result_keys, result_folders=result_folders, is_truncated=is_truncated, + next_marker=next_marker, max_keys=max_keys ) @@ -1327,6 +1328,9 @@ S3_BUCKET_GET_RESPONSE = """ {{ max_keys }} {{ delimiter }} {{ is_truncated }} + {% if next_marker %} + {{ next_marker }} + {% endif %} {% for key in result_keys %} {{ key.name }} diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 8d535420a..9a3bd630c 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -1247,6 +1247,54 @@ def test_website_redirect_location(): resp['WebsiteRedirectLocation'].should.equal(url) +@mock_s3 +def test_boto3_list_objects_truncated_response(): + s3 = boto3.client('s3', region_name='us-east-1') + s3.create_bucket(Bucket='mybucket') + s3.put_object(Bucket='mybucket', Key='one', Body=b'1') + s3.put_object(Bucket='mybucket', Key='two', Body=b'22') + s3.put_object(Bucket='mybucket', Key='three', Body=b'333') + + # First list + resp = s3.list_objects(Bucket='mybucket', MaxKeys=1) + listed_object = resp['Contents'][0] + + assert listed_object['Key'] == 'one' + assert resp['MaxKeys'] == 1 + assert resp['IsTruncated'] == True + assert resp['Prefix'] == 'None' + assert resp['Delimiter'] == 'None' + assert 'NextMarker' in resp + + next_marker = resp["NextMarker"] + + # Second list + resp = s3.list_objects( + Bucket='mybucket', MaxKeys=1, Marker=next_marker) + listed_object = resp['Contents'][0] + + assert listed_object['Key'] == 'three' + assert resp['MaxKeys'] == 1 + assert resp['IsTruncated'] == True + assert resp['Prefix'] == 'None' + assert resp['Delimiter'] == 'None' + assert 'NextMarker' in resp + + next_marker = resp["NextMarker"] + + # Third list + resp = s3.list_objects( + Bucket='mybucket', MaxKeys=1, Marker=next_marker) + listed_object = resp['Contents'][0] + + assert listed_object['Key'] == 'two' + assert resp['MaxKeys'] == 1 + assert resp['IsTruncated'] == False + assert resp['Prefix'] == 'None' + assert resp['Delimiter'] == 'None' + assert 'NextMarker' not in resp + + @mock_s3 def test_boto3_list_keys_xml_escaped(): s3 = boto3.client('s3', region_name='us-east-1')