Skip to content

Commit 90f3bf6

Browse files
authored
Merge pull request #52 from launchdarkly/eb/events-for-bad-users
send as much of a feature event as possible even if user is invalid
2 parents d102924 + 1243778 commit 90f3bf6

File tree

2 files changed

+93
-38
lines changed

2 files changed

+93
-38
lines changed

ldclient/client.py

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,8 @@ def toggle(self, key, user, default):
139139

140140
def variation(self, key, user, default):
141141
default = self._config.get_default(key, default)
142-
self._sanitize_user(user)
142+
if user is not None:
143+
self._sanitize_user(user)
143144

144145
if self._config.offline:
145146
return default
@@ -158,12 +159,7 @@ def send_event(value, version=None):
158159
send_event(default)
159160
return default
160161

161-
if user is None or user.get('key') is None:
162-
log.warn("Missing user or user key when evaluating Feature Flag key: " + key + ". Returning default.")
163-
send_event(default)
164-
return default
165-
166-
if user.get('key', "") == "":
162+
if user is not None and user.get('key', "") == "":
167163
log.warn("User key is blank. Flag evaluation will proceed, but the user will not be stored in LaunchDarkly.")
168164

169165
def cb(flag):
@@ -177,6 +173,7 @@ def cb(flag):
177173

178174
except Exception as e:
179175
log.error("Exception caught in variation: " + e.message + " for flag key: " + key + " and user: " + str(user))
176+
send_event(default)
180177

181178
return default
182179

@@ -186,13 +183,19 @@ def _evaluate(self, flag, user):
186183
return evaluate(flag, user, self._store)
187184

188185
def _evaluate_and_send_events(self, flag, user, default):
189-
result = evaluate(flag, user, self._store)
190-
for event in result.events or []:
191-
self._send_event(event)
192-
193-
value = default if result.value is None else result.value
186+
if user is None or user.get('key') is None:
187+
log.warn("Missing user or user key when evaluating Feature Flag key: " + flag.get('key') + ". Returning default.")
188+
value = default
189+
variation = None
190+
else:
191+
result = evaluate(flag, user, self._store)
192+
for event in result.events or []:
193+
self._send_event(event)
194+
value = default if result.value is None else result.value
195+
variation = result.variation
196+
194197
self._send_event({'kind': 'feature', 'key': flag.get('key'),
195-
'user': user, 'variation': result.variation, 'value': value,
198+
'user': user, 'variation': variation, 'value': value,
196199
'default': default, 'version': flag.get('version'),
197200
'trackEvents': flag.get('trackEvents'),
198201
'debugEventsUntilDate': flag.get('debugEventsUntilDate')})

testing/test_ldclient.py

Lines changed: 77 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,14 @@ def setup_function(function):
8888
}
8989

9090

91+
def make_client(store):
92+
return LDClient(config=Config(sdk_key = 'SDK_KEY',
93+
base_uri="http://localhost:3000",
94+
event_processor_class=MockEventProcessor,
95+
update_processor_class=MockUpdateProcessor,
96+
feature_store=store))
97+
98+
9199
def get_first_event(c):
92100
return c._event_processor._events.pop(0)
93101

@@ -174,25 +182,6 @@ def test_defaults_and_online_no_default():
174182
assert e['kind'] == 'feature' and e['key'] == u'baz' and e['user'] == user
175183

176184

177-
def test_exception_in_retrieval():
178-
class ExceptionFeatureRequester(FeatureRequester):
179-
def __init__(self, *_):
180-
pass
181-
182-
def get_all(self):
183-
raise Exception("blah")
184-
185-
client = LDClient(config=Config(base_uri="http://localhost:3000",
186-
defaults={"foo": "bar"},
187-
feature_store=InMemoryFeatureStore(),
188-
feature_requester_class=ExceptionFeatureRequester,
189-
event_processor_class=MockEventProcessor,
190-
update_processor_class=MockUpdateProcessor))
191-
assert "bar" == client.variation('foo', user, default="jim")
192-
e = get_first_event(client)
193-
assert e['kind'] == 'feature' and e['key'] == u'foo' and e['user'] == user
194-
195-
196185
def test_no_defaults():
197186
assert "bar" == offline_client.variation('foo', user, default="bar")
198187

@@ -205,25 +194,88 @@ def test_event_for_existing_feature():
205194
u'variations': ['a', 'b'],
206195
u'fallthrough': {
207196
u'variation': 1
208-
}
197+
},
198+
u'trackEvents': True
209199
}
210200
store = InMemoryFeatureStore()
211201
store.init({FEATURES: {'feature.key': feature}})
212-
client = LDClient(config=Config(sdk_key = 'SDK_KEY',
213-
base_uri="http://localhost:3000",
214-
event_processor_class=MockEventProcessor,
215-
update_processor_class=MockUpdateProcessor,
216-
feature_store=store))
202+
client = make_client(store)
217203
assert 'b' == client.variation('feature.key', user, default='c')
218204
e = get_first_event(client)
219205
assert (e['kind'] == 'feature' and
220206
e['key'] == 'feature.key' and
221207
e['user'] == user and
222208
e['value'] == 'b' and
223209
e['variation'] == 1 and
210+
e['default'] == 'c' and
211+
e['trackEvents'] == True)
212+
213+
214+
def test_event_for_unknown_feature():
215+
store = InMemoryFeatureStore()
216+
store.init({FEATURES: {}})
217+
client = make_client(store)
218+
assert 'c' == client.variation('feature.key', user, default='c')
219+
e = get_first_event(client)
220+
assert (e['kind'] == 'feature' and
221+
e['key'] == 'feature.key' and
222+
e['user'] == user and
223+
e['value'] == 'c' and
224+
e['variation'] == None and
224225
e['default'] == 'c')
225226

226227

228+
def test_event_for_existing_feature_with_no_user():
229+
feature = {
230+
u'key': u'feature.key',
231+
u'salt': u'abc',
232+
u'on': True,
233+
u'variations': ['a', 'b'],
234+
u'fallthrough': {
235+
u'variation': 1
236+
},
237+
u'trackEvents': True
238+
}
239+
store = InMemoryFeatureStore()
240+
store.init({FEATURES: {'feature.key': feature}})
241+
client = make_client(store)
242+
assert 'c' == client.variation('feature.key', None, default='c')
243+
e = get_first_event(client)
244+
assert (e['kind'] == 'feature' and
245+
e['key'] == 'feature.key' and
246+
e['user'] == None and
247+
e['value'] == 'c' and
248+
e['variation'] == None and
249+
e['default'] == 'c' and
250+
e['trackEvents'] == True)
251+
252+
253+
def test_event_for_existing_feature_with_no_user_key():
254+
feature = {
255+
u'key': u'feature.key',
256+
u'salt': u'abc',
257+
u'on': True,
258+
u'variations': ['a', 'b'],
259+
u'fallthrough': {
260+
u'variation': 1
261+
},
262+
u'trackEvents': True
263+
}
264+
store = InMemoryFeatureStore()
265+
store.init({FEATURES: {'feature.key': feature}})
266+
client = make_client(store)
267+
bad_user = { u'name': u'Bob' }
268+
assert 'c' == client.variation('feature.key', bad_user, default='c')
269+
e = get_first_event(client)
270+
assert (e['kind'] == 'feature' and
271+
e['key'] == 'feature.key' and
272+
e['user'] == bad_user and
273+
e['value'] == 'c' and
274+
e['variation'] == None and
275+
e['default'] == 'c' and
276+
e['trackEvents'] == True)
277+
278+
227279
def test_all_flags():
228280
feature = {
229281
u'key': u'feature.key',

0 commit comments

Comments
 (0)