Skip to content

Commit 10e031d

Browse files
committed
add cmab acceptance tests
1 parent 4720554 commit 10e031d

5 files changed

Lines changed: 647 additions & 37 deletions

File tree

tests/acceptance/datafile.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
{
2626
"experimentIds": [
2727
"16911963060",
28+
"9300002877087",
2829
"16910084756"
2930
],
3031
"id": "16911532385",
@@ -119,9 +120,53 @@
119120
"variables": []
120121
}
121122
]
123+
},
124+
{
125+
"audienceConditions": [
126+
"or",
127+
"16902921321"
128+
],
129+
"audienceIds": [
130+
"16902921321"
131+
],
132+
"cmab": {
133+
"attributeIds": [
134+
"16921322086"
135+
],
136+
"trafficAllocation": 10000
137+
},
138+
"forcedVariations": {},
139+
"id": "9300002877087",
140+
"key": "cmab-rule_1",
141+
"layerId": "9300002131372",
142+
"status": "Running",
143+
"trafficAllocation": [],
144+
"variations": [
145+
{
146+
"featureEnabled": False,
147+
"id": "1579277",
148+
"key": "off",
149+
"variables": []
150+
},
151+
{
152+
"featureEnabled": True,
153+
"id": "1579278",
154+
"key": "on",
155+
"variables": []
156+
}
157+
]
122158
}
123159
],
124160
"featureFlags": [
161+
{
162+
"experimentIds": [
163+
"9300002877087"
164+
],
165+
"id": "496419",
166+
"key": "cmab_flag",
167+
"rolloutId": "rollout-496419-16935023792",
168+
"variables": []
169+
},
125170
{
126171
"experimentIds": [],
127172
"id": "16907463855",
@@ -197,8 +242,36 @@
197242
"groups": [],
198243
"integrations": [],
199244
"projectId": "16931203314",
200-
"revision": "137",
245+
"revision": "139",
201246
"rollouts": [
247+
{
248+
"experiments": [
249+
{
250+
"audienceConditions": [],
251+
"audienceIds": [],
252+
"forcedVariations": {},
253+
"id": "default-rollout-496419-16935023792",
254+
"key": "default-rollout-496419-16935023792",
255+
"layerId": "rollout-496419-16935023792",
256+
"status": "Running",
257+
"trafficAllocation": [
258+
{
259+
"endOfRange": 10000,
260+
"entityId": "1579279"
261+
}
262+
],
263+
"variations": [
264+
{
265+
"featureEnabled": False,
266+
"id": "1579279",
267+
"key": "off",
268+
"variables": []
269+
}
270+
]
271+
}
272+
],
273+
"id": "rollout-496419-16935023792"
274+
},
202275
{
203276
"experiments": [
204277
{

tests/acceptance/test_acceptance/test_activate.py

Lines changed: 72 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ def test_activate__feature(session_obj, feature_key, expected_response,
154154
assert resp.status_code == expected_status_code, resp.text
155155

156156

157-
expected_activate_type_exper = """[
157+
expected_activate_type_exper_base = """[
158158
{
159159
"userId": "matjaz",
160160
"experimentKey": "feature_2_test",
@@ -173,7 +173,7 @@ def test_activate__feature(session_obj, feature_key, expected_response,
173173
}
174174
]"""
175175

176-
expected_activate_type_feat = """[
176+
expected_activate_type_feat_base = """[
177177
{
178178
"userId": "matjaz",
179179
"experimentKey": "feature_2_test",
@@ -232,8 +232,8 @@ def test_activate__feature(session_obj, feature_key, expected_response,
232232

233233

234234
@pytest.mark.parametrize("decision_type, expected_response, expected_status_code, bypass_validation_request", [
235-
("experiment", expected_activate_type_exper, 200, False),
236-
("feature", expected_activate_type_feat, 200, False),
235+
("experiment", expected_activate_type_exper_base, 200, False),
236+
("feature", expected_activate_type_feat_base, 200, False),
237237
("invalid decision type", {'error': 'type "invalid decision type" not supported'}, 400, True),
238238
("", {'error': 'type "" not supported'}, 400, True)
239239
], ids=["experiment decision type", "feature decision type", "invalid decision type", "empty decision type"])
@@ -255,10 +255,47 @@ def test_activate__type(session_obj, decision_type, expected_response,
255255
resp = create_and_validate_request_and_response(ENDPOINT_ACTIVATE, 'post', session_obj, bypass_validation_request,
256256
payload=payload, params=params)
257257

258-
if decision_type in ['experiment', 'feature']:
259-
sorted_actual = sort_response(
260-
resp.json(), 'experimentKey', 'featureKey')
261-
sorted_expected = sort_response(json.loads(expected_response), 'experimentKey', 'featureKey')
258+
if decision_type == 'experiment':
259+
# For experiment type, verify base experiments plus CMAB experiment
260+
actual_results = resp.json()
261+
expected_base = json.loads(expected_response)
262+
263+
# Check we have the expected count (2 base + 1 CMAB)
264+
assert len(actual_results) == 3, f"Expected 3 experiments, got {len(actual_results)}"
265+
266+
# Find and verify CMAB experiment
267+
cmab_result = next((r for r in actual_results if r['experimentKey'] == 'cmab-rule_1'), None)
268+
assert cmab_result is not None, "CMAB experiment not found"
269+
assert cmab_result['userId'] == 'matjaz'
270+
assert cmab_result['featureKey'] == ''
271+
assert cmab_result['variationKey'] in ['on', 'off'], f"Unexpected CMAB variation: {cmab_result['variationKey']}"
272+
assert cmab_result['type'] == 'experiment'
273+
274+
# Verify base experiments (excluding CMAB)
275+
base_results = [r for r in actual_results if r['experimentKey'] != 'cmab-rule_1']
276+
sorted_actual = sort_response(base_results, 'experimentKey', 'featureKey')
277+
sorted_expected = sort_response(expected_base, 'experimentKey', 'featureKey')
278+
assert sorted_actual == sorted_expected
279+
elif decision_type == 'feature':
280+
# For feature type, verify base features plus CMAB feature
281+
actual_results = resp.json()
282+
expected_base = json.loads(expected_response)
283+
284+
# Check we have the expected count (6 base + 1 CMAB)
285+
assert len(actual_results) == 7, f"Expected 7 features, got {len(actual_results)}"
286+
287+
# Find and verify CMAB feature
288+
cmab_result = next((r for r in actual_results if r['featureKey'] == 'cmab_flag'), None)
289+
assert cmab_result is not None, "CMAB feature not found"
290+
assert cmab_result['userId'] == 'matjaz'
291+
assert cmab_result['experimentKey'] == 'cmab-rule_1'
292+
assert cmab_result['variationKey'] in ['on', 'off'], f"Unexpected CMAB variation: {cmab_result['variationKey']}"
293+
assert cmab_result['type'] == 'feature'
294+
295+
# Verify base features (excluding CMAB)
296+
base_results = [r for r in actual_results if r['featureKey'] != 'cmab_flag']
297+
sorted_actual = sort_response(base_results, 'experimentKey', 'featureKey')
298+
sorted_expected = sort_response(expected_base, 'experimentKey', 'featureKey')
262299
assert sorted_actual == sorted_expected
263300
elif resp.json()['error']:
264301
with pytest.raises(requests.exceptions.HTTPError):
@@ -476,7 +513,7 @@ def test_activate__enabled(session_obj, enabled, experimentKey, featureKey,
476513
# #######################################################
477514

478515

479-
expected_activate_with_config = """[
516+
expected_activate_with_config_base = """[
480517
{
481518
"userId": "matjaz",
482519
"experimentKey": "ab_test1",
@@ -556,8 +593,8 @@ def test_activate_with_config(session_obj):
556593
validates against the whole response body.
557594
558595
In "activate"
559-
Request payload defines the who (user id and attributes)
560-
while the query parameters define the what (feature, experiment, etc)
596+
Request payload defines the "who" (user id and attributes)
597+
while the query parameters define the "what" (feature, experiment, etc)
561598
562599
Request parameter is a list of experiment keys or feature keys.
563600
If you want both add both and separate them with comma.
@@ -594,9 +631,29 @@ def test_activate_with_config(session_obj):
594631
resp_activate = create_and_validate_request_and_response(ENDPOINT_ACTIVATE, 'post', session_obj, payload=payload,
595632
params=params)
596633

597-
sorted_actual = sort_response(resp_activate.json(), 'experimentKey', 'featureKey')
598-
sorted_expected = sort_response(json.loads(expected_activate_with_config),
599-
'experimentKey',
600-
'featureKey')
634+
actual_results = resp_activate.json()
635+
expected_base = json.loads(expected_activate_with_config_base)
636+
637+
# Find CMAB entries (experiment and feature versions)
638+
cmab_experiment = next((r for r in actual_results if r.get('experimentKey') == 'cmab-rule_1' and r.get('featureKey') == ''), None)
639+
cmab_feature = next((r for r in actual_results if r.get('featureKey') == 'cmab_flag'), None)
640+
641+
# Verify CMAB experiment entry exists and is valid
642+
assert cmab_experiment is not None, "CMAB experiment not found"
643+
assert cmab_experiment['variationKey'] in ['on', 'off']
644+
assert cmab_experiment['type'] == 'experiment'
645+
646+
# Verify CMAB feature entry exists and is valid
647+
assert cmab_feature is not None, "CMAB feature not found"
648+
assert cmab_feature['experimentKey'] == 'cmab-rule_1'
649+
assert cmab_feature['variationKey'] in ['on', 'off']
650+
assert cmab_feature['type'] == 'feature'
651+
652+
# Verify base results (excluding CMAB entries)
653+
base_results = [r for r in actual_results if r.get('experimentKey') != 'cmab-rule_1' or r.get('featureKey') != '']
654+
base_results = [r for r in base_results if r.get('featureKey') != 'cmab_flag']
655+
656+
sorted_actual = sort_response(base_results, 'experimentKey', 'featureKey')
657+
sorted_expected = sort_response(expected_base, 'experimentKey', 'featureKey')
601658

602659
assert sorted_actual == sorted_expected

0 commit comments

Comments
 (0)