diff --git a/packages/optimizely-sdk/lib/core/project_config/index.js b/packages/optimizely-sdk/lib/core/project_config/index.js index 7624a266b..bcc9e20dd 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.js @@ -28,13 +28,41 @@ var EXPERIMENT_RUNNING_STATUS = 'Running'; var RESERVED_ATTRIBUTE_PREFIX = '$opt_'; var MODULE_NAME = 'PROJECT_CONFIG'; +function createMutationSafeDatafileCopy(datafile) { + var datafileCopy = fns.assign({}, datafile); + datafileCopy.audiences = (datafile.audiences || []).map(function(audience) { + return fns.assign({}, audience); + }); + datafileCopy.experiments = (datafile.experiments || []).map(function(experiment) { + return fns.assign({}, experiment); + }); + datafileCopy.featureFlags = (datafile.featureFlags || []).map(function(featureFlag) { + return fns.assign({}, featureFlag); + }); + datafileCopy.groups = (datafile.groups || []).map(function(group) { + var groupCopy = fns.assign({}, group); + groupCopy.experiments = (group.experiments || []).map(function(experiment) { + return fns.assign({}, experiment); + }); + return groupCopy; + }); + datafileCopy.rollouts = (datafile.rollouts || []).map(function(rollout) { + var rolloutCopy = fns.assign({}, rollout); + rolloutCopy.experiments = (rollout.experiments || []).map(function(experiment) { + return fns.assign({}, experiment); + }); + return rolloutCopy; + }); + return datafileCopy; +} + /** * Creates projectConfig object to be used for quick project property lookup * @param {Object} datafile JSON datafile representing the project * @return {Object} Object representing project configuration */ export var createProjectConfig = function(datafile) { - var projectConfig = fns.assign({}, datafile); + var projectConfig = createMutationSafeDatafileCopy(datafile); /* * Conditions of audiences in projectConfig.typedAudiences are not 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 a76bdfea4..fadc8a866 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.tests.js +++ b/packages/optimizely-sdk/lib/core/project_config/index.tests.js @@ -33,14 +33,13 @@ import configValidator from '../../utils/config_validator'; var logger = getLogger(); describe('lib/core/project_config', function() { - var parsedAudiences = testDatafile.getParsedAudiences; describe('createProjectConfig method', function() { it('should set properties correctly when createProjectConfig is called', function() { var testData = testDatafile.getTestProjectConfig(); var configObj = projectConfig.createProjectConfig(testData); forEach(testData.audiences, function(audience) { - audience.conditions = audience.conditions; + audience.conditions = JSON.parse(audience.conditions); }); assert.strictEqual(configObj.accountId, testData.accountId); @@ -48,6 +47,12 @@ describe('lib/core/project_config', function() { assert.strictEqual(configObj.revision, testData.revision); assert.deepEqual(configObj.events, testData.events); assert.deepEqual(configObj.audiences, testData.audiences); + testData.groups.forEach(function(group) { + group.experiments.forEach(function(experiment) { + experiment.groupId = group.id; + experiment.variationKeyMap = fns.keyBy(experiment.variations, 'key'); + }); + }); assert.deepEqual(configObj.groups, testData.groups); var expectedGroupIdMap = { @@ -170,6 +175,13 @@ describe('lib/core/project_config', function() { assert.deepEqual(configObj.variationIdMap, expectedVariationIdMap); }); + it('should not mutate the datafile', function() { + var datafile = testDatafile.getTypedAudiencesConfig(); + var datafileClone = cloneDeep(datafile); + projectConfig.createProjectConfig(datafile); + assert.deepEqual(datafileClone, datafile); + }); + describe('feature management', function() { var configObj; beforeEach(function() { diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/packages/optimizely-sdk/lib/optimizely/index.tests.js index 917820493..6e9ec01f1 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/packages/optimizely-sdk/lib/optimizely/index.tests.js @@ -245,6 +245,28 @@ describe('lib/optimizely', function() { }); }); }); + + it('should support constructing two instances using the same datafile object', function() { + var datafile = testData.getTypedAudiencesConfig(); + var optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + datafile: datafile, + errorHandler: stubErrorHandler, + eventDispatcher: stubEventDispatcher, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + }); + assert.instanceOf(optlyInstance, Optimizely); + var optlyInstance2 = new Optimizely({ + clientEngine: 'node-sdk', + datafile: datafile, + errorHandler: stubErrorHandler, + eventDispatcher: stubEventDispatcher, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + }); + assert.instanceOf(optlyInstance2, Optimizely); + }); }); });