"""Unit tests for AdjustmentFileInitializeProcessor.""" from unittest.mock import Mock, patch import pytest from pymysql.err import IntegrityError, OperationalError from src.enums import ( BatchStatus, BatchType, EventType, StatementPeriodStatus, TargetType, UploadStatus, UploadType, ) from src.errors import ( BatchStatementPeriodMismatchError, FileUploadNotFoundError, InvalidBatchStatusError, InvalidUploadStatusError, InvalidUploadTypeError, StatementPeriodAdjustmentFileCreateError, StatementPeriodAdjustmentFileStatesCreateError, StatementPeriodNotFoundError, TransientError, ) from src.processor import AdjustmentFileInitializeProcessor from src.schemas import ( AdjustmentBatch, AdjustmentFileInitializeEvent, AdjustmentFileInitializeEventData, AdjustmentFileInitializeEventDetail, AdjustmentFileInitializeEventMetadata, AdjustmentFileInitializeResponse, FileUpload, StatementPeriod, ) class TestAdjustmentFileInitializeProcessorInit: """Tests for AdjustmentFileInitializeProcessor initialization.""" def test_init_with_valid_dependencies(self): """Test processor initialization with valid dependencies.""" repository = Mock() processor = AdjustmentFileInitializeProcessor(repository) assert processor._repository == repository def test_init_stores_references(self): """Test processor stores references to dependencies.""" repository = Mock() processor = AdjustmentFileInitializeProcessor(repository) assert processor._repository is repository class TestAdjustmentFileInitializeProcessorGetAndValidateBatch: """Tests for AdjustmentFileInitializeProcessor._get_and_validate_batch method.""" def test_get_and_validate_batch_invalid_statement_period(self): """Test validation fails for invalid statement_period_id.""" mock_repository = Mock() mock_repository.get_batch_by_file_upload.return_value = AdjustmentBatch( batch_id=1, statement_period_id=99, batch_status=BatchStatus.PENDING, batch_type=BatchType.UPLOAD, source_file_upload_id=123, ) processor = AdjustmentFileInitializeProcessor(mock_repository) file_upload = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) with pytest.raises(BatchStatementPeriodMismatchError) as exc_info: processor._get_and_validate_batch(file_upload, 100) assert 'Statement period mismatch for batch_id=1' in str(exc_info.value) assert 'Expected: 100, Got: 99' in str(exc_info.value) def test_get_and_validate_batch_invalid_status(self): """Test validation fails for invalid batch_status.""" mock_repository = Mock() # Create batch with model_construct to bypass validation and test with invalid status mock_repository.get_batch_by_file_upload.return_value = ( AdjustmentBatch.model_construct( batch_id=1, statement_period_id=100, batch_status='completed', # Invalid status for testing batch_type=BatchType.UPLOAD, source_file_upload_id=123, ) ) processor = AdjustmentFileInitializeProcessor(mock_repository) file_upload = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) with pytest.raises(InvalidBatchStatusError) as exc_info: processor._get_and_validate_batch(file_upload, 100) assert 'Invalid batch status for batch_id=1' in str(exc_info.value) assert 'Expected: pending, Got: completed' in str(exc_info.value) class TestAdjustmentFileInitializeProcessorProcess: """Tests for AdjustmentFileInitializeProcessor.process method.""" def test_process_success_flow_creates_batch(self): """Test successful batch initialization flow when batch does not exist.""" # Setup mocks mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_file_upload.return_value = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) mock_repository.get_batch_by_file_upload.return_value = None mock_repository.create_batch.return_value = 789 # Create event and processor event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, correlation_id='corr-456', ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) # Execute result = processor.process(event) # Verify assert isinstance(result, AdjustmentFileInitializeResponse) assert result.detail_type == EventType.ADJUSTMENT_BATCH_INITIALIZED assert result.detail.metadata.target_id == 789 assert ( result.detail.metadata.target_type == TargetType.WORKSHEET_ADJUSTMENT_BATCH ) assert result.detail.data.s3_bucket == 'test-bucket' assert result.detail.data.s3_key == 'uploads/test.csv' assert result.detail.metadata.correlation_id == 'corr-456' # Verify method calls mock_repository.get_current_statement_period.assert_called_once() mock_repository.get_file_upload.assert_called_once_with(123) mock_repository.get_batch_by_file_upload.assert_called_once_with(123) mock_repository.create_batch.assert_called_once_with(456, 123, 'user-123') @patch('src.processor.features') @patch('src.processor.ows_royalties') @patch('src.processor.ows_abacus_state') @patch('src.processor.ows_abacus_event') def test_process_success_flow_creates_adjustment_file( self, mock_ows_abacus_event, mock_ows_abacus_state, mock_ows_royalties, mock_features, ): """Test successful batch initialization flow with creating adjustment file.""" # Mock data current_statement_period_id = 456 adjustment_file_id = 99 # Setup mocks mock_features.is_abacus_flowthrough_automation_enabled.return_value = True mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=current_statement_period_id, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_file_upload.return_value = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) mock_repository.get_batch_by_file_upload.return_value = None mock_repository.create_batch.return_value = 789 mock_ows_royalties.create_statement_period_adjustment_file.return_value.status_code = 201 mock_ows_royalties.create_statement_period_adjustment_file.return_value.json.return_value = { 'statement_period_adjustment_file_id': adjustment_file_id } mock_ows_abacus_state.create_abacus_state.return_value.status_code = 201 # Create event and processor event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, correlation_id='corr-456', ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) # Execute result = processor.process(event) # Verify assert isinstance(result, AdjustmentFileInitializeResponse) assert result.detail_type == EventType.ADJUSTMENT_BATCH_INITIALIZED assert result.detail.metadata.target_id == 789 assert ( result.detail.metadata.target_type == TargetType.WORKSHEET_ADJUSTMENT_BATCH ) assert result.detail.data.s3_bucket == 'test-bucket' assert result.detail.data.s3_key == 'uploads/test.csv' assert result.detail.metadata.correlation_id == 'corr-456' # Verify method calls mock_repository.get_current_statement_period.assert_called_once() mock_repository.get_file_upload.assert_called_once_with(123) mock_repository.get_batch_by_file_upload.assert_called_once_with(123) mock_repository.create_batch.assert_called_once_with( current_statement_period_id, 123, 'user-123' ) mock_ows_royalties.create_statement_period_adjustment_file.assert_called_once_with( current_statement_period_id, { 'file_name': 'test.csv', 'valid_file_location': 's3://test-bucket/uploads/test.csv', 'source_file_upload_id': 123, 'created_by': 'user-123', }, ) mock_ows_abacus_state.create_abacus_state.assert_called_once_with( 'statement_period_adjustment_file', adjustment_file_id, 'upload_file' ) mock_ows_abacus_event.create_abacus_event.assert_called_once_with( mock_ows_abacus_event.ADJUSTMENT_FILE_UPLOAD_EVENT, adjustment_file_id, mock_ows_abacus_event.ADJUSTMENT_FILE_UPLOAD_TARGET_TYPE, ) def test_process_success_flow_finds_existing_batch(self): """Test successful batch initialization flow when batch already exists.""" # Setup mocks mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_file_upload.return_value = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) mock_repository.get_batch_by_file_upload.return_value = AdjustmentBatch( batch_id=789, statement_period_id=456, batch_status=BatchStatus.PENDING, batch_type=BatchType.UPLOAD, source_file_upload_id=123, ) # Create event and processor event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, correlation_id='corr-456', ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) # Execute result = processor.process(event) # Verify assert isinstance(result, AdjustmentFileInitializeResponse) assert result.detail_type == EventType.ADJUSTMENT_BATCH_INITIALIZED assert result.detail.metadata.target_id == 789 assert ( result.detail.metadata.target_type == TargetType.WORKSHEET_ADJUSTMENT_BATCH ) assert result.detail.data.s3_bucket == 'test-bucket' assert result.detail.data.s3_key == 'uploads/test.csv' assert result.detail.metadata.correlation_id == 'corr-456' # Verify method calls mock_repository.get_current_statement_period.assert_called_once() mock_repository.get_file_upload.assert_called_once_with(123) mock_repository.get_batch_by_file_upload.assert_called_once_with(123) mock_repository.create_batch.assert_not_called() def test_process_statement_period_not_found(self): """Test process raises StatementPeriodNotFoundError when no current period.""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = None mock_repository.get_file_upload.return_value = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) with pytest.raises(StatementPeriodNotFoundError) as exc_info: processor.process(event) assert 'statement_period record not found' in str(exc_info.value) assert "statement_period_status='current'" in str(exc_info.value) def test_process_file_upload_not_found(self): """Test process raises FileUploadNotFoundError when record not found.""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_file_upload.return_value = None event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=999, ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) with pytest.raises(FileUploadNotFoundError) as exc_info: processor.process(event) assert 'File upload not found: file_upload_id=999' in str(exc_info.value) def test_process_invalid_upload_type(self): """Test process raises InvalidUploadTypeError for non-adjustments type.""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) # Use model_construct to bypass validation for invalid enum value mock_repository.get_file_upload.return_value = FileUpload.model_construct( file_upload_id=123, s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status=UploadStatus.COMPLETE, upload_type='wrong_type', created_by='user-123', ) event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) with pytest.raises(InvalidUploadTypeError) as exc_info: processor.process(event) assert 'Invalid upload type for file_upload_id=123' in str(exc_info.value) assert 'Expected: adjustments, Got: wrong_type' in str(exc_info.value) def test_process_invalid_upload_status(self): """Test process raises InvalidUploadStatusError for non-complete status.""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) # Use model_construct to bypass validation for invalid enum value mock_repository.get_file_upload.return_value = FileUpload.model_construct( file_upload_id=123, s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status='pending', upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) with pytest.raises(InvalidUploadStatusError) as exc_info: processor.process(event) assert 'Invalid upload status for file_upload_id=123' in str(exc_info.value) assert 'Expected: complete, Got: pending' in str(exc_info.value) def test_process_handles_transient_db_errors(self): """Test process propagates TransientError from repository.""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_file_upload.side_effect = TransientError( 'Database connection failed' ) event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) with pytest.raises(TransientError) as exc_info: processor.process(event) assert 'Database connection failed' in str(exc_info.value) def test_process_without_correlation_id(self): """Test process works correctly without correlation_id.""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_file_upload.return_value = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) mock_repository.get_batch_by_file_upload.return_value = None mock_repository.create_batch.return_value = 789 event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) result = processor.process(event) # Correlation ID should be generated as UUID when not provided assert result.detail.metadata.correlation_id is not None assert result.detail.metadata.target_id == 789 assert ( result.detail.metadata.target_type == TargetType.WORKSHEET_ADJUSTMENT_BATCH ) def test_process_null_s3_bucket(self): """Test process handles null s3_bucket from database.""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) # We can't even create a FileUploadRecord with null s3_bucket if it's required in the model # The model has: s3_bucket: str # So Pydantic will raise ValidationError if we try to instantiate it with None. # However, in the test we were mocking repository to return a dict which would fail validation # But now repository returns a model. # If the DB returns None for a required field, repository code (or Pydantic validation) would fail there. # If the intention is that s3_bucket can be None, we should update the model. # But `test_process_null_s3_bucket` in the original test expected `ValidationError` from `processor.process(event)`. # `processor.process` calls `repository.get_file_upload`. # If `repository.get_file_upload` returns a valid model, then `processor.process` continues. # If `repository.get_file_upload` fails to create the model because of validation error, that exception will propagate. # Let's look at the original test expectation: # with pytest.raises(ValidationError): # processor.process(event) # This implies that `processor.process` should raise ValidationError. # If we mock `repository.get_file_upload` to return a model, we are assuming it succeeded. # But wait, if the model requires `s3_bucket`, we can't create it with None in the test unless we bypass validation or mock the return value directly (but we want to use the model). # If the DB record has NULL, `repository.py` does `FileUploadRecord(**result)`. This will raise `ValidationError` inside repository. # The processor calls `_get_file_upload`, which calls repository. # So the exception will bubble up. # So the test should expect ValidationError when repository is called. # We can simulate this by making the mock raise ValidationError. from pydantic import ValidationError # We can't instantiate FileUploadRecord with None for s3_bucket to set as return_value. # So we should make the mock raise ValidationError directly, simulating what happens in repository. # OR we assume that s3_bucket CAN be None in the DB but the model enforces it. # Actually, if the test says "Test process handles null s3_bucket from database", it means the DB returns it. # If the model says `s3_bucket: str`, then `repository` will raise ValidationError. # So `processor.process` will raise ValidationError. # So I will mock `get_file_upload` to raise ValidationError. mock_repository.get_file_upload.side_effect = ( ValidationError.from_exception_data( 'ValidationError', [{'type': 'missing', 'loc': ('s3_bucket',), 'input': {}}], ) ) event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) with pytest.raises(ValidationError): processor.process(event) def test_process_null_s3_key(self): """Test process handles null s3_key from database.""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) from pydantic import ValidationError mock_repository.get_file_upload.side_effect = ( ValidationError.from_exception_data( 'ValidationError', [{'type': 'missing', 'loc': ('s3_key',), 'input': {}}], ) ) event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) with pytest.raises(ValidationError): processor.process(event) def test_process_various_invalid_statuses(self): """Test process rejects various non-complete statuses.""" invalid_statuses = [ 'pending', 'processing', 'failed', 'cancelled', 'quarantined', ] for status in invalid_statuses: mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) # Use model_construct to bypass validation for invalid enum values mock_repository.get_file_upload.return_value = FileUpload.model_construct( file_upload_id=123, s3_bucket='test-bucket', s3_key='test.csv', upload_status=status, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) with pytest.raises(InvalidUploadStatusError) as exc_info: processor.process(event) assert status in str(exc_info.value) def test_process_commit_failure(self): """Test process handles commit failure.""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_file_upload.return_value = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) mock_repository.get_batch_by_file_upload.return_value = None mock_repository.create_batch.return_value = 789 event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) # Simulate DB error in repo method mock_repository.create_batch.side_effect = OperationalError( 2006, 'MySQL server has gone away' ) with pytest.raises(OperationalError): processor.process(event) def test_process_integrity_error_recovery(self): """Test recovery from IntegrityError during batch creation.""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_file_upload.return_value = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) # First check returns None, second check (after IntegrityError) returns batch mock_repository.get_batch_by_file_upload.side_effect = [ None, AdjustmentBatch( batch_id=789, statement_period_id=456, batch_status='pending', batch_type='upload', source_file_upload_id=123, ), ] mock_repository.create_batch.side_effect = IntegrityError( 1062, 'Duplicate entry' ) event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, correlation_id='corr-456', ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) # Execute result = processor.process(event) # Verify assert result.detail.metadata.target_id == 789 assert ( result.detail.metadata.target_type == TargetType.WORKSHEET_ADJUSTMENT_BATCH ) assert mock_repository.create_batch.called # Should be called twice: once initially, once after recovery assert mock_repository.get_batch_by_file_upload.call_count == 2 def test_process_integrity_error_failure(self): """Test failure when IntegrityError recovery fails (batch still not found).""" mock_repository = Mock() mock_repository.get_current_statement_period.return_value = StatementPeriod( statement_period_id=456, statement_period_name='2024-01', statement_period_status=StatementPeriodStatus.CURRENT, statement_month=1, statement_year=2024, ) mock_repository.get_file_upload.return_value = FileUpload( file_upload_id=123, original_file_name='test.csv', s3_bucket='test-bucket', s3_key='uploads/test.csv', upload_status=UploadStatus.COMPLETE, upload_type=UploadType.ADJUSTMENTS, created_by='user-123', ) # Returns None both times mock_repository.get_batch_by_file_upload.return_value = None mock_repository.create_batch.side_effect = IntegrityError( 1062, 'Duplicate entry' ) event = AdjustmentFileInitializeEvent( detail_type='file_upload.completed', detail=AdjustmentFileInitializeEventDetail( metadata=AdjustmentFileInitializeEventMetadata( target_type='file_upload', target_id=123, correlation_id='corr-456', ), data=AdjustmentFileInitializeEventData(upload_type='adjustments'), ), ) processor = AdjustmentFileInitializeProcessor(mock_repository) # Execute with pytest.raises(TransientError) as exc_info: processor.process(event) assert 'Conflict creating batch' in str(exc_info.value) class TestAdjustmentFileInitializeProcessorCreateStatementPeriodAdjustmentFile: """Tests for AdjustmentFileInitializeProcessor._create_statement_period_adjustment_file method.""" @patch('src.processor.ows_royalties') @patch('src.processor.ows_abacus_state') def test_create_statement_period_adjustment_file_success( self, mock_abacus_state, mock_ows_royalties, ): """Test successful creation of statement period adjustment file.""" mock_repository = Mock() file_name = 'file_name.csv' file_location = 's3://bucket/test.csv' upload_id = 110 current_statement_period_id = 456 user_id = 'user-123' mock_create_file_response = Mock() mock_create_file_response.status_code = 201 mock_create_file_response.json.return_value = { 'statement_period_adjustment_file_id': 5, 'statement_period_id': current_statement_period_id, 'file_name': 'test.csv', 'valid_file_location': 's3://test-bucket/uploads/test.csv', } mock_create_state_response = Mock() mock_create_state_response.status_code = 201 mock_ows_royalties.create_statement_period_adjustment_file.return_value = ( mock_create_file_response ) mock_abacus_state.create_abacus_state.return_value = mock_create_state_response processor = AdjustmentFileInitializeProcessor(mock_repository) result_file_id = processor._create_statement_period_adjustment_file( file_name, file_location, upload_id, current_statement_period_id, user_id ) assert result_file_id == 5 mock_ows_royalties.create_statement_period_adjustment_file.assert_called_once_with( current_statement_period_id, { 'file_name': file_name, 'valid_file_location': file_location, 'source_file_upload_id': upload_id, 'created_by': user_id, }, ) mock_abacus_state.create_abacus_state.assert_called_once_with( 'statement_period_adjustment_file', 5, 'upload_file' ) @patch('src.processor.ows_royalties') @patch('src.processor.ows_abacus_state') def test_create_statement_period_adjustment_file_failed( self, mock_abacus_state, mock_ows_royalties, ): """Test failed creation of statement period adjustment file.""" mock_repository = Mock() file_name = 'file_name.csv' file_location = 's3://bucket/test.csv' upload_id = 110 current_statement_period_id = 456 user_id = 'user-123' # Mock 500 error response mock_create_file_response = Mock() mock_create_file_response.status_code = 500 mock_create_file_response.json.return_value = { 'message': 'Internal server error' } mock_ows_royalties.create_statement_period_adjustment_file.return_value = ( mock_create_file_response ) processor = AdjustmentFileInitializeProcessor(mock_repository) with pytest.raises(StatementPeriodAdjustmentFileCreateError) as exc_info: processor._create_statement_period_adjustment_file( file_name, file_location, upload_id, current_statement_period_id, user_id, ) assert 'Error creating statement period adjustment file' in str(exc_info.value) assert file_name in str(exc_info.value) mock_ows_royalties.create_statement_period_adjustment_file.assert_called_once_with( current_statement_period_id, { 'file_name': file_name, 'valid_file_location': file_location, 'source_file_upload_id': upload_id, 'created_by': user_id, }, ) mock_abacus_state.create_abacus_state.assert_not_called() @patch('src.processor.ows_royalties') @patch('src.processor.ows_abacus_state') def test_create_statement_period_adjustment_file_create_states_failed( self, mock_abacus_state, mock_ows_royalties, ): """Test failed creation of statement period adjustment file when states creation failed.""" mock_repository = Mock() file_name = 'file_name.csv' file_location = 's3://bucket/test.csv' upload_id = 110 current_statement_period_id = 456 user_id = 'user-123' mock_create_file_response = Mock() mock_create_file_response.status_code = 201 mock_create_file_response.json.return_value = { 'statement_period_adjustment_file_id': 5, 'statement_period_id': current_statement_period_id, 'file_name': 'test.csv', 'valid_file_location': 's3://test-bucket/uploads/test.csv', } mock_create_state_response = Mock() mock_create_state_response.status_code = 500 mock_ows_royalties.create_statement_period_adjustment_file.return_value = ( mock_create_file_response ) mock_abacus_state.create_abacus_state.return_value = mock_create_state_response processor = AdjustmentFileInitializeProcessor(mock_repository) with pytest.raises(StatementPeriodAdjustmentFileStatesCreateError) as exc_info: processor._create_statement_period_adjustment_file( file_name, file_location, upload_id, current_statement_period_id, user_id, ) assert 'Error creating statement period adjustment file states' in str( exc_info.value ) assert file_name in str(exc_info.value) mock_ows_royalties.create_statement_period_adjustment_file.assert_called_once_with( current_statement_period_id, { 'file_name': file_name, 'valid_file_location': file_location, 'source_file_upload_id': upload_id, 'created_by': user_id, }, ) mock_abacus_state.create_abacus_state.assert_called_once_with( 'statement_period_adjustment_file', 5, 'upload_file' )