From 2ff9d2e2ad2ebe0ebf9381a40772c8e0100ff8bd Mon Sep 17 00:00:00 2001 From: James Fox Date: Tue, 12 Feb 2019 13:43:58 -0800 Subject: [PATCH 01/12] add the ability to provide custom condition evaluators rebase with latest audience editor changes updates per code review --- .../lib/core/audience_evaluator/index.js | 112 ++++++++++++------ .../core/audience_evaluator/index.tests.js | 45 +++---- .../index.js | 1 - .../lib/core/decision_service/index.js | 9 +- .../lib/core/decision_service/index.tests.js | 4 +- .../optimizely-sdk/lib/optimizely/index.js | 1 + .../lib/optimizely/index.tests.js | 6 +- .../optimizely-sdk/lib/utils/enums/index.js | 1 + 8 files changed, 111 insertions(+), 68 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.js index 419086bbe..d1059a2d6 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.js @@ -16,54 +16,88 @@ var conditionTreeEvaluator = require('../condition_tree_evaluator'); var customAttributeConditionEvaluator = require('../custom_attribute_condition_evaluator'); var enums = require('../../utils/enums'); +var fns = require('../../utils/fns'); var sprintf = require('@optimizely/js-sdk-utils').sprintf; +var ERROR_MESSAGES = enums.ERROR_MESSAGES; var LOG_LEVEL = enums.LOG_LEVEL; var LOG_MESSAGES = enums.LOG_MESSAGES; var MODULE_NAME = 'AUDIENCE_EVALUATOR'; -module.exports = { - /** - * Determine if the given user attributes satisfy the given audience conditions - * @param {Array|String|null|undefined} audienceConditions Audience conditions to match the user attributes against - can be an array - * of audience IDs, a nested array of conditions, or a single leaf condition. - * Examples: ["5", "6"], ["and", ["or", "1", "2"], "3"], "1" - * @param {Object} audiencesById Object providing access to full audience objects for audience IDs - * contained in audienceConditions. Keys should be audience IDs, values - * should be full audience objects with conditions properties - * @param {Object} [userAttributes] User attributes which will be used in determining if audience conditions - * are met. If not provided, defaults to an empty object - * @param {Object} logger Logger instance. - * @return {Boolean} true if the user attributes match the given audience conditions, false - * otherwise - */ - evaluate: function(audienceConditions, audiencesById, userAttributes, logger) { - // if there are no audiences, return true because that means ALL users are included in the experiment - if (!audienceConditions || audienceConditions.length === 0) { - return true; - } - if (!userAttributes) { - userAttributes = {}; - } +/** + * Construct an instance of AudienceEvaluator with a given logger and options + * @param {Logger} logger The Logger instance + * @param {Object=} __exploratoryConditionEvaluators A map of condition evaluators provided by the consumer. This enables matching + * condition types which are not supported natively by the SDK. Note that built in + * Optimizely evaluators cannot be overridden. + * @constructor + */ +function AudienceEvaluator(logger, __exploratoryConditionEvaluators) { + this.logger = logger; + this.typeToEvaluatorMap = fns.assignIn({}, __exploratoryConditionEvaluators, { + 'custom_attribute': customAttributeConditionEvaluator + }); +} - var evaluateConditionWithUserAttributes = function(condition) { - return customAttributeConditionEvaluator.evaluate(condition, userAttributes, logger); - }; +/** + * Determine if the given user attributes satisfy the given audience conditions + * @param {Array|String|null|undefined} audienceConditions Audience conditions to match the user attributes against - can be an array + * of audience IDs, a nested array of conditions, or a single leaf condition. + * Examples: ["5", "6"], ["and", ["or", "1", "2"], "3"], "1" + * @param {Object} audiencesById Object providing access to full audience objects for audience IDs + * contained in audienceConditions. Keys should be audience IDs, values + * should be full audience objects with conditions properties + * @param {Object} [userAttributes] User attributes which will be used in determining if audience conditions + * are met. If not provided, defaults to an empty object + * @return {Boolean} true if the user attributes match the given audience conditions, false + * otherwise + */ +AudienceEvaluator.prototype.evaluate = function(audienceConditions, audiencesById, userAttributes) { + // if there are no audiences, return true because that means ALL users are included in the experiment + if (!audienceConditions || audienceConditions.length === 0) { + return true; + } - var evaluateAudience = function(audienceId) { - var audience = audiencesById[audienceId]; - if (audience) { - logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.EVALUATING_AUDIENCE, MODULE_NAME, audienceId, JSON.stringify(audience.conditions))); - var result = conditionTreeEvaluator.evaluate(audience.conditions, evaluateConditionWithUserAttributes); - var resultText = result === null ? 'UNKNOWN' : result.toString().toUpperCase(); - logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT, MODULE_NAME, audienceId, resultText)); - return result; - } + if (!userAttributes) { + userAttributes = {}; + } - return null; - }; + var evaluateAudience = function(audienceId) { + var audience = audiencesById[audienceId]; + if (audience) { + this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.EVALUATING_AUDIENCE, MODULE_NAME, audienceId, JSON.stringify(audience.conditions))); + var result = conditionTreeEvaluator.evaluate(audience.conditions, this.evaluateConditionWithUserAttributes.bind(this, userAttributes)); + var resultText = result === null ? 'UNKNOWN' : result.toString().toUpperCase(); + this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT, MODULE_NAME, audienceId, resultText)); + return result; + } - return conditionTreeEvaluator.evaluate(audienceConditions, evaluateAudience) || false; - }, + return null; + }.bind(this); + + return conditionTreeEvaluator.evaluate(audienceConditions, evaluateAudience) || false; +}; + +/** + * Wrapper around evaluator.evaluate that is passed to the conditionTreeEvaluator. + * Evaluates the condition provided given the user attributes if an evaluator has been defined for the condition type. + * @param {Object} userAttributes A map of user attributes. + * @param {Object} condition A single condition object to evaluate. + * @return {Boolean|null} true if the condition is satisfied, null if a matcher is not found. + */ +AudienceEvaluator.prototype.evaluateConditionWithUserAttributes = function(userAttributes, condition) { + var evaluator = this.typeToEvaluatorMap[condition.type]; + if (!evaluator) { + this.logger.log(LOG_LEVEL.WARNING, sprintf(LOG_MESSAGES.UNKNOWN_CONDITION_TYPE, MODULE_NAME, JSON.stringify(condition))); + return null; + } + try { + return evaluator.evaluate(condition, userAttributes, this.logger); + } catch (err) { + this.logger.log(LOG_LEVEL.ERROR, sprintf(ERROR_MESSAGES.CONDITION_EVALUATOR_ERROR, MODULE_NAME, condition.type, err.message)); + } + return null; }; + +module.exports = AudienceEvaluator; diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js index 7ea283b90..d7503d612 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js @@ -13,9 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -var audienceEvaluator = require('./'); +var AudienceEvaluator = require('./'); var chai = require('chai'); -var sprintf = require('@optimizely/js-sdk-utils').sprintf; var conditionTreeEvaluator = require('../condition_tree_evaluator'); var customAttributeConditionEvaluator = require('../custom_attribute_condition_evaluator'); var sinon = require('sinon'); @@ -53,10 +52,14 @@ var audiencesById = { }; describe('lib/core/audience_evaluator', function() { + var audienceEvaluator; + var mockLogger = logger.createLogger({logLevel: LOG_LEVEL.INFO}); + beforeEach(function() { + audienceEvaluator = new AudienceEvaluator(mockLogger); + }); + describe('APIs', function() { describe('evaluate', function() { - var mockLogger = logger.createLogger({logLevel: LOG_LEVEL.INFO}); - beforeEach(function () { sinon.stub(mockLogger, 'log'); }); @@ -66,11 +69,11 @@ describe('lib/core/audience_evaluator', function() { }); it('should return true if there are no audiences', function() { - assert.isTrue(audienceEvaluator.evaluate([], audiencesById, {}, mockLogger)); + assert.isTrue(audienceEvaluator.evaluate([], audiencesById, {})); }); it('should return false if there are audiences but no attributes', function() { - assert.isFalse(audienceEvaluator.evaluate(['0'], audiencesById, {}, mockLogger)); + assert.isFalse(audienceEvaluator.evaluate(['0'], audiencesById, {})); }); it('should return true if any of the audience conditions are met', function() { @@ -87,9 +90,9 @@ describe('lib/core/audience_evaluator', function() { 'device_model': 'iphone', }; - assert.isTrue(audienceEvaluator.evaluate(['0', '1'], audiencesById, iphoneUsers, mockLogger)); - assert.isTrue(audienceEvaluator.evaluate(['0', '1'], audiencesById, chromeUsers, mockLogger)); - assert.isTrue(audienceEvaluator.evaluate(['0', '1'], audiencesById, iphoneChromeUsers, mockLogger)); + assert.isTrue(audienceEvaluator.evaluate(['0', '1'], audiencesById, iphoneUsers)); + assert.isTrue(audienceEvaluator.evaluate(['0', '1'], audiencesById, chromeUsers)); + assert.isTrue(audienceEvaluator.evaluate(['0', '1'], audiencesById, iphoneChromeUsers)); }); it('should return false if none of the audience conditions are met', function() { @@ -106,13 +109,13 @@ describe('lib/core/audience_evaluator', function() { 'device_model': 'nexus5', }; - assert.isFalse(audienceEvaluator.evaluate(['0', '1'], audiencesById, nexusUsers, mockLogger)); - assert.isFalse(audienceEvaluator.evaluate(['0', '1'], audiencesById, safariUsers, mockLogger)); - assert.isFalse(audienceEvaluator.evaluate(['0', '1'], audiencesById, nexusSafariUsers, mockLogger)); + assert.isFalse(audienceEvaluator.evaluate(['0', '1'], audiencesById, nexusUsers)); + assert.isFalse(audienceEvaluator.evaluate(['0', '1'], audiencesById, safariUsers)); + assert.isFalse(audienceEvaluator.evaluate(['0', '1'], audiencesById, nexusSafariUsers)); }); it('should return true if no attributes are passed and the audience conditions evaluate to true in the absence of attributes', function() { - assert.isTrue(audienceEvaluator.evaluate(['2'], audiencesById, null, mockLogger)); + assert.isTrue(audienceEvaluator.evaluate(['2'], audiencesById, null)); }); describe('complex audience conditions', function() { @@ -199,9 +202,9 @@ describe('lib/core/audience_evaluator', function() { }); customAttributeConditionEvaluator.evaluate.returns(false); var userAttributes = { device_model: 'android' }; - var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes, mockLogger); + var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); assert.isFalse(result); }); }); @@ -224,9 +227,9 @@ describe('lib/core/audience_evaluator', function() { }); customAttributeConditionEvaluator.evaluate.returns(null); var userAttributes = { device_model: 5.5 }; - var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes, mockLogger); + var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); assert.isFalse(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); @@ -239,9 +242,9 @@ describe('lib/core/audience_evaluator', function() { }); customAttributeConditionEvaluator.evaluate.returns(true); var userAttributes = { device_model: 'iphone' }; - var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes, mockLogger); + var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); assert.isTrue(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); @@ -254,9 +257,9 @@ describe('lib/core/audience_evaluator', function() { }); customAttributeConditionEvaluator.evaluate.returns(false); var userAttributes = { device_model: 'android' }; - var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes, mockLogger); + var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); assert.isFalse(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js index 8f83208c3..3be27051d 100644 --- a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js +++ b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js @@ -56,7 +56,6 @@ EVALUATORS_BY_MATCH_TYPE[SUBSTRING_MATCH_TYPE] = substringEvaluator; */ function evaluate(condition, userAttributes, logger) { if (condition.type !== CUSTOM_ATTRIBUTE_CONDITION_TYPE) { - logger.log(LOG_LEVEL.WARNING, sprintf(LOG_MESSAGES.UNKNOWN_CONDITION_TYPE, MODULE_NAME, JSON.stringify(condition))); return null; } diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.js b/packages/optimizely-sdk/lib/core/decision_service/index.js index 45b5e2c4e..3c6b9193c 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.js @@ -14,7 +14,7 @@ * limitations under the License. * ***************************************************************************/ -var audienceEvaluator = require('../audience_evaluator'); +var AudienceEvaluator = require('../audience_evaluator'); var bucketer = require('../bucketer'); var enums = require('../../utils/enums'); var fns = require('../../utils/fns'); @@ -49,9 +49,10 @@ var DECISION_SOURCES = enums.DECISION_SOURCES; * @returns {Object} */ function DecisionService(options) { - this.userProfileService = options.userProfileService || null; - this.logger = options.logger; + this.audienceEvaluator = new AudienceEvaluator(options.logger, options.__exploratoryConditionEvaluators); this.forcedVariationMap = {}; + this.logger = options.logger; + this.userProfileService = options.userProfileService || null; } /** @@ -171,7 +172,7 @@ DecisionService.prototype.__checkIfUserIsInAudience = function(configObj, experi var experimentAudienceConditions = projectConfig.getExperimentAudienceConditions(configObj, experimentKey); var audiencesById = projectConfig.getAudiencesById(configObj); this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.EVALUATING_AUDIENCES_COMBINED, MODULE_NAME, experimentKey, JSON.stringify(experimentAudienceConditions))); - var result = audienceEvaluator.evaluate(experimentAudienceConditions, audiencesById, attributes, this.logger); + var result = this.audienceEvaluator.evaluate(experimentAudienceConditions, audiencesById, attributes); this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, MODULE_NAME, experimentKey, result.toString().toUpperCase())); if (!result) { 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 5e46493e1..cb6b08c94 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js @@ -28,7 +28,7 @@ var sprintf = require('@optimizely/js-sdk-utils').sprintf; var testData = require('../../tests/test_data').getTestProjectConfig(); var testDataWithFeatures = require('../../tests/test_data').getTestProjectConfigWithFeatures(); var jsonSchemaValidator = require('../../utils/json_schema_validator'); -var audienceEvaluator = require('../audience_evaluator'); +var AudienceEvaluator = require('../audience_evaluator'); var chai = require('chai'); var sinon = require('sinon'); @@ -420,7 +420,7 @@ describe('lib/core/decision_service', function() { var __audienceEvaluateSpy; beforeEach(function() { - __audienceEvaluateSpy = sinon.spy(audienceEvaluator, 'evaluate'); + __audienceEvaluateSpy = sinon.spy(AudienceEvaluator.prototype, 'evaluate'); }); afterEach(function() { diff --git a/packages/optimizely-sdk/lib/optimizely/index.js b/packages/optimizely-sdk/lib/optimizely/index.js index 0bded5ca2..4afae5fd4 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.js +++ b/packages/optimizely-sdk/lib/optimizely/index.js @@ -100,6 +100,7 @@ function Optimizely(config) { this.decisionService = decisionService.createDecisionService({ userProfileService: userProfileService, logger: this.logger, + __exploratoryConditionEvaluators: config.__exploratoryConditionEvaluators }); this.notificationCenter = notificationCenter.createNotificationCenter({ diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index 81fe1fda0..d26f9f750 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -15,7 +15,7 @@ ***************************************************************************/ var Optimizely = require('./'); -var audienceEvaluator = require('../core/audience_evaluator'); +var AudienceEvaluator = require('../core/audience_evaluator'); var bluebird = require('bluebird'); var bucketer = require('../core/bucketer'); var projectConfigManager = require('../core/project_config/project_config_manager'); @@ -167,6 +167,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: userProfileServiceInstance, logger: createdLogger, + __exploratoryConditionEvaluators: undefined }); var logMessage = createdLogger.log.args[0][1]; @@ -189,6 +190,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: null, logger: createdLogger, + __exploratoryConditionEvaluators: undefined }); var logMessage = createdLogger.log.args[0][1]; @@ -4363,6 +4365,7 @@ describe('lib/optimizely', function() { logToConsole: false, }); var optlyInstance; + var audienceEvaluator; beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', @@ -4374,6 +4377,7 @@ describe('lib/optimizely', function() { logger: createdLogger, isValidInstance: true, }); + audienceEvaluator = AudienceEvaluator.prototype; sandbox.stub(eventDispatcher, 'dispatchEvent'); sandbox.stub(errorHandler, 'handleError'); diff --git a/packages/optimizely-sdk/lib/utils/enums/index.js b/packages/optimizely-sdk/lib/utils/enums/index.js index 07874507a..10f573de6 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.js +++ b/packages/optimizely-sdk/lib/utils/enums/index.js @@ -26,6 +26,7 @@ exports.LOG_LEVEL = { }; exports.ERROR_MESSAGES = { + CONDITION_EVALUATOR_ERROR: '%s: Error evaluating condition type %s: %s', DATAFILE_AND_SDK_KEY_MISSING: '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely', EXPERIMENT_KEY_NOT_IN_DATAFILE: '%s: Experiment key %s is not in datafile.', FEATURE_NOT_IN_DATAFILE: '%s: Feature key %s is not in datafile.', From 7015e7dd1918af78182379beb6c4e4cd48dafe7c Mon Sep 17 00:00:00 2001 From: James Fox Date: Fri, 7 Jun 2019 14:24:36 -0700 Subject: [PATCH 02/12] fix tests --- .../core/audience_evaluator/index.tests.js | 8 ++--- .../index.tests.js | 36 ------------------- .../lib/optimizely/index.tests.js | 18 ++++------ 3 files changed, 10 insertions(+), 52 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js index d7503d612..ff594a755 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js @@ -204,7 +204,7 @@ describe('lib/core/audience_evaluator', function() { var userAttributes = { device_model: 'android' }; var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); assert.isFalse(result); }); }); @@ -229,7 +229,7 @@ describe('lib/core/audience_evaluator', function() { var userAttributes = { device_model: 5.5 }; var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); assert.isFalse(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); @@ -244,7 +244,7 @@ describe('lib/core/audience_evaluator', function() { var userAttributes = { device_model: 'iphone' }; var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); assert.isTrue(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); @@ -259,7 +259,7 @@ describe('lib/core/audience_evaluator', function() { var userAttributes = { device_model: 'android' }; var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); assert.isFalse(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js index 9a1e7f4ef..f936c604f 100644 --- a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js +++ b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js @@ -85,42 +85,6 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { assert.isTrue(customAttributeEvaluator.evaluate(doubleCondition, userAttributes, mockLogger)); }); - it('should log and return null when condition has an invalid type property', function() { - var result = customAttributeEvaluator.evaluate( - { match: 'exact', name: 'weird_condition', type: 'weird', value: 'hi' }, - { weird_condition: 'bye' }, - mockLogger - ); - assert.isNull(result); - sinon.assert.calledOnce(mockLogger.log); - sinon.assert.calledWithExactly(mockLogger.log, LOG_LEVEL.WARNING, - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"exact","name":"weird_condition","type":"weird","value":"hi"} has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.'); - }); - - it('should log and return null when condition has no type property', function() { - var result = customAttributeEvaluator.evaluate( - { match: 'exact', name: 'weird_condition', value: 'hi' }, - { weird_condition: 'bye' }, - mockLogger - ); - assert.isNull(result); - sinon.assert.calledOnce(mockLogger.log); - sinon.assert.calledWithExactly(mockLogger.log, LOG_LEVEL.WARNING, - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"exact","name":"weird_condition","value":"hi"} has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.'); - }); - - it('should log and return null when condition has an invalid match property', function() { - var result = customAttributeEvaluator.evaluate( - { match: 'weird', name: 'weird_condition', type: 'custom_attribute', value: 'hi' }, - { weird_condition: 'bye' }, - mockLogger - ); - assert.isNull(result); - sinon.assert.calledOnce(mockLogger.log); - sinon.assert.calledWithExactly(mockLogger.log, LOG_LEVEL.WARNING, - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"weird","name":"weird_condition","type":"custom_attribute","value":"hi"} uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.'); - }); - describe('exists match type', function() { var existsCondition = { match: 'exists', diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index d26f9f750..d83177b9b 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -4409,8 +4409,7 @@ describe('lib/optimizely', function() { audienceEvaluator.evaluate, optlyInstance.projectConfigManager.getConfig().experiments[2].audienceConditions, optlyInstance.projectConfigManager.getConfig().audiencesById, - { house: 'Welcome to Slytherin!', lasers: 45.5 }, - createdLogger + { house: 'Welcome to Slytherin!', lasers: 45.5 } ); }); @@ -4427,8 +4426,7 @@ describe('lib/optimizely', function() { audienceEvaluator.evaluate, optlyInstance.projectConfigManager.getConfig().experiments[2].audienceConditions, optlyInstance.projectConfigManager.getConfig().audiencesById, - { house: 'Hufflepuff', lasers: 45.5 }, - createdLogger + { house: 'Hufflepuff', lasers: 45.5 } ); }); @@ -4461,8 +4459,7 @@ describe('lib/optimizely', function() { audienceEvaluator.evaluate, optlyInstance.projectConfigManager.getConfig().rollouts[2].experiments[0].audienceConditions, optlyInstance.projectConfigManager.getConfig().audiencesById, - { house: '...Slytherinnn...sss.', favorite_ice_cream: 'matcha' }, - createdLogger + { house: '...Slytherinnn...sss.', favorite_ice_cream: 'matcha' } ); }); @@ -4477,8 +4474,7 @@ describe('lib/optimizely', function() { audienceEvaluator.evaluate, optlyInstance.projectConfigManager.getConfig().rollouts[2].experiments[0].audienceConditions, optlyInstance.projectConfigManager.getConfig().audiencesById, - { house: 'Lannister' }, - createdLogger + { house: 'Lannister' } ); }); @@ -4494,8 +4490,7 @@ describe('lib/optimizely', function() { audienceEvaluator.evaluate, optlyInstance.projectConfigManager.getConfig().experiments[3].audienceConditions, optlyInstance.projectConfigManager.getConfig().audiencesById, - { house: 'Gryffindor', lasers: 700 }, - createdLogger + { house: 'Gryffindor', lasers: 700 } ); }); @@ -4508,8 +4503,7 @@ describe('lib/optimizely', function() { audienceEvaluator.evaluate, optlyInstance.projectConfigManager.getConfig().experiments[3].audienceConditions, optlyInstance.projectConfigManager.getConfig().audiencesById, - {}, - createdLogger + {} ); }); }); From db7adddc68a52391b93132dc03aa8239060823fb Mon Sep 17 00:00:00 2001 From: Lauren Pappone Date: Fri, 7 Jun 2019 14:29:13 -0700 Subject: [PATCH 03/12] update lint command --- packages/optimizely-sdk/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json index 59e2c5109..9f77f33bd 100644 --- a/packages/optimizely-sdk/package.json +++ b/packages/optimizely-sdk/package.json @@ -11,7 +11,7 @@ "test-umdbrowser": "npm run build-browser-umd && karma start karma.umd.conf.js --single-run", "build-browser-umd": "rm -rf dist && webpack", "test-ci": "npm run test-xbrowser && npm run test-umdbrowser", - "lint": "eslint lib/**", + "lint": "eslint 'lib/**/*.js'", "cover": "istanbul cover _mocha ./lib/*.tests.js ./lib/**/*.tests.js ./lib/**/**/*tests.js", "coveralls": "npm run cover -- --report lcovonly && cat ./coverage/lcov.info | coveralls", "prepublishOnly": "npm run build-browser-umd && npm test && npm run test-xbrowser && npm run test-umdbrowser" From fda872d7a52c8c0e7aaa7cfbf18e2cc09fd06c9e Mon Sep 17 00:00:00 2001 From: Lauren Pappone Date: Fri, 7 Jun 2019 14:30:54 -0700 Subject: [PATCH 04/12] update function name --- .../optimizely-sdk/lib/core/audience_evaluator/index.js | 6 +++--- packages/optimizely-sdk/lib/core/decision_service/index.js | 2 +- packages/optimizely-sdk/lib/optimizely/index.js | 2 +- packages/optimizely-sdk/lib/optimizely/index.tests.js | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.js index d1059a2d6..142ec52cd 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.js @@ -28,14 +28,14 @@ var MODULE_NAME = 'AUDIENCE_EVALUATOR'; /** * Construct an instance of AudienceEvaluator with a given logger and options * @param {Logger} logger The Logger instance - * @param {Object=} __exploratoryConditionEvaluators A map of condition evaluators provided by the consumer. This enables matching + * @param {Object=} UNSTABLE_conditionEvaluators A map of condition evaluators provided by the consumer. This enables matching * condition types which are not supported natively by the SDK. Note that built in * Optimizely evaluators cannot be overridden. * @constructor */ -function AudienceEvaluator(logger, __exploratoryConditionEvaluators) { +function AudienceEvaluator(logger, UNSTABLE_conditionEvaluators) { this.logger = logger; - this.typeToEvaluatorMap = fns.assignIn({}, __exploratoryConditionEvaluators, { + this.typeToEvaluatorMap = fns.assignIn({}, UNSTABLE_conditionEvaluators, { 'custom_attribute': customAttributeConditionEvaluator }); } diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.js b/packages/optimizely-sdk/lib/core/decision_service/index.js index 3c6b9193c..cb5d56380 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.js @@ -49,7 +49,7 @@ var DECISION_SOURCES = enums.DECISION_SOURCES; * @returns {Object} */ function DecisionService(options) { - this.audienceEvaluator = new AudienceEvaluator(options.logger, options.__exploratoryConditionEvaluators); + this.audienceEvaluator = new AudienceEvaluator(options.logger, options.UNSTABLE_conditionEvaluators); this.forcedVariationMap = {}; this.logger = options.logger; this.userProfileService = options.userProfileService || null; diff --git a/packages/optimizely-sdk/lib/optimizely/index.js b/packages/optimizely-sdk/lib/optimizely/index.js index 4afae5fd4..08dbfa4db 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.js +++ b/packages/optimizely-sdk/lib/optimizely/index.js @@ -100,7 +100,7 @@ function Optimizely(config) { this.decisionService = decisionService.createDecisionService({ userProfileService: userProfileService, logger: this.logger, - __exploratoryConditionEvaluators: config.__exploratoryConditionEvaluators + UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators }); this.notificationCenter = notificationCenter.createNotificationCenter({ diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index d83177b9b..65bd95857 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -167,7 +167,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: userProfileServiceInstance, logger: createdLogger, - __exploratoryConditionEvaluators: undefined + UNSTABLE_conditionEvaluators: undefined }); var logMessage = createdLogger.log.args[0][1]; @@ -190,7 +190,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: null, logger: createdLogger, - __exploratoryConditionEvaluators: undefined + UNSTABLE_conditionEvaluators: undefined }); var logMessage = createdLogger.log.args[0][1]; From 713212dc7a4614d3912f8c2308d35abb3b13384d Mon Sep 17 00:00:00 2001 From: Lauren Pappone Date: Fri, 7 Jun 2019 15:11:08 -0700 Subject: [PATCH 05/12] remove unneccessary check --- .../lib/core/custom_attribute_condition_evaluator/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js index 3be27051d..3b5e443e9 100644 --- a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js +++ b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js @@ -55,10 +55,6 @@ EVALUATORS_BY_MATCH_TYPE[SUBSTRING_MATCH_TYPE] = substringEvaluator; * null if the given user attributes and condition can't be evaluated */ function evaluate(condition, userAttributes, logger) { - if (condition.type !== CUSTOM_ATTRIBUTE_CONDITION_TYPE) { - return null; - } - var conditionMatch = condition.match; if (typeof conditionMatch !== 'undefined' && MATCH_TYPES.indexOf(conditionMatch) === -1) { logger.log(LOG_LEVEL.WARNING, sprintf(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, JSON.stringify(condition))); From fcbc80ed5450ccb32a5bbb5e1ec91ed551372ded Mon Sep 17 00:00:00 2001 From: Lauren Pappone Date: Tue, 11 Jun 2019 16:14:38 -0700 Subject: [PATCH 06/12] use singleton logger --- .../lib/core/audience_evaluator/index.js | 18 ++++++------ .../core/audience_evaluator/index.tests.js | 28 ++++++------------- .../lib/core/decision_service/index.js | 3 +- .../lib/core/decision_service/index.tests.js | 22 +++++++-------- 4 files changed, 30 insertions(+), 41 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.js index 142ec52cd..8ec9bb7d5 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.js @@ -18,6 +18,8 @@ var customAttributeConditionEvaluator = require('../custom_attribute_condition_e var enums = require('../../utils/enums'); var fns = require('../../utils/fns'); var sprintf = require('@optimizely/js-sdk-utils').sprintf; +var logging = require('@optimizely/js-sdk-logging'); +var logger = logging.getLogger(); var ERROR_MESSAGES = enums.ERROR_MESSAGES; var LOG_LEVEL = enums.LOG_LEVEL; @@ -26,15 +28,13 @@ var MODULE_NAME = 'AUDIENCE_EVALUATOR'; /** - * Construct an instance of AudienceEvaluator with a given logger and options - * @param {Logger} logger The Logger instance + * Construct an instance of AudienceEvaluator with given options * @param {Object=} UNSTABLE_conditionEvaluators A map of condition evaluators provided by the consumer. This enables matching * condition types which are not supported natively by the SDK. Note that built in * Optimizely evaluators cannot be overridden. * @constructor */ -function AudienceEvaluator(logger, UNSTABLE_conditionEvaluators) { - this.logger = logger; +function AudienceEvaluator(UNSTABLE_conditionEvaluators) { this.typeToEvaluatorMap = fns.assignIn({}, UNSTABLE_conditionEvaluators, { 'custom_attribute': customAttributeConditionEvaluator }); @@ -66,10 +66,10 @@ AudienceEvaluator.prototype.evaluate = function(audienceConditions, audiencesByI var evaluateAudience = function(audienceId) { var audience = audiencesById[audienceId]; if (audience) { - this.logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.EVALUATING_AUDIENCE, MODULE_NAME, audienceId, JSON.stringify(audience.conditions))); + logger.log(LOG_LEVEL.DEBUG, sprintf(LOG_MESSAGES.EVALUATING_AUDIENCE, MODULE_NAME, audienceId, JSON.stringify(audience.conditions))); var result = conditionTreeEvaluator.evaluate(audience.conditions, this.evaluateConditionWithUserAttributes.bind(this, userAttributes)); var resultText = result === null ? 'UNKNOWN' : result.toString().toUpperCase(); - this.logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT, MODULE_NAME, audienceId, resultText)); + logger.log(LOG_LEVEL.INFO, sprintf(LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT, MODULE_NAME, audienceId, resultText)); return result; } @@ -89,13 +89,13 @@ AudienceEvaluator.prototype.evaluate = function(audienceConditions, audiencesByI AudienceEvaluator.prototype.evaluateConditionWithUserAttributes = function(userAttributes, condition) { var evaluator = this.typeToEvaluatorMap[condition.type]; if (!evaluator) { - this.logger.log(LOG_LEVEL.WARNING, sprintf(LOG_MESSAGES.UNKNOWN_CONDITION_TYPE, MODULE_NAME, JSON.stringify(condition))); + logger.log(LOG_LEVEL.WARNING, sprintf(LOG_MESSAGES.UNKNOWN_CONDITION_TYPE, MODULE_NAME, JSON.stringify(condition))); return null; } try { - return evaluator.evaluate(condition, userAttributes, this.logger); + return evaluator.evaluate(condition, userAttributes, logger); } catch (err) { - this.logger.log(LOG_LEVEL.ERROR, sprintf(ERROR_MESSAGES.CONDITION_EVALUATOR_ERROR, MODULE_NAME, condition.type, err.message)); + logger.log(LOG_LEVEL.ERROR, sprintf(ERROR_MESSAGES.CONDITION_EVALUATOR_ERROR, MODULE_NAME, condition.type, err.message)); } return null; }; diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js index ff594a755..8dd5e9e29 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js @@ -20,6 +20,8 @@ var customAttributeConditionEvaluator = require('../custom_attribute_condition_e var sinon = require('sinon'); var assert = chai.assert; var logger = require('../../plugins/logger'); +// var logging = require('@optimizely/js-sdk-logging'); +// var logger = logging.getLogger(); var enums = require('../../utils/enums'); var LOG_LEVEL = enums.LOG_LEVEL; @@ -55,19 +57,11 @@ describe('lib/core/audience_evaluator', function() { var audienceEvaluator; var mockLogger = logger.createLogger({logLevel: LOG_LEVEL.INFO}); beforeEach(function() { - audienceEvaluator = new AudienceEvaluator(mockLogger); + audienceEvaluator = new AudienceEvaluator(); }); describe('APIs', function() { describe('evaluate', function() { - beforeEach(function () { - sinon.stub(mockLogger, 'log'); - }); - - afterEach(function() { - mockLogger.log.restore(); - }); - it('should return true if there are no audiences', function() { assert.isTrue(audienceEvaluator.evaluate([], audiencesById, {})); }); @@ -124,7 +118,6 @@ describe('lib/core/audience_evaluator', function() { ['or', '0', '1'], audiencesById, { browser_type: 'chrome' }, - mockLogger ); assert.isTrue(result); }); @@ -134,7 +127,6 @@ describe('lib/core/audience_evaluator', function() { ['and', '0', '1'], audiencesById, { browser_type: 'chrome', device_model: 'iphone' }, - mockLogger ); assert.isTrue(result); }); @@ -144,7 +136,6 @@ describe('lib/core/audience_evaluator', function() { ['not', '1'], audiencesById, { device_model: 'android' }, - mockLogger ); assert.isTrue(result); }); @@ -169,7 +160,6 @@ describe('lib/core/audience_evaluator', function() { ['or', '0', '1'], audiencesById, { browser_type: 'chrome' }, - mockLogger ); assert.isTrue(result); }); @@ -180,7 +170,6 @@ describe('lib/core/audience_evaluator', function() { ['or', '0', '1'], audiencesById, { browser_type: 'safari' }, - mockLogger ); assert.isFalse(result); }); @@ -191,7 +180,6 @@ describe('lib/core/audience_evaluator', function() { ['or', '0', '1'], audiencesById, { state: 'California' }, - mockLogger ); assert.isFalse(result); }); @@ -204,7 +192,8 @@ describe('lib/core/audience_evaluator', function() { var userAttributes = { device_model: 'android' }; var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); + console.log('args: ', customAttributeConditionEvaluator.evaluate.firstCall.args) + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); assert.isFalse(result); }); }); @@ -229,7 +218,7 @@ describe('lib/core/audience_evaluator', function() { var userAttributes = { device_model: 5.5 }; var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); assert.isFalse(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); @@ -244,7 +233,7 @@ describe('lib/core/audience_evaluator', function() { var userAttributes = { device_model: 'iphone' }; var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); assert.isTrue(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); @@ -259,7 +248,8 @@ describe('lib/core/audience_evaluator', function() { var userAttributes = { device_model: 'android' }; var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); + console.log('args: ', customAttributeConditionEvaluator.evaluate.firstCall.args) + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); assert.isFalse(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.js b/packages/optimizely-sdk/lib/core/decision_service/index.js index cb5d56380..75c4ed25b 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.js @@ -45,11 +45,10 @@ var DECISION_SOURCES = enums.DECISION_SOURCES; * @constructor * @param {Object} options * @param {Object} options.userProfileService An instance of the user profile service for sticky bucketing. - * @param {Object} options.logger An instance of a logger to log messages with. * @returns {Object} */ function DecisionService(options) { - this.audienceEvaluator = new AudienceEvaluator(options.logger, options.UNSTABLE_conditionEvaluators); + this.audienceEvaluator = new AudienceEvaluator(options.UNSTABLE_conditionEvaluators); this.forcedVariationMap = {}; this.logger = options.logger; this.userProfileService = options.userProfileService || null; 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 cb6b08c94..beed5bade 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.tests.js @@ -75,11 +75,11 @@ describe('lib/core/decision_service', function() { it('should return null if the user does not meet audience conditions', function () { assert.isNull(decisionServiceInstance.getVariation(configObj, 'testExperimentWithAudiences', 'user3', {foo: 'bar'})); - assert.strictEqual(7, mockLogger.log.callCount); + assert.strictEqual(4, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'DECISION_SERVICE: User user3 is not in the forced variation map.'); assert.strictEqual(mockLogger.log.args[1][1], 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].'); - assert.strictEqual(mockLogger.log.args[5][1], 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.'); - assert.strictEqual(mockLogger.log.args[6][1], 'DECISION_SERVICE: User user3 does not meet conditions to be in experiment testExperimentWithAudiences.'); + assert.strictEqual(mockLogger.log.args[2][1], 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.'); + assert.strictEqual(mockLogger.log.args[3][1], 'DECISION_SERVICE: User user3 does not meet conditions to be in experiment testExperimentWithAudiences.'); }); it('should return null if the experiment is not running', function () { @@ -429,9 +429,9 @@ describe('lib/core/decision_service', function() { it('should return true when audience conditions are met', function () { assert.isTrue(decisionServiceInstance.__checkIfUserIsInAudience(configObj, 'testExperimentWithAudiences', 'testUser', {browser_type: 'firefox'})); - assert.strictEqual(4, mockLogger.log.callCount); + assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].'); - assert.strictEqual(mockLogger.log.args[3][1], 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to TRUE.'); + assert.strictEqual(mockLogger.log.args[1][1], 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to TRUE.'); }); it('should return true when experiment has no audience', function () { @@ -447,20 +447,20 @@ describe('lib/core/decision_service', function() { assert.isFalse(decisionServiceInstance.__checkIfUserIsInAudience(configObj, 'testExperimentWithAudiences', 'testUser')); assert.isTrue(__audienceEvaluateSpy.alwaysReturned(false)); - assert.strictEqual(6, mockLogger.log.callCount); + assert.strictEqual(3, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].'); - assert.strictEqual(mockLogger.log.args[4][1], 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.'); - assert.strictEqual(mockLogger.log.args[5][1], 'DECISION_SERVICE: User testUser does not meet conditions to be in experiment testExperimentWithAudiences.'); + assert.strictEqual(mockLogger.log.args[1][1], 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.'); + assert.strictEqual(mockLogger.log.args[2][1], 'DECISION_SERVICE: User testUser does not meet conditions to be in experiment testExperimentWithAudiences.'); }); it('should return false when audience conditions are not met', function () { assert.isFalse(decisionServiceInstance.__checkIfUserIsInAudience(configObj, 'testExperimentWithAudiences', 'testUser', {browser_type: 'chrome'})); assert.isTrue(__audienceEvaluateSpy.alwaysReturned(false)); - assert.strictEqual(5, mockLogger.log.callCount); + assert.strictEqual(3, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].'); - assert.strictEqual(mockLogger.log.args[3][1], 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.'); - assert.strictEqual(mockLogger.log.args[4][1], 'DECISION_SERVICE: User testUser does not meet conditions to be in experiment testExperimentWithAudiences.'); + assert.strictEqual(mockLogger.log.args[1][1], 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.'); + assert.strictEqual(mockLogger.log.args[2][1], 'DECISION_SERVICE: User testUser does not meet conditions to be in experiment testExperimentWithAudiences.'); }); }); From f7e881658a1ac315af9ae989bae4f387690783af Mon Sep 17 00:00:00 2001 From: Lauren Pappone Date: Wed, 12 Jun 2019 10:39:37 -0700 Subject: [PATCH 07/12] fix tests --- .../lib/core/audience_evaluator/index.js | 2 +- .../lib/core/audience_evaluator/index.tests.js | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.js index 8ec9bb7d5..9c257c2e8 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.js @@ -93,7 +93,7 @@ AudienceEvaluator.prototype.evaluateConditionWithUserAttributes = function(userA return null; } try { - return evaluator.evaluate(condition, userAttributes, logger); + return evaluator.evaluate(condition, userAttributes); } catch (err) { logger.log(LOG_LEVEL.ERROR, sprintf(ERROR_MESSAGES.CONDITION_EVALUATOR_ERROR, MODULE_NAME, condition.type, err.message)); } diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js index 8dd5e9e29..d09d105f1 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js @@ -19,9 +19,8 @@ var conditionTreeEvaluator = require('../condition_tree_evaluator'); var customAttributeConditionEvaluator = require('../custom_attribute_condition_evaluator'); var sinon = require('sinon'); var assert = chai.assert; -var logger = require('../../plugins/logger'); -// var logging = require('@optimizely/js-sdk-logging'); -// var logger = logging.getLogger(); +var logging = require('@optimizely/js-sdk-logging'); +var mockLogger = logging.getLogger(); var enums = require('../../utils/enums'); var LOG_LEVEL = enums.LOG_LEVEL; @@ -55,13 +54,20 @@ var audiencesById = { describe('lib/core/audience_evaluator', function() { var audienceEvaluator; - var mockLogger = logger.createLogger({logLevel: LOG_LEVEL.INFO}); beforeEach(function() { audienceEvaluator = new AudienceEvaluator(); }); describe('APIs', function() { describe('evaluate', function() { + beforeEach(function() { + sinon.stub(mockLogger, 'log'); + }); + + afterEach(function() { + mockLogger.log.restore(); + }); + it('should return true if there are no audiences', function() { assert.isTrue(audienceEvaluator.evaluate([], audiencesById, {})); }); From 5ba2116e1b42f8a1ba922de73484ac7bc7a887e6 Mon Sep 17 00:00:00 2001 From: Lauren Pappone Date: Wed, 12 Jun 2019 13:45:19 -0700 Subject: [PATCH 08/12] fix lint errors --- .../lib/core/audience_evaluator/index.tests.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js index d09d105f1..db73de85b 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js @@ -123,7 +123,7 @@ describe('lib/core/audience_evaluator', function() { var result = audienceEvaluator.evaluate( ['or', '0', '1'], audiencesById, - { browser_type: 'chrome' }, + { browser_type: 'chrome' } ); assert.isTrue(result); }); @@ -132,7 +132,7 @@ describe('lib/core/audience_evaluator', function() { var result = audienceEvaluator.evaluate( ['and', '0', '1'], audiencesById, - { browser_type: 'chrome', device_model: 'iphone' }, + { browser_type: 'chrome', device_model: 'iphone' } ); assert.isTrue(result); }); @@ -141,7 +141,7 @@ describe('lib/core/audience_evaluator', function() { var result = audienceEvaluator.evaluate( ['not', '1'], audiencesById, - { device_model: 'android' }, + { device_model: 'android' } ); assert.isTrue(result); }); @@ -165,7 +165,7 @@ describe('lib/core/audience_evaluator', function() { var result = audienceEvaluator.evaluate( ['or', '0', '1'], audiencesById, - { browser_type: 'chrome' }, + { browser_type: 'chrome' } ); assert.isTrue(result); }); @@ -175,7 +175,7 @@ describe('lib/core/audience_evaluator', function() { var result = audienceEvaluator.evaluate( ['or', '0', '1'], audiencesById, - { browser_type: 'safari' }, + { browser_type: 'safari' } ); assert.isFalse(result); }); @@ -185,7 +185,7 @@ describe('lib/core/audience_evaluator', function() { var result = audienceEvaluator.evaluate( ['or', '0', '1'], audiencesById, - { state: 'California' }, + { state: 'California' } ); assert.isFalse(result); }); From e4fd82ebb8401cbad5071ebb3032db17d3780589 Mon Sep 17 00:00:00 2001 From: Lauren Pappone Date: Mon, 17 Jun 2019 10:39:57 -0700 Subject: [PATCH 09/12] Update packages/optimizely-sdk/lib/utils/enums/index.js Co-Authored-By: Nikhil Chelliah --- packages/optimizely-sdk/lib/utils/enums/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/optimizely-sdk/lib/utils/enums/index.js b/packages/optimizely-sdk/lib/utils/enums/index.js index 10f573de6..93de70a71 100644 --- a/packages/optimizely-sdk/lib/utils/enums/index.js +++ b/packages/optimizely-sdk/lib/utils/enums/index.js @@ -26,7 +26,7 @@ exports.LOG_LEVEL = { }; exports.ERROR_MESSAGES = { - CONDITION_EVALUATOR_ERROR: '%s: Error evaluating condition type %s: %s', + CONDITION_EVALUATOR_ERROR: '%s: Error evaluating audience condition of type %s: %s', DATAFILE_AND_SDK_KEY_MISSING: '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely', EXPERIMENT_KEY_NOT_IN_DATAFILE: '%s: Experiment key %s is not in datafile.', FEATURE_NOT_IN_DATAFILE: '%s: Feature key %s is not in datafile.', From b68e444a4931c98894cf855aa96f66ef74a07501 Mon Sep 17 00:00:00 2001 From: Lauren Pappone Date: Mon, 17 Jun 2019 10:40:22 -0700 Subject: [PATCH 10/12] put back some accidentally deleted things --- .../index.tests.js | 26 ++++++++++++++----- .../lib/core/decision_service/index.js | 1 + .../lib/optimizely/index.tests.js | 4 +-- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js index f936c604f..417951487 100644 --- a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js +++ b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js @@ -85,6 +85,18 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { assert.isTrue(customAttributeEvaluator.evaluate(doubleCondition, userAttributes, mockLogger)); }); + it('should log and return null when condition has an invalid match property', function() { + var result = customAttributeEvaluator.evaluate( + { match: 'weird', name: 'weird_condition', type: 'custom_attribute', value: 'hi' }, + { weird_condition: 'bye' }, + mockLogger + ); + assert.isNull(result); + sinon.assert.calledOnce(mockLogger.log); + sinon.assert.calledWithExactly(mockLogger.log, LOG_LEVEL.WARNING, + 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"weird","name":"weird_condition","type":"custom_attribute","value":"hi"} uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.'); + }); + describe('exists match type', function() { var existsCondition = { match: 'exists', @@ -177,7 +189,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var result = customAttributeEvaluator.evaluate(exactStringCondition, {}, mockLogger); assert.isNull(result); sinon.assert.calledOnce(mockLogger.log); - sinon.assert.calledWithExactly(mockLogger.log, LOG_LEVEL.DEBUG, + sinon.assert.calledWithExactly(mockLogger.log, LOG_LEVEL.DEBUG, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"exact","name":"favorite_constellation","type":"custom_attribute","value":"Lacerta"} evaluated to UNKNOWN because no value was passed for user attribute "favorite_constellation".'); }); @@ -211,10 +223,10 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should log and return null if the user-provided value is of a different type than the condition value', function() { var result = customAttributeEvaluator.evaluate(exactNumberCondition, { lasers_count: 'yes' }, mockLogger); assert.isNull(result); - + result = customAttributeEvaluator.evaluate(exactNumberCondition, { lasers_count: '1000' }, mockLogger); assert.isNull(result); - + assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][0], LOG_LEVEL.WARNING); assert.strictEqual(mockLogger.log.args[0][1], @@ -227,10 +239,10 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should log and return null if the user-provided number value is out of bounds', function() { var result = customAttributeEvaluator.evaluate(exactNumberCondition, { lasers_count: -Infinity }, mockLogger); assert.isNull(result); - + result = customAttributeEvaluator.evaluate(exactNumberCondition, { lasers_count: -Math.pow(2, 53) - 2 }, mockLogger); assert.isNull(result); - + assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][0], LOG_LEVEL.WARNING); assert.strictEqual(mockLogger.log.args[0][1], @@ -501,7 +513,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { meters_travelled: Math.pow(2, 53) + 2, }, mockLogger); assert.isNull(result); - + assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][0], LOG_LEVEL.WARNING); assert.strictEqual(mockLogger.log.args[0][1], @@ -545,7 +557,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { sinon.assert.calledThrice(mockLogger.log); var logMessage = mockLogger.log.args[2][1]; - assert.strictEqual(logMessage, + assert.strictEqual(logMessage, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"lt","name":"meters_travelled","type":"custom_attribute","value":9007199254740994} evaluated to UNKNOWN because the condition value is not supported.'); }); }); diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.js b/packages/optimizely-sdk/lib/core/decision_service/index.js index 75c4ed25b..574cf6df8 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.js +++ b/packages/optimizely-sdk/lib/core/decision_service/index.js @@ -45,6 +45,7 @@ var DECISION_SOURCES = enums.DECISION_SOURCES; * @constructor * @param {Object} options * @param {Object} options.userProfileService An instance of the user profile service for sticky bucketing. + * @param {Object} options.logger An instance of a logger to log messages. * @returns {Object} */ function DecisionService(options) { diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index 65bd95857..28597c194 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -167,7 +167,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: userProfileServiceInstance, logger: createdLogger, - UNSTABLE_conditionEvaluators: undefined + UNSTABLE_conditionEvaluators: undefined, }); var logMessage = createdLogger.log.args[0][1]; @@ -190,7 +190,7 @@ describe('lib/optimizely', function() { sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: null, logger: createdLogger, - UNSTABLE_conditionEvaluators: undefined + UNSTABLE_conditionEvaluators: undefined, }); var logMessage = createdLogger.log.args[0][1]; From 69001d799ffddad9f41f2232de4c7b0dadb6bd76 Mon Sep 17 00:00:00 2001 From: Lauren Pappone Date: Tue, 18 Jun 2019 10:20:14 -0700 Subject: [PATCH 11/12] pass logger to evaluate --- .../optimizely-sdk/lib/core/audience_evaluator/index.js | 2 +- .../lib/core/audience_evaluator/index.tests.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.js index 9c257c2e8..8ec9bb7d5 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.js @@ -93,7 +93,7 @@ AudienceEvaluator.prototype.evaluateConditionWithUserAttributes = function(userA return null; } try { - return evaluator.evaluate(condition, userAttributes); + return evaluator.evaluate(condition, userAttributes, logger); } catch (err) { logger.log(LOG_LEVEL.ERROR, sprintf(ERROR_MESSAGES.CONDITION_EVALUATOR_ERROR, MODULE_NAME, condition.type, err.message)); } diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js index db73de85b..76076cdd3 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js +++ b/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js @@ -199,7 +199,7 @@ describe('lib/core/audience_evaluator', function() { var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); console.log('args: ', customAttributeConditionEvaluator.evaluate.firstCall.args) - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); assert.isFalse(result); }); }); @@ -224,7 +224,7 @@ describe('lib/core/audience_evaluator', function() { var userAttributes = { device_model: 5.5 }; var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); assert.isFalse(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); @@ -239,7 +239,7 @@ describe('lib/core/audience_evaluator', function() { var userAttributes = { device_model: 'iphone' }; var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); assert.isTrue(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); @@ -255,7 +255,7 @@ describe('lib/core/audience_evaluator', function() { var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, userAttributes); sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); console.log('args: ', customAttributeConditionEvaluator.evaluate.firstCall.args) - sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes); + sinon.assert.calledWithExactly(customAttributeConditionEvaluator.evaluate, iphoneUserAudience.conditions[1], userAttributes, mockLogger); assert.isFalse(result); assert.strictEqual(2, mockLogger.log.callCount); assert.strictEqual(mockLogger.log.args[0][1], 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].'); From 0eeb31da1406e6a0745c83a58ca3a0c967cd166f Mon Sep 17 00:00:00 2001 From: Lauren Pappone Date: Wed, 19 Jun 2019 15:18:29 -0700 Subject: [PATCH 12/12] add todo --- .../lib/core/custom_attribute_condition_evaluator/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js index 3b5e443e9..8b60fa509 100644 --- a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js +++ b/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.js @@ -53,6 +53,7 @@ EVALUATORS_BY_MATCH_TYPE[SUBSTRING_MATCH_TYPE] = substringEvaluator; * @param {Object} logger * @return {?Boolean} true/false if the given user attributes match/don't match the given condition, * null if the given user attributes and condition can't be evaluated + * TODO: Change to accept and object with named properties */ function evaluate(condition, userAttributes, logger) { var conditionMatch = condition.match;