Skip to content

Commit 2cc80ed

Browse files
committed
add uit and acceptance tests
1 parent 943b08b commit 2cc80ed

4 files changed

Lines changed: 301 additions & 1 deletion

File tree

pkg/handlers/optimizely_config_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,24 @@ func (suite *OptimizelyConfigTestSuite) TestConfig() {
7777
suite.Equal(*suite.oc.GetOptimizelyConfig(), actual)
7878
}
7979

80+
func (suite *OptimizelyConfigTestSuite) TestConfigIncludesHoldouts() {
81+
req := httptest.NewRequest("GET", "/config", nil)
82+
rec := httptest.NewRecorder()
83+
suite.mux.ServeHTTP(rec, req)
84+
suite.Equal(http.StatusOK, rec.Code)
85+
86+
// Unmarshal response
87+
var actual config.OptimizelyConfig
88+
err := json.Unmarshal(rec.Body.Bytes(), &actual)
89+
suite.NoError(err)
90+
91+
// Verify holdouts field is present
92+
suite.NotNil(actual.Holdouts, "Holdouts field should be present in config")
93+
94+
// Verify it's an empty array (test datafile has no holdouts)
95+
suite.Empty(actual.Holdouts, "Holdouts should be empty for test datafile")
96+
}
97+
8098
// In order for 'go test' to run this suite, we need to create
8199
// a normal test function and pass our suite to suite.Run
82100
func TestOptimizelyConfigTestSuite(t *testing.T) {
Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
holdouts_datafile = {
2+
"accountId": "12133785640",
3+
"projectId": "6460519658291200",
4+
"revision": "12",
5+
"attributes": [
6+
{"id": "5502380200951808", "key": "all"},
7+
{"id": "5750214343000064", "key": "ho"}
8+
],
9+
"audiences": [
10+
{
11+
"name": "ho_3_aud",
12+
"conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]",
13+
"id": "5435551013142528"
14+
},
15+
{
16+
"name": "ho_6_aud",
17+
"conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]",
18+
"id": "5841838209236992"
19+
},
20+
{
21+
"name": "ho_4_aud",
22+
"conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]",
23+
"id": "6043616745881600"
24+
},
25+
{
26+
"name": "ho_5_aud",
27+
"conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]",
28+
"id": "6410995866796032"
29+
},
30+
{
31+
"id": "$opt_dummy_audience",
32+
"name": "Optimizely-Generated Audience for Backwards Compatibility",
33+
"conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]"
34+
}
35+
],
36+
"version": "4",
37+
"events": [
38+
{"id": "6554438379241472", "experimentIds": [], "key": "event1"}
39+
],
40+
"integrations": [],
41+
"holdouts": [
42+
{
43+
"id": "1673115",
44+
"key": "holdout_6",
45+
"status": "Running",
46+
"variations": [
47+
{"id": "$opt_dummy_variation_id", "key": "off", "featureEnabled": False, "variables": []}
48+
],
49+
"trafficAllocation": [
50+
{"entityId": "$opt_dummy_variation_id", "endOfRange": 4000}
51+
],
52+
"audienceIds": ["5841838209236992"],
53+
"audienceConditions": ["or", "5841838209236992"]
54+
},
55+
{
56+
"id": "1673114",
57+
"key": "holdout_5",
58+
"status": "Running",
59+
"variations": [
60+
{"id": "$opt_dummy_variation_id", "key": "off", "featureEnabled": False, "variables": []}
61+
],
62+
"trafficAllocation": [
63+
{"entityId": "$opt_dummy_variation_id", "endOfRange": 2000}
64+
],
65+
"audienceIds": ["6410995866796032"],
66+
"audienceConditions": ["or", "6410995866796032"]
67+
},
68+
{
69+
"id": "1673113",
70+
"key": "holdouts_4",
71+
"status": "Running",
72+
"variations": [
73+
{"id": "$opt_dummy_variation_id", "key": "off", "featureEnabled": False, "variables": []}
74+
],
75+
"trafficAllocation": [
76+
{"entityId": "$opt_dummy_variation_id", "endOfRange": 5000}
77+
],
78+
"audienceIds": ["6043616745881600"],
79+
"audienceConditions": ["or", "6043616745881600"]
80+
},
81+
{
82+
"id": "1673112",
83+
"key": "holdout_3",
84+
"status": "Running",
85+
"variations": [
86+
{"id": "$opt_dummy_variation_id", "key": "off", "featureEnabled": False, "variables": []}
87+
],
88+
"trafficAllocation": [
89+
{"entityId": "$opt_dummy_variation_id", "endOfRange": 1000}
90+
],
91+
"audienceIds": ["5435551013142528"],
92+
"audienceConditions": ["or", "5435551013142528"]
93+
}
94+
],
95+
"anonymizeIP": True,
96+
"botFiltering": False,
97+
"typedAudiences": [
98+
{
99+
"name": "ho_3_aud",
100+
"conditions": ["and", ["or", ["or", {"match": "exact", "name": "ho", "type": "custom_attribute", "value": 3}], ["or", {"match": "le", "name": "all", "type": "custom_attribute", "value": 3}]]],
101+
"id": "5435551013142528"
102+
},
103+
{
104+
"name": "ho_6_aud",
105+
"conditions": ["and", ["or", ["or", {"match": "exact", "name": "ho", "type": "custom_attribute", "value": 6}], ["or", {"match": "le", "name": "all", "type": "custom_attribute", "value": 6}]]],
106+
"id": "5841838209236992"
107+
},
108+
{
109+
"name": "ho_4_aud",
110+
"conditions": ["and", ["or", ["or", {"match": "exact", "name": "ho", "type": "custom_attribute", "value": 4}], ["or", {"match": "le", "name": "all", "type": "custom_attribute", "value": 4}]]],
111+
"id": "6043616745881600"
112+
},
113+
{
114+
"name": "ho_5_aud",
115+
"conditions": ["and", ["or", ["or", {"match": "exact", "name": "ho", "type": "custom_attribute", "value": 5}], ["or", {"match": "le", "name": "all", "type": "custom_attribute", "value": 5}]]],
116+
"id": "6410995866796032"
117+
}
118+
],
119+
"variables": [],
120+
"environmentKey": "production",
121+
"sdkKey": "BLsSFScP7tSY5SCYuKn8c",
122+
"featureFlags": [
123+
{"id": "497759", "key": "flag1", "rolloutId": "rollout-497759-631765411405174", "experimentIds": [], "variables": []},
124+
{"id": "497760", "key": "flag2", "rolloutId": "rollout-497760-631765411405174", "experimentIds": [], "variables": []}
125+
],
126+
"rollouts": [
127+
{
128+
"id": "rollout-497759-631765411405174",
129+
"experiments": [
130+
{
131+
"id": "default-rollout-497759-631765411405174",
132+
"key": "default-rollout-497759-631765411405174",
133+
"status": "Running",
134+
"layerId": "rollout-497759-631765411405174",
135+
"variations": [{"id": "1583341", "key": "variation_1", "featureEnabled": True, "variables": []}],
136+
"trafficAllocation": [{"entityId": "1583341", "endOfRange": 10000}],
137+
"forcedVariations": {},
138+
"audienceIds": [],
139+
"audienceConditions": []
140+
}
141+
]
142+
},
143+
{
144+
"id": "rollout-497760-631765411405174",
145+
"experiments": [
146+
{
147+
"id": "default-rollout-497760-631765411405174",
148+
"key": "default-rollout-497760-631765411405174",
149+
"status": "Running",
150+
"layerId": "rollout-497760-631765411405174",
151+
"variations": [{"id": "1583340", "key": "variation_2", "featureEnabled": True, "variables": []}],
152+
"trafficAllocation": [{"entityId": "1583340", "endOfRange": 10000}],
153+
"forcedVariations": {},
154+
"audienceIds": [],
155+
"audienceConditions": []
156+
}
157+
]
158+
}
159+
],
160+
"experiments": [],
161+
"groups": [],
162+
"region": "US"
163+
}

tests/acceptance/test_acceptance/conftest.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
# sdk key of the project "Agent Acceptance w ODP", under QA account
1010
sdk_key_odp = "91GuiKYH8ZF1hLLXR7DR1"
1111

12+
# sdk key for holdouts datafile
13+
sdk_key_holdouts = "BLsSFScP7tSY5SCYuKn8c"
14+
1215
@pytest.fixture
1316
def session_obj():
1417
"""
@@ -47,6 +50,17 @@ def session_override_sdk_key(session_obj):
4750
return session_obj
4851

4952

53+
@pytest.fixture(scope='function')
54+
def session_override_sdk_key_holdouts(session_obj):
55+
"""
56+
Override session_obj fixture with holdouts SDK key.
57+
:param session_obj: session fixture object
58+
:return: updated session object
59+
"""
60+
session_obj.headers['X-Optimizely-SDK-Key'] = sdk_key_holdouts
61+
return session_obj
62+
63+
5064
def pytest_addoption(parser):
5165
"""
5266
Adding CLI option to specify host URL to run tests on.

tests/acceptance/test_acceptance/test_config.py

Lines changed: 106 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from tests.acceptance.helpers import ENDPOINT_CONFIG
77
from tests.acceptance.helpers import create_and_validate_request_and_response
8+
from tests.acceptance.holdouts_datafile import holdouts_datafile
89

910
expected_config = """{
1011
"environmentKey": "production",
@@ -469,7 +470,8 @@
469470
"16910084756"
470471
]
471472
}
472-
]
473+
],
474+
"holdouts": []
473475
}"""
474476

475477

@@ -503,3 +505,106 @@ def test_config_403(session_override_sdk_key):
503505
'rechecking SDK key), status code: 403 Forbidden'
504506

505507
resp.raise_for_status()
508+
509+
510+
def test_config_includes_holdouts(session_obj):
511+
"""
512+
Test that the config endpoint includes the holdouts field.
513+
Validates the holdouts structure is present even if empty.
514+
:param session_obj: session object
515+
"""
516+
resp = create_and_validate_request_and_response(ENDPOINT_CONFIG, 'get', session_obj)
517+
518+
assert resp.status_code == 200
519+
resp.raise_for_status()
520+
521+
config = resp.json()
522+
523+
# Verify holdouts field exists
524+
assert 'holdouts' in config, "Config response should include 'holdouts' field"
525+
526+
# Verify it's a list
527+
assert isinstance(config['holdouts'], list), "Holdouts should be a list"
528+
529+
# Current datafile has no holdouts, so should be empty
530+
# When datafiles with holdouts are added, this test can be extended
531+
# to validate holdout structure (id, key, audiences, variationsMap)
532+
assert config['holdouts'] == [], "Current datafile should have no holdouts"
533+
534+
535+
def validate_holdout_structure(holdout):
536+
"""
537+
Helper function to validate a single holdout object structure.
538+
:param holdout: holdout object to validate
539+
"""
540+
# Verify required fields exist
541+
assert 'id' in holdout, "Holdout should have 'id' field"
542+
assert 'key' in holdout, "Holdout should have 'key' field"
543+
assert 'audiences' in holdout, "Holdout should have 'audiences' field"
544+
assert 'variationsMap' in holdout, "Holdout should have 'variationsMap' field"
545+
546+
# Verify field types
547+
assert isinstance(holdout['id'], str), "Holdout id should be a string"
548+
assert isinstance(holdout['key'], str), "Holdout key should be a string"
549+
assert isinstance(holdout['audiences'], str), "Holdout audiences should be a string"
550+
assert isinstance(holdout['variationsMap'], dict), "Holdout variationsMap should be a dict"
551+
552+
# Verify variationsMap contains valid variation objects
553+
for variation_key, variation in holdout['variationsMap'].items():
554+
assert isinstance(variation_key, str), "Variation key should be a string"
555+
assert 'id' in variation, "Variation should have 'id' field"
556+
assert 'key' in variation, "Variation should have 'key' field"
557+
assert 'featureEnabled' in variation, "Variation should have 'featureEnabled' field"
558+
assert 'variablesMap' in variation, "Variation should have 'variablesMap' field"
559+
560+
assert isinstance(variation['id'], str), "Variation id should be a string"
561+
assert isinstance(variation['key'], str), "Variation key should be a string"
562+
assert isinstance(variation['featureEnabled'], bool), "Variation featureEnabled should be a bool"
563+
assert isinstance(variation['variablesMap'], dict), "Variation variablesMap should be a dict"
564+
565+
566+
def test_config_with_holdouts(session_override_sdk_key_holdouts):
567+
"""
568+
Test that the config endpoint properly returns holdout data when the datafile contains holdouts.
569+
This test validates the full structure of holdouts including id, key, audiences, and variationsMap.
570+
:param session_override_sdk_key_holdouts: session object with holdouts SDK key
571+
"""
572+
resp = create_and_validate_request_and_response(ENDPOINT_CONFIG, 'get', session_override_sdk_key_holdouts)
573+
574+
assert resp.status_code == 200
575+
resp.raise_for_status()
576+
577+
config = resp.json()
578+
579+
# Verify holdouts field exists and is a list
580+
assert 'holdouts' in config, "Config response should include 'holdouts' field"
581+
assert isinstance(config['holdouts'], list), "Holdouts should be a list"
582+
583+
# Verify we have holdouts data (holdouts_datafile has 4 holdouts)
584+
assert len(config['holdouts']) == 4, f"Expected 4 holdouts, got {len(config['holdouts'])}"
585+
586+
# Validate each holdout structure
587+
for holdout in config['holdouts']:
588+
validate_holdout_structure(holdout)
589+
590+
# Verify specific holdout keys are present
591+
holdout_keys = {h['key'] for h in config['holdouts']}
592+
expected_keys = {'holdout_3', 'holdout_5', 'holdouts_4', 'holdout_6'}
593+
assert holdout_keys == expected_keys, f"Expected holdout keys {expected_keys}, got {holdout_keys}"
594+
595+
# Verify holdout IDs are present
596+
holdout_ids = {h['id'] for h in config['holdouts']}
597+
expected_ids = {'1673112', '1673113', '1673114', '1673115'}
598+
assert holdout_ids == expected_ids, f"Expected holdout IDs {expected_ids}, got {holdout_ids}"
599+
600+
# Verify each holdout has the dummy variation
601+
for holdout in config['holdouts']:
602+
assert 'off' in holdout['variationsMap'], f"Holdout {holdout['key']} should have 'off' variation"
603+
off_variation = holdout['variationsMap']['off']
604+
assert off_variation['id'] == '$opt_dummy_variation_id', "Off variation should have dummy ID"
605+
assert off_variation['featureEnabled'] is False, "Off variation should have featureEnabled=False"
606+
607+
# Verify audiences are properly formatted
608+
for holdout in config['holdouts']:
609+
# Audiences should be a non-empty string containing audience information
610+
assert len(holdout['audiences']) > 0, f"Holdout {holdout['key']} should have audiences"

0 commit comments

Comments
 (0)