Skip to content

[FSSDK-11175] Update: Implement Decision Service methods to handle CMAB #457

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
88b4f1e
update: integrate CMAB components into OptimizelyFactory
FarhanAnjum-opti Jun 4, 2025
2563c7b
update: add cmab_service parameter to Optimizely constructor for CMAB…
FarhanAnjum-opti Jun 4, 2025
fac8946
update: add docstring to DefaultCmabService class for improved docume…
FarhanAnjum-opti Jun 4, 2025
f74bc8c
update: implement CMAB support in bucketer and decision service, reve…
FarhanAnjum-opti Jun 13, 2025
6d1f73d
linting fix
FarhanAnjum-opti Jun 13, 2025
91d53b6
update: add cmab_uuid handling in DecisionService and related tests
FarhanAnjum-opti Jun 16, 2025
3eb755f
- updated function bucket_to_entity_id
FarhanAnjum-opti Jun 16, 2025
a5e4993
update: add None parameter to Decision constructor in user context tests
FarhanAnjum-opti Jun 16, 2025
c1cd97a
update: enhance CMAB decision handling and add related tests
FarhanAnjum-opti Jun 16, 2025
fd7c723
update: fix logger message formatting in CMAB experiment tests
FarhanAnjum-opti Jun 16, 2025
ec19c3b
mypy fix
FarhanAnjum-opti Jun 16, 2025
029262d
update: refine traffic allocation type hints and key naming in bucket…
FarhanAnjum-opti Jun 16, 2025
180fdee
update: remove unused import of cast in bucketer.py
FarhanAnjum-opti Jun 16, 2025
cd5ba39
update: fix return type for numeric_metric_value in get_numeric_value…
FarhanAnjum-opti Jun 16, 2025
92a3258
update: specify type hint for numeric_metric_value in get_numeric_val…
FarhanAnjum-opti Jun 16, 2025
fe100cb
update: fix logger reference in DefaultCmabClient initialization and …
FarhanAnjum-opti Jun 17, 2025
60a4ada
update: enhance error logging for CMAB fetch failures with detailed m…
FarhanAnjum-opti Jun 20, 2025
265d82b
update: enhance decision result handling by introducing VariationResu…
FarhanAnjum-opti Jun 20, 2025
6ca1102
update: refactor get_variation return structure and change tests acco…
FarhanAnjum-opti Jun 20, 2025
c2b3d96
-Error propagated to optimizely.py
FarhanAnjum-opti Jun 23, 2025
b901c5f
update: modify get_variation to return VariationResult and adjust rel…
FarhanAnjum-opti Jun 27, 2025
d2fc631
update: unit test fixes
FarhanAnjum-opti Jun 27, 2025
b9a8555
Revert "update: unit test fixes"
FarhanAnjum-opti Jun 30, 2025
a129854
Revert "update: modify get_variation to return VariationResult and ad…
FarhanAnjum-opti Jun 30, 2025
c637878
update: enhance decision service to handle error states and improve b…
FarhanAnjum-opti Jul 3, 2025
0bc4fbd
update: remove debug print statement from Optimizely class
FarhanAnjum-opti Jul 3, 2025
fcdad1f
update: enhance bucketing logic to support CMAB traffic allocations
FarhanAnjum-opti Jul 3, 2025
aca7df4
update: improve error logging for CMAB decision fetch failures
FarhanAnjum-opti Jul 3, 2025
72955a0
update: improve logging and error handling in bucketer and decision s…
FarhanAnjum-opti Jul 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
update: refactor get_variation return structure and change tests acco…
…rdingly
  • Loading branch information
FarhanAnjum-opti committed Jun 20, 2025
commit 6ca1102ffde42db24677d22f3eefeb13f08ba1b7
78 changes: 51 additions & 27 deletions tests/test_decision_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,9 +457,10 @@ def test_get_variation__experiment_not_running(self):
) as mock_lookup, mock.patch(
"optimizely.user_profile.UserProfileService.save"
) as mock_save:
variation, _, _ = self.decision_service.get_variation(
variation_result = self.decision_service.get_variation(
self.project_config, experiment, user, None
)
variation = variation_result['variation']
self.assertIsNone(
variation
)
Expand Down Expand Up @@ -500,7 +501,7 @@ def test_get_variation__bucketing_id_provided(self):
"optimizely.bucketer.Bucketer.bucket",
return_value=[self.project_config.get_variation_from_id("211127", "211129"), []],
) as mock_bucket:
variation, _, _ = self.decision_service.get_variation(
_ = self.decision_service.get_variation(
self.project_config,
experiment,
user,
Expand Down Expand Up @@ -535,9 +536,9 @@ def test_get_variation__user_whitelisted_for_variation(self):
) as mock_lookup, mock.patch(
"optimizely.user_profile.UserProfileService.save"
) as mock_save:
variation, _, _ = self.decision_service.get_variation(
variation = self.decision_service.get_variation(
self.project_config, experiment, user, user_profile_tracker
)
)['variation']
self.assertEqual(
entities.Variation("111128", "control"),
variation,
Expand Down Expand Up @@ -573,9 +574,9 @@ def test_get_variation__user_has_stored_decision(self):
) as mock_audience_check, mock.patch(
"optimizely.bucketer.Bucketer.bucket"
) as mock_bucket:
variation, _, _ = self.decision_service.get_variation(
variation = self.decision_service.get_variation(
self.project_config, experiment, user, user_profile_tracker
)
)['variation']
self.assertEqual(
entities.Variation("111128", "control"),
variation,
Expand Down Expand Up @@ -619,9 +620,9 @@ def test_get_variation__user_bucketed_for_new_experiment__user_profile_tracker_a
"optimizely.bucketer.Bucketer.bucket",
return_value=[entities.Variation("111129", "variation"), []],
) as mock_bucket:
variation, _, _ = self.decision_service.get_variation(
variation = self.decision_service.get_variation(
self.project_config, experiment, user, user_profile_tracker
)
)['variation']
self.assertEqual(
entities.Variation("111129", "variation"),
variation,
Expand Down Expand Up @@ -669,9 +670,9 @@ def test_get_variation__user_does_not_meet_audience_conditions(self):
) as mock_bucket, mock.patch(
"optimizely.user_profile.UserProfileService.save"
) as mock_save:
variation, _, _ = self.decision_service.get_variation(
variation = self.decision_service.get_variation(
self.project_config, experiment, user, user_profile_tracker
)
)['variation']
self.assertIsNone(
variation
)
Expand Down Expand Up @@ -719,14 +720,14 @@ def test_get_variation__ignore_user_profile_when_specified(self):
) as mock_lookup, mock.patch(
"optimizely.user_profile.UserProfileService.save"
) as mock_save:
variation, _, _ = self.decision_service.get_variation(
variation = self.decision_service.get_variation(
self.project_config,
experiment,
user,
user_profile_tracker,
[],
options=['IGNORE_USER_PROFILE_SERVICE'],
)
)['variation']
self.assertEqual(
entities.Variation("111129", "variation"),
variation,
Expand Down Expand Up @@ -796,16 +797,22 @@ def test_get_variation_cmab_experiment_user_in_traffic_allocation(self):
'logger') as mock_logger:

# Call get_variation with the CMAB experiment
variation, reasons, cmab_uuid = self.decision_service.get_variation(
variation_result = self.decision_service.get_variation(
self.project_config,
cmab_experiment,
user,
None
)
cmab_uuid = variation_result['cmab_uuid']
variation = variation_result['variation']
error = variation_result['error']
reasons = variation_result['reasons']

# Verify the variation and cmab_uuid
self.assertEqual(entities.Variation('111151', 'variation_1'), variation)
self.assertEqual('test-cmab-uuid-123', cmab_uuid)
self.assertStrictFalse(error)
self.assertIn('User "test_user" is in variation "variation_1" of experiment cmab_experiment.', reasons)

# Verify logger was called
mock_logger.info.assert_any_call('User "test_user" is in variation '
Expand Down Expand Up @@ -844,16 +851,23 @@ def test_get_variation_cmab_experiment_user_not_in_traffic_allocation(self):
'logger') as mock_logger:

# Call get_variation with the CMAB experiment
variation, reasons, cmab_uuid = self.decision_service.get_variation(
variation_result = self.decision_service.get_variation(
self.project_config,
cmab_experiment,
user,
None
)
variation = variation_result['variation']
cmab_uuid = variation_result['cmab_uuid']
error = variation_result['error']
reasons = variation_result['reasons']

# Verify we get no variation and CMAB service wasn't called
self.assertIsNone(variation)
self.assertIsNone(cmab_uuid)
self.assertStrictFalse(error)
self.assertIn('User "test_user" not in CMAB experiment "cmab_experiment" due to traffic allocation.',
reasons)
mock_cmab_decision.assert_not_called()

# Verify logger was called
Expand Down Expand Up @@ -888,25 +902,25 @@ def test_get_variation_cmab_experiment_service_error(self):
mock.patch('optimizely.helpers.audience.does_user_meet_audience_conditions', return_value=[True, []]), \
mock.patch('optimizely.bucketer.Bucketer.bucket_to_entity_id', return_value=['$', []]), \
mock.patch('optimizely.decision_service.DecisionService._get_decision_for_cmab_experiment',
return_value={'error': True, 'result': None, 'reasons': ['CMAB service error']}), \
mock.patch.object(self.decision_service,
'logger') as mock_logger:
return_value={'error': True, 'result': None, 'reasons': ['CMAB service error']}):

# Call get_variation with the CMAB experiment
variation, reasons, cmab_uuid = self.decision_service.get_variation(
variation_result = self.decision_service.get_variation(
self.project_config,
cmab_experiment,
user,
None
)
variation = variation_result['variation']
cmab_uuid = variation_result['cmab_uuid']
reasons = variation_result['reasons']
error = variation_result['error']

# Verify we get no variation due to CMAB service error
self.assertIsNone(variation)
self.assertIsNone(cmab_uuid)
self.assertIn('CMAB service error', reasons)

# Verify logger was called
mock_logger.error.assert_any_call('CMAB decision fetch failed with status: CMAB service error')
self.assertStrictTrue(error)

def test_get_variation_cmab_experiment_deep_mock_500_error(self):
"""Test the full flow of a CMAB experiment with a 500 error from the HTTP request layer."""
Expand Down Expand Up @@ -1015,17 +1029,22 @@ def test_get_variation_cmab_experiment_forced_variation(self):
) as mock_cmab_decision:

# Call get_variation with the CMAB experiment
variation, reasons, cmab_uuid = self.decision_service.get_variation(
variation_result = self.decision_service.get_variation(
self.project_config,
cmab_experiment,
user,
None
)
variation = variation_result['variation']
reasons = variation_result['reasons']
cmab_uuid = variation_result['cmab_uuid']
error = variation_result['error']

# Verify we get the forced variation
self.assertEqual(forced_variation, variation)
self.assertIsNone(cmab_uuid)
self.assertIn('User is forced into variation', reasons)
self.assertStrictFalse(error)

# Verify CMAB-specific methods weren't called
mock_bucket.assert_not_called()
Expand Down Expand Up @@ -1072,17 +1091,22 @@ def test_get_variation_cmab_experiment_with_whitelisted_variation(self):
) as mock_cmab_decision:

# Call get_variation with the CMAB experiment
variation, reasons, cmab_uuid = self.decision_service.get_variation(
variation_result = self.decision_service.get_variation(
self.project_config,
cmab_experiment,
user,
None
)
variation = variation_result['variation']
cmab_uuid = variation_result['cmab_uuid']
reasons = variation_result['reasons']
error = variation_result['error']

# Verify we get the whitelisted variation
self.assertEqual(whitelisted_variation, variation)
self.assertIsNone(cmab_uuid)
self.assertIn('User is whitelisted into variation', reasons)
self.assertStrictFalse(error)

# Verify CMAB-specific methods weren't called
mock_bucket.assert_not_called()
Expand Down Expand Up @@ -1353,7 +1377,7 @@ def test_get_variation_for_feature__returns_variation_for_feature_in_experiment(
)
decision_patch = mock.patch(
"optimizely.decision_service.DecisionService.get_variation",
return_value=[expected_variation, [], None],
return_value={'variation': expected_variation, 'cmab_uuid': None, 'reasons': [], 'error': False},
)
with decision_patch as mock_decision, self.mock_decision_logger:
variation_received, _ = self.decision_service.get_variation_for_feature(
Expand Down Expand Up @@ -1485,7 +1509,7 @@ def test_get_variation_for_feature__returns_variation_for_feature_in_group(self)
)
with mock.patch(
"optimizely.decision_service.DecisionService.get_variation",
return_value=(expected_variation, [], None),
return_value={'variation': expected_variation, 'cmab_uuid': None, 'reasons': [], 'error': False},
) as mock_decision:
variation_received, _ = self.decision_service.get_variation_for_feature(
self.project_config, feature, user, options=None
Expand Down Expand Up @@ -1520,7 +1544,7 @@ def test_get_variation_for_feature__returns_none_for_user_not_in_experiment(self

with mock.patch(
"optimizely.decision_service.DecisionService.get_variation",
return_value=[None, [], None],
return_value={'variation': None, 'cmab_uuid': None, 'reasons': [], 'error': False},
) as mock_decision:
variation_received, _ = self.decision_service.get_variation_for_feature(
self.project_config, feature, user
Expand Down Expand Up @@ -1552,7 +1576,7 @@ def test_get_variation_for_feature__returns_none_for_user_in_group_experiment_no
feature = self.project_config.get_feature_from_key("test_feature_in_group")
with mock.patch(
"optimizely.decision_service.DecisionService.get_variation",
return_value=[None, [], None],
return_value={'variation': None, 'cmab_uuid': None, 'reasons': [], 'error': False},
) as mock_decision:
variation_received, _ = self.decision_service.get_variation_for_feature(
self.project_config, feature, user, False
Expand Down
Loading
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy