"""Unit tests for event schemas.""" import pytest from pydantic import ValidationError from file_upload_complete.schemas.events import ( EventBridgeDetail, FileUploadEvent, S3Bucket, S3Object, ) class TestS3Bucket: """Tests for S3Bucket model.""" def test_valid_bucket(self): """Test creating S3Bucket with valid data.""" bucket = S3Bucket(name='my-test-bucket') assert bucket.name == 'my-test-bucket' def test_bucket_name_required(self): """Test S3Bucket requires name field.""" with pytest.raises(ValidationError) as exc_info: S3Bucket() errors = exc_info.value.errors() assert any(error['loc'] == ('name',) for error in errors) def test_bucket_dict_conversion(self): """Test S3Bucket can be converted to dict.""" bucket = S3Bucket(name='test-bucket') bucket_dict = bucket.model_dump() assert bucket_dict == {'name': 'test-bucket'} def test_bucket_with_special_characters(self): """Test S3Bucket with special characters in name.""" bucket = S3Bucket(name='my-bucket-with-dashes-123') assert bucket.name == 'my-bucket-with-dashes-123' class TestS3Object: """Tests for S3Object model.""" def test_valid_object(self): """Test creating S3Object with valid data.""" s3_object = S3Object(key='path/to/file.txt') assert s3_object.key == 'path/to/file.txt' def test_object_key_required(self): """Test S3Object requires key field.""" with pytest.raises(ValidationError) as exc_info: S3Object() errors = exc_info.value.errors() assert any(error['loc'] == ('key',) for error in errors) def test_object_dict_conversion(self): """Test S3Object can be converted to dict.""" s3_object = S3Object(key='test/path/file.txt') object_dict = s3_object.model_dump() assert object_dict == {'key': 'test/path/file.txt'} def test_object_with_nested_path(self): """Test S3Object with deeply nested path.""" s3_object = S3Object(key='a/b/c/d/e/file.txt') assert s3_object.key == 'a/b/c/d/e/file.txt' def test_object_with_special_characters(self): """Test S3Object with special characters in key.""" s3_object = S3Object(key='path/file-name_123.txt') assert s3_object.key == 'path/file-name_123.txt' class TestEventBridgeDetail: """Tests for EventBridgeDetail model.""" def test_valid_detail(self): """Test creating EventBridgeDetail with valid data.""" detail = EventBridgeDetail( bucket={'name': 'test-bucket'}, object={'key': 'test/file.txt'} ) assert detail.bucket.name == 'test-bucket' assert detail.object.key == 'test/file.txt' def test_detail_with_model_instances(self): """Test creating EventBridgeDetail with model instances.""" bucket = S3Bucket(name='my-bucket') s3_object = S3Object(key='my/file.txt') detail = EventBridgeDetail(bucket=bucket, object=s3_object) assert detail.bucket.name == 'my-bucket' assert detail.object.key == 'my/file.txt' def test_detail_bucket_required(self): """Test EventBridgeDetail requires bucket field.""" with pytest.raises(ValidationError) as exc_info: EventBridgeDetail(object={'key': 'test.txt'}) errors = exc_info.value.errors() assert any(error['loc'] == ('bucket',) for error in errors) def test_detail_object_required(self): """Test EventBridgeDetail requires object field.""" with pytest.raises(ValidationError) as exc_info: EventBridgeDetail(bucket={'name': 'test-bucket'}) errors = exc_info.value.errors() assert any(error['loc'] == ('object',) for error in errors) def test_detail_dict_conversion(self): """Test EventBridgeDetail can be converted to dict.""" detail = EventBridgeDetail( bucket={'name': 'bucket'}, object={'key': 'file.txt'} ) detail_dict = detail.model_dump() assert detail_dict == { 'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}, } class TestFileUploadEvent: """Tests for FileUploadEvent model.""" def test_valid_event_without_action(self): """Test creating FileUploadEvent without action (clean file).""" event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}} ) assert event.action is None assert event.detail.bucket.name == 'bucket' assert event.detail.object.key == 'file.txt' def test_valid_event_with_quarantine_action(self): """Test creating FileUploadEvent with quarantine action.""" event = FileUploadEvent( action='quarantine', detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}}, ) assert event.action == 'quarantine' assert event.detail.bucket.name == 'bucket' assert event.detail.object.key == 'file.txt' def test_event_detail_required(self): """Test FileUploadEvent requires detail field.""" with pytest.raises(ValidationError) as exc_info: FileUploadEvent() errors = exc_info.value.errors() assert any(error['loc'] == ('detail',) for error in errors) def test_event_with_nested_model_instances(self): """Test FileUploadEvent with nested model instances.""" bucket = S3Bucket(name='test-bucket') s3_object = S3Object(key='test/path/file.txt') detail = EventBridgeDetail(bucket=bucket, object=s3_object) event = FileUploadEvent(detail=detail) assert event.detail.bucket.name == 'test-bucket' assert event.detail.object.key == 'test/path/file.txt' def test_event_ignores_extra_fields(self): """Test FileUploadEvent ignores unknown fields from Step Functions.""" event = FileUploadEvent( detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}}, extra_field='should be ignored', another_field=123, ) assert event.action is None assert event.detail.bucket.name == 'bucket' # Extra fields should not cause validation errors assert not hasattr(event, 'extra_field') def test_event_invalid_action_value(self): """Test FileUploadEvent rejects invalid action values.""" with pytest.raises(ValidationError) as exc_info: FileUploadEvent( action='invalid_action', detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}}, ) errors = exc_info.value.errors() assert any(error['loc'] == ('action',) for error in errors) def test_event_dict_conversion(self): """Test FileUploadEvent can be converted to dict.""" event = FileUploadEvent( action='quarantine', detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}}, ) event_dict = event.model_dump() assert event_dict == { 'action': 'quarantine', 'detail': {'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}}, } def test_event_from_dict_complete_structure(self): """Test creating FileUploadEvent from complete dict structure.""" event_data = { 'action': None, 'detail': { 'bucket': {'name': 'uploads-bucket'}, 'object': {'key': 'user-uploads/2024/file.csv'}, }, } event = FileUploadEvent(**event_data) assert event.action is None assert event.detail.bucket.name == 'uploads-bucket' assert event.detail.object.key == 'user-uploads/2024/file.csv' def test_event_with_complex_s3_key(self): """Test FileUploadEvent with complex S3 object key.""" event = FileUploadEvent( detail={ 'bucket': {'name': 'my-bucket'}, 'object': {'key': 'user123/uploads/2024-01-15/report_v2_final.xlsx'}, } ) assert ( event.detail.object.key == 'user123/uploads/2024-01-15/report_v2_final.xlsx' ) def test_event_action_none_explicitly(self): """Test FileUploadEvent with action explicitly set to None.""" event = FileUploadEvent( action=None, detail={'bucket': {'name': 'bucket'}, 'object': {'key': 'file.txt'}}, ) assert event.action is None def test_event_missing_bucket_name(self): """Test FileUploadEvent validation fails with missing bucket name.""" with pytest.raises(ValidationError) as exc_info: FileUploadEvent(detail={'bucket': {}, 'object': {'key': 'file.txt'}}) errors = exc_info.value.errors() assert any('bucket' in str(error['loc']) for error in errors) def test_event_missing_object_key(self): """Test FileUploadEvent validation fails with missing object key.""" with pytest.raises(ValidationError) as exc_info: FileUploadEvent(detail={'bucket': {'name': 'bucket'}, 'object': {}}) errors = exc_info.value.errors() assert any('object' in str(error['loc']) for error in errors)