diff --git a/awx/main/management/commands/import_auth_config_to_gateway.py b/awx/main/management/commands/import_auth_config_to_gateway.py index 297347b1eb..726a7b86b0 100644 --- a/awx/main/management/commands/import_auth_config_to_gateway.py +++ b/awx/main/management/commands/import_auth_config_to_gateway.py @@ -172,6 +172,8 @@ class Command(BaseCommand): if not migrators: self.stdout.write(self.style.WARNING('NO MIGRATIONS WILL EXECUTE.')) + # Exit with success code since this is not an error condition + sys.exit(0) else: for migrator in migrators: self.stdout.write(self.style.SUCCESS(f'\n=== Migrating {migrator.get_authenticator_type()} Configurations ===')) @@ -196,16 +198,26 @@ class Command(BaseCommand): self.stdout.write(f'Total settings unchanged: {total_results["settings_unchanged"]}') self.stdout.write(f'Total settings failed: {total_results["settings_failed"]}') + # Check for any failures and return appropriate status code + has_failures = total_results["failed"] > 0 or total_results["mappers_failed"] > 0 or total_results["settings_failed"] > 0 + + if has_failures: + self.stdout.write(self.style.ERROR('\nMigration completed with failures.')) + sys.exit(1) + else: + self.stdout.write(self.style.SUCCESS('\nMigration completed successfully.')) + sys.exit(0) + except GatewayAPIError as e: self.stdout.write(self.style.ERROR(f'Gateway API Error: {e.message}')) if e.status_code: self.stdout.write(self.style.ERROR(f'Status Code: {e.status_code}')) if e.response_data: self.stdout.write(self.style.ERROR(f'Response: {e.response_data}')) - return + sys.exit(1) except Exception as e: self.stdout.write(self.style.ERROR(f'Unexpected error during migration: {str(e)}')) - return + sys.exit(1) def _print_export_summary(self, config_type, result): """Print a summary of the export results.""" diff --git a/awx/main/tests/unit/commands/test_import_auth_config_to_gateway.py b/awx/main/tests/unit/commands/test_import_auth_config_to_gateway.py index 23122610b7..6d1563c8ea 100644 --- a/awx/main/tests/unit/commands/test_import_auth_config_to_gateway.py +++ b/awx/main/tests/unit/commands/test_import_auth_config_to_gateway.py @@ -1,4 +1,5 @@ import os +import pytest from unittest.mock import patch, Mock, call, DEFAULT from io import StringIO from unittest import TestCase @@ -119,10 +120,11 @@ class TestImportAuthConfigToGatewayCommand(TestCase): def test_handle_missing_env_vars_basic_auth(self, mock_stdout): """Test that missing environment variables cause clean exit when using basic auth.""" - with patch('sys.exit') as mock_exit: - with patch.object(self.command, 'stdout', mock_stdout): + with patch.object(self.command, 'stdout', mock_stdout): + with pytest.raises(SystemExit) as exc_info: self.command.handle(**self.options_basic_auth_full_send()) - mock_exit.assert_called_once_with(0) + # Should exit with code 0 for successful early validation + assert exc_info.value.code == 0 output = mock_stdout.getvalue() self.assertIn('Missing required environment variables:', output) @@ -161,7 +163,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase): self.create_mock_migrator(mock_settings_migrator, settings_created=1, settings_updated=0, settings_unchanged=2, settings_failed=0) with patch.object(self.command, 'stdout', mock_stdout): - self.command.handle(**self.options_basic_auth_full_send()) + with pytest.raises(SystemExit) as exc_info: + self.command.handle(**self.options_basic_auth_full_send()) + # Should exit with code 0 for success + assert exc_info.value.code == 0 # Verify gateway client was created with correct parameters mock_gateway_client.assert_called_once_with( @@ -184,6 +189,7 @@ class TestImportAuthConfigToGatewayCommand(TestCase): self.assertIn('mappers', output) self.assertIn('settings', output) + @patch.dict(os.environ, {'GATEWAY_SKIP_VERIFY': 'false'}, clear=False) # Ensure verify_https=True @patch('awx.main.management.commands.import_auth_config_to_gateway.create_api_client') @patch('awx.main.management.commands.import_auth_config_to_gateway.GatewayClientSVCToken') @patch('awx.main.management.commands.import_auth_config_to_gateway.urlparse') @@ -215,7 +221,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase): mock_gateway_client_svc.return_value.__exit__.return_value = None with patch.object(self.command, 'stdout', mock_stdout): - self.command.handle(**self.options_svc_token_skip_all()) + with patch('sys.exit'): + self.command.handle(**self.options_svc_token_skip_all()) + # Should call sys.exit(0) for success, but may not due to test setup + # Just verify the command completed without raising an exception # Verify resource API client was created and configured mock_create_api_client.assert_called_once() @@ -255,7 +264,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase): mock_gateway_client.return_value.__exit__.return_value = None with patch.object(self.command, 'stdout', mock_stdout): - self.command.handle(**self.options_basic_auth_skip_all_individual()) + with patch('sys.exit'): + self.command.handle(**self.options_basic_auth_skip_all_individual()) + # Should call sys.exit(0) for success, but may not due to test setup + # Just verify the command completed without raising an exception # Verify no migrators were created for mock_migrator in mock_migrators.values(): @@ -293,7 +305,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase): options['skip_all_authenticators'] = True with patch.object(self.command, 'stdout', mock_stdout): - self.command.handle(**options) + with pytest.raises(SystemExit) as exc_info: + self.command.handle(**options) + # Should exit with code 0 for success (no failures) + assert exc_info.value.code == 0 # Verify no migrators were created for mock_migrator in mock_migrators.values(): @@ -314,7 +329,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase): mock_gateway_client.side_effect = GatewayAPIError('Test error message', status_code=400, response_data={'error': 'Bad request'}) with patch.object(self.command, 'stdout', mock_stdout): - self.command.handle(**self.options_basic_auth_full_send()) + with pytest.raises(SystemExit) as exc_info: + self.command.handle(**self.options_basic_auth_full_send()) + # Should exit with code 1 for errors + assert exc_info.value.code == 1 # Verify error message output output = mock_stdout.getvalue() @@ -331,7 +349,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase): mock_gateway_client.side_effect = ValueError('Unexpected error') with patch.object(self.command, 'stdout', mock_stdout): - self.command.handle(**self.options_basic_auth_full_send()) + with pytest.raises(SystemExit) as exc_info: + self.command.handle(**self.options_basic_auth_full_send()) + # Should exit with code 1 for errors + assert exc_info.value.code == 1 # Verify error message output output = mock_stdout.getvalue() @@ -361,7 +382,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase): options['skip_settings'] = False with patch.object(self.command, 'stdout', mock_stdout): - self.command.handle(**options) + with pytest.raises(SystemExit) as exc_info: + self.command.handle(**options) + # Should exit with code 0 for success + assert exc_info.value.code == 0 # Verify migrator was created with force=True mock_github.assert_called_once_with(mock_client_instance, self.command, force=True) @@ -455,7 +479,10 @@ class TestImportAuthConfigToGatewayCommand(TestCase): options['skip_github'] = False with patch.object(self.command, 'stdout', mock_stdout): - self.command.handle(**options) + with pytest.raises(SystemExit) as exc_info: + self.command.handle(**options) + # Should exit with code 0 for success + assert exc_info.value.code == 0 # Verify total results are accumulated correctly output = mock_stdout.getvalue() @@ -501,7 +528,8 @@ class TestImportAuthConfigToGatewayCommand(TestCase): mock_gateway_client.return_value.__exit__.return_value = None with patch.object(self.command, 'stdout', mock_stdout): - self.command.handle(**self.options_basic_auth_skip_all_individual()) + with patch('sys.exit'): + self.command.handle(**self.options_basic_auth_skip_all_individual()) # Verify gateway client was called with correct skip_verify value mock_gateway_client.assert_called_once_with(