Skip to content

Commit 5a0f58a

Browse files
Implement get_feature_variable and create unit tests (#190)
1 parent 17c07b6 commit 5a0f58a

File tree

3 files changed

+217
-0
lines changed

3 files changed

+217
-0
lines changed

lib/optimizely.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -369,6 +369,32 @@ def get_enabled_features(user_id, attributes = nil)
369369
enabled_features
370370
end
371371

372+
# Get the value of the specified variable in the feature flag.
373+
#
374+
# @param feature_flag_key - String key of feature flag the variable belongs to
375+
# @param variable_key - String key of variable for which we are getting the value
376+
# @param user_id - String user ID
377+
# @param attributes - Hash representing visitor attributes and values which need to be recorded.
378+
#
379+
# @return [*] the type-casted variable value.
380+
# @return [nil] if the feature flag or variable are not found.
381+
382+
def get_feature_variable(feature_flag_key, variable_key, user_id, attributes = nil)
383+
unless is_valid
384+
@logger.log(Logger::ERROR, InvalidProjectConfigError.new('get_feature_variable').message)
385+
return nil
386+
end
387+
variable_value = get_feature_variable_for_type(
388+
feature_flag_key,
389+
variable_key,
390+
nil,
391+
user_id,
392+
attributes
393+
)
394+
395+
variable_value
396+
end
397+
372398
# Get the String value of the specified variable in the feature flag.
373399
#
374400
# @param feature_flag_key - String key of feature flag the variable belongs to
@@ -556,6 +582,9 @@ def get_feature_variable_for_type(feature_flag_key, variable_key, variable_type,
556582
return nil if variable.nil?
557583

558584
feature_enabled = false
585+
586+
# If variable_type is nil, set it equal to variable['type']
587+
variable_type ||= variable['type']
559588
# Returns nil if type differs
560589
if variable['type'] != variable_type
561590
@logger.log(Logger::WARN,

lib/optimizely/helpers/validator.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,15 @@ def inputs_valid?(variables, logger = NoOpLogger.new, level = Logger::ERROR)
132132
variables.delete :user_id
133133
end
134134

135+
if variables.include? :variable_type
136+
# Empty variable_type is a valid user ID.
137+
unless variables[:variable_type].is_a?(String) || !variables[:variable_type]
138+
is_valid = false
139+
logger.log(level, "#{Constants::INPUT_VARIABLES['VARIABLE_TYPE']} is invalid")
140+
end
141+
variables.delete :variable_type
142+
end
143+
135144
variables.each do |key, value|
136145
next if value.is_a?(String) && !value.empty?
137146

spec/project_spec.rb

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2110,6 +2110,185 @@ class InvalidErrorHandler; end
21102110
end
21112111
end
21122112

2113+
describe '#get_feature_variable' do
2114+
user_id = 'test_user'
2115+
user_attributes = {}
2116+
2117+
it 'should return nil when called with invalid project config' do
2118+
logger = double('logger')
2119+
allow(logger).to receive(:log)
2120+
allow(Optimizely::SimpleLogger).to receive(:new) { logger }
2121+
invalid_project = Optimizely::Project.new('invalid', nil, spy_logger)
2122+
expect(invalid_project.get_feature_variable('string_single_variable_feature', 'string_variable', user_id, user_attributes))
2123+
.to eq(nil)
2124+
expect(logger).to have_received(:log).once.with(Logger::ERROR, 'Provided datafile is in an invalid format.')
2125+
expect(spy_logger).to have_received(:log).once.with(Logger::ERROR, "Optimizely instance is not valid. Failing 'get_feature_variable'.")
2126+
end
2127+
2128+
it 'should return nil and log an error when Config Manager returns nil config' do
2129+
allow(project_instance.config_manager).to receive(:config).and_return(nil)
2130+
expect(project_instance.get_feature_variable('string_single_variable_feature', 'string_variable', user_id, user_attributes)).to eq(nil)
2131+
expect(spy_logger).to have_received(:log).once.with(
2132+
Logger::ERROR,
2133+
"Optimizely instance is not valid. Failing 'get_feature_variable'."
2134+
)
2135+
end
2136+
2137+
describe 'when the feature flag is enabled for the user' do
2138+
describe 'and a variable usage instance is not found' do
2139+
it 'should return the default variable value!!!' do
2140+
variation_to_return = project_instance.config_manager.config.rollout_id_map['166661']['experiments'][0]['variations'][0]
2141+
decision_to_return = {
2142+
'experiment' => nil,
2143+
'variation' => variation_to_return
2144+
}
2145+
allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return)
2146+
2147+
expect(project_instance.get_feature_variable('string_single_variable_feature', 'string_variable', user_id, user_attributes))
2148+
.to eq('wingardium leviosa')
2149+
expect(spy_logger).to have_received(:log).once
2150+
.with(
2151+
Logger::DEBUG,
2152+
"Variable 'string_variable' is not used in variation '177775'. Returning the default variable value 'wingardium leviosa'."
2153+
)
2154+
end
2155+
end
2156+
2157+
describe 'and a variable usage instance is found' do
2158+
it 'should return the string variable value for the variation for the user is bucketed into' do
2159+
experiment_to_return = project_instance.config_manager.config.experiment_key_map['test_experiment_with_feature_rollout']
2160+
variation_to_return = experiment_to_return['variations'][0]
2161+
decision_to_return = {
2162+
'experiment' => experiment_to_return,
2163+
'variation' => variation_to_return
2164+
}
2165+
allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return)
2166+
2167+
expect(project_instance.get_feature_variable('string_single_variable_feature', 'string_variable', user_id, user_attributes))
2168+
.to eq('cta_1')
2169+
2170+
expect(spy_logger).to have_received(:log).once
2171+
expect(spy_logger).to have_received(:log).once
2172+
.with(
2173+
Logger::INFO,
2174+
"Got variable value 'cta_1' for variable 'string_variable' of feature flag 'string_single_variable_feature'."
2175+
)
2176+
end
2177+
2178+
it 'should return the boolean variable value for the variation for the user is bucketed into' do
2179+
boolean_feature = project_instance.config_manager.config.feature_flag_key_map['boolean_single_variable_feature']
2180+
rollout = project_instance.config_manager.config.rollout_id_map[boolean_feature['rolloutId']]
2181+
variation_to_return = rollout['experiments'][0]['variations'][0]
2182+
decision_to_return = {
2183+
'experiment' => nil,
2184+
'variation' => variation_to_return
2185+
}
2186+
allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return)
2187+
2188+
expect(project_instance.get_feature_variable('boolean_single_variable_feature', 'boolean_variable', user_id, user_attributes))
2189+
.to eq(true)
2190+
2191+
expect(spy_logger).to have_received(:log).once
2192+
expect(spy_logger).to have_received(:log).once
2193+
.with(
2194+
Logger::INFO,
2195+
"Got variable value 'true' for variable 'boolean_variable' of feature flag 'boolean_single_variable_feature'."
2196+
)
2197+
end
2198+
2199+
it 'should return the double variable value for the variation for the user is bucketed into' do
2200+
double_feature = project_instance.config_manager.config.feature_flag_key_map['double_single_variable_feature']
2201+
experiment_to_return = project_instance.config_manager.config.experiment_id_map[double_feature['experimentIds'][0]]
2202+
variation_to_return = experiment_to_return['variations'][0]
2203+
decision_to_return = {
2204+
'experiment' => experiment_to_return,
2205+
'variation' => variation_to_return
2206+
}
2207+
2208+
allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return)
2209+
2210+
expect(project_instance.get_feature_variable('double_single_variable_feature', 'double_variable', user_id, user_attributes))
2211+
.to eq(42.42)
2212+
2213+
expect(spy_logger).to have_received(:log).once
2214+
expect(spy_logger).to have_received(:log).once
2215+
.with(
2216+
Logger::INFO,
2217+
"Got variable value '42.42' for variable 'double_variable' of feature flag 'double_single_variable_feature'."
2218+
)
2219+
end
2220+
2221+
it 'should return the integer variable value for the variation for the user is bucketed into' do
2222+
integer_feature = project_instance.config_manager.config.feature_flag_key_map['integer_single_variable_feature']
2223+
experiment_to_return = project_instance.config_manager.config.experiment_id_map[integer_feature['experimentIds'][0]]
2224+
variation_to_return = experiment_to_return['variations'][0]
2225+
decision_to_return = {
2226+
'experiment' => experiment_to_return,
2227+
'variation' => variation_to_return
2228+
}
2229+
2230+
allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(decision_to_return)
2231+
2232+
expect(project_instance.get_feature_variable('integer_single_variable_feature', 'integer_variable', user_id, user_attributes))
2233+
.to eq(42)
2234+
2235+
expect(spy_logger).to have_received(:log).once
2236+
expect(spy_logger).to have_received(:log).once
2237+
.with(
2238+
Logger::INFO,
2239+
"Got variable value '42' for variable 'integer_variable' of feature flag 'integer_single_variable_feature'."
2240+
)
2241+
end
2242+
end
2243+
end
2244+
2245+
describe 'when the feature flag is not enabled for the user' do
2246+
it 'should return the default variable value' do
2247+
allow(project_instance.decision_service).to receive(:get_variation_for_feature).and_return(nil)
2248+
2249+
expect(project_instance.get_feature_variable('string_single_variable_feature', 'string_variable', user_id, user_attributes))
2250+
.to eq('wingardium leviosa')
2251+
expect(spy_logger).to have_received(:log).once
2252+
expect(spy_logger).to have_received(:log).once
2253+
.with(
2254+
Logger::INFO,
2255+
"User 'test_user' was not bucketed into any variation for feature flag 'string_single_variable_feature'. Returning the default variable value 'wingardium leviosa'."
2256+
)
2257+
end
2258+
end
2259+
2260+
describe 'when the specified feature flag is invalid' do
2261+
it 'should log an error message and return nil' do
2262+
expect(project_instance.get_feature_variable('totally_invalid_feature_key', 'string_variable', user_id, user_attributes))
2263+
.to eq(nil)
2264+
expect(spy_logger).to have_received(:log).twice
2265+
expect(spy_logger).to have_received(:log).once
2266+
.with(
2267+
Logger::ERROR,
2268+
"Feature flag key 'totally_invalid_feature_key' is not in datafile."
2269+
)
2270+
expect(spy_logger).to have_received(:log).once
2271+
.with(
2272+
Logger::INFO,
2273+
"No feature flag was found for key 'totally_invalid_feature_key'."
2274+
)
2275+
end
2276+
end
2277+
2278+
describe 'when the specified feature variable is invalid' do
2279+
it 'should log an error message and return nil' do
2280+
expect(project_instance.get_feature_variable('string_single_variable_feature', 'invalid_string_variable', user_id, user_attributes))
2281+
.to eq(nil)
2282+
expect(spy_logger).to have_received(:log).once
2283+
expect(spy_logger).to have_received(:log).once
2284+
.with(
2285+
Logger::ERROR,
2286+
"No feature variable was found for key 'invalid_string_variable' in feature flag 'string_single_variable_feature'."
2287+
)
2288+
end
2289+
end
2290+
end
2291+
21132292
describe '#get_feature_variable_for_type with empty params' do
21142293
user_id = 'test_user'
21152294
user_attributes = {}

0 commit comments

Comments
 (0)