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')