Skip to content

Commit 60a4ada

Browse files
update: enhance error logging for CMAB fetch failures with detailed messages and add a test for handling 500 errors
1 parent fe100cb commit 60a4ada

File tree

3 files changed

+68
-2
lines changed

3 files changed

+68
-2
lines changed

optimizely/decision_service.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,10 @@ def _get_decision_for_cmab_experiment(
124124
"reasons": [],
125125
}
126126
except Exception as e:
127-
error_message = Errors.CMAB_FETCH_FAILED.format(str(e))
127+
error_message = Errors.CMAB_FETCH_FAILED_DETAILED.format(
128+
experiment.key,
129+
str(e)
130+
)
128131
if self.logger:
129132
self.logger.error(error_message)
130133
return {

optimizely/helpers/enums.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,8 @@ class Errors:
128128
ODP_INVALID_ACTION: Final = 'ODP action is not valid (cannot be empty).'
129129
MISSING_SDK_KEY: Final = 'SDK key not provided/cannot be found in the datafile.'
130130
CMAB_FETCH_FAILED: Final = 'CMAB decision fetch failed with status: {}'
131-
INVALID_CMAB_FETCH_RESPONSE = 'Invalid CMAB fetch response'
131+
INVALID_CMAB_FETCH_RESPONSE: Final = 'Invalid CMAB fetch response'
132+
CMAB_FETCH_FAILED_DETAILED: Final = 'Failed to fetch CMAB decision for experiment key "{}" - {}'
132133

133134

134135
class ForcedDecisionLogs:

tests/test_decision_service.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -908,6 +908,68 @@ def test_get_variation_cmab_experiment_service_error(self):
908908
# Verify logger was called
909909
mock_logger.error.assert_any_call('CMAB decision fetch failed with status: CMAB service error')
910910

911+
def test_get_variation_cmab_experiment_deep_mock_500_error(self):
912+
"""Test the full flow of a CMAB experiment with a 500 error from the HTTP request layer."""
913+
import requests
914+
from optimizely.exceptions import CmabFetchError
915+
from optimizely.helpers.enums import Errors
916+
917+
# Create a user context
918+
user = optimizely_user_context.OptimizelyUserContext(
919+
optimizely_client=None,
920+
logger=None,
921+
user_id="test_user",
922+
user_attributes={}
923+
)
924+
925+
# Create a CMAB experiment
926+
cmab_experiment = entities.Experiment(
927+
'111150',
928+
'cmab_experiment',
929+
'Running',
930+
'111150',
931+
[], # No audience IDs
932+
{},
933+
[entities.Variation('111151', 'variation_1')],
934+
[{'entityId': '111151', 'endOfRange': 10000}],
935+
cmab={'trafficAllocation': 5000}
936+
)
937+
938+
# Define HTTP error details
939+
http_error = requests.exceptions.HTTPError("500 Server Error")
940+
error_message = Errors.CMAB_FETCH_FAILED.format(http_error)
941+
detailed_error_message = Errors.CMAB_FETCH_FAILED_DETAILED.format(cmab_experiment.key, error_message)
942+
943+
# Set up mocks for the entire call chain
944+
with mock.patch('optimizely.helpers.experiment.is_experiment_running', return_value=True), \
945+
mock.patch('optimizely.helpers.audience.does_user_meet_audience_conditions', return_value=[True, []]), \
946+
mock.patch('optimizely.bucketer.Bucketer.bucket_to_entity_id', return_value=['$', []]), \
947+
mock.patch.object(self.decision_service.cmab_service, 'get_decision',
948+
side_effect=lambda *args, **kwargs: self.decision_service.cmab_service._fetch_decision(*args, **kwargs)), \
949+
mock.patch.object(self.decision_service.cmab_service, '_fetch_decision',
950+
side_effect=lambda *args, **kwargs: self.decision_service.cmab_service.cmab_client.fetch_decision(*args, **kwargs)), \
951+
mock.patch.object(self.decision_service.cmab_service.cmab_client, 'fetch_decision',
952+
side_effect=lambda *args, **kwargs: self.decision_service.cmab_service.cmab_client._do_fetch(*args, **kwargs)), \
953+
mock.patch.object(self.decision_service.cmab_service.cmab_client, '_do_fetch',
954+
side_effect=CmabFetchError(error_message)), \
955+
mock.patch.object(self.decision_service, 'logger') as mock_logger:
956+
957+
# Call get_variation with the CMAB experiment
958+
variation, reasons, cmab_uuid = self.decision_service.get_variation(
959+
self.project_config,
960+
cmab_experiment,
961+
user,
962+
None
963+
)
964+
965+
# Verify we get no variation due to CMAB service error
966+
self.assertIsNone(variation)
967+
self.assertIsNone(cmab_uuid)
968+
self.assertIn(detailed_error_message, reasons)
969+
970+
# Verify logger was called with the specific 500 error
971+
mock_logger.error.assert_any_call(detailed_error_message)
972+
911973
def test_get_variation_cmab_experiment_forced_variation(self):
912974
"""Test get_variation with CMAB experiment when user has a forced variation."""
913975

0 commit comments

Comments
 (0)