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 96bd3bdb9..6571364d1 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js @@ -1147,6 +1147,10 @@ describe('lib/core/decision_service', function() { id: '6199684360044544', value: '20.25', }, + { + id: '1547854156498475', + value: '{ "num_buttons": 1, "text": "first variation"}', + }, ], featureEnabled: true, key: 'variation', @@ -1170,6 +1174,10 @@ describe('lib/core/decision_service', function() { id: '6199684360044544', value: '50.55', }, + { + id: '1547854156498475', + value: '{ "num_buttons": 2, "text": "second variation"}', + }, ], featureEnabled: true, key: 'control', @@ -1193,6 +1201,10 @@ describe('lib/core/decision_service', function() { id: '6199684360044544', value: '99.99', }, + { + id: '1547854156498475', + value: '{ "num_buttons": 3, "text": "third variation"}', + }, ], featureEnabled: false, key: 'variation2', @@ -1224,6 +1236,10 @@ describe('lib/core/decision_service', function() { id: '6199684360044544', value: '50.55', }, + { + id: '1547854156498475', + value: '{ "num_buttons": 2, "text": "second variation"}', + }, ], featureEnabled: true, key: 'control', @@ -1247,6 +1263,10 @@ describe('lib/core/decision_service', function() { id: '6199684360044544', value: '20.25', }, + { + id: '1547854156498475', + value: '{ "num_buttons": 1, "text": "first variation"}', + }, ], featureEnabled: true, key: 'variation', @@ -1270,6 +1290,10 @@ describe('lib/core/decision_service', function() { id: '6199684360044544', value: '99.99', }, + { + id: '1547854156498475', + value: '{ "num_buttons": 3, "text": "third variation"}', + }, ], featureEnabled: false, key: 'variation2', @@ -1295,6 +1319,10 @@ describe('lib/core/decision_service', function() { id: '6199684360044544', value: '20.25', }, + { + id: '1547854156498475', + value: '{ "num_buttons": 1, "text": "first variation"}', + }, ], featureEnabled: true, key: 'variation', @@ -1504,6 +1532,10 @@ describe('lib/core/decision_service', function() { id: '6327227708866560', value: 'Hello audience', }, + { + id: "8765345281230956", + value: '{ "count": 2, "message": "Hello audience" }', + }, ], featureEnabled: true, key: '594032', @@ -1529,6 +1561,10 @@ describe('lib/core/decision_service', function() { id: '6327227708866560', value: 'Hello audience', }, + { + id: "8765345281230956", + value: '{ "count": 2, "message": "Hello audience" }', + }, ], featureEnabled: true, key: '594032', @@ -1557,6 +1593,10 @@ describe('lib/core/decision_service', function() { id: '6327227708866560', value: 'Hello audience', }, + { + id: "8765345281230956", + value: '{ "count": 2, "message": "Hello audience" }', + }, ], featureEnabled: true, key: '594032', @@ -1616,6 +1656,10 @@ describe('lib/core/decision_service', function() { id: '6327227708866560', value: 'Hello', }, + { + id: '8765345281230956', + value: '{ "count": 1, "message": "Hello" }', + }, ], featureEnabled: false, key: '594038', @@ -1644,6 +1688,10 @@ describe('lib/core/decision_service', function() { id: '6327227708866560', value: 'Hello', }, + { + id: '8765345281230956', + value: '{ "count": 1, "message": "Hello" }', + }, ], featureEnabled: false, key: '594038', @@ -1669,6 +1717,10 @@ describe('lib/core/decision_service', function() { id: '6327227708866560', value: 'Hello', }, + { + id: '8765345281230956', + value: '{ "count": 1, "message": "Hello" }', + }, ], featureEnabled: false, key: '594038', @@ -1761,6 +1813,10 @@ describe('lib/core/decision_service', function() { id: '6327227708866560', value: 'Hello', }, + { + id: '8765345281230956', + value: '{ "count": 1, "message": "Hello" }', + }, ], featureEnabled: false, key: '594038', @@ -1789,6 +1845,10 @@ describe('lib/core/decision_service', function() { id: '6327227708866560', value: 'Hello', }, + { + id: '8765345281230956', + value: '{ "count": 1, "message": "Hello" }', + }, ], featureEnabled: false, key: '594038', @@ -1814,6 +1874,10 @@ describe('lib/core/decision_service', function() { id: '6327227708866560', value: 'Hello', }, + { + id: '8765345281230956', + value: '{ "count": 1, "message": "Hello" }', + }, ], featureEnabled: false, key: '594038', diff --git a/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js b/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js index d17a9bd62..f39b86918 100644 --- a/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js +++ b/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js @@ -81,10 +81,13 @@ describe('lib/core/optimizely_config', function() { }); var variablesMap = featuresMap[featureFlag.key].variablesMap; featureFlag.variables.forEach(function(variable) { + // json is represented as sub type of string to support backwards compatibility in datafile. + // project config treats it as a first-class type. + var expectedVariableType = (variable.type === "string" && variable.subType === "json") ? "json" : variable.type; assert.include(variablesMap[variable.key], { id: variable.id, key: variable.key, - type: variable.type, + type: expectedVariableType, value: variable.defaultValue, }); }); @@ -107,11 +110,21 @@ describe('lib/core/optimizely_config', function() { variations.forEach(function(variation) { featureFlag.variables.forEach(function(variable) { var variableToAssert = variationsMap[variation.key].variablesMap[variable.key]; - assert.include(variable, { - id: variableToAssert.id, - key: variableToAssert.key, - type: variableToAssert.type, - }); + // json is represented as sub type of string to support backwards compatibility in datafile. + // project config treats it as a first-class type. + var expectedVariableType = (variable.type === "string" && variable.subType === "json") ? "json" : variable.type; + assert.include( + { + id: variable.id, + key: variable.key, + type: expectedVariableType, + }, + { + id: variableToAssert.id, + key: variableToAssert.key, + type: variableToAssert.type, + } + ); if (!variation.featureEnabled) { assert.equal(variable.defaultValue, variableToAssert.value); } diff --git a/packages/optimizely-sdk/lib/core/project_config/index.js b/packages/optimizely-sdk/lib/core/project_config/index.js index bcc9e20dd..e11236ac3 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.js @@ -119,6 +119,15 @@ export var createProjectConfig = function(datafile) { projectConfig.featureKeyMap = fns.keyBy(projectConfig.featureFlags || [], 'key'); objectValues(projectConfig.featureKeyMap || {}).forEach(function(feature) { + // Json type is represented in datafile as a subtype of string for the sake of backwards compatibility. + // Converting it to a first-class json type while creating Project Config + feature.variables.forEach(function(variable) { + if (variable.type === FEATURE_VARIABLE_TYPES.STRING && variable.subType === FEATURE_VARIABLE_TYPES.JSON) { + variable.type = FEATURE_VARIABLE_TYPES.JSON; + delete variable.subType; + } + }); + feature.variableKeyMap = fns.keyBy(feature.variables, 'key'); (feature.experimentIds || []).forEach(function(experimentId) { // Add this experiment in experiment-feature map. @@ -479,6 +488,18 @@ export var getTypeCastValue = function(variableValue, variableType, logger) { } break; + case FEATURE_VARIABLE_TYPES.JSON: + try { + castValue = JSON.parse(variableValue); + } catch (e) { + logger.log( + LOG_LEVEL.ERROR, + sprintf(ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, MODULE_NAME, variableValue, variableType) + ); + castValue = null; + } + break; + default: // type is STRING castValue = variableValue; 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 fadc8a866..1840e72cf 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.tests.js @@ -210,6 +210,7 @@ describe('lib/core/project_config', function() { { value: '395', id: '5482802778734592' }, { value: '4.99', id: '6045752732155904' }, { value: 'Hello audience', id: '6327227708866560' }, + { value: '{ "count": 2, "message": "Hello audience" }', id: '8765345281230956' }, ], featureEnabled: true, key: '594032', @@ -221,6 +222,7 @@ describe('lib/core/project_config', function() { { value: '400', id: '5482802778734592' }, { value: '14.99', id: '6045752732155904' }, { value: 'Hello', id: '6327227708866560' }, + { value: '{ "count": 1, "message": "Hello" }', id: '8765345281230956' }, ], featureEnabled: false, key: '594038', diff --git a/packages/optimizely-sdk/lib/optimizely/index.js b/packages/optimizely-sdk/lib/optimizely/index.js index d52659b67..e2fadba9d 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.js +++ b/packages/optimizely-sdk/lib/optimizely/index.js @@ -933,6 +933,30 @@ Optimizely.prototype.getFeatureVariableString = function(featureKey, variableKey } }; +/** + * Returns value for the given json variable attached to the given feature + * flag. + * @param {string} featureKey Key of the feature whose variable's value is + * being accessed + * @param {string} variableKey Key of the variable whose value is being + * accessed + * @param {string} userId ID for the user + * @param {Object} attributes Optional user attributes + * @return {object|null} Object value of the variable, or null if the + * feature key is invalid, the variable key is + * invalid, or there is a mismatch with the type + * of the variable + */ +Optimizely.prototype.getFeatureVariableJson = function(featureKey, variableKey, userId, attributes) { + try { + return this._getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.JSON, userId, attributes); + } catch (e) { + this.logger.log(LOG_LEVEL.ERROR, e.message); + this.errorHandler.handleError(e); + return null; + } +}; + /** * Returns OptimizelyConfig object containing experiments and features data * @return {Object} diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index 6e9ec01f1..50baafa94 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -157,7 +157,7 @@ describe('lib/optimizely', function() { save: function() {}, }; - var optlyInstance = new Optimizely({ + new Optimizely({ clientEngine: 'node-sdk', logger: createdLogger, datafile: testData.getTestProjectConfig(), @@ -180,7 +180,7 @@ describe('lib/optimizely', function() { save: function() {}, }; - var optlyInstance = new Optimizely({ + new Optimizely({ clientEngine: 'node-sdk', logger: createdLogger, datafile: testData.getTestProjectConfig(), @@ -2898,7 +2898,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the right value from getFeatureVariable and send notification with featureEnabled true', function() { + it('returns the right value from getFeatureVariable when variable type is boolean and send notification with featureEnabled true', function() { var result = optlyInstance.getFeatureVariable( 'test_feature_for_experiment', 'is_button_animated', @@ -2925,7 +2925,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the right value from getFeatureVariable and send notification with featureEnabled true', function() { + it('returns the right value from getFeatureVariable when variable type is double and send notification with featureEnabled true', function() { var result = optlyInstance.getFeatureVariable( 'test_feature_for_experiment', 'button_width', @@ -2952,7 +2952,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the right value from getFeatureVariable and send notification with featureEnabled true', function() { + it('returns the right value from getFeatureVariable when variable type is integer and send notification with featureEnabled true', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value', }); @@ -2976,7 +2976,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the right value from getFeatureVariable and send notification with featureEnabled true', function() { + it('returns the right value from getFeatureVariable when variable type is string and send notification with featureEnabled true', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value', }); @@ -3000,6 +3000,36 @@ describe('lib/optimizely', function() { }); }); + it('returns the right value from getFeatureVariable when variable type is json and send notification with featureEnabled true', function() { + var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + num_buttons: 1, + text: 'first variation', + }); + sinon.assert.calledWith(decisionListener, { + type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, + userId: 'user1', + attributes: { test_attribute: 'test_value' }, + decisionInfo: { + featureKey: 'test_feature_for_experiment', + featureEnabled: true, + variableKey: 'button_info', + variableValue: { + num_buttons: 1, + text: "first variation", + }, + variableType: FEATURE_VARIABLE_TYPES.JSON, + source: DECISION_SOURCES.FEATURE_TEST, + sourceInfo: { + experimentKey: 'testing_my_feature', + variationKey: 'variation', + }, + }, + }); + }); + it('returns the right value from getFeatureVariableBoolean and send notification with featureEnabled true', function() { var result = optlyInstance.getFeatureVariableBoolean( 'test_feature_for_experiment', @@ -3107,6 +3137,39 @@ describe('lib/optimizely', function() { }, }); }); + + it('returns the right value from getFeatureVariableJson and send notification with featureEnabled true', function() { + var result = optlyInstance.getFeatureVariableJson( + 'test_feature_for_experiment', + 'button_info', + 'user1', + { test_attribute: 'test_value' } + ); + assert.deepEqual(result, { + num_buttons: 1, + text: 'first variation', + }); + sinon.assert.calledWith(decisionListener, { + type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, + userId: 'user1', + attributes: { test_attribute: 'test_value' }, + decisionInfo: { + featureKey: 'test_feature_for_experiment', + featureEnabled: true, + variableKey: 'button_info', + variableValue: { + num_buttons: 1, + text: 'first variation', + }, + variableType: FEATURE_VARIABLE_TYPES.JSON, + source: DECISION_SOURCES.FEATURE_TEST, + sourceInfo: { + experimentKey: 'testing_my_feature', + variationKey: 'variation', + }, + }, + }); + }); }); describe('when the variation is toggled OFF', function() { @@ -3230,6 +3293,39 @@ describe('lib/optimizely', function() { }, }); }); + + it('returns the default value from getFeatureVariableJson and send notification with featureEnabled false', function() { + var result = optlyInstance.getFeatureVariableJson( + 'test_feature_for_experiment', + 'button_info', + 'user1', + { test_attribute: 'test_value' } + ); + assert.deepEqual(result, { + num_buttons: 0, + text: 'default value', + }); + sinon.assert.calledWith(decisionListener, { + type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, + userId: 'user1', + attributes: { test_attribute: 'test_value' }, + decisionInfo: { + featureKey: 'test_feature_for_experiment', + featureEnabled: false, + variableKey: 'button_info', + variableValue: { + num_buttons: 0, + text: 'default value', + }, + variableType: FEATURE_VARIABLE_TYPES.JSON, + source: DECISION_SOURCES.FEATURE_TEST, + sourceInfo: { + experimentKey: 'testing_my_feature', + variationKey: 'variation2', + }, + }, + }); + }); }); }); @@ -3248,7 +3344,7 @@ describe('lib/optimizely', function() { }); }); - it('should return the right value from getFeatureVariable and send notification with featureEnabled true', function() { + it('should return the right value from getFeatureVariable when variable type is boolean and send notification with featureEnabled true', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'new_content', 'user1', { test_attribute: 'test_value', }); @@ -3269,7 +3365,7 @@ describe('lib/optimizely', function() { }); }); - it('should return the right value from getFeatureVariable and send notification with featureEnabled true', function() { + it('should return the right value from getFeatureVariable when variable type is double and send notification with featureEnabled true', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'price', 'user1', { test_attribute: 'test_value', }); @@ -3290,7 +3386,7 @@ describe('lib/optimizely', function() { }); }); - it('should return the right value from getFeatureVariable and send notification with featureEnabled true', function() { + it('should return the right value from getFeatureVariable when variable type is integer and send notification with featureEnabled true', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'lasers', 'user1', { test_attribute: 'test_value', }); @@ -3311,7 +3407,7 @@ describe('lib/optimizely', function() { }); }); - it('should return the right value from getFeatureVariable and send notification with featureEnabled true', function() { + it('should return the right value from getFeatureVariable when variable type is string and send notification with featureEnabled true', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'message', 'user1', { test_attribute: 'test_value', }); @@ -3332,6 +3428,33 @@ describe('lib/optimizely', function() { }); }); + it('should return the right value from getFeatureVariable when variable type is json and send notification with featureEnabled true', function() { + var result = optlyInstance.getFeatureVariable('test_feature', 'message_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + count: 2, + message: 'Hello audience', + }); + sinon.assert.calledWith(decisionListener, { + type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, + userId: 'user1', + attributes: { test_attribute: 'test_value' }, + decisionInfo: { + featureKey: 'test_feature', + featureEnabled: true, + variableKey: 'message_info', + variableValue: { + count: 2, + message: 'Hello audience', + }, + variableType: FEATURE_VARIABLE_TYPES.JSON, + source: DECISION_SOURCES.ROLLOUT, + sourceInfo: {}, + }, + }); + }); + it('should return the right value from getFeatureVariableBoolean and send notification with featureEnabled true', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature', 'new_content', 'user1', { test_attribute: 'test_value', @@ -3415,6 +3538,33 @@ describe('lib/optimizely', function() { }, }); }); + + it('should return the right value from getFeatureVariableJson and send notification with featureEnabled true', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature', 'message_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + count: 2, + message: 'Hello audience', + }); + sinon.assert.calledWith(decisionListener, { + type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, + userId: 'user1', + attributes: { test_attribute: 'test_value' }, + decisionInfo: { + featureKey: 'test_feature', + featureEnabled: true, + variableKey: 'message_info', + variableValue: { + count: 2, + message: 'Hello audience', + }, + variableType: FEATURE_VARIABLE_TYPES.JSON, + source: DECISION_SOURCES.ROLLOUT, + sourceInfo: {}, + }, + }); + }); }); describe('when the variation is toggled OFF', function() { @@ -3431,7 +3581,7 @@ describe('lib/optimizely', function() { }); }); - it('should return the default value from getFeatureVariable and send notification with featureEnabled false', function() { + it('should return the default value from getFeatureVariable when variable type is boolean and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'new_content', 'user1', { test_attribute: 'test_value', }); @@ -3452,7 +3602,7 @@ describe('lib/optimizely', function() { }); }); - it('should return the default value from getFeatureVariable and send notification with featureEnabled false', function() { + it('should return the default value from getFeatureVariable when variable type is double and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'price', 'user1', { test_attribute: 'test_value', }); @@ -3473,7 +3623,7 @@ describe('lib/optimizely', function() { }); }); - it('should return the default value from getFeatureVariable and send notification with featureEnabled false', function() { + it('should return the default value from getFeatureVariable when variable type is integer and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'lasers', 'user1', { test_attribute: 'test_value', }); @@ -3494,7 +3644,7 @@ describe('lib/optimizely', function() { }); }); - it('should return the default value from getFeatureVariable and send notification with featureEnabled false', function() { + it('should return the default value from getFeatureVariable when variable type is string and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'message', 'user1', { test_attribute: 'test_value', }); @@ -3515,6 +3665,30 @@ describe('lib/optimizely', function() { }); }); + it('should return the default value from getFeatureVariable when variable type is json and send notification with featureEnabled false', function() { + var result = optlyInstance.getFeatureVariable('test_feature', 'message_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + count: 1, + message: 'Hello' + }); + sinon.assert.calledWith(decisionListener, { + type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, + userId: 'user1', + attributes: { test_attribute: 'test_value' }, + decisionInfo: { + featureKey: 'test_feature', + featureEnabled: false, + variableKey: 'message_info', + variableValue: { count: 1, message: 'Hello' }, + variableType: FEATURE_VARIABLE_TYPES.JSON, + source: DECISION_SOURCES.ROLLOUT, + sourceInfo: {}, + }, + }); + }); + 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', @@ -3598,6 +3772,33 @@ describe('lib/optimizely', function() { }, }); }); + + it('should return the default value from getFeatureVariableJson and send notification with featureEnabled false', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature', 'message_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + count: 1, + message: 'Hello', + }); + sinon.assert.calledWith(decisionListener, { + type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, + userId: 'user1', + attributes: { test_attribute: 'test_value' }, + decisionInfo: { + featureKey: 'test_feature', + featureEnabled: false, + variableKey: 'message_info', + variableValue: { + count: 1, + message: 'Hello', + }, + variableType: FEATURE_VARIABLE_TYPES.JSON, + source: DECISION_SOURCES.ROLLOUT, + sourceInfo: {}, + }, + }); + }); }); }); @@ -3610,7 +3811,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the variable default value from getFeatureVariable and send notification with featureEnabled false', function() { + it('returns the variable default value from getFeatureVariable when variable type is boolean and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariable( 'test_feature_for_experiment', 'is_button_animated', @@ -3634,7 +3835,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the variable default value from getFeatureVariable and send notification with featureEnabled false', function() { + it('returns the variable default value from getFeatureVariable when variable type is double and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width', 'user1', { test_attribute: 'test_value', }); @@ -3655,7 +3856,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the variable default value from getFeatureVariable and send notification with featureEnabled false', function() { + it('returns the variable default value from getFeatureVariable when variable type is integer and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value', }); @@ -3676,7 +3877,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the variable default value from getFeatureVariable and send notification with featureEnabled false', function() { + it('returns the variable default value from getFeatureVariable when variable type is string and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value', }); @@ -3697,6 +3898,33 @@ describe('lib/optimizely', function() { }); }); + it('returns the variable default value from getFeatureVariable when variable type is json and send notification with featureEnabled false', function() { + var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + num_buttons: 0, + text: 'default value', + }); + sinon.assert.calledWith(decisionListener, { + type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, + userId: 'user1', + attributes: { test_attribute: 'test_value' }, + decisionInfo: { + featureKey: 'test_feature_for_experiment', + featureEnabled: false, + variableKey: 'button_info', + variableValue: { + num_buttons: 0, + text: 'default value', + }, + variableType: FEATURE_VARIABLE_TYPES.JSON, + source: DECISION_SOURCES.ROLLOUT, + sourceInfo: {}, + }, + }); + }); + it('returns the variable default value from getFeatureVariableBoolean and send notification with featureEnabled false', function() { var result = optlyInstance.getFeatureVariableBoolean( 'test_feature_for_experiment', @@ -3792,6 +4020,36 @@ describe('lib/optimizely', function() { }, }); }); + + it('returns the variable default value from getFeatureVariableJson and send notification with featureEnabled false', function() { + var result = optlyInstance.getFeatureVariableJson( + 'test_feature_for_experiment', + 'button_info', + 'user1', + { test_attribute: 'test_value' } + ); + assert.deepEqual(result, { + num_buttons: 0, + text: 'default value', + }); + sinon.assert.calledWith(decisionListener, { + type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, + userId: 'user1', + attributes: { test_attribute: 'test_value' }, + decisionInfo: { + featureKey: 'test_feature_for_experiment', + featureEnabled: false, + variableKey: 'button_info', + variableValue: { + num_buttons: 0, + text: 'default value', + }, + variableType: FEATURE_VARIABLE_TYPES.JSON, + source: DECISION_SOURCES.ROLLOUT, + sourceInfo: {}, + }, + }); + }); }); }); }); @@ -4489,7 +4747,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the right value from getFeatureVariable', function() { + it('returns the right value from getFeatureVariable when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable( 'test_feature_for_experiment', 'is_button_animated', @@ -4504,7 +4762,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the right value from getFeatureVariable', function() { + it('returns the right value from getFeatureVariable when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width', 'user1', { test_attribute: 'test_value', }); @@ -4516,7 +4774,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the right value from getFeatureVariable', function() { + it('returns the right value from getFeatureVariable when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value', }); @@ -4528,7 +4786,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the right value from getFeatureVariable', function() { + it('returns the right value from getFeatureVariable when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value', }); @@ -4540,6 +4798,21 @@ describe('lib/optimizely', function() { ); }); + it('returns the right value from getFeatureVariable when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + num_buttons: 1, + text: 'first variation', + }); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.INFO, + 'OPTIMIZELY: Value for variable "button_info" of feature flag "test_feature_for_experiment" is { "num_buttons": 1, "text": "first variation"} for user "user1"' + ); + }); + it('returns the right value from getFeatureVariableBoolean', function() { var result = optlyInstance.getFeatureVariableBoolean( 'test_feature_for_experiment', @@ -4597,12 +4870,27 @@ describe('lib/optimizely', function() { ); }); + it('returns the right value from getFeatureVariableJson', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + num_buttons: 1, + text: 'first variation', + }); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.INFO, + 'OPTIMIZELY: Value for variable "button_info" of feature flag "test_feature_for_experiment" is { "num_buttons": 1, "text": "first variation"} for user "user1"' + ); + }); + 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 getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable( 'test_feature_for_experiment', 'is_button_animated', @@ -4617,7 +4905,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width', 'user1', { test_attribute: 'test_value', }); @@ -4629,7 +4917,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value', }); @@ -4641,7 +4929,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value', }); @@ -4653,6 +4941,21 @@ describe('lib/optimizely', function() { ); }); + it('returns the variable default value from getFeatureVariable when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + num_buttons: 0, + text: 'default value', + }); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.INFO, + 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' + ); + }); + it('returns the variable default value from getFeatureVariableBoolean', function() { var result = optlyInstance.getFeatureVariableBoolean( 'test_feature_for_experiment', @@ -4712,6 +5015,24 @@ describe('lib/optimizely', function() { 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' ); }); + + it('returns the variable default value from getFeatureVariableJson', function() { + var result = optlyInstance.getFeatureVariableJson( + 'test_feature_for_experiment', + 'button_info', + 'user1', + { test_attribute: 'test_value' } + ); + assert.deepEqual(result, { + num_buttons: 0, + text: "default value", + }); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.INFO, + 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' + ); + }); }); }); @@ -4729,7 +5050,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable( 'test_feature_for_experiment', 'is_button_animated', @@ -4744,7 +5065,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width', 'user1', { test_attribute: 'test_value', }); @@ -4756,7 +5077,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value', }); @@ -4768,7 +5089,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value', }); @@ -4780,6 +5101,21 @@ describe('lib/optimizely', function() { ); }); + it('returns the variable default value from getFeatureVariable when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + num_buttons: 0, + text: "default value", + }); + 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_info".' + ); + }); + it('returns the variable default value from getFeatureVariableBoolean', function() { var result = optlyInstance.getFeatureVariableBoolean( 'test_feature_for_experiment', @@ -4836,6 +5172,21 @@ describe('lib/optimizely', function() { 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning default value for variable "button_txt".' ); }); + + it('returns the variable default value from getFeatureVariableJson', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + num_buttons: 0, + text: 'default value', + }); + 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_info".' + ); + }); }); }); @@ -4854,7 +5205,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the right value from getFeatureVariable', function() { + it('returns the right value from getFeatureVariable when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'new_content', 'user1', { test_attribute: 'test_value', }); @@ -4866,7 +5217,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the right value from getFeatureVariable', function() { + it('returns the right value from getFeatureVariable when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'price', 'user1', { test_attribute: 'test_value', }); @@ -4878,7 +5229,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the right value from getFeatureVariable', function() { + it('returns the right value from getFeatureVariable when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'lasers', 'user1', { test_attribute: 'test_value', }); @@ -4890,7 +5241,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the right value from getFeatureVariable', function() { + it('returns the right value from getFeatureVariable when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'message', 'user1', { test_attribute: 'test_value', }); @@ -4902,6 +5253,21 @@ describe('lib/optimizely', function() { ); }); + it('returns the right value from getFeatureVariable when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('test_feature', 'message_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + count: 2, + message: 'Hello audience', + }); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.INFO, + 'OPTIMIZELY: Value for variable "message_info" of feature flag "test_feature" is { "count": 2, "message": "Hello audience" } for user "user1"' + ); + }); + it('returns the right value from getFeatureVariableBoolean', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature', 'new_content', 'user1', { test_attribute: 'test_value', @@ -4950,12 +5316,27 @@ describe('lib/optimizely', function() { ); }); + it('returns the right value from getFeatureVariableJson', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature', 'message_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + count: 2, + message: 'Hello audience', + }); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.INFO, + 'OPTIMIZELY: Value for variable "message_info" of feature flag "test_feature" is { "count": 2, "message": "Hello audience" } for user "user1"' + ); + }); + 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 getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'new_content', 'user1', { test_attribute: 'test_value', }); @@ -4967,7 +5348,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'price', 'user1', { test_attribute: 'test_value', }); @@ -4979,7 +5360,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'lasers', 'user1', { test_attribute: 'test_value', }); @@ -4991,7 +5372,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'message', 'user1', { test_attribute: 'test_value', }); @@ -5003,6 +5384,21 @@ describe('lib/optimizely', function() { ); }); + it('returns the variable default value from getFeatureVariable when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('test_feature', 'message_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + count: 1, + message: 'Hello', + }); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.INFO, + 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' + ); + }); + it('returns the variable default value from getFeatureVariableBoolean', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature', 'new_content', 'user1', { test_attribute: 'test_value', @@ -5050,6 +5446,21 @@ describe('lib/optimizely', function() { 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' ); }); + + it('returns the variable default value from getFeatureVariableJson', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature', 'message_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + count: 1, + message: 'Hello' + }); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.INFO, + 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' + ); + }); }); }); @@ -5067,7 +5478,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'new_content', 'user1', { test_attribute: 'test_value', }); @@ -5079,7 +5490,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'price', 'user1', { test_attribute: 'test_value', }); @@ -5091,7 +5502,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'lasers', 'user1', { test_attribute: 'test_value', }); @@ -5103,7 +5514,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature', 'message', 'user1', { test_attribute: 'test_value', }); @@ -5115,6 +5526,21 @@ describe('lib/optimizely', function() { ); }); + it('returns the variable default value from getFeatureVariable when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('test_feature', 'message_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + count: 1, + message: '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_info".' + ); + }); + it('returns the variable default value from getFeatureVariableBoolean', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature', 'new_content', 'user1', { test_attribute: 'test_value', @@ -5162,6 +5588,21 @@ describe('lib/optimizely', function() { 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning default value for variable "message".' ); }); + + it('returns the variable default value from getFeatureVariableJson', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature', 'message_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + count: 1, + message: '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_info".' + ); + }); }); }); @@ -5174,7 +5615,7 @@ describe('lib/optimizely', function() { }); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'is_button_animated', 'user1', { test_attribute: 'test_value', }); @@ -5186,7 +5627,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width', 'user1', { test_attribute: 'test_value', }); @@ -5198,7 +5639,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons', 'user1', { test_attribute: 'test_value', }); @@ -5210,7 +5651,7 @@ describe('lib/optimizely', function() { ); }); - it('returns the variable default value from getFeatureVariable', function() { + it('returns the variable default value from getFeatureVariable when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt', 'user1', { test_attribute: 'test_value', }); @@ -5222,6 +5663,21 @@ describe('lib/optimizely', function() { ); }); + it('returns the variable default value from getFeatureVariable when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + num_buttons: 0, + text: 'default value', + }); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.INFO, + 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' + ); + }); + it('returns the variable default value from getFeatureVariableBoolean', function() { var result = optlyInstance.getFeatureVariableBoolean( 'test_feature_for_experiment', @@ -5272,9 +5728,25 @@ describe('lib/optimizely', function() { 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' ); }); + + it('returns the variable default value from getFeatureVariableJson', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); + assert.deepEqual(result, { + num_buttons: 0, + text: 'default value', + }); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.INFO, + 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' + ); + }); + }); - it('returns null from getFeatureVariable if user id is null', function() { + it('returns null from getFeatureVariable if user id is null when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'is_button_animated', null, { test_attribute: 'test_value', }); @@ -5286,7 +5758,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is undefined', function() { + it('returns null from getFeatureVariable if user id is undefined when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'is_button_animated', undefined, { test_attribute: 'test_value', }); @@ -5298,7 +5770,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is not provided', function() { + it('returns null from getFeatureVariable if user id is not provided when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'is_button_animated'); assert.strictEqual(result, null); sinon.assert.calledWith( @@ -5308,7 +5780,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is null', function() { + it('returns null from getFeatureVariable if user id is null when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width', null, { test_attribute: 'test_value', }); @@ -5320,7 +5792,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is undefined', function() { + it('returns null from getFeatureVariable if user id is undefined when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width', undefined, { test_attribute: 'test_value', }); @@ -5332,7 +5804,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is not provided', function() { + it('returns null from getFeatureVariable if user id is not provided when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width'); assert.strictEqual(result, null); sinon.assert.calledWith( @@ -5342,7 +5814,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is null', function() { + it('returns null from getFeatureVariable if user id is null when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons', null, { test_attribute: 'test_value', }); @@ -5354,7 +5826,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is undefined', function() { + it('returns null from getFeatureVariable if user id is undefined when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons', undefined, { test_attribute: 'test_value', }); @@ -5366,7 +5838,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is not provided', function() { + it('returns null from getFeatureVariable if user id is not provided when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons'); assert.strictEqual(result, null); sinon.assert.calledWith( @@ -5376,7 +5848,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is null', function() { + it('returns null from getFeatureVariable if user id is null when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt', null, { test_attribute: 'test_value', }); @@ -5388,7 +5860,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is undefined', function() { + it('returns null from getFeatureVariable if user id is undefined when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt', undefined, { test_attribute: 'test_value', }); @@ -5400,7 +5872,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if user id is not provided', function() { + it('returns null from getFeatureVariable if user id is not provided when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt'); assert.strictEqual(result, null); sinon.assert.calledWith( @@ -5410,6 +5882,40 @@ describe('lib/optimizely', function() { ); }); + it('returns null from getFeatureVariable if user id is null when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info', 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 getFeatureVariable if user id is undefined when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info', 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 getFeatureVariable if user id is not provided when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info'); + 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 when called with a non-boolean variable', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'button_width', 'user1'); assert.strictEqual(result, null); @@ -5454,6 +5960,16 @@ describe('lib/optimizely', function() { ); }); + it('returns null from getFeatureVariableJson when called with a non-json variable', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature_for_experiment', 'button_txt', 'user1'); + assert.strictEqual(result, null); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.WARNING, + 'OPTIMIZELY: Requested variable type "json", but variable is of type "string". 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', @@ -5596,6 +6112,40 @@ describe('lib/optimizely', function() { ); }); + it('returns null from getFeatureVariableJson if user id is null', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature_for_experiment', 'button_info', 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 getFeatureVariableJson if user id is undefined', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature_for_experiment', 'button_info', 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 getFeatureVariableJson if user id is not provided', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature_for_experiment', 'button_info'); + 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() { @@ -5648,9 +6198,25 @@ describe('lib/optimizely', function() { ); }); }); + + describe('invalid json', function() { + beforeEach(function() { + sandbox.stub(projectConfig, 'getVariableValueForVariation').returns('zzz44.55'); + }); + + it('should return null and log an error', function() { + var result = optlyInstance.getFeatureVariableJson('test_feature_for_experiment', 'button_info', 'user1'); + assert.strictEqual(result, null); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.ERROR, + 'PROJECT_CONFIG: Unable to cast value zzz44.55 to type json, returning null.' + ); + }); + }); }); - it('returns null from getFeatureVariable if the argument feature key is invalid', function() { + it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'is_button_animated', 'user1'); assert.strictEqual(result, null); sinon.assert.calledWith( @@ -5660,7 +6226,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if the argument feature key is invalid', function() { + it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is double', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_width', 'user1'); assert.strictEqual(result, null); sinon.assert.calledWith( @@ -5670,7 +6236,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if the argument feature key is invalid', function() { + it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'num_buttons', 'user1'); assert.strictEqual(result, null); sinon.assert.calledWith( @@ -5680,7 +6246,7 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if the argument feature key is invalid', function() { + it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is string', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_txt', 'user1'); assert.strictEqual(result, null); sinon.assert.calledWith( @@ -5690,45 +6256,13 @@ describe('lib/optimizely', function() { ); }); - it('returns null from getFeatureVariable if the argument variable key is invalid', function() { - var result = optlyInstance.getFeatureVariable( - 'test_feature_for_experiment', - 'thisIsNotAVariableKey****', - 'user1' - ); - assert.strictEqual(result, null); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.ERROR, - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); - }); - - it('returns null from getFeatureVariable if the argument variable key is invalid', function() { - var result = optlyInstance.getFeatureVariable( - 'test_feature_for_experiment', - 'thisIsNotAVariableKey****', - 'user1' - ); - assert.strictEqual(result, null); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.ERROR, - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); - }); - - it('returns null from getFeatureVariable if the argument variable key is invalid', function() { - var result = optlyInstance.getFeatureVariable( - 'test_feature_for_experiment', - 'thisIsNotAVariableKey****', - 'user1' - ); + it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is json', function() { + var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_info', 'user1'); assert.strictEqual(result, null); sinon.assert.calledWith( createdLogger.log, LOG_LEVEL.ERROR, - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' + 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' ); }); @@ -5786,6 +6320,16 @@ describe('lib/optimizely', function() { ); }); + it('returns null from getFeatureVariableJson if the argument feature key is invalid', function() { + var result = optlyInstance.getFeatureVariableJson('thisIsNotAValidKey<><><>', 'button_info', 'user1'); + assert.strictEqual(result, null); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.ERROR, + 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' + ); + }); + it('returns null from getFeatureVariableBoolean if the argument variable key is invalid', function() { var result = optlyInstance.getFeatureVariableBoolean( 'test_feature_for_experiment', @@ -5842,6 +6386,20 @@ describe('lib/optimizely', function() { ); }); + it('returns null from getFeatureVariableJson if the argument variable key is invalid', function() { + var result = optlyInstance.getFeatureVariableJson( + 'test_feature_for_experiment', + 'thisIsNotAVariableKey****', + 'user1' + ); + assert.strictEqual(result, null); + sinon.assert.calledWith( + createdLogger.log, + LOG_LEVEL.ERROR, + 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' + ); + }); + it('returns null from getFeatureVariable when optimizely object is not a valid instance', function() { var instance = new Optimizely({ datafile: {}, @@ -5876,23 +6434,6 @@ describe('lib/optimizely', function() { assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableBoolean')); }); - it('returns null from getFeatureVariable when optimizely object is not a valid instance', function() { - var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - logger: createdLogger, - }); - - createdLogger.log.reset(); - - instance.getFeatureVariable('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = createdLogger.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariable')); - }); - it('returns null from getFeatureVariableDouble when optimizely object is not a valid instance', function() { var instance = new Optimizely({ datafile: {}, @@ -5910,23 +6451,6 @@ describe('lib/optimizely', function() { assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableDouble')); }); - it('returns null from getFeatureVariable when optimizely object is not a valid instance', function() { - var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - logger: createdLogger, - }); - - createdLogger.log.reset(); - - instance.getFeatureVariable('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = createdLogger.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariable')); - }); - it('returns null from getFeatureVariableInteger when optimizely object is not a valid instance', function() { var instance = new Optimizely({ datafile: {}, @@ -5944,7 +6468,7 @@ describe('lib/optimizely', function() { assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableInteger')); }); - it('returns null from getFeatureVariable when optimizely object is not a valid instance', function() { + it('returns null from getFeatureVariableString when optimizely object is not a valid instance', function() { var instance = new Optimizely({ datafile: {}, errorHandler: errorHandler, @@ -5954,14 +6478,14 @@ describe('lib/optimizely', function() { createdLogger.log.reset(); - instance.getFeatureVariable('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + instance.getFeatureVariableString('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); sinon.assert.calledOnce(createdLogger.log); var logMessage = createdLogger.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariable')); + assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableString')); }); - it('returns null from getFeatureVariableString when optimizely object is not a valid instance', function() { + it('returns null from getFeatureVariableJson when optimizely object is not a valid instance', function() { var instance = new Optimizely({ datafile: {}, errorHandler: errorHandler, @@ -5971,11 +6495,11 @@ describe('lib/optimizely', function() { createdLogger.log.reset(); - instance.getFeatureVariableString('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + instance.getFeatureVariableJson('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); sinon.assert.calledOnce(createdLogger.log); var logMessage = createdLogger.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableString')); + assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableJson')); }); }); }); diff --git a/packages/optimizely-sdk/lib/tests/test_data.js b/packages/optimizely-sdk/lib/tests/test_data.js index d0592c359..eeb0f1215 100644 --- a/packages/optimizely-sdk/lib/tests/test_data.js +++ b/packages/optimizely-sdk/lib/tests/test_data.js @@ -387,6 +387,13 @@ var configWithFeatures = { id: '6327227708866560', defaultValue: 'Hello', }, + { + type: 'string', + subType: 'json', + key: 'message_info', + id: '8765345281230956', + defaultValue: '{ "count": 1, "message": "Hello" }', + }, ], }, { @@ -444,13 +451,20 @@ var configWithFeatures = { key: 'button_txt', id: '5636734406623232', defaultValue: 'Buy me', - }, + }, { type: 'double', key: 'button_width', id: '6199684360044544', defaultValue: '50.55', }, + { + type: 'string', + subType: 'json', + key: 'button_info', + id: '1547854156498475', + defaultValue: '{ "num_buttons": 0, "text": "default value"}', + }, ], }, { @@ -532,6 +546,10 @@ var configWithFeatures = { id: '6199684360044544', value: '20.25', }, + { + id: '1547854156498475', + value: '{ "num_buttons": 1, "text": "first variation"}', + }, ], }, { @@ -555,6 +573,10 @@ var configWithFeatures = { id: '6199684360044544', value: '50.55', }, + { + id: '1547854156498475', + value: '{ "num_buttons": 2, "text": "second variation"}', + }, ], }, { @@ -578,6 +600,10 @@ var configWithFeatures = { id: '6199684360044544', value: '99.99', }, + { + id: '1547854156498475', + value: '{ "num_buttons": 3, "text": "third variation"}', + }, ], }, ], @@ -835,6 +861,10 @@ var configWithFeatures = { id: '6327227708866560', value: 'Hello audience', }, + { + id: "8765345281230956", + value: '{ "count": 2, "message": "Hello audience" }', + } ], }, ], @@ -874,6 +904,10 @@ var configWithFeatures = { id: '6327227708866560', value: 'Hello', }, + { + id: '8765345281230956', + value: '{ "count": 1, "message": "Hello" }', + } ], }, ], @@ -1094,6 +1128,10 @@ export var datafileWithFeaturesExpectedData = { value: 'Hello audience', id: '6327227708866560', }, + { + id: '8765345281230956', + value: '{ "count": 2, "message": "Hello audience" }', + }, ], featureEnabled: true, key: '594032', @@ -1127,6 +1165,10 @@ export var datafileWithFeaturesExpectedData = { value: 'Hello audience', id: '6327227708866560', }, + { + id: '8765345281230956', + value: '{ "count": 2, "message": "Hello audience" }', + }, ], featureEnabled: true, key: '594032', @@ -1158,6 +1200,10 @@ export var datafileWithFeaturesExpectedData = { value: 'Hello', id: '6327227708866560', }, + { + id: '8765345281230956', + value: '{ "count": 1, "message": "Hello" }', + }, ], featureEnabled: false, key: '594038', @@ -1191,6 +1237,10 @@ export var datafileWithFeaturesExpectedData = { value: 'Hello', id: '6327227708866560', }, + { + id: '8765345281230956', + value: '{ "count": 1, "message": "Hello" }', + }, ], featureEnabled: false, key: '594038', @@ -1354,6 +1404,10 @@ export var datafileWithFeaturesExpectedData = { id: '6327227708866560', value: 'Hello audience', }, + 8765345281230956: { + id:'8765345281230956', + value: '{ "count": 2, "message": "Hello audience" }', + } }, 594038: { 4919852825313280: { @@ -1372,6 +1426,10 @@ export var datafileWithFeaturesExpectedData = { id: '6327227708866560', value: 'Hello', }, + 8765345281230956: { + id:'8765345281230956', + value: '{ "count": 1, "message": "Hello" }', + } }, 594061: { 5060590313668608: { @@ -1426,6 +1484,10 @@ export var datafileWithFeaturesExpectedData = { value: '20.25', id: '6199684360044544', }, + 1547854156498475: { + id:'1547854156498475', + value: '{ "num_buttons": 1, "text": "first variation"}', + }, }, 594097: { 4792309476491264: { @@ -1444,6 +1506,10 @@ export var datafileWithFeaturesExpectedData = { value: '50.55', id: '6199684360044544', }, + 1547854156498475: { + id:'1547854156498475', + value: '{ "num_buttons": 2, "text": "second variation"}', + }, }, 594099: { 4792309476491264: { @@ -1462,6 +1528,10 @@ export var datafileWithFeaturesExpectedData = { value: '99.99', id: '6199684360044544', }, + 1547854156498475: { + id:'1547854156498475', + value: '{ "num_buttons": 3, "text": "third variation"}', + }, }, 595008: {}, 595009: {}, @@ -1530,6 +1600,12 @@ export var datafileWithFeaturesExpectedData = { type: 'string', id: '6327227708866560', }, + { + type: 'json', + key: 'message_info', + id: '8765345281230956', + defaultValue: '{ "count": 1, "message": "Hello" }', + }, ], experimentIds: [], rolloutId: '594030', @@ -1560,6 +1636,12 @@ export var datafileWithFeaturesExpectedData = { type: 'string', id: '6327227708866560', }, + message_info: { + type: 'json', + key: 'message_info', + id: '8765345281230956', + defaultValue: '{ "count": 1, "message": "Hello" }', + }, }, }, test_feature_2: { @@ -1646,6 +1728,12 @@ export var datafileWithFeaturesExpectedData = { type: 'double', id: '6199684360044544', }, + { + type: 'json', + key: 'button_info', + id: '1547854156498475', + defaultValue: "{ \"num_buttons\": 0, \"text\": \"default value\"}" + }, ], experimentIds: ['594098'], rolloutId: '', @@ -1676,6 +1764,12 @@ export var datafileWithFeaturesExpectedData = { type: 'double', id: '6199684360044544', }, + button_info: { + defaultValue: "{ \"num_buttons\": 0, \"text\": \"default value\"}", + id: '1547854156498475', + key: 'button_info', + type: 'json', + }, }, }, // This feature should have a groupId assigned because its experiment is in a group diff --git a/packages/optimizely-sdk/lib/utils/enums/index.js b/packages/optimizely-sdk/lib/utils/enums/index.js index 2da75b1a0..a664b7687 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.js +++ b/packages/optimizely-sdk/lib/utils/enums/index.js @@ -209,6 +209,7 @@ export var FEATURE_VARIABLE_TYPES = { DOUBLE: 'double', INTEGER: 'integer', STRING: 'string', + JSON: 'json', }; /*