diff --git a/optimizely/config_manager.py b/optimizely/config_manager.py index 21c74973..22468b05 100644 --- a/optimizely/config_manager.py +++ b/optimizely/config_manager.py @@ -145,14 +145,14 @@ def _set_config(self, datafile: Optional[str | bytes]) -> None: except optimizely_exceptions.UnsupportedDatafileVersionException as error: error_msg = error.args[0] error_to_handle = error - except: + except Exception: error_msg = enums.Errors.INVALID_INPUT.format('datafile') error_to_handle = optimizely_exceptions.InvalidInputException(error_msg) - finally: - if error_msg or config is None: - self.logger.error(error_msg) - self.error_handler.handle_error(error_to_handle or Exception('Unknown Error')) - return + + if error_msg or config is None: + self.logger.error(error_msg) + self.error_handler.handle_error(error_to_handle or Exception('Unknown Error')) + return previous_revision = self._config.get_revision() if self._config else None diff --git a/tests/test_config_manager.py b/tests/test_config_manager.py index 0e8cb7b3..8fa19693 100644 --- a/tests/test_config_manager.py +++ b/tests/test_config_manager.py @@ -200,6 +200,36 @@ def test_set_config__invalid_datafile(self): mock_logger.error.assert_called_once_with('Provided "datafile" is in an invalid format.') self.assertEqual(0, mock_notification_center.call_count) + def test_set_config__exception_in_except_block_is_not_swallowed(self): + """ Test that an exception raised inside the except block propagates + to the caller instead of being silently swallowed. + + Per Python docs, a return inside a finally clause suppresses any + in-flight exception. The _set_config method must not use return in + finally, so that unexpected errors in the error-handling path are + surfaced rather than silently lost. + """ + test_datafile = json.dumps(self.config_dict_with_features) + mock_logger = mock.Mock() + + with mock.patch('optimizely.config_manager.BaseConfigManager._validate_instantiation_options'): + project_config_manager = config_manager.StaticConfigManager( + datafile=test_datafile, logger=mock_logger, + ) + + # Make ProjectConfig raise so we enter the bare except block, + # then make the except block itself raise by breaking INVALID_INPUT. + with mock.patch( + 'optimizely.project_config.ProjectConfig.__init__', + side_effect=Exception('something broke'), + ), mock.patch.object( + enums.Errors, 'INVALID_INPUT', new=None, + ): + # The except block will raise AttributeError ('NoneType' has no 'format'). + # Correct behavior: this exception must propagate to the caller. + with self.assertRaises(AttributeError): + project_config_manager._set_config(test_datafile) + def test_get_config(self): """ Test get_config. """ test_datafile = json.dumps(self.config_dict_with_features)