diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js index 00124fcea..73d6efb8a 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js @@ -843,7 +843,7 @@ describe('lib/core/decision_service', function() { ], 'featureEnabled': false, 'key': 'variation2' - } + }, }, }, variation: { diff --git a/packages/optimizely-sdk/lib/core/project_config/index.js b/packages/optimizely-sdk/lib/core/project_config/index.js index 6f7129cf6..d0cb86ed9 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.js @@ -496,16 +496,15 @@ module.exports = { /** * Get the value of the given variable for the given variation. If the given - * variable has no value for the given variation, return the variable's - * default value. Log an error message if the variation is invalid. If the + * variable has no value for the given variation, return null. Log an error message if the variation is invalid. If the * variable or variation are invalid, return null. * @param {Object} projectConfig * @param {Object} variable * @param {Object} variation * @param {Object} logger * @return {string|null} The value of the given variable for the given - * variation, or the variable default value if the given variable has no value - * for the given variation, or null if the variation or variable are invalid + * variation, or null if the given variable has no value + * for the given variation or if the variation or variable are invalid */ getVariableValueForVariation: function(projectConfig, variable, variation, logger) { if (!variable || !variation) { @@ -519,7 +518,8 @@ module.exports = { var variableUsages = projectConfig.variationVariableUsageMap[variation.id]; var variableUsage = variableUsages[variable.id]; - return variableUsage ? variableUsage.value : variable.defaultValue; + + return variableUsage ? variableUsage.value : null; }, /** diff --git a/packages/optimizely-sdk/lib/core/project_config/index.tests.js b/packages/optimizely-sdk/lib/core/project_config/index.tests.js index d6599ab4a..f840b4e4d 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.tests.js @@ -453,11 +453,11 @@ describe('lib/core/project_config', function() { assert.strictEqual(result, null); }); - it('returns the variable default value if the variation does not have a value for this variable', function() { + it('returns null if the variation does not have a value for this variable', function() { var variation = configObj.variationIdMap['595008']; // This variation has no variable values associated with it var variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; var result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); - assert.strictEqual(result, '10'); + assert.isNull(result); }); }); diff --git a/packages/optimizely-sdk/lib/optimizely/index.js b/packages/optimizely-sdk/lib/optimizely/index.js index 1212dc149..a852a4528 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.js +++ b/packages/optimizely-sdk/lib/optimizely/index.js @@ -632,17 +632,27 @@ Optimizely.prototype._getFeatureVariableForType = function(featureKey, variableK return null; } - var variableValue; var featureEnabled = false; + var variableValue = variable.defaultValue; var decision = this.decisionService.getVariationForFeature(this.configObj, featureFlag, userId, attributes); if (decision.variation !== null) { featureEnabled = decision.variation.featureEnabled; - variableValue = projectConfig.getVariableValueForVariation(this.configObj, variable, decision.variation, this.logger); - this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.USER_RECEIVED_VARIABLE_VALUE, MODULE_NAME, variableKey, featureFlag.key, variableValue, userId)); + var value = projectConfig.getVariableValueForVariation(this.configObj, variable, decision.variation, this.logger); + if (value !== null) { + if (featureEnabled === true) { + variableValue = value; + this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.USER_RECEIVED_VARIABLE_VALUE, MODULE_NAME, variableKey, featureFlag.key, variableValue, userId)); + } else { + this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, MODULE_NAME, + featureFlag.key, userId, variableKey)); + } + } else { + this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, MODULE_NAME, variableKey, decision.variation.key)); + } } else { - variableValue = variable.defaultValue; - this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.USER_RECEIVED_DEFAULT_VARIABLE_VALUE, MODULE_NAME, userId, variableKey, featureFlag.key)); + this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.USER_RECEIVED_DEFAULT_VARIABLE_VALUE, MODULE_NAME, userId, + variableKey, featureFlag.key)); } var experimentKey = null; diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index 2168cd16d..3b56e96d7 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -2728,9 +2728,9 @@ describe('lib/optimizely', function() { }); }); - it('returns the right value from getFeatureVariableBoolean and send notification with featureEnabled false', function() { + it('returns the default value from getFeatureVariableBoolean and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', 'user1', { test_attribute: 'test_value' }); - assert.strictEqual(result, true); + assert.strictEqual(result, false); sinon.assert.calledWith(decisionListener, { type: DECISION_INFO_TYPES.FEATURE_VARIABLE, userId: 'user1', @@ -2739,7 +2739,7 @@ describe('lib/optimizely', function() { featureKey: 'test_feature_for_experiment', featureEnabled: false, variableKey: 'is_button_animated', - variableValue: true, + variableValue: false, variableType: FEATURE_VARIABLE_TYPES.BOOLEAN, source: DECISION_SOURCES.EXPERIMENT, sourceExperimentKey: 'testing_my_feature', @@ -2748,9 +2748,9 @@ describe('lib/optimizely', function() { }); }); - it('returns the right value from getFeatureVariableDouble and send notification with featureEnabled false', function() { + it('returns the default value from getFeatureVariableDouble and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', 'user1', { test_attribute: 'test_value' }); - assert.strictEqual(result, 99.99); + assert.strictEqual(result, 50.55); sinon.assert.calledWith(decisionListener, { type: DECISION_INFO_TYPES.FEATURE_VARIABLE, userId: 'user1', @@ -2759,7 +2759,7 @@ describe('lib/optimizely', function() { featureKey: 'test_feature_for_experiment', featureEnabled: false, variableKey: 'button_width', - variableValue: 99.99, + variableValue: 50.55, variableType: FEATURE_VARIABLE_TYPES.DOUBLE, source: DECISION_SOURCES.EXPERIMENT, sourceExperimentKey: 'testing_my_feature', @@ -2768,9 +2768,9 @@ describe('lib/optimizely', function() { }); }); - it('returns the right value from getFeatureVariableInteger and send notification with featureEnabled false', function() { + it('returns the default value from getFeatureVariableInteger and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value' }); - assert.strictEqual(result, 40); + assert.strictEqual(result, 10); sinon.assert.calledWith(decisionListener, { type: DECISION_INFO_TYPES.FEATURE_VARIABLE, userId: 'user1', @@ -2779,7 +2779,7 @@ describe('lib/optimizely', function() { featureKey: 'test_feature_for_experiment', featureEnabled: false, variableKey: 'num_buttons', - variableValue: 40, + variableValue: 10, variableType: FEATURE_VARIABLE_TYPES.INTEGER, source: DECISION_SOURCES.EXPERIMENT, sourceExperimentKey: 'testing_my_feature', @@ -2788,9 +2788,9 @@ describe('lib/optimizely', function() { }); }); - it('returns the right value from getFeatureVariableString and send notification with featureEnabled false', function() { + it('returns the default value from getFeatureVariableString and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value' }); - assert.strictEqual(result, 'Buy me Later'); + assert.strictEqual(result, 'Buy me'); sinon.assert.calledWith(decisionListener, { type: DECISION_INFO_TYPES.FEATURE_VARIABLE, userId: 'user1', @@ -2799,7 +2799,7 @@ describe('lib/optimizely', function() { featureKey: 'test_feature_for_experiment', featureEnabled: false, variableKey: 'button_txt', - variableValue: 'Buy me Later', + variableValue: 'Buy me', variableType: FEATURE_VARIABLE_TYPES.STRING, source: DECISION_SOURCES.EXPERIMENT, sourceExperimentKey: 'testing_my_feature', @@ -2914,7 +2914,7 @@ describe('lib/optimizely', function() { }); }); - it('should return the right value from getFeatureVariableBoolean and send notification with featureEnabled false', function() { + it('should return the default value from getFeatureVariableBoolean and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature', 'new_content', 'user1', { test_attribute: 'test_value' }); assert.strictEqual(result, false); sinon.assert.calledWith(decisionListener, { @@ -2934,7 +2934,7 @@ describe('lib/optimizely', function() { }); }); - it('should return the right value from getFeatureVariableDouble and send notification with featureEnabled false', function() { + it('should return the default value from getFeatureVariableDouble and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariableDouble('test_feature', 'price', 'user1', { test_attribute: 'test_value' }); assert.strictEqual(result, 14.99); sinon.assert.calledWith(decisionListener, { @@ -3741,176 +3741,214 @@ describe('lib/optimizely', function() { describe('feature variable APIs', function() { describe('bucketed into variation in an experiment with variable values', function() { - beforeEach(function() { - var experiment = optlyInstance.configObj.experimentKeyMap.testing_my_feature; - var variation = experiment.variations[0]; - sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns({ - experiment: experiment, - variation: variation, - decisionSource: DECISION_SOURCES.EXPERIMENT, + describe('when the variation is toggled ON', function() { + beforeEach(function() { + var experiment = projectConfig.getExperimentFromKey(optlyInstance.configObj, 'testing_my_feature'); + var variation = experiment.variations[0]; + sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns({ + experiment: experiment, + variation: variation, + decisionSource: DECISION_SOURCES.EXPERIMENT, + }); }); - }); - it('returns the right value from getFeatureVariableBoolean', function() { - var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', 'user1', { test_attribute: 'test_value' }); - assert.strictEqual(result, true); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "is_button_animated" of feature flag "test_feature_for_experiment" is true for user "user1"'); - }); - - it('returns the right value from getFeatureVariableDouble', function() { - var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', 'user1', { test_attribute: 'test_value' }); - assert.strictEqual(result, 20.25); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "button_width" of feature flag "test_feature_for_experiment" is 20.25 for user "user1"'); - }); - - it('returns the right value from getFeatureVariableInteger', function() { - var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value' }); - assert.strictEqual(result, 2); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "num_buttons" of feature flag "test_feature_for_experiment" is 2 for user "user1"'); - }); - - it('returns the right value from getFeatureVariableString', function() { - var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value' }); - assert.strictEqual(result, 'Buy me NOW'); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "button_txt" of feature flag "test_feature_for_experiment" is Buy me NOW for user "user1"'); - }); - - it('returns null from getFeatureVariableBoolean when called with a non-boolean variable', function() { - var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'button_width', 'user1'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.WARNING, 'OPTIMIZELY: Requested variable type "boolean", but variable is of type "double". Use correct API to retrieve value. Returning None.'); - }); - - it('returns null from getFeatureVariableDouble when called with a non-double variable', function() { - var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'is_button_animated', 'user1'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.WARNING, 'OPTIMIZELY: Requested variable type "double", but variable is of type "boolean". Use correct API to retrieve value. Returning None.'); - }); + it('returns the right value from getFeatureVariableBoolean', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, true); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "is_button_animated" of feature flag "test_feature_for_experiment" is true for user "user1"'); + }); - it('returns null from getFeatureVariableInteger when called with a non-integer variable', function() { - var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'button_width', 'user1'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.WARNING, 'OPTIMIZELY: Requested variable type "integer", but variable is of type "double". Use correct API to retrieve value. Returning None.'); - }); + it('returns the right value from getFeatureVariableDouble', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 20.25); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "button_width" of feature flag "test_feature_for_experiment" is 20.25 for user "user1"'); + }); - it('returns null from getFeatureVariableString when called with a non-string variable', function() { - var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'num_buttons', 'user1'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.WARNING, 'OPTIMIZELY: Requested variable type "string", but variable is of type "integer". Use correct API to retrieve value. Returning None.'); - }); + it('returns the right value from getFeatureVariableInteger', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 2); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "num_buttons" of feature flag "test_feature_for_experiment" is 2 for user "user1"'); + }); - it('returns null from getFeatureVariableBoolean if user id is null', function() { - var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', null, { test_attribute: 'test_value' }); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); - }); + it('returns the right value from getFeatureVariableString', function() { + var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 'Buy me NOW'); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "button_txt" of feature flag "test_feature_for_experiment" is Buy me NOW for user "user1"'); + }); - it('returns null from getFeatureVariableBoolean if user id is undefined', function() { - var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', undefined, { test_attribute: 'test_value' }); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + describe('when the variable is not used in the variation', function() { + beforeEach(function() { + sandbox.stub(projectConfig, 'getVariableValueForVariation').returns(null); + }); + + it('returns the variable default value from getFeatureVariableBoolean', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, false); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.'); + }); + + it('returns the variable default value from getFeatureVariableDouble', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 50.55); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.'); + }); + + it('returns the variable default value from getFeatureVariableInteger', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 10); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.'); + }); + + it('returns the variable default value from getFeatureVariableString', function() { + var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 'Buy me'); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.'); + }); + }); }); - it('returns null from getFeatureVariableBoolean if user id is not provided', function() { - var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); - }); + describe('when the variation is toggled OFF', function() { + beforeEach(function() { + var experiment = projectConfig.getExperimentFromKey(optlyInstance.configObj, 'testing_my_feature'); + var variation = experiment.variations[2]; + sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns({ + experiment: experiment, + variation: variation, + decisionSource: DECISION_SOURCES.EXPERIMENT, + }); + }); - it('returns null from getFeatureVariableDouble if user id is null', function() { - var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', null, { test_attribute: 'test_value' }); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); - }); + it('returns the variable default value from getFeatureVariableBoolean', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, false); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning default value for variable "is_button_animated".'); + }); - it('returns null from getFeatureVariableDouble if user id is undefined', function() { - var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', undefined, { test_attribute: 'test_value' }); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); - }); + it('returns the variable default value from getFeatureVariableDouble', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 50.55); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning default value for variable "button_width".'); + }); - it('returns null from getFeatureVariableDouble if user id is not provided', function() { - var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); - }); + it('returns the variable default value from getFeatureVariableInteger', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 10); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning default value for variable "num_buttons".'); + }); - it('returns null from getFeatureVariableInteger if user id is null', function() { - var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', null, { test_attribute: 'test_value' }); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + it('returns the variable default value from getFeatureVariableString', function() { + var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 'Buy me'); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning default value for variable "button_txt".'); + }); }); + }); - it('returns null from getFeatureVariableInteger if user id is undefined', function() { - var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', undefined, { test_attribute: 'test_value' }); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); - }); + describe('bucketed into variation of a rollout with variable values', function() { + describe('when the variation is toggled ON', function() { + beforeEach(function() { + var experiment = projectConfig.getExperimentFromKey(optlyInstance.configObj, '594031'); + var variation = experiment.variations[0]; + sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns({ + experiment: experiment, + variation: variation, + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + }); - it('returns null from getFeatureVariableInteger if user id is not provided', function() { - var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); - }); + it('returns the right value from getFeatureVariableBoolean', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature', 'new_content', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, true); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "new_content" of feature flag "test_feature" is true for user "user1"'); + }); - it('returns null from getFeatureVariableString if user id is null', function() { - var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt', null, { test_attribute: 'test_value' }); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); - }); + it('returns the right value from getFeatureVariableDouble', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature', 'price', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 4.99); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "price" of feature flag "test_feature" is 4.99 for user "user1"'); + }); - it('returns null from getFeatureVariableString if user id is undefined', function() { - var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt', undefined, { test_attribute: 'test_value' }); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); - }); + it('returns the right value from getFeatureVariableInteger', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature', 'lasers', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 395); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "lasers" of feature flag "test_feature" is 395 for user "user1"'); + }); - it('returns null from getFeatureVariableString if user id is not provided', function() { - var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); - }); + it('returns the right value from getFeatureVariableString', function() { + var result = optlyInstance.getFeatureVariableString('test_feature', 'message', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 'Hello audience'); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Value for variable "message" of feature flag "test_feature" is Hello audience for user "user1"'); + }); - describe('type casting failures', function() { - describe('invalid boolean', function() { + describe('when the variable is not used in the variation', function() { beforeEach(function() { - sandbox.stub(projectConfig, 'getVariableValueForVariation').returns('falsezzz'); + sandbox.stub(projectConfig, 'getVariableValueForVariation').returns(null); }); - - it('should return null and log an error', function() { - var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', 'user1'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'PROJECT_CONFIG: Unable to cast value falsezzz to type boolean, returning null.'); + + it('returns the variable default value from getFeatureVariableBoolean', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature', 'new_content', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, false); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.'); + }); + + it('returns the variable default value from getFeatureVariableDouble', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature', 'price', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 14.99); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.'); + }); + + it('returns the variable default value from getFeatureVariableInteger', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature', 'lasers', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 400); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.'); + }); + + it('returns the variable default value from getFeatureVariableString', function() { + var result = optlyInstance.getFeatureVariableString('test_feature', 'message', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 'Hello'); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.'); }); }); + }); - describe('invalid integer', function() { - beforeEach(function() { - sandbox.stub(projectConfig, 'getVariableValueForVariation').returns('zzz123'); + describe('when the variation is toggled OFF', function() { + beforeEach(function() { + var experiment = projectConfig.getExperimentFromKey(optlyInstance.configObj, '594037'); + var variation = experiment.variations[0]; + sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns({ + experiment: experiment, + variation: variation, + decisionSource: DECISION_SOURCES.ROLLOUT, }); + }); - it('should return null and log an error', function() { - var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', 'user1'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'PROJECT_CONFIG: Unable to cast value zzz123 to type integer, returning null.'); - }); + it('returns the variable default value from getFeatureVariableBoolean', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature', 'new_content', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, false); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning default value for variable "new_content".'); }); - describe('invalid double', function() { - beforeEach(function() { - sandbox.stub(projectConfig, 'getVariableValueForVariation').returns('zzz44.55'); - }); + it('returns the variable default value from getFeatureVariableDouble', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature', 'price', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 14.99); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning default value for variable "price".'); + }); - it('should return null and log an error', function() { - var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', 'user1'); - assert.strictEqual(result, null); - sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'PROJECT_CONFIG: Unable to cast value zzz44.55 to type double, returning null.'); - }); + it('returns the variable default value from getFeatureVariableInteger', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature', 'lasers', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 400); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning default value for variable "lasers".'); + }); + + it('returns the variable default value from getFeatureVariableString', function() { + var result = optlyInstance.getFeatureVariableString('test_feature', 'message', 'user1', { test_attribute: 'test_value' }); + assert.strictEqual(result, 'Hello'); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.INFO, 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning default value for variable "message".'); }); }); }); - describe('not bucketed into a variation', function() { + describe('not bucketed into an experiment or a rollout ', function() { beforeEach(function() { sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns({ experiment: null, @@ -3944,6 +3982,140 @@ describe('lib/optimizely', function() { }); }); + it('returns null from getFeatureVariableBoolean when called with a non-boolean variable', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'button_width', 'user1'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.WARNING, 'OPTIMIZELY: Requested variable type "boolean", but variable is of type "double". Use correct API to retrieve value. Returning None.'); + }); + + it('returns null from getFeatureVariableDouble when called with a non-double variable', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'is_button_animated', 'user1'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.WARNING, 'OPTIMIZELY: Requested variable type "double", but variable is of type "boolean". Use correct API to retrieve value. Returning None.'); + }); + + it('returns null from getFeatureVariableInteger when called with a non-integer variable', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'button_width', 'user1'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.WARNING, 'OPTIMIZELY: Requested variable type "integer", but variable is of type "double". Use correct API to retrieve value. Returning None.'); + }); + + it('returns null from getFeatureVariableString when called with a non-string variable', function() { + var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'num_buttons', 'user1'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.WARNING, 'OPTIMIZELY: Requested variable type "string", but variable is of type "integer". Use correct API to retrieve value. Returning None.'); + }); + + it('returns null from getFeatureVariableBoolean if user id is null', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', null, { test_attribute: 'test_value' }); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableBoolean if user id is undefined', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', undefined, { test_attribute: 'test_value' }); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableBoolean if user id is not provided', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableDouble if user id is null', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', null, { test_attribute: 'test_value' }); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableDouble if user id is undefined', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', undefined, { test_attribute: 'test_value' }); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableDouble if user id is not provided', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableInteger if user id is null', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', null, { test_attribute: 'test_value' }); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableInteger if user id is undefined', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', undefined, { test_attribute: 'test_value' }); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableInteger if user id is not provided', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableString if user id is null', function() { + var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt', null, { test_attribute: 'test_value' }); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableString if user id is undefined', function() { + var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt', undefined, { test_attribute: 'test_value' }); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + it('returns null from getFeatureVariableString if user id is not provided', function() { + var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'OPTIMIZELY: Provided user_id is in an invalid format.'); + }); + + describe('type casting failures', function() { + describe('invalid boolean', function() { + beforeEach(function() { + sandbox.stub(projectConfig, 'getVariableValueForVariation').returns('falsezzz'); + }); + + it('should return null and log an error', function() { + var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated', 'user1'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'PROJECT_CONFIG: Unable to cast value falsezzz to type boolean, returning null.'); + }); + }); + + describe('invalid integer', function() { + beforeEach(function() { + sandbox.stub(projectConfig, 'getVariableValueForVariation').returns('zzz123'); + }); + + it('should return null and log an error', function() { + var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', 'user1'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'PROJECT_CONFIG: Unable to cast value zzz123 to type integer, returning null.'); + }); + }); + + describe('invalid double', function() { + beforeEach(function() { + sandbox.stub(projectConfig, 'getVariableValueForVariation').returns('zzz44.55'); + }); + + it('should return null and log an error', function() { + var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', 'user1'); + assert.strictEqual(result, null); + sinon.assert.calledWith(createdLogger.log, LOG_LEVEL.ERROR, 'PROJECT_CONFIG: Unable to cast value zzz44.55 to type double, returning null.'); + }); + }); + }); + it('returns null from getFeatureVariableBoolean if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableBoolean('thisIsNotAValidKey<><><>', 'is_button_animated', 'user1'); assert.strictEqual(result, null); diff --git a/packages/optimizely-sdk/lib/utils/enums/index.js b/packages/optimizely-sdk/lib/utils/enums/index.js index 9e6395332..2f5e7a13d 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.js +++ b/packages/optimizely-sdk/lib/utils/enums/index.js @@ -116,6 +116,8 @@ exports.LOG_MESSAGES = { USER_NOT_IN_ANY_EXPERIMENT: '%s: User %s is not in any experiment of group %s.', USER_NOT_IN_EXPERIMENT: '%s: User %s does not meet conditions to be in experiment %s.', USER_RECEIVED_DEFAULT_VARIABLE_VALUE: '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE: '%s: Feature "%s" is not enabled for user %s. Returning default value for variable "%s".', + VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE: '%s: Variable "%s" is not used in variation "%s". Returning default value.', USER_RECEIVED_VARIABLE_VALUE: '%s: Value for variable "%s" of feature flag "%s" is %s for user "%s"', VALID_DATAFILE: '%s: Datafile is valid.', VALID_USER_PROFILE_SERVICE: '%s: Valid user profile service provided.',