1818from optimizely .event import event_id_normalizer
1919
2020
21+ class IsNonEmptyStringTest (unittest .TestCase ):
22+ """Cover :func:`event_id_normalizer.is_non_empty_string` (FR-001/FR-009).
23+
24+ Any non-empty string is valid for ``campaign_id`` / ``entity_id`` — IDs
25+ may be numeric like ``"12345"`` or opaque like ``"default-12345"``.
26+ """
27+
28+ def test_returns_true_for_numeric_string (self ):
29+ self .assertTrue (event_id_normalizer .is_non_empty_string ('12345' ))
30+
31+ def test_returns_true_for_opaque_string (self ):
32+ # Opaque IDs are explicitly valid for campaign_id / entity_id.
33+ self .assertTrue (event_id_normalizer .is_non_empty_string ('default-12345' ))
34+ self .assertTrue (event_id_normalizer .is_non_empty_string ('layer_abc' ))
35+ self .assertTrue (event_id_normalizer .is_non_empty_string ('abc' ))
36+
37+ def test_returns_true_for_whitespace_string (self ):
38+ # Whitespace is a non-empty string and so is accepted; the spec
39+ # explicitly defers any character-content validation upstream.
40+ self .assertTrue (event_id_normalizer .is_non_empty_string (' ' ))
41+
42+ def test_returns_false_for_empty_string (self ):
43+ self .assertFalse (event_id_normalizer .is_non_empty_string ('' ))
44+
45+ def test_returns_false_for_none (self ):
46+ self .assertFalse (event_id_normalizer .is_non_empty_string (None ))
47+
48+ def test_returns_false_for_non_string_types (self ):
49+ # Non-string types are out of scope per the spec assumptions; the
50+ # predicate rejects them so the fallback path fires.
51+ self .assertFalse (event_id_normalizer .is_non_empty_string (12345 ))
52+ self .assertFalse (event_id_normalizer .is_non_empty_string (123.0 ))
53+ self .assertFalse (event_id_normalizer .is_non_empty_string (True ))
54+ self .assertFalse (event_id_normalizer .is_non_empty_string (['123' ]))
55+ self .assertFalse (event_id_normalizer .is_non_empty_string ({'id' : '123' }))
56+
57+
2158class IsNumericIdStringTest (unittest .TestCase ):
22- """Cover :func:`event_id_normalizer.is_numeric_id_string` edge cases."""
59+ """Cover :func:`event_id_normalizer.is_numeric_id_string` edge cases.
60+
61+ Used only for ``variation_id`` (FR-003), which retains the strict
62+ decimal-digit contract.
63+ """
2364
2465 def test_returns_true_for_decimal_digit_string (self ):
2566 self .assertTrue (event_id_normalizer .is_numeric_id_string ('12345' ))
@@ -29,7 +70,7 @@ def test_returns_true_for_single_digit(self):
2970 self .assertTrue (event_id_normalizer .is_numeric_id_string ('9' ))
3071
3172 def test_returns_true_for_leading_zeros (self ):
32- # FR-001 explicitly allows leading zeros.
73+ # FR-003 explicitly allows leading zeros.
3374 self .assertTrue (event_id_normalizer .is_numeric_id_string ('007' ))
3475 self .assertTrue (event_id_normalizer .is_numeric_id_string ('00000' ))
3576
@@ -40,7 +81,7 @@ def test_returns_false_for_none(self):
4081 self .assertFalse (event_id_normalizer .is_numeric_id_string (None ))
4182
4283 def test_returns_false_for_int (self ):
43- # FR-001 requires the value to be a string.
84+ # FR-003 requires the value to be a string.
4485 self .assertFalse (event_id_normalizer .is_numeric_id_string (12345 ))
4586 self .assertFalse (event_id_normalizer .is_numeric_id_string (0 ))
4687
@@ -89,50 +130,60 @@ def test_returns_false_for_collections(self):
89130
90131
91132class NormalizeCampaignIdTest (unittest .TestCase ):
92- """Cover :func:`event_id_normalizer.normalize_campaign_id` per FR-001/002, FR-009."""
133+ """Cover :func:`event_id_normalizer.normalize_campaign_id` per FR-001/002, FR-009.
134+
135+ Per the relaxed spec, any non-empty string is valid for campaign_id —
136+ fallback to ``experiment_id`` fires only on empty/None/missing.
137+ """
93138
94- def test_returns_campaign_id_when_valid (self ):
139+ def test_returns_campaign_id_when_numeric (self ):
95140 self .assertEqual (
96141 '111182' ,
97142 event_id_normalizer .normalize_campaign_id ('111182' , '111127' ),
98143 )
99144
100- def test_falls_back_to_experiment_id_when_campaign_id_empty (self ):
145+ def test_returns_campaign_id_when_opaque_string (self ):
146+ # FSSDK-12813: opaque IDs (e.g. holdout layer IDs) pass through.
101147 self .assertEqual (
102- '111127' ,
103- event_id_normalizer .normalize_campaign_id ('' , '111127' ),
148+ 'default-12345' ,
149+ event_id_normalizer .normalize_campaign_id ('default-12345' , '111127' ),
150+ )
151+ self .assertEqual (
152+ 'layer_abc' ,
153+ event_id_normalizer .normalize_campaign_id ('layer_abc' , '111127' ),
104154 )
105155
106- def test_falls_back_to_experiment_id_when_campaign_id_none (self ):
156+ def test_returns_campaign_id_when_whitespace_string (self ):
157+ # Whitespace is non-empty; passes through (validation deferred upstream).
107158 self .assertEqual (
108- '111127 ' ,
109- event_id_normalizer .normalize_campaign_id (None , '111127' ),
159+ ' ' ,
160+ event_id_normalizer .normalize_campaign_id (' ' , '111127' ),
110161 )
111162
112- def test_falls_back_to_experiment_id_when_campaign_id_non_numeric (self ):
163+ def test_falls_back_to_experiment_id_when_campaign_id_empty (self ):
113164 self .assertEqual (
114165 '111127' ,
115- event_id_normalizer .normalize_campaign_id ('abc ' , '111127' ),
166+ event_id_normalizer .normalize_campaign_id ('' , '111127' ),
116167 )
117168
118- def test_falls_back_to_experiment_id_when_campaign_id_whitespace (self ):
169+ def test_falls_back_to_experiment_id_when_campaign_id_none (self ):
119170 self .assertEqual (
120171 '111127' ,
121- event_id_normalizer .normalize_campaign_id (' ' , '111127' ),
172+ event_id_normalizer .normalize_campaign_id (None , '111127' ),
122173 )
123174
124- def test_falls_back_to_experiment_id_when_campaign_id_int (self ):
125- # An int input is invalid (FR-001 requires a string) .
175+ def test_falls_back_to_opaque_experiment_id (self ):
176+ # Both fields may be opaque non-numeric strings .
126177 self .assertEqual (
127- '111127 ' ,
128- event_id_normalizer .normalize_campaign_id (111182 , '111127 ' ),
178+ 'exp_42 ' ,
179+ event_id_normalizer .normalize_campaign_id ('' , 'exp_42 ' ),
129180 )
130181
131- def test_returns_empty_string_when_both_invalid (self ):
182+ def test_returns_empty_string_when_both_empty_or_none (self ):
132183 # Do not drop / fail dispatch (FR-006); return ''.
133184 self .assertEqual ('' , event_id_normalizer .normalize_campaign_id (None , None ))
134185 self .assertEqual ('' , event_id_normalizer .normalize_campaign_id ('' , '' ))
135- self .assertEqual ('' , event_id_normalizer .normalize_campaign_id ('abc' , 'xyz ' ))
186+ self .assertEqual ('' , event_id_normalizer .normalize_campaign_id (None , '' ))
136187
137188 def test_preserves_leading_zeros (self ):
138189 self .assertEqual (
@@ -142,7 +193,10 @@ def test_preserves_leading_zeros(self):
142193
143194
144195class NormalizeVariationIdTest (unittest .TestCase ):
145- """Cover :func:`event_id_normalizer.normalize_variation_id` per FR-003/004."""
196+ """Cover :func:`event_id_normalizer.normalize_variation_id` per FR-003/004.
197+
198+ ``variation_id`` retains the strict numeric-string contract.
199+ """
146200
147201 def test_returns_variation_id_when_valid (self ):
148202 self .assertEqual (
@@ -176,17 +230,5 @@ def test_preserves_leading_zeros(self):
176230 self .assertEqual ('007' , event_id_normalizer .normalize_variation_id ('007' ))
177231
178232
179- class NormalizeStringIdTest (unittest .TestCase ):
180- """Cover the generic :func:`event_id_normalizer.normalize_string_id` helper."""
181-
182- def test_returns_value_when_valid (self ):
183- self .assertEqual ('42' , event_id_normalizer .normalize_string_id ('42' ))
184-
185- def test_returns_none_when_invalid (self ):
186- self .assertIsNone (event_id_normalizer .normalize_string_id ('' ))
187- self .assertIsNone (event_id_normalizer .normalize_string_id (None ))
188- self .assertIsNone (event_id_normalizer .normalize_string_id ('xx' ))
189-
190-
191233if __name__ == '__main__' :
192234 unittest .main ()
0 commit comments