"""Tests for abacus_file_upload.logic.file_upload_config module.""" from unittest.mock import Mock, patch import pytest from sqlalchemy.exc import IntegrityError from werkzeug.exceptions import NotFound from abacus_file_upload.constants import UPLOAD_TYPES from abacus_file_upload.logic import file_upload_config from abacus_file_upload.tests.utils.factories import ( FileUploadConfigFactory, ) class TestCreateFileUploadConfig: """Tests for create_file_upload_config function.""" @patch('abacus_file_upload.logic.file_upload_config.db.session.refresh') @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.create') @patch( 'abacus_file_upload.logic.file_upload_config.file_upload_config_detail_schema' ) def test_successful_creation( self, mock_schema, mock_create, mock_refresh, sample_config_params ): """Test successful config creation.""" mock_config = Mock() mock_config.file_upload_config_id = 1 mock_create.return_value = mock_config # Mock the schema dump to return a dict mock_schema.dump.return_value = { 'file_upload_config_id': 1, 'upload_type': 'adjustments', } response = file_upload_config.create_file_upload_config(**sample_config_params) assert response.status == 201 mock_create.assert_called_once_with( upload_type='adjustments', s3_key_template='{year}/{month}/{file_key}.{ext}', allowed_file_types=['csv', 'xlsx'], max_file_size_bytes=10485760, multipart_threshold_bytes=104857600, min_multipart_chunk_size_bytes=10485760, description='Test config', ) mock_refresh.assert_called_once_with(mock_config) mock_schema.dump.assert_called_once_with(mock_config) def test_invalid_data(self): """Test creation with invalid data.""" response = file_upload_config.create_file_upload_config( upload_type='adjustments' # Missing required s3_key_template ) assert response.status == 400 @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.create') @patch('abacus_file_upload.logic.file_upload_config.db.session.rollback') def test_duplicate_upload_type( self, mock_rollback, mock_create, sample_config_params ): """Test creation with duplicate upload type.""" mock_create.side_effect = IntegrityError('', '', '') response = file_upload_config.create_file_upload_config(**sample_config_params) assert response.status == 409 assert 'already exists' in response.message.lower() mock_rollback.assert_called_once() @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.create') @patch('abacus_file_upload.logic.file_upload_config.db.session.rollback') def test_database_error(self, mock_rollback, mock_create, sample_config_params): """Test creation with database error.""" mock_create.side_effect = Exception('Database error') response = file_upload_config.create_file_upload_config(**sample_config_params) assert response.status == 500 mock_rollback.assert_called_once() class TestGetFileUploadConfig: """Tests for get_file_upload_config function.""" @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.get_by_id') @patch( 'abacus_file_upload.logic.file_upload_config.file_upload_config_detail_schema' ) def test_get_existing_config(self, mock_schema, mock_get, mock_file_upload_config): """Test getting existing config.""" mock_get.return_value = mock_file_upload_config mock_schema.dump.return_value = { 'file_upload_config_id': 1, 'upload_type': 'adjustments', } response = file_upload_config.get_file_upload_config(1) assert response.status == 200 assert response.message['file_upload_config_id'] == 1 assert response.message['upload_type'] == 'adjustments' @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.get_by_id') def test_get_nonexistent_config(self, mock_get): """Test getting nonexistent config.""" mock_get.return_value = None response = file_upload_config.get_file_upload_config(999) assert response.status == 404 assert 'not found' in response.message.lower() @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.get_by_id') @patch( 'abacus_file_upload.logic.file_upload_config.file_upload_config_detail_verbose_schema' ) def test_get_existing_config_verbose( self, mock_verbose_schema, mock_get, mock_file_upload_config ): """Test getting existing config with verbose=True.""" mock_get.return_value = mock_file_upload_config mock_verbose_schema.dump.return_value = { 'file_upload_config_id': 1, 'upload_type': 'adjustments', 'created_at': '2025-11-24T12:00:00Z', 'created_by': 'user123', } response = file_upload_config.get_file_upload_config(1, verbose=True) assert response.status == 200 assert response.message['file_upload_config_id'] == 1 assert 'created_at' in response.message mock_verbose_schema.dump.assert_called_once_with(mock_file_upload_config) @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.get_by_id') def test_get_deleted_config(self, mock_get): """Test getting deleted config.""" mock_file_upload_config = FileUploadConfigFactory.build( deleted_at='2025-12-12', deleted_by='Test User', ) mock_get.return_value = mock_file_upload_config file_upload_config_id = mock_file_upload_config.file_upload_config_id response = file_upload_config.get_file_upload_config( mock_file_upload_config.file_upload_config_id ) assert response.status == 404 assert ( response.message == f"Upload config with ID '{file_upload_config_id}' not found" ) class TestGetFileUploadConfigByType: """Tests for get_file_upload_config_by_type function.""" @patch( 'abacus_file_upload.logic.file_upload_config.FileUploadConfig.find_by_upload_type' ) @patch( 'abacus_file_upload.logic.file_upload_config.file_upload_config_detail_schema' ) def test_get_by_type_success(self, mock_schema, mock_find, mock_file_upload_config): """Test getting config by type successfully.""" mock_find.return_value = mock_file_upload_config mock_schema.dump.return_value = { 'file_upload_config_id': 1, 'upload_type': 'adjustments', } response = file_upload_config.get_file_upload_config_by_type('adjustments') assert response.status == 200 assert response.message['upload_type'] == 'adjustments' @patch( 'abacus_file_upload.logic.file_upload_config.FileUploadConfig.find_by_upload_type' ) def test_get_by_type_not_found(self, mock_find): """Test getting config by nonexistent type.""" mock_find.return_value = None response = file_upload_config.get_file_upload_config_by_type('nonexistent') assert response.status == 404 @patch( 'abacus_file_upload.logic.file_upload_config.FileUploadConfig.find_by_upload_type' ) @patch( 'abacus_file_upload.logic.file_upload_config.file_upload_config_detail_verbose_schema' ) def test_get_by_type_verbose( self, mock_verbose_schema, mock_find, mock_file_upload_config ): """Test getting config by type with verbose=True.""" mock_find.return_value = mock_file_upload_config mock_verbose_schema.dump.return_value = { 'file_upload_config_id': 1, 'upload_type': 'adjustments', 'created_at': '2025-11-24T12:00:00Z', } response = file_upload_config.get_file_upload_config_by_type( 'adjustments', verbose=True ) assert response.status == 200 assert 'created_at' in response.message mock_verbose_schema.dump.assert_called_once_with(mock_file_upload_config) class TestGetAllFileUploadConfigs: """Tests for get_all_file_upload_configs function.""" @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.query') @patch( 'abacus_file_upload.logic.file_upload_config.file_upload_config_detail_schema' ) def test_get_all_configs(self, mock_schema, mock_query, mock_file_upload_config): """Test getting all configs.""" mock_config2 = Mock() mock_config2.file_upload_config_id = 2 mock_config2.upload_type = 'flowthrough' mock_query.filter.return_value.order_by.return_value.all.return_value = [ mock_file_upload_config, mock_config2, ] # Mock the schema dump to return dict mock_schema.dump.side_effect = lambda x: { 'file_upload_config_id': x.file_upload_config_id, 'upload_type': x.upload_type, } response = file_upload_config.get_all_file_upload_configs() assert response.status == 200 assert len(response.message) == 2 @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.query') @patch( 'abacus_file_upload.logic.file_upload_config.file_upload_config_detail_schema' ) def test_get_all_configs_empty(self, mock_schema, mock_query): """Test getting all configs when none exist.""" mock_query.filter.return_value.order_by.return_value.all.return_value = [] response = file_upload_config.get_all_file_upload_configs() assert response.status == 200 assert len(response.message) == 0 @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.query') @patch( 'abacus_file_upload.logic.file_upload_config.file_upload_config_detail_verbose_schema' ) def test_get_all_configs_verbose( self, mock_verbose_schema, mock_query, mock_file_upload_config ): """Test getting all configs with verbose=True.""" mock_query.filter.return_value.order_by.return_value.all.return_value = [ mock_file_upload_config ] mock_verbose_schema.dump.return_value = { 'file_upload_config_id': 1, 'upload_type': 'adjustments', 'created_at': '2025-11-24T12:00:00Z', } response = file_upload_config.get_all_file_upload_configs(verbose=True) assert response.status == 200 assert len(response.message) == 1 assert 'created_at' in response.message[0] mock_verbose_schema.dump.assert_called_once() class TestUpdateFileUploadConfig: """Tests for update_file_upload_config function.""" @patch('abacus_file_upload.logic.file_upload_config.db.session.refresh') @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.get_by_id') @patch( 'abacus_file_upload.logic.file_upload_config.FileUploadConfig.commit_changes' ) @patch( 'abacus_file_upload.logic.file_upload_config.file_upload_config_detail_verbose_schema' ) def test_successful_update( self, mock_schema, mock_commit, mock_get, mock_refresh, mock_file_upload_config ): """Test successful config update.""" mock_get.return_value = mock_file_upload_config mock_file_upload_config.update_attributes = Mock() mock_schema.dump.return_value = { 'file_upload_config_id': 1, 'upload_type': 'adjustments', 'max_file_size_bytes': 20971520, } response = file_upload_config.update_file_upload_config( 1, max_file_size_bytes=20971520 ) assert response.status == 200 mock_file_upload_config.update_attributes.assert_called_once() mock_commit.assert_called_once() mock_refresh.assert_called_once_with(mock_file_upload_config) mock_schema.dump.assert_called_once_with(mock_file_upload_config) @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.get_by_id') def test_update_nonexistent_config(self, mock_get): """Test updating nonexistent config.""" mock_get.return_value = None response = file_upload_config.update_file_upload_config( 999, max_file_size_bytes=20971520 ) assert response.status == 404 @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.get_by_id') def test_update_with_invalid_data(self, mock_get, mock_file_upload_config): """Test update with invalid data.""" mock_get.return_value = mock_file_upload_config response = file_upload_config.update_file_upload_config( 1, max_file_size_bytes='invalid', # Should be int ) assert response.status == 400 @patch('abacus_file_upload.logic.file_upload_config.FileUploadConfig.get_by_id') @patch( 'abacus_file_upload.logic.file_upload_config.FileUploadConfig.commit_changes' ) @patch('abacus_file_upload.logic.file_upload_config.db.session.rollback') def test_update_database_error( self, mock_rollback, mock_commit, mock_get, mock_file_upload_config ): """Test update with database error.""" mock_get.return_value = mock_file_upload_config mock_commit.side_effect = Exception('Database error') response = file_upload_config.update_file_upload_config( 1, max_file_size_bytes=20971520 ) assert response.status == 500 mock_rollback.assert_called_once() class TestDeleteFileUploadConfig: """Tests for delete_file_upload_config function.""" @patch( 'abacus_file_upload.logic.file_upload_config.FileUploadConfig.delete_by_id_or_error' ) def test_successful_deletion(self, mock_delete): """Test successful config deletion.""" mock_delete.return_value = True response = file_upload_config.delete_file_upload_config(1) assert response.status == 204 mock_delete.assert_called_once_with(1, error_status=404, soft_delete=True) @patch( 'abacus_file_upload.logic.file_upload_config.FileUploadConfig.delete_by_id_or_error' ) def test_delete_nonexistent_config(self, mock_delete): """Test deleting nonexistent config.""" mock_delete.side_effect = NotFound('FileUploadConfig not found') with pytest.raises(NotFound) as exec_info: file_upload_config.delete_file_upload_config(999) assert str(exec_info.value) == '404 Not Found: FileUploadConfig not found'