Skip to content

Commit 72ac0b2

Browse files
Refactor comments for clarity and remove spec references in event ID normalization and holdout configuration tests
1 parent f79048b commit 72ac0b2

5 files changed

Lines changed: 28 additions & 48 deletions

File tree

optimizely/event/event_id_normalizer.py

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,20 @@
2222
string** (numeric like ``"12345"`` or opaque like ``"default-12345"`` /
2323
``"layer_abc"``). The fallback to ``experiment_id`` fires ONLY when the
2424
value is the empty string, ``None``, or missing. Non-string types are
25-
out of scope for this normalization path (per spec assumptions; the
26-
upstream datafile producer delivers string or null values).
25+
out of scope for this normalization path (the upstream datafile
26+
producer delivers string or null values).
2727
* ``variation_id`` retains the stricter contract: it MUST be a non-empty
2828
string of decimal digits ``0-9`` (leading zeros allowed). Empty,
2929
whitespace, non-string, and non-numeric inputs are normalized to
3030
``None`` so the wire payload carries an explicit null.
3131
* ``entity_id`` on impression events shares the campaign_id normalization
3232
and is therefore byte-equivalent to the normalized campaign_id for the
33-
same impression (FR-009).
33+
same impression.
3434
3535
The normalization path MUST NOT log, warn, or raise. It must never drop or
3636
defer event dispatch.
3737
"""
3838

39-
from __future__ import annotations
4039

4140
from sys import version_info
4241
from typing import Any, Optional
@@ -49,41 +48,33 @@
4948

5049
def is_non_empty_string(value: Any) -> TypeGuard[str]:
5150
"""Return ``True`` if ``value`` is a non-empty :class:`str`.
52-
53-
Used for ``campaign_id`` and ``entity_id`` validation per the relaxed
54-
FR-001 / FR-009 contract: any non-empty string is accepted regardless of
51+
Any non-empty string is accepted regardless of
5552
character content (IDs may be opaque, e.g. ``"default-12345"``).
5653
"""
5754
return isinstance(value, str) and value != ''
5855

5956

6057
def is_numeric_id_string(value: Any) -> TypeGuard[str]:
6158
"""Return ``True`` if ``value`` is a non-empty decimal-digit string.
62-
63-
Used for ``variation_id`` validation per FR-003 (the only field that
64-
retains the strict numeric-string contract). Whitespace, signs, decimal
65-
points, exponents, and non-string types all return ``False``. Leading
59+
Whitespace, signs, decimal points, exponents
60+
and non-string types all return ``False``. Leading
6661
zeros are accepted.
6762
"""
6863
if not isinstance(value, str):
6964
return False
7065
if value == '':
7166
return False
72-
# ``str.isdigit`` rejects everything except [0-9] characters and the
73-
# empty string. We've already excluded the empty case above. Note that
74-
# ``isdigit`` also accepts some non-ASCII digit code points; ``isascii``
75-
# combined with ``isdigit`` restricts us to plain decimal digits.
7667
return value.isascii() and value.isdigit()
7768

7869

7970
def normalize_campaign_id(campaign_id: Any, experiment_id: Any) -> str:
80-
"""Normalize a decision-event ``campaign_id`` (FR-001/FR-002, FR-009).
71+
"""Normalize a decision-event ``campaign_id``.
8172
8273
Returns ``campaign_id`` unchanged when it is a non-empty string (any
8374
character content — numeric like ``"12345"`` or opaque like
8475
``"default-12345"``). Otherwise falls back to ``experiment_id`` (when it
8576
is itself a non-empty string). If neither is a non-empty string, returns
86-
an empty string so the event still dispatches (FR-006).
77+
an empty string so the event still dispatches.
8778
"""
8879
if is_non_empty_string(campaign_id):
8980
return campaign_id
@@ -93,7 +84,7 @@ def normalize_campaign_id(campaign_id: Any, experiment_id: Any) -> str:
9384

9485

9586
def normalize_variation_id(variation_id: Any) -> Optional[str]:
96-
"""Normalize a decision-event ``variation_id`` (FR-003/FR-004).
87+
"""Normalize a decision-event ``variation_id``.
9788
9889
Returns the original value if it is a valid numeric ID string. Otherwise
9990
returns ``None`` so the event payload carries an explicit null for the

optimizely/project_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def __init__(self, datafile: str | bytes, logger: Logger, error_handler: Any):
122122
self.global_holdouts.append(holdout)
123123

124124
# Process local holdouts: every entry must carry 'includedRules' (list of rule IDs).
125-
# Entries without 'includedRules' are invalid per spec — log an error and exclude
125+
# Entries without 'includedRules' are invalid — log an error and exclude
126126
# them from evaluation (do NOT fall back to global application).
127127
for holdout_data in local_holdouts_data:
128128
if 'includedRules' not in holdout_data or holdout_data.get('includedRules') is None:

tests/test_event_factory.py

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1244,7 +1244,7 @@ class EventFactoryIdNormalizationIntegrationTest(base.BaseTest):
12441244
12451245
These tests build real ``ImpressionEvent`` instances using crafted
12461246
Experiment/Variation objects, then call ``EventFactory.create_log_event``
1247-
and inspect the dispatched payload. They exercise FR-001..FR-009.
1247+
and inspect the dispatched payload.
12481248
"""
12491249

12501250
def setUp(self, *args, **kwargs):
@@ -1307,15 +1307,13 @@ def _dispatched_decision(self, impression_event):
13071307
snapshot = log_event.params['visitors'][0]['snapshots'][0]
13081308
return snapshot['decisions'][0], snapshot['events'][0]
13091309

1310-
# ------------------------------------------------------------------ FR-001
13111310
def test_valid_campaign_id_is_passed_through(self):
13121311
impression = self._build_impression('111127', '111182', '111129')
13131312
decision, event = self._dispatched_decision(impression)
13141313
self.assertEqual('111182', decision['campaign_id'])
1315-
# FR-009: entity_id mirrors campaign_id byte-for-byte.
1314+
# entity_id mirrors campaign_id byte-for-byte.
13161315
self.assertEqual(decision['campaign_id'], event['entity_id'])
13171316

1318-
# ------------------------------------------------------------------ FR-002
13191317
def test_empty_campaign_id_falls_back_to_experiment_id(self):
13201318
impression = self._build_impression('111127', '', '111129')
13211319
decision, event = self._dispatched_decision(impression)
@@ -1343,13 +1341,11 @@ def test_whitespace_campaign_id_passes_through(self):
13431341
self.assertEqual(' ', decision['campaign_id'])
13441342
self.assertEqual(' ', event['entity_id'])
13451343

1346-
# ------------------------------------------------------------------ FR-003
13471344
def test_valid_variation_id_is_passed_through(self):
13481345
impression = self._build_impression('111127', '111182', '111129')
13491346
decision, _ = self._dispatched_decision(impression)
13501347
self.assertEqual('111129', decision['variation_id'])
13511348

1352-
# ------------------------------------------------------------------ FR-004
13531349
def test_empty_variation_id_becomes_none(self):
13541350
impression = self._build_impression('111127', '111182', '')
13551351
decision, _ = self._dispatched_decision(impression)
@@ -1365,7 +1361,6 @@ def test_whitespace_variation_id_becomes_none(self):
13651361
decision, _ = self._dispatched_decision(impression)
13661362
self.assertIsNone(decision['variation_id'])
13671363

1368-
# ------------------------------------------------------------------ FR-005
13691364
def test_normalization_applies_to_rollout_decisions(self):
13701365
impression = self._build_impression(
13711366
'111127', '', 'bad_var', rule_type='rollout'
@@ -1403,9 +1398,8 @@ def test_holdout_with_opaque_layer_id_passes_through(self):
14031398
self.assertEqual('default-12345', decision['campaign_id'])
14041399
self.assertEqual('default-12345', event['entity_id'])
14051400

1406-
# ------------------------------------------------------------------ FR-006
14071401
def test_event_still_dispatches_when_all_ids_invalid(self):
1408-
"""FR-006: never drop / fail dispatch."""
1402+
"""Event must still dispatch even when all IDs are invalid."""
14091403
impression = self._build_impression('', '', '')
14101404
log_event = EventFactory.create_log_event(impression, self.logger)
14111405
self.assertIsNotNone(log_event)
@@ -1416,9 +1410,8 @@ def test_event_still_dispatches_when_all_ids_invalid(self):
14161410
self.assertEqual('', event['entity_id'])
14171411
self.assertIsNone(decision['variation_id'])
14181412

1419-
# ------------------------------------------------------------------ FR-009
14201413
def test_entity_id_equals_campaign_id_byte_for_byte(self):
1421-
"""FR-009: ``events[].entity_id`` must equal ``decisions[].campaign_id``."""
1414+
"""``events[].entity_id`` must equal ``decisions[].campaign_id``."""
14221415
for layer_id, exp_id, expected in [
14231416
('111182', '111127', '111182'), # numeric campaign_id wins
14241417
('', '111127', '111127'), # empty falls back to experiment_id
@@ -1433,11 +1426,8 @@ def test_entity_id_equals_campaign_id_byte_for_byte(self):
14331426
self.assertEqual(expected, decision['campaign_id'])
14341427
self.assertEqual(decision['campaign_id'], event['entity_id'])
14351428

1436-
# ----------------------------------------------------------------- FR-010
14371429
def test_conversion_event_entity_id_unchanged(self):
1438-
"""FR-010: conversion events derive entity_id from event.id, not the
1439-
normalizer.
1440-
"""
1430+
"""Conversion events derive entity_id from event.id, not the normalizer."""
14411431
from optimizely.event.user_event_factory import UserEventFactory
14421432

14431433
with mock.patch('time.time', return_value=42.123), mock.patch(

tests/test_event_id_normalizer.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020

2121
class IsNonEmptyStringTest(unittest.TestCase):
22-
"""Cover :func:`event_id_normalizer.is_non_empty_string` (FR-001/FR-009).
22+
"""Cover :func:`event_id_normalizer.is_non_empty_string`.
2323
2424
Any non-empty string is valid for ``campaign_id`` / ``entity_id`` — IDs
2525
may be numeric like ``"12345"`` or opaque like ``"default-12345"``.
@@ -35,8 +35,8 @@ def test_returns_true_for_opaque_string(self):
3535
self.assertTrue(event_id_normalizer.is_non_empty_string('abc'))
3636

3737
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.
38+
# Whitespace is a non-empty string and so is accepted;
39+
# character-content validation is deferred upstream.
4040
self.assertTrue(event_id_normalizer.is_non_empty_string(' '))
4141

4242
def test_returns_false_for_empty_string(self):
@@ -46,8 +46,7 @@ def test_returns_false_for_none(self):
4646
self.assertFalse(event_id_normalizer.is_non_empty_string(None))
4747

4848
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.
49+
# Non-string types are rejected so the fallback path fires.
5150
self.assertFalse(event_id_normalizer.is_non_empty_string(12345))
5251
self.assertFalse(event_id_normalizer.is_non_empty_string(123.0))
5352
self.assertFalse(event_id_normalizer.is_non_empty_string(True))
@@ -58,7 +57,7 @@ def test_returns_false_for_non_string_types(self):
5857
class IsNumericIdStringTest(unittest.TestCase):
5958
"""Cover :func:`event_id_normalizer.is_numeric_id_string` edge cases.
6059
61-
Used only for ``variation_id`` (FR-003), which retains the strict
60+
Used only for ``variation_id``, which retains the strict
6261
decimal-digit contract.
6362
"""
6463

@@ -70,7 +69,7 @@ def test_returns_true_for_single_digit(self):
7069
self.assertTrue(event_id_normalizer.is_numeric_id_string('9'))
7170

7271
def test_returns_true_for_leading_zeros(self):
73-
# FR-003 explicitly allows leading zeros.
72+
# Leading zeros are explicitly allowed.
7473
self.assertTrue(event_id_normalizer.is_numeric_id_string('007'))
7574
self.assertTrue(event_id_normalizer.is_numeric_id_string('00000'))
7675

@@ -81,7 +80,7 @@ def test_returns_false_for_none(self):
8180
self.assertFalse(event_id_normalizer.is_numeric_id_string(None))
8281

8382
def test_returns_false_for_int(self):
84-
# FR-003 requires the value to be a string.
83+
# The value must be a string.
8584
self.assertFalse(event_id_normalizer.is_numeric_id_string(12345))
8685
self.assertFalse(event_id_normalizer.is_numeric_id_string(0))
8786

@@ -130,10 +129,10 @@ def test_returns_false_for_collections(self):
130129

131130

132131
class NormalizeCampaignIdTest(unittest.TestCase):
133-
"""Cover :func:`event_id_normalizer.normalize_campaign_id` per FR-001/002, FR-009.
132+
"""Cover :func:`event_id_normalizer.normalize_campaign_id`.
134133
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.
134+
Any non-empty string is valid for campaign_id — fallback to
135+
``experiment_id`` fires only on empty/None/missing.
137136
"""
138137

139138
def test_returns_campaign_id_when_numeric(self):
@@ -180,7 +179,7 @@ def test_falls_back_to_opaque_experiment_id(self):
180179
)
181180

182181
def test_returns_empty_string_when_both_empty_or_none(self):
183-
# Do not drop / fail dispatch (FR-006); return ''.
182+
# Do not drop / fail dispatch; return ''.
184183
self.assertEqual('', event_id_normalizer.normalize_campaign_id(None, None))
185184
self.assertEqual('', event_id_normalizer.normalize_campaign_id('', ''))
186185
self.assertEqual('', event_id_normalizer.normalize_campaign_id(None, ''))
@@ -193,7 +192,7 @@ def test_preserves_leading_zeros(self):
193192

194193

195194
class NormalizeVariationIdTest(unittest.TestCase):
196-
"""Cover :func:`event_id_normalizer.normalize_variation_id` per FR-003/004.
195+
"""Cover :func:`event_id_normalizer.normalize_variation_id`.
197196
198197
``variation_id`` retains the strict numeric-string contract.
199198
"""

tests/test_holdout_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ def test_local_holdouts_section_entries_excluded_from_global_list(self):
471471
self.assertEqual(config.get_global_holdouts(), [])
472472

473473
def test_local_holdouts_missing_included_rules_logged_and_excluded(self):
474-
"""Entries in 'localHoldouts' without 'includedRules' are invalid per spec.
474+
"""Entries in 'localHoldouts' without 'includedRules' are invalid.
475475
476476
SDK must log an error and exclude the entry from evaluation. It must NOT
477477
fall back to global application (the partition between sections is hard).

0 commit comments

Comments
 (0)