diff --git a/infrastructure/s3/s3_client.py b/infrastructure/s3/s3_client.py index eb2a68f5c..7e26c790b 100644 --- a/infrastructure/s3/s3_client.py +++ b/infrastructure/s3/s3_client.py @@ -21,6 +21,12 @@ class S3Client: self._client.put_object(Bucket=self._bucket, Key=key, Body=body) return f"s3://{self._bucket}/{key}" + def upload_file(self, local_path: str, key: str) -> str: + """Upload a file from local disk, using boto's managed transfer so a + large object (a multi-GB Download Package ZIP, ADR-0060) is streamed in + multipart from ``/tmp`` rather than held in memory.""" + raise NotImplementedError + def generate_presigned_url(self, key: str, expires_in: int) -> str: """A time-limited URL that lets the holder GET this object without AWS credentials (ADR-0060 — how a Download Package link is delivered). diff --git a/tests/infrastructure/test_s3_client.py b/tests/infrastructure/test_s3_client.py index b867dd26c..13e6c4714 100644 --- a/tests/infrastructure/test_s3_client.py +++ b/tests/infrastructure/test_s3_client.py @@ -47,3 +47,20 @@ def test_generate_presigned_url_signs_a_get_for_the_object(s3_client: S3Client) assert BUCKET in url assert "bulk-downloads/pkg.zip" in url assert "Signature" in url + + +def test_upload_file_streams_a_local_file_to_s3( + s3_client: S3Client, tmp_path: object +) -> None: + # arrange — a file on local disk (stands in for the /tmp ZIP) + from pathlib import Path + + local = Path(str(tmp_path)) / "package.zip" + local.write_bytes(b"ZIP-CONTENT") + + # act + uri = s3_client.upload_file(str(local), "bulk-downloads/package.zip") + + # assert — the object landed with the file's bytes + assert uri == f"s3://{BUCKET}/bulk-downloads/package.zip" + assert s3_client.get_object("bulk-downloads/package.zip") == b"ZIP-CONTENT"