128 lines
4.7 KiB
Python
128 lines
4.7 KiB
Python
from __future__ import annotations
|
|
|
|
import io
|
|
from types import ModuleType
|
|
import sys
|
|
import unittest
|
|
from unittest.mock import MagicMock, PropertyMock, patch
|
|
|
|
from govoplan_files.backend.storage.backends import S3StorageBackend, StorageBackendError
|
|
|
|
|
|
def _backend(
|
|
*,
|
|
endpoint_url: str = "https://objects.example.test",
|
|
deployment_managed: bool = False,
|
|
) -> S3StorageBackend:
|
|
return S3StorageBackend(
|
|
bucket="files",
|
|
endpoint_url=endpoint_url,
|
|
region_name="test",
|
|
access_key_id="access",
|
|
secret_access_key="secret",
|
|
deployment_managed=deployment_managed,
|
|
)
|
|
|
|
|
|
class S3StorageBackendTests(unittest.TestCase):
|
|
def test_sdk_transport_fails_closed_in_public_and_private_modes(self) -> None:
|
|
for allow_private, address in ((False, "93.184.216.34"), (True, "10.0.0.5")):
|
|
with self.subTest(allow_private=allow_private), patch.dict(
|
|
"os.environ",
|
|
{
|
|
"APP_ENV": "production",
|
|
"GOVOPLAN_CONNECTOR_ALLOW_PRIVATE_NETWORKS": str(allow_private).lower(),
|
|
},
|
|
), patch(
|
|
"govoplan_core.security.outbound_http.socket.getaddrinfo",
|
|
return_value=[(2, 1, 6, "", (address, 443))],
|
|
), self.assertRaisesRegex(StorageBackendError, "until that transport supports.*DNS/IP pinning"):
|
|
_backend().client
|
|
|
|
def test_installer_managed_garage_uses_exact_endpoint_and_path_style(self) -> None:
|
|
boto3 = ModuleType("boto3")
|
|
boto3.client = MagicMock(return_value=object())
|
|
botocore = ModuleType("botocore")
|
|
botocore.__path__ = []
|
|
botocore_config = ModuleType("botocore.config")
|
|
|
|
class Config:
|
|
def __init__(self, **values):
|
|
self.values = values
|
|
|
|
botocore_config.Config = Config
|
|
with patch.dict(
|
|
sys.modules,
|
|
{
|
|
"boto3": boto3,
|
|
"botocore": botocore,
|
|
"botocore.config": botocore_config,
|
|
},
|
|
):
|
|
_backend(
|
|
endpoint_url="http://garage:3900",
|
|
deployment_managed=True,
|
|
).client
|
|
|
|
_args, kwargs = boto3.client.call_args
|
|
self.assertEqual("http://garage:3900", kwargs["endpoint_url"])
|
|
self.assertEqual(
|
|
{"s3": {"addressing_style": "path"}},
|
|
kwargs["config"].values,
|
|
)
|
|
|
|
def test_installer_managed_garage_rejects_any_other_endpoint(self) -> None:
|
|
with self.assertRaisesRegex(
|
|
StorageBackendError,
|
|
"restricted to http://garage:3900",
|
|
):
|
|
_backend(
|
|
endpoint_url="http://other-s3:3900",
|
|
deployment_managed=True,
|
|
).client
|
|
|
|
def test_get_bytes_rejects_declared_oversize_object_without_reading(self) -> None:
|
|
body = MagicMock()
|
|
client = MagicMock()
|
|
client.get_object.return_value = {"ContentLength": 6, "Body": body}
|
|
with patch.dict("os.environ", {"GOVOPLAN_CONNECTOR_MAX_FILE_TRANSFER_BYTES": "5"}), patch.object(
|
|
S3StorageBackend,
|
|
"client",
|
|
new_callable=PropertyMock,
|
|
return_value=client,
|
|
), self.assertRaisesRegex(StorageBackendError, "deployment limit"):
|
|
_backend().get_bytes("large.bin")
|
|
body.read.assert_not_called()
|
|
body.close.assert_called_once_with()
|
|
|
|
def test_iter_bytes_rejects_undeclared_oversize_object(self) -> None:
|
|
client = MagicMock()
|
|
client.get_object.return_value = {"Body": io.BytesIO(b"123456")}
|
|
with patch.dict("os.environ", {"GOVOPLAN_CONNECTOR_MAX_FILE_TRANSFER_BYTES": "5"}), patch.object(
|
|
S3StorageBackend,
|
|
"client",
|
|
new_callable=PropertyMock,
|
|
return_value=client,
|
|
), self.assertRaisesRegex(StorageBackendError, "deployment limit"):
|
|
list(_backend().iter_bytes("large.bin", chunk_size=3))
|
|
|
|
def test_iter_bytes_closes_streaming_body_when_consumer_stops_early(self) -> None:
|
|
body = MagicMock()
|
|
body.read.side_effect = (b"123", b"456", b"")
|
|
client = MagicMock()
|
|
client.get_object.return_value = {"ContentLength": 6, "Body": body}
|
|
with patch.dict("os.environ", {"GOVOPLAN_CONNECTOR_MAX_FILE_TRANSFER_BYTES": "10"}), patch.object(
|
|
S3StorageBackend,
|
|
"client",
|
|
new_callable=PropertyMock,
|
|
return_value=client,
|
|
):
|
|
chunks = _backend().iter_bytes("object.bin", chunk_size=3)
|
|
self.assertEqual(b"123", next(chunks))
|
|
chunks.close()
|
|
body.close.assert_called_once_with()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|