"""Tests for s3 file operations.""" from typing import TypedDict import botocore import pytest from botocore.exceptions import ClientError from flexmock import flexmock from vectororder.connectors import s3 from vectororder.models import s3_file class BucketKey(TypedDict): bucket: str key: str @pytest.fixture def fixture_bucket_key() -> BucketKey: """Return common bucket/key values.""" return {"bucket": "test-bucket", "key": "path/to/object.txt"} def test_check_s3_file_exists_true(fixture_bucket_key: BucketKey) -> None: """check_s3_file_exists returns True when HeadObject returns HTTP 200.""" bucket = fixture_bucket_key["bucket"] key = fixture_bucket_key["key"] # Mock the underlying botocore call that boto3 makes for head_object ( flexmock(botocore.client.BaseClient) .should_receive("_make_api_call") .with_args( "HeadObject", {"Bucket": bucket, "Key": key}, ) .and_return({"ResponseMetadata": {"HTTPStatusCode": 200}}) .once() ) assert s3_file.check_s3_file_exists(bucket, key) is True def test_check_s3_file_exists_false_non_200( fixture_bucket_key: BucketKey, ) -> None: """check_s3_file_exists returns False when status code is not 200.""" bucket = fixture_bucket_key["bucket"] key = fixture_bucket_key["key"] ( flexmock(botocore.client.BaseClient) .should_receive("_make_api_call") .with_args( "HeadObject", {"Bucket": bucket, "Key": key}, ) .and_return({"ResponseMetadata": {"HTTPStatusCode": 204}}) .once() ) assert s3_file.check_s3_file_exists(bucket, key) is False def test_check_s3_file_exists_false_client_error( fixture_bucket_key: BucketKey, ) -> None: """check_s3_file_exists swallows ClientError and returns False.""" bucket = fixture_bucket_key["bucket"] key = fixture_bucket_key["key"] ( flexmock(botocore.client.BaseClient) .should_receive("_make_api_call") .with_args( "HeadObject", {"Bucket": bucket, "Key": key}, ) .and_raise( ClientError( error_response={"Error": {"Code": "404", "Message": "Not Found"}}, operation_name="HeadObject", ) ) .once() ) assert s3_file.check_s3_file_exists(bucket, key) is False def test_get_s3_file_storage_class_explicit( fixture_bucket_key: BucketKey, ) -> None: """get_s3_file_storage_class returns StorageClass when present.""" bucket = fixture_bucket_key["bucket"] key = fixture_bucket_key["key"] ( flexmock(botocore.client.BaseClient) .should_receive("_make_api_call") .with_args( "HeadObject", {"Bucket": bucket, "Key": key}, ) .and_return( { "ResponseMetadata": {"HTTPStatusCode": 200}, "StorageClass": "INTELLIGENT_TIERING", } ) .once() ) result = s3_file.get_s3_file_storage_class(bucket, key) assert result == "INTELLIGENT_TIERING" def test_get_s3_file_storage_class_default_standard( fixture_bucket_key: BucketKey, ) -> None: """get_s3_file_storage_class defaults to 'STANDARD' when StorageClass is missing.""" bucket = fixture_bucket_key["bucket"] key = fixture_bucket_key["key"] ( flexmock(botocore.client.BaseClient) .should_receive("_make_api_call") .with_args( "HeadObject", {"Bucket": bucket, "Key": key}, ) .and_return({"ResponseMetadata": {"HTTPStatusCode": 200}}) .once() ) result = s3_file.get_s3_file_storage_class(bucket, key) assert result == "STANDARD" def test_create_presigned_url_minimal( fixture_bucket_key: BucketKey, ) -> None: """create_presigned_url with default expiration and no content_disposition.""" bucket = fixture_bucket_key["bucket"] key = fixture_bucket_key["key"] expected_url = "https://example.com/s3/test-bucket/path/to/object.txt?sig=xyz" mock_client = flexmock() ( flexmock(mock_client) .should_receive("generate_presigned_url") .with_args( ClientMethod="get_object", Params={"Bucket": bucket, "Key": key}, ExpiresIn=3600, ) .and_return(expected_url) .once() ) flexmock(s3).should_receive("get_s3_client").and_return(mock_client) result = s3_file.create_presigned_url(bucket=bucket, key=key) assert result == expected_url def test_create_presigned_url_with_disposition_and_expiration( fixture_bucket_key: BucketKey, ) -> None: """create_presigned_url passes ResponseContentDisposition and custom expiration.""" bucket = fixture_bucket_key["bucket"] key = fixture_bucket_key["key"] content_disposition = 'attachment; filename="object.txt"' expiration = 900 expected_url = "https://example.com/s3/test-bucket/path/to/object.txt?sig=abc" mock_client = flexmock() ( mock_client.should_receive("generate_presigned_url") .with_args( ClientMethod="get_object", Params={ "Bucket": bucket, "Key": key, "ResponseContentDisposition": content_disposition, }, ExpiresIn=expiration, ) .and_return(expected_url) .once() ) flexmock(s3).should_receive("get_s3_client").and_return(mock_client) result = s3_file.create_presigned_url( bucket=bucket, key=key, expiration=expiration, content_disposition=content_disposition, ) assert result == expected_url