Model/tests/infrastructure/test_csv_s3_client.py
Jun-te Kim 7b00a33cd2 infrastructure: typed S3/SQS clients (S3Client, CsvS3Client, SqsClient, Address2UprnQueueClient)
Slice 3/6 of the postcode_splitter refactor (Hestia-Homes/Model#1101).
Introduces a thin typed infrastructure layer wrapping boto3 for the AWS
side of the splitter. S3Client/SqsClient are bucket-/queue-bound byte
adapters; CsvS3Client subclasses S3Client to round-trip CSV row dicts
via the existing parse_s3_uri helper in utils/s3.py; Address2UprnQueueClient
subclasses SqsClient to publish the typed {task_id, sub_task_id, s3_uri}
fan-out body the downstream consumer expects. moto[s3,sqs] is pulled into
test.requirements.txt and the new tests/infrastructure/ suite exercises
each client against the moto backend (S3 round-trip, CSV round-trip,
SQS send + body inspection, typed publish + body inspection). pyright
--strict is clean on the new modules.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 17:12:21 +00:00

43 lines
1.4 KiB
Python

from collections.abc import Iterator
import pytest
from moto import mock_aws
from infrastructure.csv_s3_client import CsvS3Client
from tests.infrastructure import make_boto_client
BUCKET = "csv-bucket"
@pytest.fixture
def csv_client() -> Iterator[CsvS3Client]:
with mock_aws():
boto_client = make_boto_client("s3")
boto_client.create_bucket(Bucket=BUCKET)
yield CsvS3Client(boto_client, BUCKET)
def test_save_rows_returns_s3_uri(csv_client: CsvS3Client) -> None:
rows = [{"address": "1 High St", "postcode": "AB1 2CD"}]
uri = csv_client.save_rows(rows, "uploads/addresses.csv")
assert uri == f"s3://{BUCKET}/uploads/addresses.csv"
def test_round_trip_preserves_rows(csv_client: CsvS3Client) -> None:
rows = [
{"address": "1 High St", "postcode": "AB1 2CD"},
{"address": "2 Low St", "postcode": "XY9 8ZW"},
]
uri = csv_client.save_rows(rows, "uploads/addresses.csv")
fetched = csv_client.read_rows(uri)
assert fetched == rows
def test_save_rows_rejects_empty_list(csv_client: CsvS3Client) -> None:
with pytest.raises(ValueError, match="empty"):
csv_client.save_rows([], "uploads/empty.csv")
def test_read_rows_rejects_wrong_bucket(csv_client: CsvS3Client) -> None:
with pytest.raises(ValueError, match="does not match client bucket"):
csv_client.read_rows("s3://other-bucket/uploads/addresses.csv")