cledar-sdk 2.0.2__py3-none-any.whl → 2.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cledar/__init__.py +0 -0
- cledar/kafka/README.md +239 -0
- cledar/kafka/__init__.py +40 -0
- cledar/kafka/clients/base.py +98 -0
- cledar/kafka/clients/consumer.py +110 -0
- cledar/kafka/clients/producer.py +80 -0
- cledar/kafka/config/schemas.py +178 -0
- cledar/kafka/exceptions.py +22 -0
- cledar/kafka/handlers/dead_letter.py +82 -0
- cledar/kafka/handlers/parser.py +49 -0
- cledar/kafka/logger.py +3 -0
- cledar/kafka/models/input.py +13 -0
- cledar/kafka/models/message.py +10 -0
- cledar/kafka/models/output.py +8 -0
- cledar/kafka/tests/.env.test.kafka +3 -0
- cledar/kafka/tests/README.md +216 -0
- cledar/kafka/tests/conftest.py +104 -0
- cledar/kafka/tests/integration/__init__.py +1 -0
- cledar/kafka/tests/integration/conftest.py +78 -0
- cledar/kafka/tests/integration/helpers.py +47 -0
- cledar/kafka/tests/integration/test_consumer_integration.py +375 -0
- cledar/kafka/tests/integration/test_integration.py +394 -0
- cledar/kafka/tests/integration/test_producer_consumer_interaction.py +388 -0
- cledar/kafka/tests/integration/test_producer_integration.py +217 -0
- cledar/kafka/tests/unit/__init__.py +1 -0
- cledar/kafka/tests/unit/test_base_kafka_client.py +391 -0
- cledar/kafka/tests/unit/test_config_validation.py +609 -0
- cledar/kafka/tests/unit/test_dead_letter_handler.py +443 -0
- cledar/kafka/tests/unit/test_error_handling.py +674 -0
- cledar/kafka/tests/unit/test_input_parser.py +310 -0
- cledar/kafka/tests/unit/test_input_parser_comprehensive.py +489 -0
- cledar/kafka/tests/unit/test_utils.py +25 -0
- cledar/kafka/tests/unit/test_utils_comprehensive.py +408 -0
- cledar/kafka/utils/callbacks.py +19 -0
- cledar/kafka/utils/messages.py +28 -0
- cledar/kafka/utils/topics.py +2 -0
- cledar/kserve/README.md +352 -0
- cledar/kserve/__init__.py +3 -0
- cledar/kserve/tests/__init__.py +0 -0
- cledar/kserve/tests/test_utils.py +64 -0
- cledar/kserve/utils.py +27 -0
- cledar/logging/README.md +53 -0
- cledar/logging/__init__.py +3 -0
- cledar/logging/tests/test_universal_plaintext_formatter.py +249 -0
- cledar/logging/universal_plaintext_formatter.py +94 -0
- cledar/monitoring/README.md +71 -0
- cledar/monitoring/__init__.py +3 -0
- cledar/monitoring/monitoring_server.py +112 -0
- cledar/monitoring/tests/integration/test_monitoring_server_int.py +162 -0
- cledar/monitoring/tests/test_monitoring_server.py +59 -0
- cledar/nonce/README.md +99 -0
- cledar/nonce/__init__.py +3 -0
- cledar/nonce/nonce_service.py +36 -0
- cledar/nonce/tests/__init__.py +0 -0
- cledar/nonce/tests/test_nonce_service.py +136 -0
- cledar/redis/README.md +536 -0
- cledar/redis/__init__.py +15 -0
- cledar/redis/async_example.py +111 -0
- cledar/redis/example.py +37 -0
- cledar/redis/exceptions.py +22 -0
- cledar/redis/logger.py +3 -0
- cledar/redis/model.py +10 -0
- cledar/redis/redis.py +525 -0
- cledar/redis/redis_config_store.py +252 -0
- cledar/redis/tests/test_async_integration_redis.py +158 -0
- cledar/redis/tests/test_async_redis_service.py +380 -0
- cledar/redis/tests/test_integration_redis.py +119 -0
- cledar/redis/tests/test_redis_service.py +319 -0
- cledar/storage/README.md +529 -0
- cledar/storage/__init__.py +4 -0
- cledar/storage/constants.py +3 -0
- cledar/storage/exceptions.py +50 -0
- cledar/storage/models.py +19 -0
- cledar/storage/object_storage.py +955 -0
- cledar/storage/tests/conftest.py +18 -0
- cledar/storage/tests/test_abfs.py +164 -0
- cledar/storage/tests/test_integration_filesystem.py +359 -0
- cledar/storage/tests/test_integration_s3.py +453 -0
- cledar/storage/tests/test_local.py +384 -0
- cledar/storage/tests/test_s3.py +521 -0
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/METADATA +1 -1
- cledar_sdk-2.0.3.dist-info/RECORD +84 -0
- cledar_sdk-2.0.2.dist-info/RECORD +0 -4
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/WHEEL +0 -0
- {cledar_sdk-2.0.2.dist-info → cledar_sdk-2.0.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
# mypy: disable-error-code=no-untyped-def
|
|
2
|
+
import io
|
|
3
|
+
import tempfile
|
|
4
|
+
from collections.abc import Generator
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
from faker import Faker
|
|
9
|
+
from testcontainers.minio import MinioContainer
|
|
10
|
+
|
|
11
|
+
from cledar.storage.exceptions import ReadFileError, RequiredBucketNotFoundError
|
|
12
|
+
from cledar.storage.models import ObjectStorageServiceConfig
|
|
13
|
+
from cledar.storage.object_storage import ObjectStorageService
|
|
14
|
+
|
|
15
|
+
fake = Faker()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.fixture(scope="module")
|
|
19
|
+
def minio_container():
|
|
20
|
+
"""
|
|
21
|
+
Start a MinIO container for testing.
|
|
22
|
+
"""
|
|
23
|
+
with MinioContainer(
|
|
24
|
+
access_key="minioadmin",
|
|
25
|
+
secret_key="minioadmin",
|
|
26
|
+
) as minio:
|
|
27
|
+
yield minio
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@pytest.fixture(scope="module")
|
|
31
|
+
def object_storage_service(minio_container: MinioContainer) -> ObjectStorageService:
|
|
32
|
+
"""
|
|
33
|
+
Create an ObjectStorageService connected to MinIO testcontainer.
|
|
34
|
+
"""
|
|
35
|
+
host = minio_container.get_container_host_ip()
|
|
36
|
+
port = minio_container.get_exposed_port(minio_container.port)
|
|
37
|
+
endpoint_url = f"http://{host}:{port}"
|
|
38
|
+
|
|
39
|
+
config = ObjectStorageServiceConfig(
|
|
40
|
+
s3_endpoint_url=endpoint_url,
|
|
41
|
+
s3_access_key=minio_container.access_key,
|
|
42
|
+
s3_secret_key=minio_container.secret_key,
|
|
43
|
+
s3_max_concurrency=10,
|
|
44
|
+
)
|
|
45
|
+
return ObjectStorageService(config)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@pytest.fixture
|
|
49
|
+
def test_bucket(
|
|
50
|
+
object_storage_service: ObjectStorageService,
|
|
51
|
+
) -> Generator[str, None, None]:
|
|
52
|
+
"""
|
|
53
|
+
Create a test bucket and clean it up after test.
|
|
54
|
+
"""
|
|
55
|
+
bucket_name = f"test-bucket-{str(fake.uuid4())}"
|
|
56
|
+
object_storage_service.client.mkdir(f"s3://{bucket_name}")
|
|
57
|
+
yield bucket_name
|
|
58
|
+
objects = object_storage_service.list_objects(bucket=bucket_name, recursive=True)
|
|
59
|
+
for obj in objects:
|
|
60
|
+
object_storage_service.delete_file(bucket=bucket_name, key=obj)
|
|
61
|
+
object_storage_service.client.rmdir(bucket_name)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_is_alive(object_storage_service: ObjectStorageService) -> None:
|
|
65
|
+
"""
|
|
66
|
+
Test that the service can connect to MinIO.
|
|
67
|
+
"""
|
|
68
|
+
assert object_storage_service.is_alive() is True
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_has_bucket_exists(
|
|
72
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
73
|
+
) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Test checking if a bucket exists.
|
|
76
|
+
"""
|
|
77
|
+
result = object_storage_service.has_bucket(bucket=test_bucket)
|
|
78
|
+
assert result is True
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_has_bucket_not_exists(
|
|
82
|
+
object_storage_service: ObjectStorageService,
|
|
83
|
+
) -> None:
|
|
84
|
+
"""
|
|
85
|
+
Test checking if a non-existent bucket does not exist.
|
|
86
|
+
"""
|
|
87
|
+
non_existent_bucket = f"non-existent-{str(fake.uuid4())}"
|
|
88
|
+
result = object_storage_service.has_bucket(bucket=non_existent_bucket)
|
|
89
|
+
assert result is False
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def test_has_bucket_throw_not_exists(
|
|
93
|
+
object_storage_service: ObjectStorageService,
|
|
94
|
+
) -> None:
|
|
95
|
+
"""
|
|
96
|
+
Test that has_bucket throws exception when throw=True.
|
|
97
|
+
"""
|
|
98
|
+
non_existent_bucket = f"non-existent-{str(fake.uuid4())}"
|
|
99
|
+
with pytest.raises(RequiredBucketNotFoundError):
|
|
100
|
+
object_storage_service.has_bucket(bucket=non_existent_bucket, throw=True)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_upload_and_read_buffer(
|
|
104
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
105
|
+
) -> None:
|
|
106
|
+
"""
|
|
107
|
+
Test uploading a buffer and reading it back.
|
|
108
|
+
"""
|
|
109
|
+
test_content = fake.text().encode()
|
|
110
|
+
buffer = io.BytesIO(test_content)
|
|
111
|
+
key = f"test/buffer/{fake.file_name()}"
|
|
112
|
+
|
|
113
|
+
object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
|
|
114
|
+
|
|
115
|
+
result = object_storage_service.read_file(bucket=test_bucket, key=key)
|
|
116
|
+
assert result == test_content
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_upload_buffer_invalid_params(
|
|
120
|
+
object_storage_service: ObjectStorageService,
|
|
121
|
+
) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Test that upload_buffer raises error with invalid params.
|
|
124
|
+
"""
|
|
125
|
+
buffer = io.BytesIO(b"test")
|
|
126
|
+
with pytest.raises(
|
|
127
|
+
ValueError,
|
|
128
|
+
match="Either destination_path or bucket and key must be provided",
|
|
129
|
+
):
|
|
130
|
+
object_storage_service.upload_buffer(buffer=buffer)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_upload_and_download_file(
|
|
134
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
135
|
+
) -> None:
|
|
136
|
+
"""
|
|
137
|
+
Test uploading a file and downloading it.
|
|
138
|
+
"""
|
|
139
|
+
test_content = fake.text().encode()
|
|
140
|
+
key = f"test/files/{fake.file_name()}"
|
|
141
|
+
|
|
142
|
+
with tempfile.NamedTemporaryFile(mode="wb", delete=False) as temp_file:
|
|
143
|
+
temp_file.write(test_content)
|
|
144
|
+
temp_file_path = temp_file.name
|
|
145
|
+
|
|
146
|
+
try:
|
|
147
|
+
object_storage_service.upload_file(
|
|
148
|
+
file_path=temp_file_path, bucket=test_bucket, key=key
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
with tempfile.NamedTemporaryFile(mode="rb", delete=False) as download_file:
|
|
152
|
+
download_path = download_file.name
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
object_storage_service.download_file(
|
|
156
|
+
dest_path=download_path, bucket=test_bucket, key=key
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
with open(download_path, "rb") as f:
|
|
160
|
+
downloaded_content = f.read()
|
|
161
|
+
assert downloaded_content == test_content
|
|
162
|
+
finally:
|
|
163
|
+
Path(download_path).unlink(missing_ok=True)
|
|
164
|
+
finally:
|
|
165
|
+
Path(temp_file_path).unlink(missing_ok=True)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_read_file_retry_mechanism(
|
|
169
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
170
|
+
) -> None:
|
|
171
|
+
"""
|
|
172
|
+
Test that read_file succeeds with valid file.
|
|
173
|
+
"""
|
|
174
|
+
test_content = fake.text().encode()
|
|
175
|
+
buffer = io.BytesIO(test_content)
|
|
176
|
+
key = f"test/retry/{fake.file_name()}"
|
|
177
|
+
|
|
178
|
+
object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
|
|
179
|
+
|
|
180
|
+
result = object_storage_service.read_file(bucket=test_bucket, key=key, max_tries=3)
|
|
181
|
+
assert result == test_content
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def test_list_objects_recursive(
|
|
185
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
186
|
+
) -> None:
|
|
187
|
+
"""
|
|
188
|
+
Test listing objects recursively.
|
|
189
|
+
"""
|
|
190
|
+
files = [
|
|
191
|
+
"folder1/file1.txt",
|
|
192
|
+
"folder1/file2.txt",
|
|
193
|
+
"folder1/subfolder/file3.txt",
|
|
194
|
+
"folder2/file4.txt",
|
|
195
|
+
]
|
|
196
|
+
|
|
197
|
+
for file_key in files:
|
|
198
|
+
buffer = io.BytesIO(fake.text().encode())
|
|
199
|
+
object_storage_service.upload_buffer(
|
|
200
|
+
buffer=buffer, bucket=test_bucket, key=file_key
|
|
201
|
+
)
|
|
202
|
+
result = object_storage_service.list_objects(bucket=test_bucket, recursive=True)
|
|
203
|
+
|
|
204
|
+
files_only = [r for r in result if not r.endswith("/")]
|
|
205
|
+
|
|
206
|
+
assert len(files_only) >= 4
|
|
207
|
+
for file_key in files:
|
|
208
|
+
assert file_key in result or file_key in files_only
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def test_list_objects_with_prefix(
|
|
212
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
213
|
+
) -> None:
|
|
214
|
+
"""
|
|
215
|
+
Test listing objects with prefix filter.
|
|
216
|
+
"""
|
|
217
|
+
files = [
|
|
218
|
+
"prefix1/file1.txt",
|
|
219
|
+
"prefix1/file2.txt",
|
|
220
|
+
"prefix2/file3.txt",
|
|
221
|
+
]
|
|
222
|
+
|
|
223
|
+
for file_key in files:
|
|
224
|
+
buffer = io.BytesIO(fake.text().encode())
|
|
225
|
+
object_storage_service.upload_buffer(
|
|
226
|
+
buffer=buffer, bucket=test_bucket, key=file_key
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
result = object_storage_service.list_objects(
|
|
230
|
+
bucket=test_bucket, prefix="prefix1/", recursive=True
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
files_only = [r for r in result if not r.endswith("/")]
|
|
234
|
+
|
|
235
|
+
assert len(files_only) >= 2
|
|
236
|
+
assert "prefix1/file1.txt" in result or "prefix1/file1.txt" in files_only
|
|
237
|
+
assert "prefix1/file2.txt" in result or "prefix1/file2.txt" in files_only
|
|
238
|
+
assert "prefix2/file3.txt" not in result
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_list_objects_non_recursive(
|
|
242
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
243
|
+
) -> None:
|
|
244
|
+
"""
|
|
245
|
+
Test listing objects non-recursively.
|
|
246
|
+
"""
|
|
247
|
+
files = [
|
|
248
|
+
"root1.txt",
|
|
249
|
+
"root2.txt",
|
|
250
|
+
"folder/nested.txt",
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
for file_key in files:
|
|
254
|
+
buffer = io.BytesIO(fake.text().encode())
|
|
255
|
+
object_storage_service.upload_buffer(
|
|
256
|
+
buffer=buffer, bucket=test_bucket, key=file_key
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
result = object_storage_service.list_objects(bucket=test_bucket, recursive=False)
|
|
260
|
+
|
|
261
|
+
assert len(result) >= 1
|
|
262
|
+
has_root_file = any("root" in r for r in result)
|
|
263
|
+
assert has_root_file or len(result) > 0
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def test_file_exists(
|
|
267
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
268
|
+
) -> None:
|
|
269
|
+
"""
|
|
270
|
+
Test checking if file exists.
|
|
271
|
+
"""
|
|
272
|
+
key = f"test/exists/{fake.file_name()}"
|
|
273
|
+
|
|
274
|
+
assert object_storage_service.file_exists(bucket=test_bucket, key=key) is False
|
|
275
|
+
|
|
276
|
+
buffer = io.BytesIO(fake.text().encode())
|
|
277
|
+
object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
|
|
278
|
+
|
|
279
|
+
assert object_storage_service.file_exists(bucket=test_bucket, key=key) is True
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def test_delete_file(
|
|
283
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
284
|
+
) -> None:
|
|
285
|
+
"""
|
|
286
|
+
Test deleting a file.
|
|
287
|
+
"""
|
|
288
|
+
key = f"test/delete/{fake.file_name()}"
|
|
289
|
+
|
|
290
|
+
buffer = io.BytesIO(fake.text().encode())
|
|
291
|
+
object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
|
|
292
|
+
|
|
293
|
+
assert object_storage_service.file_exists(bucket=test_bucket, key=key) is True
|
|
294
|
+
|
|
295
|
+
object_storage_service.delete_file(bucket=test_bucket, key=key)
|
|
296
|
+
|
|
297
|
+
assert object_storage_service.file_exists(bucket=test_bucket, key=key) is False
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_get_file_size(
|
|
301
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
302
|
+
) -> None:
|
|
303
|
+
"""
|
|
304
|
+
Test getting file size.
|
|
305
|
+
"""
|
|
306
|
+
test_content = fake.text().encode()
|
|
307
|
+
key = f"test/size/{fake.file_name()}"
|
|
308
|
+
|
|
309
|
+
buffer = io.BytesIO(test_content)
|
|
310
|
+
object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
|
|
311
|
+
|
|
312
|
+
size = object_storage_service.get_file_size(bucket=test_bucket, key=key)
|
|
313
|
+
assert size == len(test_content)
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def test_get_file_info(
|
|
317
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
318
|
+
) -> None:
|
|
319
|
+
"""
|
|
320
|
+
Test getting file metadata.
|
|
321
|
+
"""
|
|
322
|
+
test_content = fake.text().encode()
|
|
323
|
+
key = f"test/info/{fake.file_name()}"
|
|
324
|
+
|
|
325
|
+
buffer = io.BytesIO(test_content)
|
|
326
|
+
object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
|
|
327
|
+
|
|
328
|
+
info = object_storage_service.get_file_info(bucket=test_bucket, key=key)
|
|
329
|
+
|
|
330
|
+
assert "size" in info
|
|
331
|
+
assert info["size"] == len(test_content)
|
|
332
|
+
assert info is not None
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def test_copy_file(
|
|
336
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
337
|
+
) -> None:
|
|
338
|
+
"""
|
|
339
|
+
Test copying a file.
|
|
340
|
+
"""
|
|
341
|
+
test_content = fake.text().encode()
|
|
342
|
+
source_key = f"test/copy/source/{fake.file_name()}"
|
|
343
|
+
dest_key = f"test/copy/dest/{fake.file_name()}"
|
|
344
|
+
|
|
345
|
+
buffer = io.BytesIO(test_content)
|
|
346
|
+
object_storage_service.upload_buffer(
|
|
347
|
+
buffer=buffer, bucket=test_bucket, key=source_key
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
object_storage_service.copy_file(
|
|
351
|
+
source_bucket=test_bucket,
|
|
352
|
+
source_key=source_key,
|
|
353
|
+
dest_bucket=test_bucket,
|
|
354
|
+
dest_key=dest_key,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
assert (
|
|
358
|
+
object_storage_service.file_exists(bucket=test_bucket, key=source_key) is True
|
|
359
|
+
)
|
|
360
|
+
assert object_storage_service.file_exists(bucket=test_bucket, key=dest_key) is True
|
|
361
|
+
|
|
362
|
+
dest_content = object_storage_service.read_file(bucket=test_bucket, key=dest_key)
|
|
363
|
+
assert dest_content == test_content
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def test_move_file(
|
|
367
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
368
|
+
) -> None:
|
|
369
|
+
"""
|
|
370
|
+
Test moving a file.
|
|
371
|
+
"""
|
|
372
|
+
test_content = fake.text().encode()
|
|
373
|
+
source_key = f"test/move/source/{fake.file_name()}"
|
|
374
|
+
dest_key = f"test/move/dest/{fake.file_name()}"
|
|
375
|
+
|
|
376
|
+
buffer = io.BytesIO(test_content)
|
|
377
|
+
object_storage_service.upload_buffer(
|
|
378
|
+
buffer=buffer, bucket=test_bucket, key=source_key
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
object_storage_service.move_file(
|
|
382
|
+
source_bucket=test_bucket,
|
|
383
|
+
source_key=source_key,
|
|
384
|
+
dest_bucket=test_bucket,
|
|
385
|
+
dest_key=dest_key,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
assert (
|
|
389
|
+
object_storage_service.file_exists(bucket=test_bucket, key=source_key) is False
|
|
390
|
+
)
|
|
391
|
+
assert object_storage_service.file_exists(bucket=test_bucket, key=dest_key) is True
|
|
392
|
+
|
|
393
|
+
dest_content = object_storage_service.read_file(bucket=test_bucket, key=dest_key)
|
|
394
|
+
assert dest_content == test_content
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
def test_read_nonexistent_file(
|
|
398
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
399
|
+
) -> None:
|
|
400
|
+
"""
|
|
401
|
+
Test reading a file that doesn't exist.
|
|
402
|
+
"""
|
|
403
|
+
non_existent_key = f"test/nonexistent/{str(fake.uuid4())}.txt"
|
|
404
|
+
|
|
405
|
+
with pytest.raises(ReadFileError):
|
|
406
|
+
object_storage_service.read_file(
|
|
407
|
+
bucket=test_bucket, key=non_existent_key, max_tries=1
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
def test_invalid_parameters(
|
|
412
|
+
object_storage_service: ObjectStorageService,
|
|
413
|
+
) -> None:
|
|
414
|
+
"""
|
|
415
|
+
Test operations with invalid parameters.
|
|
416
|
+
"""
|
|
417
|
+
with pytest.raises(
|
|
418
|
+
ValueError, match="Either path or bucket and key must be provided"
|
|
419
|
+
):
|
|
420
|
+
object_storage_service.read_file()
|
|
421
|
+
|
|
422
|
+
with pytest.raises(
|
|
423
|
+
ValueError,
|
|
424
|
+
match="Either destination_path or bucket and key must be provided",
|
|
425
|
+
):
|
|
426
|
+
object_storage_service.upload_buffer(buffer=io.BytesIO(b"test"))
|
|
427
|
+
|
|
428
|
+
with pytest.raises(
|
|
429
|
+
ValueError,
|
|
430
|
+
match="Either source_path or source_bucket and source_key must be provided",
|
|
431
|
+
):
|
|
432
|
+
object_storage_service.copy_file(dest_bucket="test", dest_key="test")
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def test_large_file_upload_download(
|
|
436
|
+
object_storage_service: ObjectStorageService, test_bucket: str
|
|
437
|
+
) -> None:
|
|
438
|
+
"""
|
|
439
|
+
Test uploading and downloading a larger file (10MB).
|
|
440
|
+
"""
|
|
441
|
+
size_mb = 10
|
|
442
|
+
test_content = fake.binary(length=size_mb * 1024 * 1024)
|
|
443
|
+
key = f"test/large/{fake.file_name()}"
|
|
444
|
+
|
|
445
|
+
buffer = io.BytesIO(test_content)
|
|
446
|
+
object_storage_service.upload_buffer(buffer=buffer, bucket=test_bucket, key=key)
|
|
447
|
+
|
|
448
|
+
size = object_storage_service.get_file_size(bucket=test_bucket, key=key)
|
|
449
|
+
assert size == len(test_content)
|
|
450
|
+
|
|
451
|
+
result = object_storage_service.read_file(bucket=test_bucket, key=key)
|
|
452
|
+
assert result == test_content
|
|
453
|
+
assert len(result) == size_mb * 1024 * 1024
|