mirror of
https://github.com/Hestia-Homes/Model.git
synced 2026-07-12 13:29:04 +00:00
Addresses PR #1498 review: - A per-file read failure (missing/deleted object, or a bucket the role can't reach) is now a SkippedDocument(reason=unreadable), not a whole-run abort; an all-unreadable selection still fails (no empty package). Best-effort on the read path (ADR-0060). - Each ZIP member is streamed to a temp file (S3DocumentDownloader.download, boto download_file) and added from disk, so a single multi-GB member never hits the heap — the 'never held whole in memory' claim is now true. - The 409 double-submit guard is DB-arbitrated: the sub_task insert is conditional on the task having none, so a race creates only one. - Local env gets a placeholder recipient email so the route is exercisable. - PackageEntry carries landlord_property_id so a read failure can be reported. - TODO noting the UploadedFile.s3_upload_timestamp typing fix. Two new tests: per-file read failure skips-and-reports; address fallback flows to the folder name. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
36 lines
1.2 KiB
Python
36 lines
1.2 KiB
Python
"""The multi-bucket document downloader (ADR-0060) — streams object bytes from
|
|
whichever source bucket an uploaded file lives in, to local disk."""
|
|
|
|
from collections.abc import Iterator
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
from moto import mock_aws
|
|
|
|
from infrastructure.s3.s3_document_downloader import S3DocumentDownloader
|
|
from tests.infrastructure import make_boto_client
|
|
|
|
|
|
@pytest.fixture
|
|
def downloader() -> Iterator[S3DocumentDownloader]:
|
|
with mock_aws():
|
|
boto_client = make_boto_client("s3")
|
|
boto_client.create_bucket(Bucket="src-a")
|
|
boto_client.create_bucket(Bucket="src-b")
|
|
boto_client.put_object(Bucket="src-a", Key="surveys/one.pdf", Body=b"AAA")
|
|
boto_client.put_object(Bucket="src-b", Key="photos/two.zip", Body=b"BBB")
|
|
yield S3DocumentDownloader(boto_client)
|
|
|
|
|
|
def test_downloads_bytes_from_the_named_source_bucket(
|
|
downloader: S3DocumentDownloader, tmp_path: Path
|
|
) -> None:
|
|
# act — each object streams from its own bucket to a local file.
|
|
a = tmp_path / "a"
|
|
b = tmp_path / "b"
|
|
downloader.download("src-a", "surveys/one.pdf", str(a))
|
|
downloader.download("src-b", "photos/two.zip", str(b))
|
|
|
|
# assert
|
|
assert a.read_bytes() == b"AAA"
|
|
assert b.read_bytes() == b"BBB"
|