|
1 | 1 | import logging |
2 | 2 | import os |
3 | | -from typing import Any, List |
| 3 | +from typing import Any, List, cast |
4 | 4 | from uuid import uuid4 |
5 | 5 |
|
6 | 6 | import pytest |
7 | 7 | from botocore.config import Config |
8 | 8 | from botocore.exceptions import ClientError |
9 | | -from mypy_boto3_s3.service_resource import Bucket |
| 9 | +from mypy_boto3_s3.service_resource import Bucket, ObjectSummary, ObjectVersion |
10 | 10 | from pytest_httpserver import HTTPServer |
11 | 11 |
|
12 | 12 | from nhs_aws_helpers import ( |
13 | 13 | dynamodb_retry_backoff, |
14 | 14 | post_create_client, |
15 | 15 | register_config_default, |
16 | 16 | register_retry_handler, |
| 17 | + s3_build_uri, |
17 | 18 | s3_client, |
| 19 | + s3_delete_keys, |
| 20 | + s3_get_all_keys, |
18 | 21 | s3_list_folders, |
| 22 | + s3_ls, |
19 | 23 | s3_resource, |
20 | 24 | s3_upload_multipart_from_copy, |
21 | 25 | transaction_cancellation_reasons, |
@@ -59,6 +63,129 @@ def fail_me(): |
59 | 63 | assert len(calls) == 3 |
60 | 64 |
|
61 | 65 |
|
| 66 | +def test_s3_delete_keys_all(temp_s3_bucket: Bucket): |
| 67 | + |
| 68 | + keys = [str(key) for key in range(1003)] |
| 69 | + |
| 70 | + for key in keys: |
| 71 | + temp_s3_bucket.put_object(Key=key, Body=f"Some data {uuid4().hex}".encode()) |
| 72 | + |
| 73 | + deleted_keys = s3_delete_keys(keys, temp_s3_bucket.name) |
| 74 | + |
| 75 | + remaining_keys = list(s3_get_all_keys(temp_s3_bucket.name, "")) |
| 76 | + |
| 77 | + assert len(deleted_keys) == len(keys) |
| 78 | + assert set(deleted_keys) == set(keys) |
| 79 | + assert len(remaining_keys) == 0 |
| 80 | + |
| 81 | + |
| 82 | +def test_s3_delete_keys_partial(temp_s3_bucket: Bucket): |
| 83 | + |
| 84 | + keys = [str(key) for key in range(1003)] |
| 85 | + |
| 86 | + for key in keys: |
| 87 | + temp_s3_bucket.put_object(Key=key, Body=f"Some data {uuid4().hex}".encode()) |
| 88 | + |
| 89 | + leaving_keys = [keys.pop(i) for i in (1002, 456, 3)] |
| 90 | + |
| 91 | + deleted_keys = s3_delete_keys(keys, temp_s3_bucket.name) |
| 92 | + |
| 93 | + remaining_keys = list(s3_get_all_keys(temp_s3_bucket.name, "")) |
| 94 | + |
| 95 | + assert len(deleted_keys) == len(keys) |
| 96 | + assert set(deleted_keys) == set(keys) |
| 97 | + assert len(remaining_keys) == len(leaving_keys) |
| 98 | + assert set(remaining_keys) == set(leaving_keys) |
| 99 | + |
| 100 | + |
| 101 | +@pytest.mark.parametrize( |
| 102 | + "keys", |
| 103 | + [(), ("a",), ("a/b",), ("a/b", "a/c")], |
| 104 | +) |
| 105 | +def test_s3_get_all_keys(temp_s3_bucket: Bucket, keys: List[str]): |
| 106 | + for key in keys: |
| 107 | + temp_s3_bucket.put_object(Key=key, Body=f"Some data {uuid4().hex}".encode()) |
| 108 | + |
| 109 | + result_keys = list(s3_get_all_keys(temp_s3_bucket.name, "")) |
| 110 | + |
| 111 | + assert len(result_keys) == len(keys) |
| 112 | + assert set(result_keys) == set(keys) |
| 113 | + |
| 114 | + |
| 115 | +@pytest.mark.parametrize( |
| 116 | + "keys", |
| 117 | + [(), ("a",), ("a/b",), ("a/b", "a/c")], |
| 118 | +) |
| 119 | +def test_s3_get_all_keys_under_prefix(temp_s3_bucket: Bucket, keys: List[str]): |
| 120 | + expected_folder = uuid4().hex |
| 121 | + |
| 122 | + # Keys that shouldn't be included |
| 123 | + for key in ("x", "y/z"): |
| 124 | + temp_s3_bucket.put_object(Key=key, Body=f"Some data {uuid4().hex}".encode()) |
| 125 | + |
| 126 | + for key in keys: |
| 127 | + temp_s3_bucket.put_object(Key=f"{expected_folder}/{key}", Body=f"Some data {uuid4().hex}".encode()) |
| 128 | + |
| 129 | + result_keys = list(s3_get_all_keys(temp_s3_bucket.name, expected_folder)) |
| 130 | + |
| 131 | + assert len(result_keys) == len(keys) |
| 132 | + assert set(result_keys) == {f"{expected_folder}/{key}" for key in keys} |
| 133 | + |
| 134 | + |
| 135 | +def test_s3_ls(temp_s3_bucket: Bucket): |
| 136 | + expected_folder = uuid4().hex |
| 137 | + temp_s3_bucket.put_object(Key=f"{expected_folder}/1/a.txt", Body=f"Some data {uuid4().hex}".encode()) |
| 138 | + temp_s3_bucket.put_object(Key=f"{expected_folder}/1/b.txt", Body=f"Some data {uuid4().hex}".encode()) |
| 139 | + temp_s3_bucket.put_object(Key=f"{expected_folder}/2/c.txt", Body=f"Some data {uuid4().hex}".encode()) |
| 140 | + |
| 141 | + files = cast(List[ObjectSummary], list(s3_ls(s3_build_uri(temp_s3_bucket.name, expected_folder)))) |
| 142 | + |
| 143 | + assert len(files) == 3 |
| 144 | + assert {f.key for f in files} == { |
| 145 | + f"{expected_folder}/1/a.txt", |
| 146 | + f"{expected_folder}/1/b.txt", |
| 147 | + f"{expected_folder}/2/c.txt", |
| 148 | + } |
| 149 | + |
| 150 | + |
| 151 | +def test_s3_ls_versioning_on_non_versioned_bucket(temp_s3_bucket: Bucket): |
| 152 | + expected_folder = uuid4().hex |
| 153 | + temp_s3_bucket.put_object(Key=f"{expected_folder}/1/a.txt", Body=f"Some data {uuid4().hex}".encode()) |
| 154 | + temp_s3_bucket.put_object(Key=f"{expected_folder}/1/b.txt", Body=f"Some data {uuid4().hex}".encode()) |
| 155 | + temp_s3_bucket.put_object(Key=f"{expected_folder}/2/c.txt", Body=f"Some data {uuid4().hex}".encode()) |
| 156 | + temp_s3_bucket.put_object(Key=f"{expected_folder}/2/c.txt", Body=f"Some new data {uuid4().hex}".encode()) |
| 157 | + |
| 158 | + files = cast(List[ObjectVersion], list(s3_ls(s3_build_uri(temp_s3_bucket.name, expected_folder), versioning=True))) |
| 159 | + |
| 160 | + assert len(files) == 3 |
| 161 | + assert {f.key for f in files} == { |
| 162 | + f"{expected_folder}/1/a.txt", |
| 163 | + f"{expected_folder}/1/b.txt", |
| 164 | + f"{expected_folder}/2/c.txt", |
| 165 | + } |
| 166 | + assert len({f.id for f in files}) == 1 |
| 167 | + |
| 168 | + |
| 169 | +def test_s3_ls_versioning(temp_versioned_s3_bucket: Bucket): |
| 170 | + expected_folder = uuid4().hex |
| 171 | + temp_versioned_s3_bucket.put_object(Key=f"{expected_folder}/1/a.txt", Body=f"Some data {uuid4().hex}".encode()) |
| 172 | + temp_versioned_s3_bucket.put_object(Key=f"{expected_folder}/1/b.txt", Body=f"Some data {uuid4().hex}".encode()) |
| 173 | + temp_versioned_s3_bucket.put_object(Key=f"{expected_folder}/2/c.txt", Body=f"Some data {uuid4().hex}".encode()) |
| 174 | + temp_versioned_s3_bucket.put_object(Key=f"{expected_folder}/2/c.txt", Body=f"Some new data {uuid4().hex}".encode()) |
| 175 | + |
| 176 | + files = cast( |
| 177 | + List[ObjectVersion], list(s3_ls(s3_build_uri(temp_versioned_s3_bucket.name, expected_folder), versioning=True)) |
| 178 | + ) |
| 179 | + |
| 180 | + assert len(files) == 4 |
| 181 | + assert {f.key for f in files} == { |
| 182 | + f"{expected_folder}/1/a.txt", |
| 183 | + f"{expected_folder}/1/b.txt", |
| 184 | + f"{expected_folder}/2/c.txt", |
| 185 | + } |
| 186 | + assert len({f.id for f in files}) == 4 |
| 187 | + |
| 188 | + |
62 | 189 | def test_s3_list_folders_root(temp_s3_bucket: Bucket): |
63 | 190 | expected_folder = uuid4().hex |
64 | 191 |
|
|
0 commit comments