Skip to content

Commit a94ed9c

Browse files
authored
fix: Fix an issue with rollouts with a missing attribute. (#153)
When a rollout had a seed, and the context being evaluated was missing the attribute referenced by `bucketBy`, then the `null` atom would attempt to be used as a binary. When the attribute is `null`, then the bucket value should always be `0`. This doesn't affect the experimentation logic related to presence of the context itself. Fixes: #152
1 parent 878607e commit a94ed9c

File tree

2 files changed

+83
-3
lines changed

2 files changed

+83
-3
lines changed

src/ldclient_rollout.erl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,8 @@ match_weighted_variations(Bucket, [#{weight := Weight}|Rest], Sum) ->
147147
FlagKey :: ldclient_flag:key(),
148148
Salt :: binary(),
149149
ContextValue :: binary() | null) -> float().
150-
bucket_context_value(null, _FlagKey, _Salt, null) -> 0.0;
150+
151+
bucket_context_value(_Seed, _FlagKey, _Salt, null) -> 0.0;
151152
%% when no seed is present hash with `key.salt.attribute`
152153
bucket_context_value(null, FlagKey, Salt, ContextValue) ->
153154
bucket_hash(<<FlagKey/binary, $., Salt/binary, $., ContextValue/binary>>);

test/ldclient_rollout_SUITE.erl

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
can_rollout_experiment/1,
1616
untracked_variation_for_experiment_rollout_is_not_in_experiment/1,
1717
missing_kind_is_not_in_experiment/1,
18-
experiment_rollouts_do_not_use_bucket_by/1
18+
experiment_rollouts_do_not_use_bucket_by/1,
19+
missing_bucket_bucket_by_does_not_error_with_seed/1,
20+
bucket_by_null_attribute_does_not_error_with_seed/1,
21+
missing_bucket_bucket_by_does_not_error_without_seed/1,
22+
bucket_by_null_attribute_does_not_error_without_seed/1,
23+
missing_kind_is_not_in_experiment_with_seed/1
1924
]).
2025

2126
%%====================================================================
@@ -28,7 +33,12 @@ all() ->
2833
can_rollout_experiment,
2934
untracked_variation_for_experiment_rollout_is_not_in_experiment,
3035
missing_kind_is_not_in_experiment,
31-
experiment_rollouts_do_not_use_bucket_by
36+
experiment_rollouts_do_not_use_bucket_by,
37+
missing_bucket_bucket_by_does_not_error_with_seed,
38+
bucket_by_null_attribute_does_not_error_with_seed,
39+
missing_bucket_bucket_by_does_not_error_without_seed,
40+
bucket_by_null_attribute_does_not_error_without_seed,
41+
missing_kind_is_not_in_experiment_with_seed
3242
].
3343

3444
init_per_suite(Config) ->
@@ -87,6 +97,17 @@ missing_kind_is_not_in_experiment(_) ->
8797
#{key => <<"flagKey">>, salt => <<"flagSalt">>},
8898
ldclient_context:new(<<"user-key">>)).
8999

100+
missing_kind_is_not_in_experiment_with_seed(_) ->
101+
{0, false} = ldclient_rollout:rollout_context(
102+
ldclient_rollout:new(#{
103+
<<"kind">> => <<"experiment">>,
104+
<<"contextKind">> => <<"org">>,
105+
<<"seed">> => 1234567890,
106+
<<"variations">> => [#{<<"variation">> => 0, <<"weight">> => 100000, <<"untracked">> => false}]
107+
}),
108+
#{key => <<"flagKey">>, salt => <<"flagSalt">>},
109+
ldclient_context:new(<<"user-key">>)).
110+
90111
experiment_rollouts_do_not_use_bucket_by(_) ->
91112
{1, true} = ldclient_rollout:rollout_context(
92113
ldclient_rollout:new(#{
@@ -116,3 +137,61 @@ experiment_rollouts_do_not_use_bucket_by(_) ->
116137
}),
117138
#{key => <<"flagKey">>, salt => <<"flagSalt">>},
118139
ldclient_context:set(<<"decoy">>, <<"valueZZZ">>, ldclient_context:new(<<"org-key">>, <<"org">>))).
140+
141+
missing_bucket_bucket_by_does_not_error_with_seed(_) ->
142+
{0, false} = ldclient_rollout:rollout_context(
143+
ldclient_rollout:new(#{
144+
<<"kind">> => <<"rollout">>,
145+
<<"contextKind">> => <<"user">>,
146+
<<"bucketBy">> => <<"missing">>,
147+
<<"seed">> => 1234567890,
148+
<<"variations">> => [
149+
#{<<"variation">> => 0, <<"weight">> => 50000, <<"untracked">> => false},
150+
#{<<"variation">> => 1, <<"weight">> => 50000, <<"untracked">> => false}
151+
]
152+
}),
153+
#{key => <<"flagKey">>, salt => <<"flagSalt">>},
154+
ldclient_context:new(<<"user-key">>)).
155+
156+
bucket_by_null_attribute_does_not_error_with_seed(_) ->
157+
{0, false} = ldclient_rollout:rollout_context(
158+
ldclient_rollout:new(#{
159+
<<"kind">> => <<"rollout">>,
160+
<<"contextKind">> => <<"user">>,
161+
<<"bucketBy">> => <<"null">>,
162+
<<"seed">> => 1234567890,
163+
<<"variations">> => [
164+
#{<<"variation">> => 0, <<"weight">> => 50000, <<"untracked">> => false},
165+
#{<<"variation">> => 1, <<"weight">> => 50000, <<"untracked">> => false}
166+
]
167+
}),
168+
#{key => <<"flagKey">>, salt => <<"flagSalt">>},
169+
ldclient_context:set(<<"null">>, null, ldclient_context:new(<<"user-key">>))).
170+
171+
missing_bucket_bucket_by_does_not_error_without_seed(_) ->
172+
{0, false} = ldclient_rollout:rollout_context(
173+
ldclient_rollout:new(#{
174+
<<"kind">> => <<"rollout">>,
175+
<<"contextKind">> => <<"user">>,
176+
<<"bucketBy">> => <<"missing">>,
177+
<<"variations">> => [
178+
#{<<"variation">> => 0, <<"weight">> => 50000, <<"untracked">> => false},
179+
#{<<"variation">> => 1, <<"weight">> => 50000, <<"untracked">> => false}
180+
]
181+
}),
182+
#{key => <<"flagKey">>, salt => <<"flagSalt">>},
183+
ldclient_context:new(<<"user-key">>)).
184+
185+
bucket_by_null_attribute_does_not_error_without_seed(_) ->
186+
{0, false} = ldclient_rollout:rollout_context(
187+
ldclient_rollout:new(#{
188+
<<"kind">> => <<"rollout">>,
189+
<<"contextKind">> => <<"user">>,
190+
<<"bucketBy">> => <<"null">>,
191+
<<"variations">> => [
192+
#{<<"variation">> => 0, <<"weight">> => 50000, <<"untracked">> => false},
193+
#{<<"variation">> => 1, <<"weight">> => 50000, <<"untracked">> => false}
194+
]
195+
}),
196+
#{key => <<"flagKey">>, salt => <<"flagSalt">>},
197+
ldclient_context:set(<<"null">>, null, ldclient_context:new(<<"user-key">>))).

0 commit comments

Comments
 (0)