From 4828321a5d17b1910f62529751bb835c474677bd Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Tue, 25 Aug 2020 21:14:01 +0500 Subject: [PATCH 01/11] datafile accessor and test cases --- .../Config/DatafileProjectConfig.php | 1004 +++++++---------- .../Config/ProjectConfigInterface.php | 7 + .../OptimizelyConfig/OptimizelyConfig.php | 17 +- .../OptimizelyConfigService.php | 6 + .../OptimizelyConfigServiceTest.php | 7 + 5 files changed, 443 insertions(+), 598 deletions(-) diff --git a/src/Optimizely/Config/DatafileProjectConfig.php b/src/Optimizely/Config/DatafileProjectConfig.php index efceebe6..a8ec96a6 100644 --- a/src/Optimizely/Config/DatafileProjectConfig.php +++ b/src/Optimizely/Config/DatafileProjectConfig.php @@ -1,6 +1,6 @@ associative array of feature keys to feature flags - */ - private $_featureKeyMap; - - /** - * internal mapping of rollout IDs to Rollout models. - * - * @var associative array of rollout ids to rollouts - */ - private $_rolloutIdMap; - - /** - * Feature Flag key to Feature Variable key to Feature Variable map - * - * @var > - */ - private $_featureFlagVariableMap; - - /** - * Associative array of experiment ID to Feature ID(s) in the datafile. - * - * @var - */ - private $_experimentFeatureMap; - - /** - * DatafileProjectConfig constructor to load and set project configuration data. - * - * @param $datafile string JSON string representing the project. - * @param $logger LoggerInterface - * @param $errorHandler ErrorHandlerInterface - */ - public function __construct($datafile, $logger, $errorHandler) - { - $supportedVersions = array(self::V2, self::V3, self::V4); - $config = json_decode($datafile, true); - $this->_logger = $logger; - $this->_errorHandler = $errorHandler; - $this->_version = $config['version']; - if (!in_array($this->_version, $supportedVersions)) { - throw new InvalidDatafileVersionException( - "This version of the PHP SDK does not support the given datafile version: {$this->_version}." - ); - } - - $this->_accountId = $config['accountId']; - $this->_projectId = $config['projectId']; - $this->_anonymizeIP = isset($config['anonymizeIP'])? $config['anonymizeIP'] : false; - $this->_botFiltering = isset($config['botFiltering'])? $config['botFiltering'] : null; - $this->_revision = $config['revision']; - - $groups = $config['groups'] ?: []; - $experiments = $config['experiments'] ?: []; - $events = $config['events'] ?: []; - $attributes = $config['attributes'] ?: []; - $audiences = $config['audiences'] ?: []; - $typedAudiences = isset($config['typedAudiences']) ? $config['typedAudiences']: []; - $rollouts = isset($config['rollouts']) ? $config['rollouts'] : []; - $featureFlags = isset($config['featureFlags']) ? $config['featureFlags']: []; - - // 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 - foreach ($featureFlags as $featureFlagKey => $featureFlag) { - foreach ($featureFlag['variables'] as $variableKey => $variable) { - if (isset($variable['subType']) && $variable['type'] === FeatureVariable::STRING_TYPE && $variable['subType'] === FeatureVariable::JSON_TYPE) { - $variable['type'] = FeatureVariable::JSON_TYPE; - unset($variable['subType']); - $featureFlags[$featureFlagKey]['variables'][$variableKey] = $variable; - } - } - } - - $this->_groupIdMap = ConfigParser::generateMap($groups, 'id', Group::class); - $this->_experimentKeyMap = ConfigParser::generateMap($experiments, 'key', Experiment::class); - $this->_eventKeyMap = ConfigParser::generateMap($events, 'key', Event::class); - $this->_attributeKeyMap = ConfigParser::generateMap($attributes, 'key', Attribute::class); - $typedAudienceIdMap = ConfigParser::generateMap($typedAudiences, 'id', Audience::class); - $this->_audienceIdMap = ConfigParser::generateMap($audiences, 'id', Audience::class); - $this->_rollouts = ConfigParser::generateMap($rollouts, null, Rollout::class); - $this->_featureFlags = ConfigParser::generateMap($featureFlags, null, FeatureFlag::class); - - foreach (array_values($this->_groupIdMap) as $group) { - $experimentsInGroup = ConfigParser::generateMap($group->getExperiments(), 'key', Experiment::class); - foreach (array_values($experimentsInGroup) as $experiment) { - $experiment->setGroupId($group->getId()); - $experiment->setGroupPolicy($group->getPolicy()); - } - $this->_experimentKeyMap = $this->_experimentKeyMap + $experimentsInGroup; - } - - $this->_variationKeyMap = []; - $this->_variationIdMap = []; - $this->_experimentIdMap = []; - - foreach (array_values($this->_experimentKeyMap) as $experiment) { - $this->_variationKeyMap[$experiment->getKey()] = []; - $this->_variationIdMap[$experiment->getKey()] = []; - $this->_experimentIdMap[$experiment->getId()] = $experiment; - foreach ($experiment->getVariations() as $variation) { - $this->_variationKeyMap[$experiment->getKey()][$variation->getKey()] = $variation; - $this->_variationIdMap[$experiment->getKey()][$variation->getId()] = $variation; - } - } - - foreach (array_values($this->_audienceIdMap) as $audience) { - $audience->setConditionsList(json_decode($audience->getConditions(), true)); - } - - // Conditions in typedAudiences are not expected to be string-encoded so they don't need - // to be decoded unlike audiences. - foreach (array_values($typedAudienceIdMap) as $typedAudience) { - $typedAudience->setConditionsList($typedAudience->getConditions()); - } - - // Overwrite audiences by typedAudiences. - $this->_audienceIdMap = array_replace($this->_audienceIdMap, $typedAudienceIdMap); - - $rolloutVariationIdMap = []; - $rolloutVariationKeyMap = []; - foreach ($this->_rollouts as $rollout) { - $this->_rolloutIdMap[$rollout->getId()] = $rollout; - - foreach ($rollout->getExperiments() as $rule) { - $rolloutVariationIdMap[$rule->getKey()] = []; - $rolloutVariationKeyMap[$rule->getKey()] = []; - - $variations = $rule->getVariations(); - foreach ($variations as $variation) { - $rolloutVariationIdMap[$rule->getKey()][$variation->getId()] = $variation; - $rolloutVariationKeyMap[$rule->getKey()][$variation->getKey()] = $variation; - } - } - } - - // Add variations for rollout experiments to variationIdMap and variationKeyMap - $this->_variationIdMap = $this->_variationIdMap + $rolloutVariationIdMap; - $this->_variationKeyMap = $this->_variationKeyMap + $rolloutVariationKeyMap; - - foreach (array_values($this->_featureFlags) as $featureFlag) { - $this->_featureKeyMap[$featureFlag->getKey()] = $featureFlag; - } - - $this->_experimentFeatureMap = []; - if ($this->_featureKeyMap) { - foreach ($this->_featureKeyMap as $featureKey => $featureFlag) { - $this->_featureFlagVariableMap[$featureKey] = ConfigParser::generateMap( - $featureFlag->getVariables(), - 'key', - FeatureVariable::class - ); - - $featureFlagId = $featureFlag->getId(); - foreach ($featureFlag->getExperimentIds() as $experimentId) { - $this->_experimentFeatureMap[$experimentId] = [$featureFlagId]; - } - } - } - } - - /** - * Create ProjectConfig based on datafile string. - * - * @param string $datafile JSON string representing the Optimizely project. - * @param bool $skipJsonValidation boolean representing whether JSON schema validation needs to be performed. - * @param LoggerInterface $logger Logger instance - * @param ErrorHandlerInterface $errorHandler ErrorHandler instance. - * @return ProjectConfig ProjectConfig instance or null; - */ - public static function createProjectConfigFromDatafile($datafile, $skipJsonValidation, $logger, $errorHandler) + public function setUp() { - if (!$skipJsonValidation) { - if (!Validator::validateJsonSchema($datafile)) { - $defaultLogger = new DefaultLogger(); - $defaultLogger->log(Logger::ERROR, 'Provided "datafile" has invalid schema.'); - $logger->log(Logger::ERROR, 'Provided "datafile" has invalid schema.'); - return null; - } - } - - try { - $config = new DatafileProjectConfig($datafile, $logger, $errorHandler); - } catch (Exception $exception) { - $defaultLogger = new DefaultLogger(); - $errorMsg = $exception->getCode() == InvalidDatafileVersionException::class ? $exception->getMessage() : sprintf(Errors::INVALID_FORMAT, 'datafile'); - $errorToHandle = $exception->getCode() == InvalidDatafileVersionException::class ? new InvalidDatafileVersionException($errorMsg) : new InvalidInputException($errorMsg); - $defaultLogger->log(Logger::ERROR, $errorMsg); - $logger->log(Logger::ERROR, $errorMsg); - $errorHandler->handleError($errorToHandle); - return null; - } - - return $config; - } + $this->datafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; - /** - * @return string String representing account ID from the datafile. - */ - public function getAccountId() - { - return $this->_accountId; - } + $this->projectConfig = new DatafileProjectConfig( + $this->datafile, + new NoOpLogger(), + new NoOpErrorHandler() + ); - /** - * @return string String representing project ID from the datafile. - */ - public function getProjectId() - { - return $this->_projectId; - } + $this->optConfigService = new OptimizelyConfigService($this->projectConfig); + + // Create expected default variables map for feat_experiment variation_b + $boolDefaultVariable = new OptimizelyVariable('17252790456', 'boolean_var', 'boolean', 'false'); + $intDefaultVariable = new OptimizelyVariable('17258820367', 'integer_var', 'integer', 1); + $doubleDefaultVariable = new OptimizelyVariable('17260550714', 'double_var', 'double', 0.5); + $strDefaultVariable = new OptimizelyVariable('17290540010', 'string_var', 'string', 'i am default value'); + $jsonDefaultVariable = new OptimizelyVariable('17260550458', 'json_var', 'json', "{\"text\": \"default value\"}"); + + $this->expectedDefaultVariableKeyMap = []; + $this->expectedDefaultVariableKeyMap['boolean_var'] = $boolDefaultVariable; + $this->expectedDefaultVariableKeyMap['integer_var'] = $intDefaultVariable; + $this->expectedDefaultVariableKeyMap['double_var'] = $doubleDefaultVariable; + $this->expectedDefaultVariableKeyMap['string_var'] = $strDefaultVariable; + $this->expectedDefaultVariableKeyMap['json_var'] = $jsonDefaultVariable; + + // Create variable variables map for feat_experiment variation_a + $boolFeatVariable = new OptimizelyVariable('17252790456', 'boolean_var', 'boolean', 'true'); + $intFeatVariable = new OptimizelyVariable('17258820367', 'integer_var', 'integer', 5); + $doubleFeatVariable = new OptimizelyVariable('17260550714', 'double_var', 'double', 5.5); + $strFeatVariable = new OptimizelyVariable('17290540010', 'string_var', 'string', 'i am variable value'); + $jsonFeatVariable = new OptimizelyVariable('17260550458', 'json_var', 'json', "{\"text\": \"variable value\"}"); + + $this->expectedVariableKeyMap = []; + $this->expectedVariableKeyMap['boolean_var'] = $boolFeatVariable; + $this->expectedVariableKeyMap['integer_var'] = $intFeatVariable; + $this->expectedVariableKeyMap['double_var'] = $doubleFeatVariable; + $this->expectedVariableKeyMap['string_var'] = $strFeatVariable; + $this->expectedVariableKeyMap['json_var'] = $jsonFeatVariable; + + // Create variations map for feat_experiment + $this->featExpVariationMap = []; + $this->featExpVariationMap['variation_a'] = + new OptimizelyVariation('17289540366', 'variation_a', true, $this->expectedVariableKeyMap); + + $this->featExpVariationMap['variation_b'] = + new OptimizelyVariation('17304990114', 'variation_b', false, $this->expectedDefaultVariableKeyMap); + + // create feat_experiment + $featExperiment = + new OptimizelyExperiment("17279300791", "feat_experiment", $this->featExpVariationMap); + + + // create feature + $experimentsMap = ['feat_experiment' => $featExperiment]; + $this->feature = + new OptimizelyFeature( + '17266500726', + 'test_feature', + $experimentsMap, + $this->expectedDefaultVariableKeyMap + ); - /** - * @return boolean Flag denoting if Optimizely should remove last block - * of visitors' IP address before storing event data - */ - public function getAnonymizeIP() - { - return $this->_anonymizeIP; + // create ab experiment and variations + $variationA = new OptimizelyVariation('17277380360', 'variation_a', null, []); + $variationB = new OptimizelyVariation('17273501081', 'variation_b', null, []); + $variationsMap = []; + $variationsMap['variation_a'] = $variationA; + $variationsMap['variation_b'] = $variationB; + + $abExperiment = new OptimizelyExperiment('17301270474', 'ab_experiment', $variationsMap); + + // create group_ab_experiment and variations + $variationA = new OptimizelyVariation('17287500312', 'variation_a', null, []); + $variationB = new OptimizelyVariation('17283640326', 'variation_b', null, []); + $variationsMap = []; + $variationsMap['variation_a'] = $variationA; + $variationsMap['variation_b'] = $variationB; + + $groupExperiment = + new OptimizelyExperiment('17258450439', 'group_ab_experiment', $variationsMap); + + // create experiment key map + $this->expectedExpKeyMap = []; + $this->expectedExpKeyMap['ab_experiment'] = $abExperiment; + $this->expectedExpKeyMap['group_ab_experiment'] = $groupExperiment; + $this->expectedExpKeyMap['feat_experiment'] = $featExperiment; + + // create experiment id map + $this->expectedExpIdMap = []; + $this->expectedExpIdMap['17301270474'] = $abExperiment; + $this->expectedExpIdMap['17258450439'] = $groupExperiment; + $this->expectedExpIdMap['17279300791'] = $featExperiment; } - /** - * @return boolean Flag denoting if Optimizely should perform - * bot filtering on your dispatched events. - */ - public function getBotFiltering() + protected static function getMethod($name) { - return $this->_botFiltering; + $class = new \ReflectionClass('Optimizely\\OptimizelyConfig\\OptimizelyConfigService'); + $method = $class->getMethod($name); + $method->setAccessible(true); + return $method; } - /** - * @return string String representing revision of the datafile. - */ - public function getRevision() + public function testGetVariablesMapReturnsEmptyForAbExpVariation() { - return $this->_revision; - } + $abExp = $this->projectConfig->getExperimentFromKey("ab_experiment"); + $abExpVarA = $abExp->getVariations()[0]; - /** - * @return array List of feature flags parsed from the datafile - */ - public function getFeatureFlags() - { - return $this->_featureFlags; - } + $getVariablesMap = self::getMethod("getVariablesMap"); + $response = $getVariablesMap->invokeArgs($this->optConfigService, array($abExp, $abExpVarA)); - /** - * @return array List of all experiments (including group experiments) - * parsed from the datafile - */ - public function getAllExperiments() - { - return array_values($this->_experimentKeyMap); + $this->assertEmpty($response); } - /** - * @param $groupId string ID of the group. - * - * @return Group Entity corresponding to the ID. - * Dummy entity is returned if ID is invalid. - */ - public function getGroup($groupId) + public function testGetVariablesMapReturnsVariableMapForEnabledVariation() { - if (isset($this->_groupIdMap[$groupId])) { - return $this->_groupIdMap[$groupId]; - } - - $this->_logger->log(Logger::ERROR, sprintf('Group ID "%s" is not in datafile.', $groupId)); - $this->_errorHandler->handleError(new InvalidGroupException('Provided group is not in datafile.')); - return new Group(); - } + $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); + $featureDisabledVar = $featExp->getVariations()[0]; - /** - * @param $experimentKey string Key of the experiment. - * - * @return Experiment Entity corresponding to the key. - * Dummy entity is returned if key is invalid. - */ - public function getExperimentFromKey($experimentKey) - { - if (isset($this->_experimentKeyMap[$experimentKey])) { - return $this->_experimentKeyMap[$experimentKey]; - } + $getVariablesMap = self::getMethod("getVariablesMap"); + $response = $getVariablesMap->invokeArgs($this->optConfigService, array($featExp, $featureDisabledVar)); - $this->_logger->log(Logger::ERROR, sprintf('Experiment key "%s" is not in datafile.', $experimentKey)); - $this->_errorHandler->handleError(new InvalidExperimentException('Provided experiment is not in datafile.')); - return new Experiment(); + $this->assertEquals($this->expectedVariableKeyMap, $response); } - /** - * @param $experimentId string ID of the experiment. - * - * @return Experiment Entity corresponding to the key. - * Dummy entity is returned if ID is invalid. - */ - public function getExperimentFromId($experimentId) + public function testGetVariablesMapReturnsVariableMapForDisabledVariation() { - if (isset($this->_experimentIdMap[$experimentId])) { - return $this->_experimentIdMap[$experimentId]; - } + $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); + $featureDisabledVar = $featExp->getVariations()[1]; - $this->_logger->log(Logger::ERROR, sprintf('Experiment ID "%s" is not in datafile.', $experimentId)); - $this->_errorHandler->handleError(new InvalidExperimentException('Provided experiment is not in datafile.')); - return new Experiment(); - } + $getVariablesMap = self::getMethod("getVariablesMap"); + $response = $getVariablesMap->invokeArgs($this->optConfigService, array($featExp, $featureDisabledVar)); - /** - * @param String $featureKey Key of the feature flag - * - * @return FeatureFlag Entity corresponding to the key. - */ - public function getFeatureFlagFromKey($featureKey) - { - if (isset($this->_featureKeyMap[$featureKey])) { - return $this->_featureKeyMap[$featureKey]; - } - - $this->_logger->log(Logger::ERROR, sprintf('FeatureFlag Key "%s" is not in datafile.', $featureKey)); - $this->_errorHandler->handleError(new InvalidFeatureFlagException('Provided feature flag is not in datafile.')); - return new FeatureFlag(); + $this->assertEquals($this->expectedDefaultVariableKeyMap, $response); } - /** - * @param String $rolloutId - * - * @return Rollout - */ - public function getRolloutFromId($rolloutId) + public function testGetVariationsMap() { - if (isset($this->_rolloutIdMap[$rolloutId])) { - return $this->_rolloutIdMap[$rolloutId]; - } + $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); - $this->_logger->log(Logger::ERROR, sprintf('Rollout with ID "%s" is not in the datafile.', $rolloutId)); + $getVariationsMap = self::getMethod("getVariationsMap"); + $response = $getVariationsMap->invokeArgs($this->optConfigService, array($featExp)); - $this->_errorHandler->handleError(new InvalidRolloutException('Provided rollout is not in datafile.')); - return new Rollout(); + $this->assertEquals($this->featExpVariationMap, $response); } - /** - * @param $eventKey string Key of the event. - * - * @return Event Entity corresponding to the key. - * Dummy entity is returned if key is invalid. - */ - public function getEvent($eventKey) + public function testGetExperimentsMaps() { - if (isset($this->_eventKeyMap[$eventKey])) { - return $this->_eventKeyMap[$eventKey]; - } + $getExperimentsMap = self::getMethod("getExperimentsMaps"); + $response = $getExperimentsMap->invokeArgs($this->optConfigService, array()); + + // assert experiment key map + $this->assertEquals($this->expectedExpKeyMap, $response[0]); - $this->_logger->log(Logger::ERROR, sprintf('Event key "%s" is not in datafile.', $eventKey)); - $this->_errorHandler->handleError(new InvalidEventException('Provided event is not in datafile.')); - return new Event(); + // assert experiment id map + $this->assertEquals($this->expectedExpIdMap, $response[1]); } - /** - * @param $audienceId string ID of the audience. - * - * @return Audience Entity corresponding to the ID. - * Null is returned if ID is invalid. - */ - public function getAudience($audienceId) + public function testGetFeaturesMap() { - if (isset($this->_audienceIdMap[$audienceId])) { - return $this->_audienceIdMap[$audienceId]; - } - - $this->_logger->log(Logger::ERROR, sprintf('Audience ID "%s" is not in datafile.', $audienceId)); - $this->_errorHandler->handleError(new InvalidAudienceException('Provided audience is not in datafile.')); + $getFeaturesMap = self::getMethod("getFeaturesMap"); + $response = $getFeaturesMap->invokeArgs( + $this->optConfigService, + array($this->expectedExpIdMap) + ); - return null; + $this->assertEquals(['test_feature' => $this->feature], $response); } - /** - * @param $attributeKey string Key of the attribute. - * - * @return Attribute Entity corresponding to the key. - * Null is returned if key is invalid. - */ - public function getAttribute($attributeKey) + public function testGetConfig() { - $hasReservedPrefix = strpos($attributeKey, self::RESERVED_ATTRIBUTE_PREFIX) === 0; - - if (isset($this->_attributeKeyMap[$attributeKey])) { - if ($hasReservedPrefix) { - $this->_logger->log( - Logger::WARNING, - sprintf('Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.', $attributeKey, self::RESERVED_ATTRIBUTE_PREFIX) - ); - } + $response = $this->optConfigService->getConfig(); - return $this->_attributeKeyMap[$attributeKey]; - } + $this->assertInstanceof(OptimizelyConfig::class, $response); - if ($hasReservedPrefix) { - return new Attribute($attributeKey, $attributeKey); - } + // assert revision + $this->assertEquals('16', $response->getRevision()); - $this->_logger->log(Logger::ERROR, sprintf('Attribute key "%s" is not in datafile.', $attributeKey)); - $this->_errorHandler->handleError(new InvalidAttributeException('Provided attribute is not in datafile.')); + // assert experiments map + $this->assertEquals($this->expectedExpKeyMap, $response->getExperimentsMap()); - return null; + // assert features map + $this->assertEquals(['test_feature' => $this->feature], $response->getFeaturesMap()); } - /** - * @param $experimentKey string Key for experiment. - * @param $variationKey string Key for variation. - * - * @return Variation Entity corresponding to the provided experiment key and variation key. - * Dummy entity is returned if key or ID is invalid. - */ - public function getVariationFromKey($experimentKey, $variationKey) + public function testJsonEncodeofOptimizelyConfig() { - if (isset($this->_variationKeyMap[$experimentKey]) - && isset($this->_variationKeyMap[$experimentKey][$variationKey]) - ) { - return $this->_variationKeyMap[$experimentKey][$variationKey]; - } - - $this->_logger->log( - Logger::ERROR, - sprintf( - 'No variation key "%s" defined in datafile for experiment "%s".', - $variationKey, - $experimentKey - ) - ); - $this->_errorHandler->handleError(new InvalidVariationException('Provided variation is not in datafile.')); - return new Variation(); - } - - /** - * @param $experimentKey string Key for experiment. - * @param $variationId string ID for variation. - * - * @return Variation Entity corresponding to the provided experiment key and variation ID. - * Dummy entity is returned if key or ID is invalid. - */ - public function getVariationFromId($experimentKey, $variationId) - { - if (isset($this->_variationIdMap[$experimentKey]) - && isset($this->_variationIdMap[$experimentKey][$variationId]) - ) { - return $this->_variationIdMap[$experimentKey][$variationId]; - } - - $this->_logger->log( - Logger::ERROR, - sprintf( - 'No variation ID "%s" defined in datafile for experiment "%s".', - $variationId, - $experimentKey - ) - ); - $this->_errorHandler->handleError(new InvalidVariationException('Provided variation is not in datafile.')); - return new Variation(); - } + $response = $this->optConfigService->getConfig(); + + $expectedJSON = '{ + "revision": "16", + "experimentsMap": { + "ab_experiment": { + "id": "17301270474", + "key": "ab_experiment", + "variationsMap": { + "variation_a": { + "id": "17277380360", + "key": "variation_a", + "variablesMap": [ + + ] + }, + "variation_b": { + "id": "17273501081", + "key": "variation_b", + "variablesMap": [ + + ] + } + } + }, + "feat_experiment": { + "id": "17279300791", + "key": "feat_experiment", + "variationsMap": { + "variation_a": { + "id": "17289540366", + "key": "variation_a", + "featureEnabled": true, + "variablesMap": { + "boolean_var": { + "id": "17252790456", + "key": "boolean_var", + "type": "boolean", + "value": "true" + }, + "integer_var": { + "id": "17258820367", + "key": "integer_var", + "type": "integer", + "value": "5" + }, + "double_var": { + "id": "17260550714", + "key": "double_var", + "type": "double", + "value": "5.5" + }, + "string_var": { + "id": "17290540010", + "key": "string_var", + "type": "string", + "value": "i am variable value" + }, + "json_var": { + "id": "17260550458", + "key": "json_var", + "type": "json", + "value": "{\"text\": \"variable value\"}" + } + } + }, + "variation_b": { + "id": "17304990114", + "key": "variation_b", + "featureEnabled": false, + "variablesMap": { + "boolean_var": { + "id": "17252790456", + "key": "boolean_var", + "type": "boolean", + "value": "false" + }, + "integer_var": { + "id": "17258820367", + "key": "integer_var", + "type": "integer", + "value": "1" + }, + "double_var": { + "id": "17260550714", + "key": "double_var", + "type": "double", + "value": "0.5" + }, + "string_var": { + "id": "17290540010", + "key": "string_var", + "type": "string", + "value": "i am default value" + }, + "json_var": { + "id": "17260550458", + "key": "json_var", + "type": "json", + "value": "{\"text\": \"default value\"}" + } + } + } + } + }, + "group_ab_experiment": { + "id": "17258450439", + "key": "group_ab_experiment", + "variationsMap": { + "variation_a": { + "id": "17287500312", + "key": "variation_a", + "variablesMap": [ + + ] + }, + "variation_b": { + "id": "17283640326", + "key": "variation_b", + "variablesMap": [ + + ] + } + } + } + }, + "featuresMap": { + "test_feature": { + "id": "17266500726", + "key": "test_feature", + "experimentsMap": { + "feat_experiment": { + "id": "17279300791", + "key": "feat_experiment", + "variationsMap": { + "variation_a": { + "id": "17289540366", + "key": "variation_a", + "featureEnabled": true, + "variablesMap": { + "boolean_var": { + "id": "17252790456", + "key": "boolean_var", + "type": "boolean", + "value": "true" + }, + "integer_var": { + "id": "17258820367", + "key": "integer_var", + "type": "integer", + "value": "5" + }, + "double_var": { + "id": "17260550714", + "key": "double_var", + "type": "double", + "value": "5.5" + }, + "string_var": { + "id": "17290540010", + "key": "string_var", + "type": "string", + "value": "i am variable value" + }, + "json_var": { + "id": "17260550458", + "key": "json_var", + "type": "json", + "value": "{\"text\": \"variable value\"}" + } + } + }, + "variation_b": { + "id": "17304990114", + "key": "variation_b", + "featureEnabled": false, + "variablesMap": { + "boolean_var": { + "id": "17252790456", + "key": "boolean_var", + "type": "boolean", + "value": "false" + }, + "integer_var": { + "id": "17258820367", + "key": "integer_var", + "type": "integer", + "value": "1" + }, + "double_var": { + "id": "17260550714", + "key": "double_var", + "type": "double", + "value": "0.5" + }, + "string_var": { + "id": "17290540010", + "key": "string_var", + "type": "string", + "value": "i am default value" + }, + "json_var": { + "id": "17260550458", + "key": "json_var", + "type": "json", + "value": "{\"text\": \"default value\"}" + } + } + } + } + } + }, + "variablesMap": { + "boolean_var": { + "id": "17252790456", + "key": "boolean_var", + "type": "boolean", + "value": "false" + }, + "integer_var": { + "id": "17258820367", + "key": "integer_var", + "type": "integer", + "value": "1" + }, + "double_var": { + "id": "17260550714", + "key": "double_var", + "type": "double", + "value": "0.5" + }, + "string_var": { + "id": "17290540010", + "key": "string_var", + "type": "string", + "value": "i am default value" + }, + "json_var": { + "id": "17260550458", + "key": "json_var", + "type": "json", + "value": "{\"text\": \"default value\"}" + } + } + } + } + }'; - /** - * Gets the feature variable instance given feature flag key and variable key - * - * @param string Feature flag key - * @param string Variable key - * - * @return FeatureVariable / null - */ - public function getFeatureVariableFromKey($featureFlagKey, $variableKey) - { - $featureFlag = $this->getFeatureFlagFromKey($featureFlagKey); - if ($featureFlag && !($featureFlag->getKey())) { - return null; - } - - if (isset($this->_featureFlagVariableMap[$featureFlagKey]) - && isset($this->_featureFlagVariableMap[$featureFlagKey][$variableKey]) - ) { - return $this->_featureFlagVariableMap[$featureFlagKey][$variableKey]; - } - - $this->_logger->log( - Logger::ERROR, - sprintf( - 'No variable key "%s" defined in datafile for feature flag "%s".', - $variableKey, - $featureFlagKey - ) - ); - $this->_errorHandler->handleError( - new InvalidFeatureVariableException('Provided feature variable is not in datafile.') - ); - return null; + $this->assertEquals(json_encode(json_decode($expectedJSON)), json_encode($response)); } - /** - * Determines if given experiment is a feature test. - * - * @param string Experiment ID. - * - * @return boolean A boolean value that indicates if the experiment is a feature test. - */ - public function isFeatureExperiment($experimentId) + public function testGetDatafile() { - return array_key_exists($experimentId, $this->_experimentFeatureMap); + $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; + $actualDatafile = $this->optConfigService->getDatafile(); + $this->assertEquals($expectedDatafile, $actualDatafile); } } diff --git a/src/Optimizely/Config/ProjectConfigInterface.php b/src/Optimizely/Config/ProjectConfigInterface.php index 4507c0a9..554e4d54 100644 --- a/src/Optimizely/Config/ProjectConfigInterface.php +++ b/src/Optimizely/Config/ProjectConfigInterface.php @@ -159,4 +159,11 @@ public function getFeatureVariableFromKey($featureFlagKey, $variableKey); * @return boolean A boolean value that indicates if the experiment is a feature test. */ public function isFeatureExperiment($experimentId); + + /** + * Gets datafile. + * + * @return string A string value that contains datafile contents. + */ + public function toDatafile(); } diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php index 133f071b..709e04b6 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php @@ -37,11 +37,18 @@ class OptimizelyConfig implements \JsonSerializable */ private $featuresMap; - public function __construct($revision, array $experimentsMap, array $featuresMap) + /** + * @var string Contents of datafile. + */ + private $datafile; + + + public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile=null) { $this->revision = $revision; $this->experimentsMap = $experimentsMap; $this->featuresMap = $featuresMap; + $this->datafile = $datafile; } /** @@ -52,6 +59,14 @@ public function getRevision() return $this->revision; } + /** + * @return string Datafile contents. + */ + public function getDatafile() + { + return $this->datafile; + } + /** * @return array Map of Experiment Keys to OptimizelyExperiments. */ diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php index 6032548d..bc1e889a 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php @@ -37,6 +37,11 @@ class OptimizelyConfigService */ private $revision; + /** + * @var string config datafile. + */ + private $datafile; + /** * Map of experiment IDs to FeatureFlags. * @@ -63,6 +68,7 @@ public function __construct(ProjectConfigInterface $projectConfig) $this->experiments = $projectConfig->getAllExperiments(); $this->featureFlags = $projectConfig->getFeatureFlags(); $this->revision = $projectConfig->getRevision(); + $this->datafile = $projectConfig->toDatafile(); $this->createLookupMaps(); } diff --git a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php index a64e5032..a8ec96a6 100644 --- a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php @@ -467,4 +467,11 @@ public function testJsonEncodeofOptimizelyConfig() $this->assertEquals(json_encode(json_decode($expectedJSON)), json_encode($response)); } + + public function testGetDatafile() + { + $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; + $actualDatafile = $this->optConfigService->getDatafile(); + $this->assertEquals($expectedDatafile, $actualDatafile); + } } From a500260cf207ff890bd4cedf9467d2b0673790d4 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Tue, 25 Aug 2020 21:26:55 +0500 Subject: [PATCH 02/11] Revert "datafile accessor and test cases" This reverts commit 4828321a5d17b1910f62529751bb835c474677bd. --- .../Config/DatafileProjectConfig.php | 1004 ++++++++++------- .../Config/ProjectConfigInterface.php | 7 - .../OptimizelyConfig/OptimizelyConfig.php | 17 +- .../OptimizelyConfigService.php | 6 - .../OptimizelyConfigServiceTest.php | 7 - 5 files changed, 598 insertions(+), 443 deletions(-) diff --git a/src/Optimizely/Config/DatafileProjectConfig.php b/src/Optimizely/Config/DatafileProjectConfig.php index a8ec96a6..efceebe6 100644 --- a/src/Optimizely/Config/DatafileProjectConfig.php +++ b/src/Optimizely/Config/DatafileProjectConfig.php @@ -1,6 +1,6 @@ associative array of feature keys to feature flags + */ + private $_featureKeyMap; + + /** + * internal mapping of rollout IDs to Rollout models. + * + * @var associative array of rollout ids to rollouts + */ + private $_rolloutIdMap; + + /** + * Feature Flag key to Feature Variable key to Feature Variable map + * + * @var > + */ + private $_featureFlagVariableMap; + + /** + * Associative array of experiment ID to Feature ID(s) in the datafile. + * + * @var + */ + private $_experimentFeatureMap; + + /** + * DatafileProjectConfig constructor to load and set project configuration data. + * + * @param $datafile string JSON string representing the project. + * @param $logger LoggerInterface + * @param $errorHandler ErrorHandlerInterface + */ + public function __construct($datafile, $logger, $errorHandler) { - $this->datafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; + $supportedVersions = array(self::V2, self::V3, self::V4); + $config = json_decode($datafile, true); + $this->_logger = $logger; + $this->_errorHandler = $errorHandler; + $this->_version = $config['version']; + if (!in_array($this->_version, $supportedVersions)) { + throw new InvalidDatafileVersionException( + "This version of the PHP SDK does not support the given datafile version: {$this->_version}." + ); + } + + $this->_accountId = $config['accountId']; + $this->_projectId = $config['projectId']; + $this->_anonymizeIP = isset($config['anonymizeIP'])? $config['anonymizeIP'] : false; + $this->_botFiltering = isset($config['botFiltering'])? $config['botFiltering'] : null; + $this->_revision = $config['revision']; + + $groups = $config['groups'] ?: []; + $experiments = $config['experiments'] ?: []; + $events = $config['events'] ?: []; + $attributes = $config['attributes'] ?: []; + $audiences = $config['audiences'] ?: []; + $typedAudiences = isset($config['typedAudiences']) ? $config['typedAudiences']: []; + $rollouts = isset($config['rollouts']) ? $config['rollouts'] : []; + $featureFlags = isset($config['featureFlags']) ? $config['featureFlags']: []; + + // 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 + foreach ($featureFlags as $featureFlagKey => $featureFlag) { + foreach ($featureFlag['variables'] as $variableKey => $variable) { + if (isset($variable['subType']) && $variable['type'] === FeatureVariable::STRING_TYPE && $variable['subType'] === FeatureVariable::JSON_TYPE) { + $variable['type'] = FeatureVariable::JSON_TYPE; + unset($variable['subType']); + $featureFlags[$featureFlagKey]['variables'][$variableKey] = $variable; + } + } + } + + $this->_groupIdMap = ConfigParser::generateMap($groups, 'id', Group::class); + $this->_experimentKeyMap = ConfigParser::generateMap($experiments, 'key', Experiment::class); + $this->_eventKeyMap = ConfigParser::generateMap($events, 'key', Event::class); + $this->_attributeKeyMap = ConfigParser::generateMap($attributes, 'key', Attribute::class); + $typedAudienceIdMap = ConfigParser::generateMap($typedAudiences, 'id', Audience::class); + $this->_audienceIdMap = ConfigParser::generateMap($audiences, 'id', Audience::class); + $this->_rollouts = ConfigParser::generateMap($rollouts, null, Rollout::class); + $this->_featureFlags = ConfigParser::generateMap($featureFlags, null, FeatureFlag::class); + + foreach (array_values($this->_groupIdMap) as $group) { + $experimentsInGroup = ConfigParser::generateMap($group->getExperiments(), 'key', Experiment::class); + foreach (array_values($experimentsInGroup) as $experiment) { + $experiment->setGroupId($group->getId()); + $experiment->setGroupPolicy($group->getPolicy()); + } + $this->_experimentKeyMap = $this->_experimentKeyMap + $experimentsInGroup; + } - $this->projectConfig = new DatafileProjectConfig( - $this->datafile, - new NoOpLogger(), - new NoOpErrorHandler() - ); + $this->_variationKeyMap = []; + $this->_variationIdMap = []; + $this->_experimentIdMap = []; - $this->optConfigService = new OptimizelyConfigService($this->projectConfig); - - // Create expected default variables map for feat_experiment variation_b - $boolDefaultVariable = new OptimizelyVariable('17252790456', 'boolean_var', 'boolean', 'false'); - $intDefaultVariable = new OptimizelyVariable('17258820367', 'integer_var', 'integer', 1); - $doubleDefaultVariable = new OptimizelyVariable('17260550714', 'double_var', 'double', 0.5); - $strDefaultVariable = new OptimizelyVariable('17290540010', 'string_var', 'string', 'i am default value'); - $jsonDefaultVariable = new OptimizelyVariable('17260550458', 'json_var', 'json', "{\"text\": \"default value\"}"); - - $this->expectedDefaultVariableKeyMap = []; - $this->expectedDefaultVariableKeyMap['boolean_var'] = $boolDefaultVariable; - $this->expectedDefaultVariableKeyMap['integer_var'] = $intDefaultVariable; - $this->expectedDefaultVariableKeyMap['double_var'] = $doubleDefaultVariable; - $this->expectedDefaultVariableKeyMap['string_var'] = $strDefaultVariable; - $this->expectedDefaultVariableKeyMap['json_var'] = $jsonDefaultVariable; - - // Create variable variables map for feat_experiment variation_a - $boolFeatVariable = new OptimizelyVariable('17252790456', 'boolean_var', 'boolean', 'true'); - $intFeatVariable = new OptimizelyVariable('17258820367', 'integer_var', 'integer', 5); - $doubleFeatVariable = new OptimizelyVariable('17260550714', 'double_var', 'double', 5.5); - $strFeatVariable = new OptimizelyVariable('17290540010', 'string_var', 'string', 'i am variable value'); - $jsonFeatVariable = new OptimizelyVariable('17260550458', 'json_var', 'json', "{\"text\": \"variable value\"}"); - - $this->expectedVariableKeyMap = []; - $this->expectedVariableKeyMap['boolean_var'] = $boolFeatVariable; - $this->expectedVariableKeyMap['integer_var'] = $intFeatVariable; - $this->expectedVariableKeyMap['double_var'] = $doubleFeatVariable; - $this->expectedVariableKeyMap['string_var'] = $strFeatVariable; - $this->expectedVariableKeyMap['json_var'] = $jsonFeatVariable; - - // Create variations map for feat_experiment - $this->featExpVariationMap = []; - $this->featExpVariationMap['variation_a'] = - new OptimizelyVariation('17289540366', 'variation_a', true, $this->expectedVariableKeyMap); - - $this->featExpVariationMap['variation_b'] = - new OptimizelyVariation('17304990114', 'variation_b', false, $this->expectedDefaultVariableKeyMap); - - // create feat_experiment - $featExperiment = - new OptimizelyExperiment("17279300791", "feat_experiment", $this->featExpVariationMap); - - - // create feature - $experimentsMap = ['feat_experiment' => $featExperiment]; - $this->feature = - new OptimizelyFeature( - '17266500726', - 'test_feature', - $experimentsMap, - $this->expectedDefaultVariableKeyMap - ); + foreach (array_values($this->_experimentKeyMap) as $experiment) { + $this->_variationKeyMap[$experiment->getKey()] = []; + $this->_variationIdMap[$experiment->getKey()] = []; + $this->_experimentIdMap[$experiment->getId()] = $experiment; - // create ab experiment and variations - $variationA = new OptimizelyVariation('17277380360', 'variation_a', null, []); - $variationB = new OptimizelyVariation('17273501081', 'variation_b', null, []); - $variationsMap = []; - $variationsMap['variation_a'] = $variationA; - $variationsMap['variation_b'] = $variationB; - - $abExperiment = new OptimizelyExperiment('17301270474', 'ab_experiment', $variationsMap); - - // create group_ab_experiment and variations - $variationA = new OptimizelyVariation('17287500312', 'variation_a', null, []); - $variationB = new OptimizelyVariation('17283640326', 'variation_b', null, []); - $variationsMap = []; - $variationsMap['variation_a'] = $variationA; - $variationsMap['variation_b'] = $variationB; - - $groupExperiment = - new OptimizelyExperiment('17258450439', 'group_ab_experiment', $variationsMap); - - // create experiment key map - $this->expectedExpKeyMap = []; - $this->expectedExpKeyMap['ab_experiment'] = $abExperiment; - $this->expectedExpKeyMap['group_ab_experiment'] = $groupExperiment; - $this->expectedExpKeyMap['feat_experiment'] = $featExperiment; - - // create experiment id map - $this->expectedExpIdMap = []; - $this->expectedExpIdMap['17301270474'] = $abExperiment; - $this->expectedExpIdMap['17258450439'] = $groupExperiment; - $this->expectedExpIdMap['17279300791'] = $featExperiment; + foreach ($experiment->getVariations() as $variation) { + $this->_variationKeyMap[$experiment->getKey()][$variation->getKey()] = $variation; + $this->_variationIdMap[$experiment->getKey()][$variation->getId()] = $variation; + } + } + + foreach (array_values($this->_audienceIdMap) as $audience) { + $audience->setConditionsList(json_decode($audience->getConditions(), true)); + } + + // Conditions in typedAudiences are not expected to be string-encoded so they don't need + // to be decoded unlike audiences. + foreach (array_values($typedAudienceIdMap) as $typedAudience) { + $typedAudience->setConditionsList($typedAudience->getConditions()); + } + + // Overwrite audiences by typedAudiences. + $this->_audienceIdMap = array_replace($this->_audienceIdMap, $typedAudienceIdMap); + + $rolloutVariationIdMap = []; + $rolloutVariationKeyMap = []; + foreach ($this->_rollouts as $rollout) { + $this->_rolloutIdMap[$rollout->getId()] = $rollout; + + foreach ($rollout->getExperiments() as $rule) { + $rolloutVariationIdMap[$rule->getKey()] = []; + $rolloutVariationKeyMap[$rule->getKey()] = []; + + $variations = $rule->getVariations(); + foreach ($variations as $variation) { + $rolloutVariationIdMap[$rule->getKey()][$variation->getId()] = $variation; + $rolloutVariationKeyMap[$rule->getKey()][$variation->getKey()] = $variation; + } + } + } + + // Add variations for rollout experiments to variationIdMap and variationKeyMap + $this->_variationIdMap = $this->_variationIdMap + $rolloutVariationIdMap; + $this->_variationKeyMap = $this->_variationKeyMap + $rolloutVariationKeyMap; + + foreach (array_values($this->_featureFlags) as $featureFlag) { + $this->_featureKeyMap[$featureFlag->getKey()] = $featureFlag; + } + + $this->_experimentFeatureMap = []; + if ($this->_featureKeyMap) { + foreach ($this->_featureKeyMap as $featureKey => $featureFlag) { + $this->_featureFlagVariableMap[$featureKey] = ConfigParser::generateMap( + $featureFlag->getVariables(), + 'key', + FeatureVariable::class + ); + + $featureFlagId = $featureFlag->getId(); + foreach ($featureFlag->getExperimentIds() as $experimentId) { + $this->_experimentFeatureMap[$experimentId] = [$featureFlagId]; + } + } + } } - protected static function getMethod($name) + /** + * Create ProjectConfig based on datafile string. + * + * @param string $datafile JSON string representing the Optimizely project. + * @param bool $skipJsonValidation boolean representing whether JSON schema validation needs to be performed. + * @param LoggerInterface $logger Logger instance + * @param ErrorHandlerInterface $errorHandler ErrorHandler instance. + * @return ProjectConfig ProjectConfig instance or null; + */ + public static function createProjectConfigFromDatafile($datafile, $skipJsonValidation, $logger, $errorHandler) { - $class = new \ReflectionClass('Optimizely\\OptimizelyConfig\\OptimizelyConfigService'); - $method = $class->getMethod($name); - $method->setAccessible(true); - return $method; + if (!$skipJsonValidation) { + if (!Validator::validateJsonSchema($datafile)) { + $defaultLogger = new DefaultLogger(); + $defaultLogger->log(Logger::ERROR, 'Provided "datafile" has invalid schema.'); + $logger->log(Logger::ERROR, 'Provided "datafile" has invalid schema.'); + return null; + } + } + + try { + $config = new DatafileProjectConfig($datafile, $logger, $errorHandler); + } catch (Exception $exception) { + $defaultLogger = new DefaultLogger(); + $errorMsg = $exception->getCode() == InvalidDatafileVersionException::class ? $exception->getMessage() : sprintf(Errors::INVALID_FORMAT, 'datafile'); + $errorToHandle = $exception->getCode() == InvalidDatafileVersionException::class ? new InvalidDatafileVersionException($errorMsg) : new InvalidInputException($errorMsg); + $defaultLogger->log(Logger::ERROR, $errorMsg); + $logger->log(Logger::ERROR, $errorMsg); + $errorHandler->handleError($errorToHandle); + return null; + } + + return $config; } - public function testGetVariablesMapReturnsEmptyForAbExpVariation() + /** + * @return string String representing account ID from the datafile. + */ + public function getAccountId() { - $abExp = $this->projectConfig->getExperimentFromKey("ab_experiment"); - $abExpVarA = $abExp->getVariations()[0]; + return $this->_accountId; + } - $getVariablesMap = self::getMethod("getVariablesMap"); - $response = $getVariablesMap->invokeArgs($this->optConfigService, array($abExp, $abExpVarA)); + /** + * @return string String representing project ID from the datafile. + */ + public function getProjectId() + { + return $this->_projectId; + } - $this->assertEmpty($response); + /** + * @return boolean Flag denoting if Optimizely should remove last block + * of visitors' IP address before storing event data + */ + public function getAnonymizeIP() + { + return $this->_anonymizeIP; } - public function testGetVariablesMapReturnsVariableMapForEnabledVariation() + /** + * @return boolean Flag denoting if Optimizely should perform + * bot filtering on your dispatched events. + */ + public function getBotFiltering() { - $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); - $featureDisabledVar = $featExp->getVariations()[0]; + return $this->_botFiltering; + } - $getVariablesMap = self::getMethod("getVariablesMap"); - $response = $getVariablesMap->invokeArgs($this->optConfigService, array($featExp, $featureDisabledVar)); + /** + * @return string String representing revision of the datafile. + */ + public function getRevision() + { + return $this->_revision; + } - $this->assertEquals($this->expectedVariableKeyMap, $response); + /** + * @return array List of feature flags parsed from the datafile + */ + public function getFeatureFlags() + { + return $this->_featureFlags; } - public function testGetVariablesMapReturnsVariableMapForDisabledVariation() + /** + * @return array List of all experiments (including group experiments) + * parsed from the datafile + */ + public function getAllExperiments() { - $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); - $featureDisabledVar = $featExp->getVariations()[1]; + return array_values($this->_experimentKeyMap); + } - $getVariablesMap = self::getMethod("getVariablesMap"); - $response = $getVariablesMap->invokeArgs($this->optConfigService, array($featExp, $featureDisabledVar)); + /** + * @param $groupId string ID of the group. + * + * @return Group Entity corresponding to the ID. + * Dummy entity is returned if ID is invalid. + */ + public function getGroup($groupId) + { + if (isset($this->_groupIdMap[$groupId])) { + return $this->_groupIdMap[$groupId]; + } - $this->assertEquals($this->expectedDefaultVariableKeyMap, $response); + $this->_logger->log(Logger::ERROR, sprintf('Group ID "%s" is not in datafile.', $groupId)); + $this->_errorHandler->handleError(new InvalidGroupException('Provided group is not in datafile.')); + return new Group(); } - public function testGetVariationsMap() + /** + * @param $experimentKey string Key of the experiment. + * + * @return Experiment Entity corresponding to the key. + * Dummy entity is returned if key is invalid. + */ + public function getExperimentFromKey($experimentKey) { - $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); - - $getVariationsMap = self::getMethod("getVariationsMap"); - $response = $getVariationsMap->invokeArgs($this->optConfigService, array($featExp)); + if (isset($this->_experimentKeyMap[$experimentKey])) { + return $this->_experimentKeyMap[$experimentKey]; + } - $this->assertEquals($this->featExpVariationMap, $response); + $this->_logger->log(Logger::ERROR, sprintf('Experiment key "%s" is not in datafile.', $experimentKey)); + $this->_errorHandler->handleError(new InvalidExperimentException('Provided experiment is not in datafile.')); + return new Experiment(); } - public function testGetExperimentsMaps() + /** + * @param $experimentId string ID of the experiment. + * + * @return Experiment Entity corresponding to the key. + * Dummy entity is returned if ID is invalid. + */ + public function getExperimentFromId($experimentId) { - $getExperimentsMap = self::getMethod("getExperimentsMaps"); - $response = $getExperimentsMap->invokeArgs($this->optConfigService, array()); + if (isset($this->_experimentIdMap[$experimentId])) { + return $this->_experimentIdMap[$experimentId]; + } - // assert experiment key map - $this->assertEquals($this->expectedExpKeyMap, $response[0]); + $this->_logger->log(Logger::ERROR, sprintf('Experiment ID "%s" is not in datafile.', $experimentId)); + $this->_errorHandler->handleError(new InvalidExperimentException('Provided experiment is not in datafile.')); + return new Experiment(); + } + + /** + * @param String $featureKey Key of the feature flag + * + * @return FeatureFlag Entity corresponding to the key. + */ + public function getFeatureFlagFromKey($featureKey) + { + if (isset($this->_featureKeyMap[$featureKey])) { + return $this->_featureKeyMap[$featureKey]; + } - // assert experiment id map - $this->assertEquals($this->expectedExpIdMap, $response[1]); + $this->_logger->log(Logger::ERROR, sprintf('FeatureFlag Key "%s" is not in datafile.', $featureKey)); + $this->_errorHandler->handleError(new InvalidFeatureFlagException('Provided feature flag is not in datafile.')); + return new FeatureFlag(); } - public function testGetFeaturesMap() + /** + * @param String $rolloutId + * + * @return Rollout + */ + public function getRolloutFromId($rolloutId) { - $getFeaturesMap = self::getMethod("getFeaturesMap"); - $response = $getFeaturesMap->invokeArgs( - $this->optConfigService, - array($this->expectedExpIdMap) - ); + if (isset($this->_rolloutIdMap[$rolloutId])) { + return $this->_rolloutIdMap[$rolloutId]; + } - $this->assertEquals(['test_feature' => $this->feature], $response); + $this->_logger->log(Logger::ERROR, sprintf('Rollout with ID "%s" is not in the datafile.', $rolloutId)); + + $this->_errorHandler->handleError(new InvalidRolloutException('Provided rollout is not in datafile.')); + return new Rollout(); } - public function testGetConfig() + /** + * @param $eventKey string Key of the event. + * + * @return Event Entity corresponding to the key. + * Dummy entity is returned if key is invalid. + */ + public function getEvent($eventKey) { - $response = $this->optConfigService->getConfig(); + if (isset($this->_eventKeyMap[$eventKey])) { + return $this->_eventKeyMap[$eventKey]; + } - $this->assertInstanceof(OptimizelyConfig::class, $response); + $this->_logger->log(Logger::ERROR, sprintf('Event key "%s" is not in datafile.', $eventKey)); + $this->_errorHandler->handleError(new InvalidEventException('Provided event is not in datafile.')); + return new Event(); + } - // assert revision - $this->assertEquals('16', $response->getRevision()); + /** + * @param $audienceId string ID of the audience. + * + * @return Audience Entity corresponding to the ID. + * Null is returned if ID is invalid. + */ + public function getAudience($audienceId) + { + if (isset($this->_audienceIdMap[$audienceId])) { + return $this->_audienceIdMap[$audienceId]; + } - // assert experiments map - $this->assertEquals($this->expectedExpKeyMap, $response->getExperimentsMap()); + $this->_logger->log(Logger::ERROR, sprintf('Audience ID "%s" is not in datafile.', $audienceId)); + $this->_errorHandler->handleError(new InvalidAudienceException('Provided audience is not in datafile.')); - // assert features map - $this->assertEquals(['test_feature' => $this->feature], $response->getFeaturesMap()); + return null; } - public function testJsonEncodeofOptimizelyConfig() + /** + * @param $attributeKey string Key of the attribute. + * + * @return Attribute Entity corresponding to the key. + * Null is returned if key is invalid. + */ + public function getAttribute($attributeKey) { - $response = $this->optConfigService->getConfig(); - - $expectedJSON = '{ - "revision": "16", - "experimentsMap": { - "ab_experiment": { - "id": "17301270474", - "key": "ab_experiment", - "variationsMap": { - "variation_a": { - "id": "17277380360", - "key": "variation_a", - "variablesMap": [ - - ] - }, - "variation_b": { - "id": "17273501081", - "key": "variation_b", - "variablesMap": [ - - ] - } - } - }, - "feat_experiment": { - "id": "17279300791", - "key": "feat_experiment", - "variationsMap": { - "variation_a": { - "id": "17289540366", - "key": "variation_a", - "featureEnabled": true, - "variablesMap": { - "boolean_var": { - "id": "17252790456", - "key": "boolean_var", - "type": "boolean", - "value": "true" - }, - "integer_var": { - "id": "17258820367", - "key": "integer_var", - "type": "integer", - "value": "5" - }, - "double_var": { - "id": "17260550714", - "key": "double_var", - "type": "double", - "value": "5.5" - }, - "string_var": { - "id": "17290540010", - "key": "string_var", - "type": "string", - "value": "i am variable value" - }, - "json_var": { - "id": "17260550458", - "key": "json_var", - "type": "json", - "value": "{\"text\": \"variable value\"}" - } - } - }, - "variation_b": { - "id": "17304990114", - "key": "variation_b", - "featureEnabled": false, - "variablesMap": { - "boolean_var": { - "id": "17252790456", - "key": "boolean_var", - "type": "boolean", - "value": "false" - }, - "integer_var": { - "id": "17258820367", - "key": "integer_var", - "type": "integer", - "value": "1" - }, - "double_var": { - "id": "17260550714", - "key": "double_var", - "type": "double", - "value": "0.5" - }, - "string_var": { - "id": "17290540010", - "key": "string_var", - "type": "string", - "value": "i am default value" - }, - "json_var": { - "id": "17260550458", - "key": "json_var", - "type": "json", - "value": "{\"text\": \"default value\"}" - } - } - } - } - }, - "group_ab_experiment": { - "id": "17258450439", - "key": "group_ab_experiment", - "variationsMap": { - "variation_a": { - "id": "17287500312", - "key": "variation_a", - "variablesMap": [ - - ] - }, - "variation_b": { - "id": "17283640326", - "key": "variation_b", - "variablesMap": [ - - ] - } - } + $hasReservedPrefix = strpos($attributeKey, self::RESERVED_ATTRIBUTE_PREFIX) === 0; + + if (isset($this->_attributeKeyMap[$attributeKey])) { + if ($hasReservedPrefix) { + $this->_logger->log( + Logger::WARNING, + sprintf('Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.', $attributeKey, self::RESERVED_ATTRIBUTE_PREFIX) + ); } - }, - "featuresMap": { - "test_feature": { - "id": "17266500726", - "key": "test_feature", - "experimentsMap": { - "feat_experiment": { - "id": "17279300791", - "key": "feat_experiment", - "variationsMap": { - "variation_a": { - "id": "17289540366", - "key": "variation_a", - "featureEnabled": true, - "variablesMap": { - "boolean_var": { - "id": "17252790456", - "key": "boolean_var", - "type": "boolean", - "value": "true" - }, - "integer_var": { - "id": "17258820367", - "key": "integer_var", - "type": "integer", - "value": "5" - }, - "double_var": { - "id": "17260550714", - "key": "double_var", - "type": "double", - "value": "5.5" - }, - "string_var": { - "id": "17290540010", - "key": "string_var", - "type": "string", - "value": "i am variable value" - }, - "json_var": { - "id": "17260550458", - "key": "json_var", - "type": "json", - "value": "{\"text\": \"variable value\"}" - } - } - }, - "variation_b": { - "id": "17304990114", - "key": "variation_b", - "featureEnabled": false, - "variablesMap": { - "boolean_var": { - "id": "17252790456", - "key": "boolean_var", - "type": "boolean", - "value": "false" - }, - "integer_var": { - "id": "17258820367", - "key": "integer_var", - "type": "integer", - "value": "1" - }, - "double_var": { - "id": "17260550714", - "key": "double_var", - "type": "double", - "value": "0.5" - }, - "string_var": { - "id": "17290540010", - "key": "string_var", - "type": "string", - "value": "i am default value" - }, - "json_var": { - "id": "17260550458", - "key": "json_var", - "type": "json", - "value": "{\"text\": \"default value\"}" - } - } - } - } - } - }, - "variablesMap": { - "boolean_var": { - "id": "17252790456", - "key": "boolean_var", - "type": "boolean", - "value": "false" - }, - "integer_var": { - "id": "17258820367", - "key": "integer_var", - "type": "integer", - "value": "1" - }, - "double_var": { - "id": "17260550714", - "key": "double_var", - "type": "double", - "value": "0.5" - }, - "string_var": { - "id": "17290540010", - "key": "string_var", - "type": "string", - "value": "i am default value" - }, - "json_var": { - "id": "17260550458", - "key": "json_var", - "type": "json", - "value": "{\"text\": \"default value\"}" - } - } - } - } - }'; - $this->assertEquals(json_encode(json_decode($expectedJSON)), json_encode($response)); + return $this->_attributeKeyMap[$attributeKey]; + } + + if ($hasReservedPrefix) { + return new Attribute($attributeKey, $attributeKey); + } + + $this->_logger->log(Logger::ERROR, sprintf('Attribute key "%s" is not in datafile.', $attributeKey)); + $this->_errorHandler->handleError(new InvalidAttributeException('Provided attribute is not in datafile.')); + + return null; + } + + /** + * @param $experimentKey string Key for experiment. + * @param $variationKey string Key for variation. + * + * @return Variation Entity corresponding to the provided experiment key and variation key. + * Dummy entity is returned if key or ID is invalid. + */ + public function getVariationFromKey($experimentKey, $variationKey) + { + if (isset($this->_variationKeyMap[$experimentKey]) + && isset($this->_variationKeyMap[$experimentKey][$variationKey]) + ) { + return $this->_variationKeyMap[$experimentKey][$variationKey]; + } + + $this->_logger->log( + Logger::ERROR, + sprintf( + 'No variation key "%s" defined in datafile for experiment "%s".', + $variationKey, + $experimentKey + ) + ); + $this->_errorHandler->handleError(new InvalidVariationException('Provided variation is not in datafile.')); + return new Variation(); + } + + /** + * @param $experimentKey string Key for experiment. + * @param $variationId string ID for variation. + * + * @return Variation Entity corresponding to the provided experiment key and variation ID. + * Dummy entity is returned if key or ID is invalid. + */ + public function getVariationFromId($experimentKey, $variationId) + { + if (isset($this->_variationIdMap[$experimentKey]) + && isset($this->_variationIdMap[$experimentKey][$variationId]) + ) { + return $this->_variationIdMap[$experimentKey][$variationId]; + } + + $this->_logger->log( + Logger::ERROR, + sprintf( + 'No variation ID "%s" defined in datafile for experiment "%s".', + $variationId, + $experimentKey + ) + ); + $this->_errorHandler->handleError(new InvalidVariationException('Provided variation is not in datafile.')); + return new Variation(); + } + + /** + * Gets the feature variable instance given feature flag key and variable key + * + * @param string Feature flag key + * @param string Variable key + * + * @return FeatureVariable / null + */ + public function getFeatureVariableFromKey($featureFlagKey, $variableKey) + { + $featureFlag = $this->getFeatureFlagFromKey($featureFlagKey); + if ($featureFlag && !($featureFlag->getKey())) { + return null; + } + + if (isset($this->_featureFlagVariableMap[$featureFlagKey]) + && isset($this->_featureFlagVariableMap[$featureFlagKey][$variableKey]) + ) { + return $this->_featureFlagVariableMap[$featureFlagKey][$variableKey]; + } + + $this->_logger->log( + Logger::ERROR, + sprintf( + 'No variable key "%s" defined in datafile for feature flag "%s".', + $variableKey, + $featureFlagKey + ) + ); + $this->_errorHandler->handleError( + new InvalidFeatureVariableException('Provided feature variable is not in datafile.') + ); + return null; } - public function testGetDatafile() + /** + * Determines if given experiment is a feature test. + * + * @param string Experiment ID. + * + * @return boolean A boolean value that indicates if the experiment is a feature test. + */ + public function isFeatureExperiment($experimentId) { - $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; - $actualDatafile = $this->optConfigService->getDatafile(); - $this->assertEquals($expectedDatafile, $actualDatafile); + return array_key_exists($experimentId, $this->_experimentFeatureMap); } } diff --git a/src/Optimizely/Config/ProjectConfigInterface.php b/src/Optimizely/Config/ProjectConfigInterface.php index 554e4d54..4507c0a9 100644 --- a/src/Optimizely/Config/ProjectConfigInterface.php +++ b/src/Optimizely/Config/ProjectConfigInterface.php @@ -159,11 +159,4 @@ public function getFeatureVariableFromKey($featureFlagKey, $variableKey); * @return boolean A boolean value that indicates if the experiment is a feature test. */ public function isFeatureExperiment($experimentId); - - /** - * Gets datafile. - * - * @return string A string value that contains datafile contents. - */ - public function toDatafile(); } diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php index 709e04b6..133f071b 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php @@ -37,18 +37,11 @@ class OptimizelyConfig implements \JsonSerializable */ private $featuresMap; - /** - * @var string Contents of datafile. - */ - private $datafile; - - - public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile=null) + public function __construct($revision, array $experimentsMap, array $featuresMap) { $this->revision = $revision; $this->experimentsMap = $experimentsMap; $this->featuresMap = $featuresMap; - $this->datafile = $datafile; } /** @@ -59,14 +52,6 @@ public function getRevision() return $this->revision; } - /** - * @return string Datafile contents. - */ - public function getDatafile() - { - return $this->datafile; - } - /** * @return array Map of Experiment Keys to OptimizelyExperiments. */ diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php index bc1e889a..6032548d 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php @@ -37,11 +37,6 @@ class OptimizelyConfigService */ private $revision; - /** - * @var string config datafile. - */ - private $datafile; - /** * Map of experiment IDs to FeatureFlags. * @@ -68,7 +63,6 @@ public function __construct(ProjectConfigInterface $projectConfig) $this->experiments = $projectConfig->getAllExperiments(); $this->featureFlags = $projectConfig->getFeatureFlags(); $this->revision = $projectConfig->getRevision(); - $this->datafile = $projectConfig->toDatafile(); $this->createLookupMaps(); } diff --git a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php index a8ec96a6..a64e5032 100644 --- a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php @@ -467,11 +467,4 @@ public function testJsonEncodeofOptimizelyConfig() $this->assertEquals(json_encode(json_decode($expectedJSON)), json_encode($response)); } - - public function testGetDatafile() - { - $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; - $actualDatafile = $this->optConfigService->getDatafile(); - $this->assertEquals($expectedDatafile, $actualDatafile); - } } From 6030dec0624c5e8d9aed97a23310adee56c4e334 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Tue, 25 Aug 2020 21:48:08 +0500 Subject: [PATCH 03/11] Datafile accessor and test cases --- .../Config/DatafileProjectConfig.php | 14 + .../Config/ProjectConfigInterface.php | 7 + .../OptimizelyConfig/OptimizelyConfig.php | 17 +- .../OptimizelyConfigService.php | 6 + .../ConfigTests/DatafileProjectConfigTest.php | 1067 +++++++---------- .../OptimizelyConfigServiceTest.php | 7 + 6 files changed, 461 insertions(+), 657 deletions(-) diff --git a/src/Optimizely/Config/DatafileProjectConfig.php b/src/Optimizely/Config/DatafileProjectConfig.php index efceebe6..f7dd3251 100644 --- a/src/Optimizely/Config/DatafileProjectConfig.php +++ b/src/Optimizely/Config/DatafileProjectConfig.php @@ -88,6 +88,11 @@ class DatafileProjectConfig implements ProjectConfigInterface */ private $_botFiltering; + /** + * @var string datafile. + */ + private $datafile; + /** * @var string Revision of the datafile. */ @@ -196,6 +201,7 @@ public function __construct($datafile, $logger, $errorHandler) { $supportedVersions = array(self::V2, self::V3, self::V4); $config = json_decode($datafile, true); + $this->datafile = $datafile; $this->_logger = $logger; $this->_errorHandler = $errorHandler; $this->_version = $config['version']; @@ -355,6 +361,14 @@ public static function createProjectConfigFromDatafile($datafile, $skipJsonValid return $config; } + /** + * @return string String representing contents of datafile. + */ + public function toDatafile() + { + return $this->datafile; + } + /** * @return string String representing account ID from the datafile. */ diff --git a/src/Optimizely/Config/ProjectConfigInterface.php b/src/Optimizely/Config/ProjectConfigInterface.php index 4507c0a9..9375f110 100644 --- a/src/Optimizely/Config/ProjectConfigInterface.php +++ b/src/Optimizely/Config/ProjectConfigInterface.php @@ -159,4 +159,11 @@ public function getFeatureVariableFromKey($featureFlagKey, $variableKey); * @return boolean A boolean value that indicates if the experiment is a feature test. */ public function isFeatureExperiment($experimentId); + + /** + * Determines if given experiment is a feature test. + * + * @return string A string value that contains datafile contents. + */ + public function toDatafile(); } diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php index 133f071b..709e04b6 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php @@ -37,11 +37,18 @@ class OptimizelyConfig implements \JsonSerializable */ private $featuresMap; - public function __construct($revision, array $experimentsMap, array $featuresMap) + /** + * @var string Contents of datafile. + */ + private $datafile; + + + public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile=null) { $this->revision = $revision; $this->experimentsMap = $experimentsMap; $this->featuresMap = $featuresMap; + $this->datafile = $datafile; } /** @@ -52,6 +59,14 @@ public function getRevision() return $this->revision; } + /** + * @return string Datafile contents. + */ + public function getDatafile() + { + return $this->datafile; + } + /** * @return array Map of Experiment Keys to OptimizelyExperiments. */ diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php index 6032548d..8608235b 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php @@ -37,6 +37,11 @@ class OptimizelyConfigService */ private $revision; + /** + * @var string config datafile. + */ + private $datafile; + /** * Map of experiment IDs to FeatureFlags. * @@ -63,6 +68,7 @@ public function __construct(ProjectConfigInterface $projectConfig) $this->experiments = $projectConfig->getAllExperiments(); $this->featureFlags = $projectConfig->getFeatureFlags(); $this->revision = $projectConfig->getRevision(); + $this->datafile = $$projectConfig->toDatafile(); $this->createLookupMaps(); } diff --git a/tests/ConfigTests/DatafileProjectConfigTest.php b/tests/ConfigTests/DatafileProjectConfigTest.php index 360055dc..a8ec96a6 100644 --- a/tests/ConfigTests/DatafileProjectConfigTest.php +++ b/tests/ConfigTests/DatafileProjectConfigTest.php @@ -1,6 +1,6 @@ loggerMock = $this->getMockBuilder(NoOpLogger::class) - ->setMethods(array('log')) - ->getMock(); - // Mock Error handler - $this->errorHandlerMock = $this->getMockBuilder(NoOpErrorHandler::class) - ->setMethods(array('handleError')) - ->getMock(); - - $this->config = new DatafileProjectConfig(DATAFILE, $this->loggerMock, $this->errorHandlerMock); - } - - public function testInit() - { - // Check version - $version = new \ReflectionProperty(DatafileProjectConfig::class, '_version'); - $version->setAccessible(true); - $this->assertEquals('4', $version->getValue($this->config)); - - // Check account ID - $accountId = new \ReflectionProperty(DatafileProjectConfig::class, '_accountId'); - $accountId->setAccessible(true); - $this->assertEquals('1592310167', $accountId->getValue($this->config)); - - // Check project ID - $projectId = new \ReflectionProperty(DatafileProjectConfig::class, '_projectId'); - $projectId->setAccessible(true); - $this->assertEquals('7720880029', $projectId->getValue($this->config)); - - // Check botFiltering - $botFiltering = new \ReflectionProperty(DatafileProjectConfig::class, '_botFiltering'); - $botFiltering->setAccessible(true); - $this->assertSame(true, $botFiltering->getValue($this->config)); - - // Check revision - $revision = new \ReflectionProperty(DatafileProjectConfig::class, '_revision'); - $revision->setAccessible(true); - $this->assertEquals('15', $revision->getValue($this->config)); - - // Check group ID map - $groupIdMap = new \ReflectionProperty(DatafileProjectConfig::class, '_groupIdMap'); - $groupIdMap->setAccessible(true); - $this->assertEquals( - [ - '7722400015' => $this->config->getGroup('7722400015') - ], - $groupIdMap->getValue($this->config) - ); + $this->datafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; - // Check experiment key map - $experimentKeyMap = new \ReflectionProperty(DatafileProjectConfig::class, '_experimentKeyMap'); - $experimentKeyMap->setAccessible(true); - $this->assertEquals( - [ - 'test_experiment' => $this->config->getExperimentFromKey('test_experiment'), - 'paused_experiment' => $this->config->getExperimentFromKey('paused_experiment'), - 'group_experiment_1' => $this->config->getExperimentFromKey('group_experiment_1'), - 'group_experiment_2' => $this->config->getExperimentFromKey('group_experiment_2'), - 'test_experiment_multivariate' => $this->config->getExperimentFromKey('test_experiment_multivariate'), - 'test_experiment_with_feature_rollout' => $this->config->getExperimentFromKey('test_experiment_with_feature_rollout'), - 'test_experiment_double_feature' => $this->config->getExperimentFromKey('test_experiment_double_feature'), - 'test_experiment_integer_feature' => $this->config->getExperimentFromKey('test_experiment_integer_feature'), - 'test_experiment_2' => $this->config->getExperimentFromKey('test_experiment_2'), - 'test_experiment_json_feature' => $this->config->getExperimentFromKey('test_experiment_json_feature') - ], - $experimentKeyMap->getValue($this->config) + $this->projectConfig = new DatafileProjectConfig( + $this->datafile, + new NoOpLogger(), + new NoOpErrorHandler() ); - // Check experiment ID map - $experimentIdMap = new \ReflectionProperty(DatafileProjectConfig::class, '_experimentIdMap'); - $experimentIdMap->setAccessible(true); - $this->assertEquals( - [ - '7716830082' => $this->config->getExperimentFromId('7716830082'), - '7723330021' => $this->config->getExperimentFromId('7723330021'), - '7718750065' => $this->config->getExperimentFromId('7718750065'), - '7716830585' => $this->config->getExperimentFromId('7716830585'), - '122230' => $this->config->getExperimentFromId('122230'), - '122235' => $this->config->getExperimentFromId('122235'), - '122238' => $this->config->getExperimentFromId('122238'), - '122241' => $this->config->getExperimentFromId('122241'), - '111133' => $this->config->getExperimentFromId('111133'), - '122245' => $this->config->getExperimentFromId('122245') - ], - $experimentIdMap->getValue($this->config) - ); - - // Check event key map - $eventKeyMap = new \ReflectionProperty(DatafileProjectConfig::class, '_eventKeyMap'); - $eventKeyMap->setAccessible(true); - $this->assertEquals( - [ - 'purchase' => $this->config->getEvent('purchase'), - 'unlinked_event' => $this->config->getEvent('unlinked_event'), - 'multi_exp_event' => $this->config->getEvent('multi_exp_event') - ], - $eventKeyMap->getValue($this->config) - ); - - // Check attribute key map - $attributeKeyMap = new \ReflectionProperty(DatafileProjectConfig::class, '_attributeKeyMap'); - $attributeKeyMap->setAccessible(true); - $this->assertEquals( - [ - 'device_type' => $this->config->getAttribute('device_type'), - 'location' => $this->config->getAttribute('location'), - '$opt_xyz' => $this->config->getAttribute('$opt_xyz'), - 'boolean_key' => $this->config->getAttribute('boolean_key'), - 'double_key' => $this->config->getAttribute('double_key'), - 'integer_key' => $this->config->getAttribute('integer_key') - ], - $attributeKeyMap->getValue($this->config) - ); - - // Check audience ID map - $audienceIdMap = new \ReflectionProperty(DatafileProjectConfig::class, '_audienceIdMap'); - $audienceIdMap->setAccessible(true); - $this->assertEquals( - [ - '7718080042' => $this->config->getAudience('7718080042'), - '11155' => $this->config->getAudience('11155') - ], - $audienceIdMap->getValue($this->config) - ); - - // Check variation key map - $variationKeyMap = new \ReflectionProperty(DatafileProjectConfig::class, '_variationKeyMap'); - $variationKeyMap->setAccessible(true); - - $this->assertEquals( - [ - 'test_experiment' => [ - 'control' => $this->config->getVariationFromKey('test_experiment', 'control'), - 'variation' => $this->config->getVariationFromKey('test_experiment', 'variation') - ], - 'paused_experiment' => [ - 'control' => $this->config->getVariationFromKey('paused_experiment', 'control'), - 'variation' => $this->config->getVariationFromKey('paused_experiment', 'variation') - ], - 'group_experiment_1' => [ - 'group_exp_1_var_1' => $this->config->getVariationFromKey('group_experiment_1', 'group_exp_1_var_1'), - 'group_exp_1_var_2' => $this->config->getVariationFromKey('group_experiment_1', 'group_exp_1_var_2') - ], - 'group_experiment_2' => [ - 'group_exp_2_var_1' => $this->config->getVariationFromKey('group_experiment_2', 'group_exp_2_var_1'), - 'group_exp_2_var_2' => $this->config->getVariationFromKey('group_experiment_2', 'group_exp_2_var_2') - ], - 'test_experiment_multivariate' => [ - 'Fred' => $this->config->getVariationFromKey('test_experiment_multivariate', 'Fred'), - 'Feorge' => $this->config->getVariationFromKey('test_experiment_multivariate', 'Feorge'), - 'Gred' => $this->config->getVariationFromKey('test_experiment_multivariate', 'Gred'), - 'George' => $this->config->getVariationFromKey('test_experiment_multivariate', 'George') - ], - 'test_experiment_with_feature_rollout' => [ - 'control' => $this->config->getVariationFromKey('test_experiment_with_feature_rollout', 'control'), - 'variation' => $this->config->getVariationFromKey('test_experiment_with_feature_rollout', 'variation') - ], - 'test_experiment_double_feature' => [ - 'control' => $this->config->getVariationFromKey('test_experiment_double_feature', 'control'), - 'variation' => $this->config->getVariationFromKey('test_experiment_double_feature', 'variation') - ], - 'test_experiment_integer_feature' => [ - 'control' => $this->config->getVariationFromKey('test_experiment_integer_feature', 'control'), - 'variation' => $this->config->getVariationFromKey('test_experiment_integer_feature', 'variation') - ], - 'rollout_1_exp_1' => [ - '177771' => $this->config->getVariationFromKey('rollout_1_exp_1', '177771') - ], - 'rollout_1_exp_2' => [ - '177773' => $this->config->getVariationFromKey('rollout_1_exp_2', '177773') - ], - 'rollout_1_exp_3' => [ - '177778' => $this->config->getVariationFromKey('rollout_1_exp_3', '177778') - ], - 'rollout_2_exp_1' => [ - '177775' => $this->config->getVariationFromKey('rollout_2_exp_1', '177775') - ], - 'rollout_2_exp_2' => [ - '177780' => $this->config->getVariationFromKey('rollout_2_exp_2', '177780') - ], - 'test_experiment_2' => [ - 'test_variation_1' => $this->config->getVariationFromKey('test_experiment_2', 'test_variation_1'), - 'test_variation_2' => $this->config->getVariationFromKey('test_experiment_2', 'test_variation_2') - ], - 'test_experiment_json_feature' => [ - 'json_variation' => $this->config->getVariationFromKey('test_experiment_json_feature', 'json_variation') - ] - ], - $variationKeyMap->getValue($this->config) - ); - - // Check variation ID map - $variationIdMap = new \ReflectionProperty(DatafileProjectConfig::class, '_variationIdMap'); - $variationIdMap->setAccessible(true); - $this->assertEquals( - [ - 'test_experiment' => [ - '7722370027' => $this->config->getVariationFromId('test_experiment', '7722370027'), - '7721010009' => $this->config->getVariationFromId('test_experiment', '7721010009') - ], - 'paused_experiment' => [ - '7722370427' => $this->config->getVariationFromId('paused_experiment', '7722370427'), - '7721010509' => $this->config->getVariationFromId('paused_experiment', '7721010509') - ], - 'group_experiment_1' => [ - '7722260071' => $this->config->getVariationFromId('group_experiment_1', '7722260071'), - '7722360022' => $this->config->getVariationFromId('group_experiment_1', '7722360022') - ], - 'group_experiment_2' => [ - '7713030086' => $this->config->getVariationFromId('group_experiment_2', '7713030086'), - '7725250007' => $this->config->getVariationFromId('group_experiment_2', '7725250007') - ], - 'test_experiment_multivariate' => [ - '122231' => $this->config->getVariationFromId('test_experiment_multivariate', '122231'), - '122232' => $this->config->getVariationFromId('test_experiment_multivariate', '122232'), - '122233' => $this->config->getVariationFromId('test_experiment_multivariate', '122233'), - '122234' => $this->config->getVariationFromId('test_experiment_multivariate', '122234') - ], - 'test_experiment_with_feature_rollout' => [ - '122236' => $this->config->getVariationFromId('test_experiment_with_feature_rollout', '122236'), - '122237' => $this->config->getVariationFromId('test_experiment_with_feature_rollout', '122237') - ], - 'test_experiment_double_feature' => [ - '122239' => $this->config->getVariationFromId('test_experiment_double_feature', '122239'), - '122240' => $this->config->getVariationFromId('test_experiment_double_feature', '122240') - ], - 'test_experiment_integer_feature' => [ - '122242' => $this->config->getVariationFromId('test_experiment_integer_feature', '122242'), - '122243' => $this->config->getVariationFromId('test_experiment_integer_feature', '122243') - ], - 'rollout_1_exp_1' => [ - '177771' => $this->config->getVariationFromId('rollout_1_exp_1', '177771') - ], - 'rollout_1_exp_2' => [ - '177773' => $this->config->getVariationFromId('rollout_1_exp_2', '177773') - ], - 'rollout_1_exp_3' => [ - '177778' => $this->config->getVariationFromId('rollout_1_exp_3', '177778') - ], - 'rollout_2_exp_1' => [ - '177775' => $this->config->getVariationFromId('rollout_2_exp_1', '177775') - ], - 'rollout_2_exp_2' => [ - '177780' => $this->config->getVariationFromId('rollout_2_exp_2', '177780') - ], - 'test_experiment_2' => [ - '151239' => $this->config->getVariationFromId('test_experiment_2', '151239'), - '151240' => $this->config->getVariationFromId('test_experiment_2', '151240') - ], - 'test_experiment_json_feature' => [ - '122246' => $this->config->getVariationFromId('test_experiment_json_feature', '122246') - ] - ], - $variationIdMap->getValue($this->config) - ); - - - // Check feature flag key map - $featureFlagKeyMap = new \ReflectionProperty(DatafileProjectConfig::class, '_featureKeyMap'); - $featureFlagKeyMap->setAccessible(true); - $this->assertEquals( - [ - 'boolean_feature' => $this->config->getFeatureFlagFromKey('boolean_feature'), - 'double_single_variable_feature' => $this->config->getFeatureFlagFromKey('double_single_variable_feature'), - 'integer_single_variable_feature' => $this->config->getFeatureFlagFromKey('integer_single_variable_feature'), - 'boolean_single_variable_feature' => $this->config->getFeatureFlagFromKey('boolean_single_variable_feature'), - 'string_single_variable_feature' => $this->config->getFeatureFlagFromKey('string_single_variable_feature'), - 'multiple_variables_feature' => $this->config->getFeatureFlagFromKey('multiple_variables_feature'), - 'multi_variate_feature' => $this->config->getFeatureFlagFromKey('multi_variate_feature'), - 'mutex_group_feature' => $this->config->getFeatureFlagFromKey('mutex_group_feature'), - 'empty_feature' => $this->config->getFeatureFlagFromKey('empty_feature') - ], - $featureFlagKeyMap->getValue($this->config) - ); - - - // Check rollout id map - $rolloutIdMap = new \ReflectionProperty(DatafileProjectConfig::class, '_rolloutIdMap'); - $rolloutIdMap->setAccessible(true); - $this->assertEquals( - [ - '166660' => $this->config->getRolloutFromId('166660'), - '166661' => $this->config->getRolloutFromId('166661') - ], - $rolloutIdMap->getValue($this->config) - ); + $this->optConfigService = new OptimizelyConfigService($this->projectConfig); + + // Create expected default variables map for feat_experiment variation_b + $boolDefaultVariable = new OptimizelyVariable('17252790456', 'boolean_var', 'boolean', 'false'); + $intDefaultVariable = new OptimizelyVariable('17258820367', 'integer_var', 'integer', 1); + $doubleDefaultVariable = new OptimizelyVariable('17260550714', 'double_var', 'double', 0.5); + $strDefaultVariable = new OptimizelyVariable('17290540010', 'string_var', 'string', 'i am default value'); + $jsonDefaultVariable = new OptimizelyVariable('17260550458', 'json_var', 'json', "{\"text\": \"default value\"}"); + + $this->expectedDefaultVariableKeyMap = []; + $this->expectedDefaultVariableKeyMap['boolean_var'] = $boolDefaultVariable; + $this->expectedDefaultVariableKeyMap['integer_var'] = $intDefaultVariable; + $this->expectedDefaultVariableKeyMap['double_var'] = $doubleDefaultVariable; + $this->expectedDefaultVariableKeyMap['string_var'] = $strDefaultVariable; + $this->expectedDefaultVariableKeyMap['json_var'] = $jsonDefaultVariable; + + // Create variable variables map for feat_experiment variation_a + $boolFeatVariable = new OptimizelyVariable('17252790456', 'boolean_var', 'boolean', 'true'); + $intFeatVariable = new OptimizelyVariable('17258820367', 'integer_var', 'integer', 5); + $doubleFeatVariable = new OptimizelyVariable('17260550714', 'double_var', 'double', 5.5); + $strFeatVariable = new OptimizelyVariable('17290540010', 'string_var', 'string', 'i am variable value'); + $jsonFeatVariable = new OptimizelyVariable('17260550458', 'json_var', 'json', "{\"text\": \"variable value\"}"); + + $this->expectedVariableKeyMap = []; + $this->expectedVariableKeyMap['boolean_var'] = $boolFeatVariable; + $this->expectedVariableKeyMap['integer_var'] = $intFeatVariable; + $this->expectedVariableKeyMap['double_var'] = $doubleFeatVariable; + $this->expectedVariableKeyMap['string_var'] = $strFeatVariable; + $this->expectedVariableKeyMap['json_var'] = $jsonFeatVariable; + + // Create variations map for feat_experiment + $this->featExpVariationMap = []; + $this->featExpVariationMap['variation_a'] = + new OptimizelyVariation('17289540366', 'variation_a', true, $this->expectedVariableKeyMap); + + $this->featExpVariationMap['variation_b'] = + new OptimizelyVariation('17304990114', 'variation_b', false, $this->expectedDefaultVariableKeyMap); + + // create feat_experiment + $featExperiment = + new OptimizelyExperiment("17279300791", "feat_experiment", $this->featExpVariationMap); + + + // create feature + $experimentsMap = ['feat_experiment' => $featExperiment]; + $this->feature = + new OptimizelyFeature( + '17266500726', + 'test_feature', + $experimentsMap, + $this->expectedDefaultVariableKeyMap + ); + // create ab experiment and variations + $variationA = new OptimizelyVariation('17277380360', 'variation_a', null, []); + $variationB = new OptimizelyVariation('17273501081', 'variation_b', null, []); + $variationsMap = []; + $variationsMap['variation_a'] = $variationA; + $variationsMap['variation_b'] = $variationB; - // Check variation entity - $variableUsages = [ - new VariableUsage("155560", "F"), - new VariableUsage("155561", "red") - ]; - $expectedVariation = new Variation("122231", "Fred", true, $variableUsages); - $actualVariation = $this->config->getVariationFromKey("test_experiment_multivariate", "Fred"); - - $this->assertEquals($expectedVariation, $actualVariation); - - // Check Experiment Feature Map - $experimentFeatureMap = new \ReflectionProperty(DatafileProjectConfig::class, '_experimentFeatureMap'); - $experimentFeatureMap->setAccessible(true); - $this->assertEquals( - [ - '111133' => ['155549'], - '122238' => ['155550'], - '122241' => ['155552'], - '122235' => ['155557'], - '122230' => ['155559'], - '7723330021' => ['155562'], - '7718750065' => ['155562'], - '122245' => ['155597'] - ], - $experimentFeatureMap->getValue($this->config) - ); - } + $abExperiment = new OptimizelyExperiment('17301270474', 'ab_experiment', $variationsMap); - public function testExceptionThrownForUnsupportedVersion() - { - // Verify that an exception is thrown when given datafile version is unsupported // - $this->setExpectedException( - InvalidDatafileVersionException::class, - 'This version of the PHP SDK does not support the given datafile version: 5.' - ); + // create group_ab_experiment and variations + $variationA = new OptimizelyVariation('17287500312', 'variation_a', null, []); + $variationB = new OptimizelyVariation('17283640326', 'variation_b', null, []); + $variationsMap = []; + $variationsMap['variation_a'] = $variationA; + $variationsMap['variation_b'] = $variationB; - $this->config = new DatafileProjectConfig( - UNSUPPORTED_DATAFILE, - $this->loggerMock, - $this->errorHandlerMock - ); - } + $groupExperiment = + new OptimizelyExperiment('17258450439', 'group_ab_experiment', $variationsMap); - public function testVariationParsingWithoutFeatureEnabledProp() - { - $variables = [ - [ - "id"=> "155556", - "value"=> "true" - ] - ]; - - $data = [ - [ - "id"=> "177771", - "key"=> "my_var", - "variables"=> $variables - ] - ]; - - $variationIdMap = ConfigParser::generateMap($data, 'id', Variation::class); - - $variation = $variationIdMap["177771"]; - $this->assertEquals("177771", $variation->getId()); - $this->assertEquals("my_var", $variation->getKey()); - - $variableUsageMap = ConfigParser::generateMap($variables, null, VariableUsage::class); - $this->assertEquals($variableUsageMap, $variation->getVariables()); - - // assert featureEnabled by default is set to false when property not provided in data file - $this->assertNull($variation->getFeatureEnabled()); - } + // create experiment key map + $this->expectedExpKeyMap = []; + $this->expectedExpKeyMap['ab_experiment'] = $abExperiment; + $this->expectedExpKeyMap['group_ab_experiment'] = $groupExperiment; + $this->expectedExpKeyMap['feat_experiment'] = $featExperiment; - public function testGetAccountId() - { - $this->assertEquals('1592310167', $this->config->getAccountId()); + // create experiment id map + $this->expectedExpIdMap = []; + $this->expectedExpIdMap['17301270474'] = $abExperiment; + $this->expectedExpIdMap['17258450439'] = $groupExperiment; + $this->expectedExpIdMap['17279300791'] = $featExperiment; } - public function testGetProjectId() + protected static function getMethod($name) { - $this->assertEquals('7720880029', $this->config->getProjectId()); + $class = new \ReflectionClass('Optimizely\\OptimizelyConfig\\OptimizelyConfigService'); + $method = $class->getMethod($name); + $method->setAccessible(true); + return $method; } - public function testGetBotFiltering() + public function testGetVariablesMapReturnsEmptyForAbExpVariation() { - $botFiltering = new \ReflectionProperty(DatafileProjectConfig::class, '_botFiltering'); - $botFiltering->setAccessible(true); - $this->assertSame($botFiltering->getValue($this->config), $this->config->getBotFiltering()); - } + $abExp = $this->projectConfig->getExperimentFromKey("ab_experiment"); + $abExpVarA = $abExp->getVariations()[0]; - public function testGetRevision() - { - $this->assertEquals('15', $this->config->getRevision()); - } + $getVariablesMap = self::getMethod("getVariablesMap"); + $response = $getVariablesMap->invokeArgs($this->optConfigService, array($abExp, $abExpVarA)); - public function testGetGroupValidId() - { - $group = $this->config->getGroup('7722400015'); - $this->assertEquals('7722400015', $group->getId()); - $this->assertEquals('random', $group->getPolicy()); + $this->assertEmpty($response); } - public function testGetGroupInvalidId() + public function testGetVariablesMapReturnsVariableMapForEnabledVariation() { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with(Logger::ERROR, 'Group ID "invalid_id" is not in datafile.'); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidGroupException('Provided group is not in datafile.')); - - $this->assertEquals(new Group(), $this->config->getGroup('invalid_id')); - } + $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); + $featureDisabledVar = $featExp->getVariations()[0]; - public function testGetExperimentValidKey() - { - $experiment = $this->config->getExperimentFromKey('test_experiment'); - $this->assertEquals('test_experiment', $experiment->getKey()); - $this->assertEquals('7716830082', $experiment->getId()); - } + $getVariablesMap = self::getMethod("getVariablesMap"); + $response = $getVariablesMap->invokeArgs($this->optConfigService, array($featExp, $featureDisabledVar)); - public function testGetExperimentInvalidKey() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with(Logger::ERROR, 'Experiment key "invalid_key" is not in datafile.'); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidExperimentException('Provided experiment is not in datafile.')); - - $this->assertEquals(new Experiment(), $this->config->getExperimentFromKey('invalid_key')); + $this->assertEquals($this->expectedVariableKeyMap, $response); } - public function testGetExperimentValidId() + public function testGetVariablesMapReturnsVariableMapForDisabledVariation() { - $experiment = $this->config->getExperimentFromId('7716830082'); - $this->assertEquals('7716830082', $experiment->getId()); - $this->assertEquals('test_experiment', $experiment->getKey()); - } + $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); + $featureDisabledVar = $featExp->getVariations()[1]; - public function testGetExperimentInvalidId() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with(Logger::ERROR, 'Experiment ID "42" is not in datafile.'); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidExperimentException('Provided experiment is not in datafile.')); - - $this->assertEquals(new Experiment(), $this->config->getExperimentFromId('42')); - } + $getVariablesMap = self::getMethod("getVariablesMap"); + $response = $getVariablesMap->invokeArgs($this->optConfigService, array($featExp, $featureDisabledVar)); - public function testGetFeatureFlagInvalidKey() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with(Logger::ERROR, 'FeatureFlag Key "42" is not in datafile.'); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidFeatureFlagException('Provided feature flag is not in datafile.')); - - $this->assertEquals(new FeatureFlag(), $this->config->getFeatureFlagFromKey('42')); + $this->assertEquals($this->expectedDefaultVariableKeyMap, $response); } - public function testGetRolloutInvalidId() + public function testGetVariationsMap() { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with(Logger::ERROR, 'Rollout with ID "42" is not in the datafile.'); + $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidRolloutException('Provided rollout is not in datafile.')); + $getVariationsMap = self::getMethod("getVariationsMap"); + $response = $getVariationsMap->invokeArgs($this->optConfigService, array($featExp)); - $this->assertEquals(new Rollout(), $this->config->getRolloutFromId('42')); + $this->assertEquals($this->featExpVariationMap, $response); } - public function testGetEventValidKey() + public function testGetExperimentsMaps() { - $event = $this->config->getEvent('purchase'); - $this->assertEquals('purchase', $event->getKey()); - $this->assertEquals('7718020063', $event->getId()); - $this->assertEquals(['7716830082', '7723330021', '7718750065', '7716830585'], $event->getExperimentIds()); - } + $getExperimentsMap = self::getMethod("getExperimentsMaps"); + $response = $getExperimentsMap->invokeArgs($this->optConfigService, array()); - public function testGetEventInvalidKey() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with(Logger::ERROR, 'Event key "invalid_key" is not in datafile.'); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidEventException('Provided event is not in datafile.')); - - $this->assertEquals(new Event(), $this->config->getEvent('invalid_key')); - } + // assert experiment key map + $this->assertEquals($this->expectedExpKeyMap, $response[0]); - public function testGetAudienceValidId() - { - $audience = $this->config->getAudience('7718080042'); - $this->assertEquals('7718080042', $audience->getId()); - $this->assertEquals('iPhone users in San Francisco', $audience->getName()); + // assert experiment id map + $this->assertEquals($this->expectedExpIdMap, $response[1]); } - public function testGetAudiencePrefersTypedAudiencesOverAudiences() + public function testGetFeaturesMap() { - $projectConfig = new DatafileProjectConfig( - DATAFILE_WITH_TYPED_AUDIENCES, - $this->loggerMock, - $this->errorHandlerMock + $getFeaturesMap = self::getMethod("getFeaturesMap"); + $response = $getFeaturesMap->invokeArgs( + $this->optConfigService, + array($this->expectedExpIdMap) ); - // test that typedAudience is returned when an audience exists with the same ID. - $audience = $projectConfig->getAudience('3988293898'); - - $this->assertEquals('3988293898', $audience->getId()); - $this->assertEquals('substringString', $audience->getName()); - - $expectedConditions = json_decode('["and", ["or", ["or", {"name": "house", "type": "custom_attribute", - "match": "substring", "value": "Slytherin"}]]]', true); - $this->assertEquals($expectedConditions, $audience->getConditions()); - $this->assertEquals($expectedConditions, $audience->getConditionsList()); - - // test that normal audience is returned if no typedAudience exists with the same ID. - $audience = $projectConfig->getAudience('3468206642'); - - $this->assertEquals('3468206642', $audience->getId()); - $this->assertEquals('exactString', $audience->getName()); - - $expectedConditions = '["and", ["or", ["or", {"name": "house", "type": "custom_attribute", "value": "Gryffindor"}]]]'; - $this->assertEquals($expectedConditions, $audience->getConditions()); - $expectedConditionsList = json_decode($expectedConditions, true); - $this->assertEquals($expectedConditionsList, $audience->getConditionsList()); - } - - public function testGetAudienceInvalidKey() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with(Logger::ERROR, 'Audience ID "invalid_id" is not in datafile.'); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidAudienceException('Provided audience is not in datafile.')); - - $this->assertNull($this->config->getAudience('invalid_id')); - } - - public function testGetAttributeValidKey() - { - $attribute = $this->config->getAttribute('device_type'); - $this->assertEquals('device_type', $attribute->getKey()); - $this->assertEquals('7723280020', $attribute->getId()); - } - - public function testGetAttributeValidKeyWithReservedPrefix() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with( - Logger::WARNING, - 'Attribute $opt_xyz unexpectedly has reserved prefix $opt_; using attribute ID instead of reserved attribute name.' - ); - - $validAttrWithReservedPrefix = new Attribute('7723340006', '$opt_xyz'); - $this->assertEquals($validAttrWithReservedPrefix, $this->config->getAttribute('$opt_xyz')); - } - - public function testGetAttributeInvalidKey() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with(Logger::ERROR, 'Attribute key "invalid_key" is not in datafile.'); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidAttributeException('Provided attribute is not in datafile.')); - - $this->assertNull($this->config->getAttribute('invalid_key')); - } - - public function testGetAttributeMissingKeyWithReservedPrefix() - { - $validAttrWithReservedPrefix = new Attribute('$opt_007', '$opt_007'); - $this->assertEquals($validAttrWithReservedPrefix, $this->config->getAttribute('$opt_007')); - } - - public function testGetAttributeForControlAttributes() - { - # Should return attribute with ID same as given Key - $optUserAgentAttr = new Attribute('$opt_user_agent', '$opt_user_agent'); - $this->assertEquals($optUserAgentAttr, $this->config->getAttribute('$opt_user_agent')); - - $optBucketingIdAttr = new Attribute('$opt_bucketing_id', '$opt_bucketing_id'); - $this->assertEquals($optBucketingIdAttr, $this->config->getAttribute('$opt_bucketing_id')); - } - - public function testGetVariationFromKeyValidExperimentKeyValidVariationKey() - { - $variation = $this->config->getVariationFromKey('test_experiment', 'control'); - $this->assertEquals('7722370027', $variation->getId()); - $this->assertEquals('control', $variation->getKey()); - } - - public function testGetVariationFromKeyValidExperimentKeyInvalidVariationKey() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with( - Logger::ERROR, - 'No variation key "invalid_key" defined in datafile for experiment "test_experiment".' - ); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidVariationException('Provided variation is not in datafile.')); - - $this->assertEquals(new Variation(), $this->config->getVariationFromKey('test_experiment', 'invalid_key')); - } - - public function testGetVariationFromKeyInvalidExperimentKey() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with( - Logger::ERROR, - 'No variation key "control" defined in datafile for experiment "invalid_experiment".' - ); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidVariationException('Provided variation is not in datafile.')); - - $this->assertEquals(new Variation(), $this->config->getVariationFromKey('invalid_experiment', 'control')); - } - - public function testGetVariationFromIdValidExperimentKeyValidVariationId() - { - $variation = $this->config->getVariationFromId('test_experiment', '7722370027'); - $this->assertEquals('control', $variation->getKey()); - $this->assertEquals('7722370027', $variation->getId()); - } - - public function testGetVariationFromIdValidExperimentKeyInvalidVariationId() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with( - Logger::ERROR, - 'No variation ID "invalid_id" defined in datafile for experiment "test_experiment".' - ); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidVariationException('Provided variation is not in datafile.')); - - $this->assertEquals(new Variation(), $this->config->getVariationFromId('test_experiment', 'invalid_id')); - } - - public function testGetVariationFromIdInvalidExperimentKey() - { - $this->loggerMock->expects($this->once()) - ->method('log') - ->with( - Logger::ERROR, - 'No variation ID "7722370027" defined in datafile for experiment "invalid_experiment".' - ); - $this->errorHandlerMock->expects($this->once()) - ->method('handleError') - ->with(new InvalidVariationException('Provided variation is not in datafile.')); - - $this->assertEquals(new Variation(), $this->config->getVariationFromId('invalid_experiment', '7722370027')); - } - - // Test that a true is returned if experiment is a feature test, false otherwise. - public function testIsFeatureExperiment() - { - $experiment = $this->config->getExperimentFromKey('test_experiment'); - $featureExperiment = $this->config->getExperimentFromKey('test_experiment_double_feature'); - - $this->assertTrue($this->config->isFeatureExperiment($featureExperiment->getId())); - $this->assertFalse($this->config->isFeatureExperiment($experiment->getId())); + $this->assertEquals(['test_feature' => $this->feature], $response); + } + + public function testGetConfig() + { + $response = $this->optConfigService->getConfig(); + + $this->assertInstanceof(OptimizelyConfig::class, $response); + + // assert revision + $this->assertEquals('16', $response->getRevision()); + + // assert experiments map + $this->assertEquals($this->expectedExpKeyMap, $response->getExperimentsMap()); + + // assert features map + $this->assertEquals(['test_feature' => $this->feature], $response->getFeaturesMap()); + } + + public function testJsonEncodeofOptimizelyConfig() + { + $response = $this->optConfigService->getConfig(); + + $expectedJSON = '{ + "revision": "16", + "experimentsMap": { + "ab_experiment": { + "id": "17301270474", + "key": "ab_experiment", + "variationsMap": { + "variation_a": { + "id": "17277380360", + "key": "variation_a", + "variablesMap": [ + + ] + }, + "variation_b": { + "id": "17273501081", + "key": "variation_b", + "variablesMap": [ + + ] + } + } + }, + "feat_experiment": { + "id": "17279300791", + "key": "feat_experiment", + "variationsMap": { + "variation_a": { + "id": "17289540366", + "key": "variation_a", + "featureEnabled": true, + "variablesMap": { + "boolean_var": { + "id": "17252790456", + "key": "boolean_var", + "type": "boolean", + "value": "true" + }, + "integer_var": { + "id": "17258820367", + "key": "integer_var", + "type": "integer", + "value": "5" + }, + "double_var": { + "id": "17260550714", + "key": "double_var", + "type": "double", + "value": "5.5" + }, + "string_var": { + "id": "17290540010", + "key": "string_var", + "type": "string", + "value": "i am variable value" + }, + "json_var": { + "id": "17260550458", + "key": "json_var", + "type": "json", + "value": "{\"text\": \"variable value\"}" + } + } + }, + "variation_b": { + "id": "17304990114", + "key": "variation_b", + "featureEnabled": false, + "variablesMap": { + "boolean_var": { + "id": "17252790456", + "key": "boolean_var", + "type": "boolean", + "value": "false" + }, + "integer_var": { + "id": "17258820367", + "key": "integer_var", + "type": "integer", + "value": "1" + }, + "double_var": { + "id": "17260550714", + "key": "double_var", + "type": "double", + "value": "0.5" + }, + "string_var": { + "id": "17290540010", + "key": "string_var", + "type": "string", + "value": "i am default value" + }, + "json_var": { + "id": "17260550458", + "key": "json_var", + "type": "json", + "value": "{\"text\": \"default value\"}" + } + } + } + } + }, + "group_ab_experiment": { + "id": "17258450439", + "key": "group_ab_experiment", + "variationsMap": { + "variation_a": { + "id": "17287500312", + "key": "variation_a", + "variablesMap": [ + + ] + }, + "variation_b": { + "id": "17283640326", + "key": "variation_b", + "variablesMap": [ + + ] + } + } + } + }, + "featuresMap": { + "test_feature": { + "id": "17266500726", + "key": "test_feature", + "experimentsMap": { + "feat_experiment": { + "id": "17279300791", + "key": "feat_experiment", + "variationsMap": { + "variation_a": { + "id": "17289540366", + "key": "variation_a", + "featureEnabled": true, + "variablesMap": { + "boolean_var": { + "id": "17252790456", + "key": "boolean_var", + "type": "boolean", + "value": "true" + }, + "integer_var": { + "id": "17258820367", + "key": "integer_var", + "type": "integer", + "value": "5" + }, + "double_var": { + "id": "17260550714", + "key": "double_var", + "type": "double", + "value": "5.5" + }, + "string_var": { + "id": "17290540010", + "key": "string_var", + "type": "string", + "value": "i am variable value" + }, + "json_var": { + "id": "17260550458", + "key": "json_var", + "type": "json", + "value": "{\"text\": \"variable value\"}" + } + } + }, + "variation_b": { + "id": "17304990114", + "key": "variation_b", + "featureEnabled": false, + "variablesMap": { + "boolean_var": { + "id": "17252790456", + "key": "boolean_var", + "type": "boolean", + "value": "false" + }, + "integer_var": { + "id": "17258820367", + "key": "integer_var", + "type": "integer", + "value": "1" + }, + "double_var": { + "id": "17260550714", + "key": "double_var", + "type": "double", + "value": "0.5" + }, + "string_var": { + "id": "17290540010", + "key": "string_var", + "type": "string", + "value": "i am default value" + }, + "json_var": { + "id": "17260550458", + "key": "json_var", + "type": "json", + "value": "{\"text\": \"default value\"}" + } + } + } + } + } + }, + "variablesMap": { + "boolean_var": { + "id": "17252790456", + "key": "boolean_var", + "type": "boolean", + "value": "false" + }, + "integer_var": { + "id": "17258820367", + "key": "integer_var", + "type": "integer", + "value": "1" + }, + "double_var": { + "id": "17260550714", + "key": "double_var", + "type": "double", + "value": "0.5" + }, + "string_var": { + "id": "17290540010", + "key": "string_var", + "type": "string", + "value": "i am default value" + }, + "json_var": { + "id": "17260550458", + "key": "json_var", + "type": "json", + "value": "{\"text\": \"default value\"}" + } + } + } + } + }'; + + $this->assertEquals(json_encode(json_decode($expectedJSON)), json_encode($response)); + } + + public function testGetDatafile() + { + $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; + $actualDatafile = $this->optConfigService->getDatafile(); + $this->assertEquals($expectedDatafile, $actualDatafile); } } diff --git a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php index a64e5032..a8ec96a6 100644 --- a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php @@ -467,4 +467,11 @@ public function testJsonEncodeofOptimizelyConfig() $this->assertEquals(json_encode(json_decode($expectedJSON)), json_encode($response)); } + + public function testGetDatafile() + { + $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; + $actualDatafile = $this->optConfigService->getDatafile(); + $this->assertEquals($expectedDatafile, $actualDatafile); + } } From 0e0246e6448dcdd8bedb63049465054b5618458d Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Tue, 25 Aug 2020 22:05:52 +0500 Subject: [PATCH 04/11] linting fixed --- tests/ConfigTests/DatafileProjectConfigTest.php | 6 +++--- tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/ConfigTests/DatafileProjectConfigTest.php b/tests/ConfigTests/DatafileProjectConfigTest.php index a8ec96a6..fc40cb36 100644 --- a/tests/ConfigTests/DatafileProjectConfigTest.php +++ b/tests/ConfigTests/DatafileProjectConfigTest.php @@ -470,8 +470,8 @@ public function testJsonEncodeofOptimizelyConfig() public function testGetDatafile() { - $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; - $actualDatafile = $this->optConfigService->getDatafile(); - $this->assertEquals($expectedDatafile, $actualDatafile); + $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; + $actualDatafile = $this->optConfigService->getDatafile(); + $this->assertEquals($expectedDatafile, $actualDatafile); } } diff --git a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php index a8ec96a6..fc40cb36 100644 --- a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php @@ -470,8 +470,8 @@ public function testJsonEncodeofOptimizelyConfig() public function testGetDatafile() { - $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; - $actualDatafile = $this->optConfigService->getDatafile(); - $this->assertEquals($expectedDatafile, $actualDatafile); + $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; + $actualDatafile = $this->optConfigService->getDatafile(); + $this->assertEquals($expectedDatafile, $actualDatafile); } } From 63e1cca1e919c6b92b2d91d011f7bc3143f9f5c7 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Tue, 25 Aug 2020 22:27:23 +0500 Subject: [PATCH 05/11] Update OptimizelyConfig.php --- src/Optimizely/OptimizelyConfig/OptimizelyConfig.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php index 709e04b6..fdd5736c 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php @@ -43,7 +43,7 @@ class OptimizelyConfig implements \JsonSerializable private $datafile; - public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile=null) + public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile = null) { $this->revision = $revision; $this->experimentsMap = $experimentsMap; From c44287c400858fb168d3e682900561bba26d3919 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Thu, 27 Aug 2020 10:44:49 +0500 Subject: [PATCH 06/11] Update DatafileProjectConfigTest.php --- tests/ConfigTests/DatafileProjectConfigTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ConfigTests/DatafileProjectConfigTest.php b/tests/ConfigTests/DatafileProjectConfigTest.php index fc40cb36..3b655a8c 100644 --- a/tests/ConfigTests/DatafileProjectConfigTest.php +++ b/tests/ConfigTests/DatafileProjectConfigTest.php @@ -15,7 +15,7 @@ * limitations under the License. */ namespace Optimizely\Tests; - +namespace Optimizely\Config\Tests; use Exception; use Optimizely\Config\DatafileProjectConfig; use Optimizely\ErrorHandler\NoOpErrorHandler; From 764e40ba84ba68a34d2c177d434824f3d4613e68 Mon Sep 17 00:00:00 2001 From: uzair-folio3 Date: Thu, 27 Aug 2020 11:11:49 +0500 Subject: [PATCH 07/11] Update DatafileProjectConfigTest.php --- tests/ConfigTests/DatafileProjectConfigTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ConfigTests/DatafileProjectConfigTest.php b/tests/ConfigTests/DatafileProjectConfigTest.php index 3b655a8c..137dd98f 100644 --- a/tests/ConfigTests/DatafileProjectConfigTest.php +++ b/tests/ConfigTests/DatafileProjectConfigTest.php @@ -1,6 +1,6 @@ Date: Thu, 27 Aug 2020 20:47:27 +0500 Subject: [PATCH 08/11] refact --- .../Config/ProjectConfigInterface.php | 2 +- .../OptimizelyConfig/OptimizelyConfig.php | 2 +- .../OptimizelyConfigService.php | 5 +- .../ConfigTests/DatafileProjectConfigTest.php | 1068 ++++++++++------- .../OptimizelyConfigServiceTest.php | 7 +- .../OptimizelyEntitiesTest.php | 6 +- 6 files changed, 675 insertions(+), 415 deletions(-) diff --git a/src/Optimizely/Config/ProjectConfigInterface.php b/src/Optimizely/Config/ProjectConfigInterface.php index 9375f110..55bc2ba4 100644 --- a/src/Optimizely/Config/ProjectConfigInterface.php +++ b/src/Optimizely/Config/ProjectConfigInterface.php @@ -161,7 +161,7 @@ public function getFeatureVariableFromKey($featureFlagKey, $variableKey); public function isFeatureExperiment($experimentId); /** - * Determines if given experiment is a feature test. + * Returns string representation of datafile. * * @return string A string value that contains datafile contents. */ diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php index fdd5736c..157a4666 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php @@ -43,7 +43,7 @@ class OptimizelyConfig implements \JsonSerializable private $datafile; - public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile = null) + public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile) { $this->revision = $revision; $this->experimentsMap = $experimentsMap; diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php index 8608235b..3f0dffd7 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php @@ -68,7 +68,7 @@ public function __construct(ProjectConfigInterface $projectConfig) $this->experiments = $projectConfig->getAllExperiments(); $this->featureFlags = $projectConfig->getFeatureFlags(); $this->revision = $projectConfig->getRevision(); - $this->datafile = $$projectConfig->toDatafile(); + $this->datafile = $projectConfig->toDatafile(); $this->createLookupMaps(); } @@ -84,7 +84,8 @@ public function getConfig() return new OptimizelyConfig( $this->revision, $experimentsMaps[0], - $featuresMap + $featuresMap, + $this->datafile ); } diff --git a/tests/ConfigTests/DatafileProjectConfigTest.php b/tests/ConfigTests/DatafileProjectConfigTest.php index 137dd98f..d7e72e6b 100644 --- a/tests/ConfigTests/DatafileProjectConfigTest.php +++ b/tests/ConfigTests/DatafileProjectConfigTest.php @@ -14,464 +14,718 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -namespace Optimizely\Tests; + namespace Optimizely\Config\Tests; -use Exception; + +require(dirname(__FILE__).'/../TestData.php'); + +use Monolog\Logger; use Optimizely\Config\DatafileProjectConfig; +use Optimizely\Entity\Attribute; +use Optimizely\Entity\Audience; +use Optimizely\Entity\Event; +use Optimizely\Entity\Experiment; +use Optimizely\Entity\FeatureFlag; +use Optimizely\Entity\Group; +use Optimizely\Entity\Rollout; +use Optimizely\Entity\Variation; +use Optimizely\Entity\VariableUsage; use Optimizely\ErrorHandler\NoOpErrorHandler; +use Optimizely\Exceptions\InvalidAttributeException; +use Optimizely\Exceptions\InvalidAudienceException; +use Optimizely\Exceptions\InvalidDatafileVersionException; +use Optimizely\Exceptions\InvalidEventException; +use Optimizely\Exceptions\InvalidExperimentException; +use Optimizely\Exceptions\InvalidFeatureFlagException; +use Optimizely\Exceptions\InvalidRolloutException; +use Optimizely\Exceptions\InvalidGroupException; +use Optimizely\Exceptions\InvalidVariationException; use Optimizely\Logger\NoOpLogger; -use Optimizely\OptimizelyConfig\OptimizelyConfig; -use Optimizely\OptimizelyConfig\OptimizelyConfigService; -use Optimizely\OptimizelyConfig\OptimizelyExperiment; -use Optimizely\OptimizelyConfig\OptimizelyFeature; -use Optimizely\OptimizelyConfig\OptimizelyVariable; -use Optimizely\OptimizelyConfig\OptimizelyVariation; - -class OptimizelyConfigServiceTest extends \PHPUnit_Framework_TestCase +use Optimizely\Optimizely; +use Optimizely\Tests\ValidEventDispatcher; +use Optimizely\Utils\ConfigParser; + +class DatafileProjectConfigTest extends \PHPUnit_Framework_TestCase { + private $config; + private $loggerMock; + private $errorHandlerMock; - public function setUp() + protected function setUp() { - $this->datafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; + // Mock Logger + $this->loggerMock = $this->getMockBuilder(NoOpLogger::class) + ->setMethods(array('log')) + ->getMock(); + // Mock Error handler + $this->errorHandlerMock = $this->getMockBuilder(NoOpErrorHandler::class) + ->setMethods(array('handleError')) + ->getMock(); + + $this->config = new DatafileProjectConfig(DATAFILE, $this->loggerMock, $this->errorHandlerMock); + } - $this->projectConfig = new DatafileProjectConfig( - $this->datafile, - new NoOpLogger(), - new NoOpErrorHandler() + public function testInit() + { + // Check version + $version = new \ReflectionProperty(DatafileProjectConfig::class, '_version'); + $version->setAccessible(true); + $this->assertEquals('4', $version->getValue($this->config)); + + // Check account ID + $accountId = new \ReflectionProperty(DatafileProjectConfig::class, '_accountId'); + $accountId->setAccessible(true); + $this->assertEquals('1592310167', $accountId->getValue($this->config)); + + // Check project ID + $projectId = new \ReflectionProperty(DatafileProjectConfig::class, '_projectId'); + $projectId->setAccessible(true); + $this->assertEquals('7720880029', $projectId->getValue($this->config)); + + // Check botFiltering + $botFiltering = new \ReflectionProperty(DatafileProjectConfig::class, '_botFiltering'); + $botFiltering->setAccessible(true); + $this->assertSame(true, $botFiltering->getValue($this->config)); + + // Check revision + $revision = new \ReflectionProperty(DatafileProjectConfig::class, '_revision'); + $revision->setAccessible(true); + $this->assertEquals('15', $revision->getValue($this->config)); + + // Check group ID map + $groupIdMap = new \ReflectionProperty(DatafileProjectConfig::class, '_groupIdMap'); + $groupIdMap->setAccessible(true); + $this->assertEquals( + [ + '7722400015' => $this->config->getGroup('7722400015') + ], + $groupIdMap->getValue($this->config) ); - $this->optConfigService = new OptimizelyConfigService($this->projectConfig); - - // Create expected default variables map for feat_experiment variation_b - $boolDefaultVariable = new OptimizelyVariable('17252790456', 'boolean_var', 'boolean', 'false'); - $intDefaultVariable = new OptimizelyVariable('17258820367', 'integer_var', 'integer', 1); - $doubleDefaultVariable = new OptimizelyVariable('17260550714', 'double_var', 'double', 0.5); - $strDefaultVariable = new OptimizelyVariable('17290540010', 'string_var', 'string', 'i am default value'); - $jsonDefaultVariable = new OptimizelyVariable('17260550458', 'json_var', 'json', "{\"text\": \"default value\"}"); - - $this->expectedDefaultVariableKeyMap = []; - $this->expectedDefaultVariableKeyMap['boolean_var'] = $boolDefaultVariable; - $this->expectedDefaultVariableKeyMap['integer_var'] = $intDefaultVariable; - $this->expectedDefaultVariableKeyMap['double_var'] = $doubleDefaultVariable; - $this->expectedDefaultVariableKeyMap['string_var'] = $strDefaultVariable; - $this->expectedDefaultVariableKeyMap['json_var'] = $jsonDefaultVariable; - - // Create variable variables map for feat_experiment variation_a - $boolFeatVariable = new OptimizelyVariable('17252790456', 'boolean_var', 'boolean', 'true'); - $intFeatVariable = new OptimizelyVariable('17258820367', 'integer_var', 'integer', 5); - $doubleFeatVariable = new OptimizelyVariable('17260550714', 'double_var', 'double', 5.5); - $strFeatVariable = new OptimizelyVariable('17290540010', 'string_var', 'string', 'i am variable value'); - $jsonFeatVariable = new OptimizelyVariable('17260550458', 'json_var', 'json', "{\"text\": \"variable value\"}"); - - $this->expectedVariableKeyMap = []; - $this->expectedVariableKeyMap['boolean_var'] = $boolFeatVariable; - $this->expectedVariableKeyMap['integer_var'] = $intFeatVariable; - $this->expectedVariableKeyMap['double_var'] = $doubleFeatVariable; - $this->expectedVariableKeyMap['string_var'] = $strFeatVariable; - $this->expectedVariableKeyMap['json_var'] = $jsonFeatVariable; - - // Create variations map for feat_experiment - $this->featExpVariationMap = []; - $this->featExpVariationMap['variation_a'] = - new OptimizelyVariation('17289540366', 'variation_a', true, $this->expectedVariableKeyMap); - - $this->featExpVariationMap['variation_b'] = - new OptimizelyVariation('17304990114', 'variation_b', false, $this->expectedDefaultVariableKeyMap); - - // create feat_experiment - $featExperiment = - new OptimizelyExperiment("17279300791", "feat_experiment", $this->featExpVariationMap); - - - // create feature - $experimentsMap = ['feat_experiment' => $featExperiment]; - $this->feature = - new OptimizelyFeature( - '17266500726', - 'test_feature', - $experimentsMap, - $this->expectedDefaultVariableKeyMap - ); + // Check experiment key map + $experimentKeyMap = new \ReflectionProperty(DatafileProjectConfig::class, '_experimentKeyMap'); + $experimentKeyMap->setAccessible(true); + $this->assertEquals( + [ + 'test_experiment' => $this->config->getExperimentFromKey('test_experiment'), + 'paused_experiment' => $this->config->getExperimentFromKey('paused_experiment'), + 'group_experiment_1' => $this->config->getExperimentFromKey('group_experiment_1'), + 'group_experiment_2' => $this->config->getExperimentFromKey('group_experiment_2'), + 'test_experiment_multivariate' => $this->config->getExperimentFromKey('test_experiment_multivariate'), + 'test_experiment_with_feature_rollout' => $this->config->getExperimentFromKey('test_experiment_with_feature_rollout'), + 'test_experiment_double_feature' => $this->config->getExperimentFromKey('test_experiment_double_feature'), + 'test_experiment_integer_feature' => $this->config->getExperimentFromKey('test_experiment_integer_feature'), + 'test_experiment_2' => $this->config->getExperimentFromKey('test_experiment_2'), + 'test_experiment_json_feature' => $this->config->getExperimentFromKey('test_experiment_json_feature') + ], + $experimentKeyMap->getValue($this->config) + ); + + // Check experiment ID map + $experimentIdMap = new \ReflectionProperty(DatafileProjectConfig::class, '_experimentIdMap'); + $experimentIdMap->setAccessible(true); + $this->assertEquals( + [ + '7716830082' => $this->config->getExperimentFromId('7716830082'), + '7723330021' => $this->config->getExperimentFromId('7723330021'), + '7718750065' => $this->config->getExperimentFromId('7718750065'), + '7716830585' => $this->config->getExperimentFromId('7716830585'), + '122230' => $this->config->getExperimentFromId('122230'), + '122235' => $this->config->getExperimentFromId('122235'), + '122238' => $this->config->getExperimentFromId('122238'), + '122241' => $this->config->getExperimentFromId('122241'), + '111133' => $this->config->getExperimentFromId('111133'), + '122245' => $this->config->getExperimentFromId('122245') + ], + $experimentIdMap->getValue($this->config) + ); + + // Check event key map + $eventKeyMap = new \ReflectionProperty(DatafileProjectConfig::class, '_eventKeyMap'); + $eventKeyMap->setAccessible(true); + $this->assertEquals( + [ + 'purchase' => $this->config->getEvent('purchase'), + 'unlinked_event' => $this->config->getEvent('unlinked_event'), + 'multi_exp_event' => $this->config->getEvent('multi_exp_event') + ], + $eventKeyMap->getValue($this->config) + ); + + // Check attribute key map + $attributeKeyMap = new \ReflectionProperty(DatafileProjectConfig::class, '_attributeKeyMap'); + $attributeKeyMap->setAccessible(true); + $this->assertEquals( + [ + 'device_type' => $this->config->getAttribute('device_type'), + 'location' => $this->config->getAttribute('location'), + '$opt_xyz' => $this->config->getAttribute('$opt_xyz'), + 'boolean_key' => $this->config->getAttribute('boolean_key'), + 'double_key' => $this->config->getAttribute('double_key'), + 'integer_key' => $this->config->getAttribute('integer_key') + ], + $attributeKeyMap->getValue($this->config) + ); + + // Check audience ID map + $audienceIdMap = new \ReflectionProperty(DatafileProjectConfig::class, '_audienceIdMap'); + $audienceIdMap->setAccessible(true); + $this->assertEquals( + [ + '7718080042' => $this->config->getAudience('7718080042'), + '11155' => $this->config->getAudience('11155') + ], + $audienceIdMap->getValue($this->config) + ); + + // Check variation key map + $variationKeyMap = new \ReflectionProperty(DatafileProjectConfig::class, '_variationKeyMap'); + $variationKeyMap->setAccessible(true); + + $this->assertEquals( + [ + 'test_experiment' => [ + 'control' => $this->config->getVariationFromKey('test_experiment', 'control'), + 'variation' => $this->config->getVariationFromKey('test_experiment', 'variation') + ], + 'paused_experiment' => [ + 'control' => $this->config->getVariationFromKey('paused_experiment', 'control'), + 'variation' => $this->config->getVariationFromKey('paused_experiment', 'variation') + ], + 'group_experiment_1' => [ + 'group_exp_1_var_1' => $this->config->getVariationFromKey('group_experiment_1', 'group_exp_1_var_1'), + 'group_exp_1_var_2' => $this->config->getVariationFromKey('group_experiment_1', 'group_exp_1_var_2') + ], + 'group_experiment_2' => [ + 'group_exp_2_var_1' => $this->config->getVariationFromKey('group_experiment_2', 'group_exp_2_var_1'), + 'group_exp_2_var_2' => $this->config->getVariationFromKey('group_experiment_2', 'group_exp_2_var_2') + ], + 'test_experiment_multivariate' => [ + 'Fred' => $this->config->getVariationFromKey('test_experiment_multivariate', 'Fred'), + 'Feorge' => $this->config->getVariationFromKey('test_experiment_multivariate', 'Feorge'), + 'Gred' => $this->config->getVariationFromKey('test_experiment_multivariate', 'Gred'), + 'George' => $this->config->getVariationFromKey('test_experiment_multivariate', 'George') + ], + 'test_experiment_with_feature_rollout' => [ + 'control' => $this->config->getVariationFromKey('test_experiment_with_feature_rollout', 'control'), + 'variation' => $this->config->getVariationFromKey('test_experiment_with_feature_rollout', 'variation') + ], + 'test_experiment_double_feature' => [ + 'control' => $this->config->getVariationFromKey('test_experiment_double_feature', 'control'), + 'variation' => $this->config->getVariationFromKey('test_experiment_double_feature', 'variation') + ], + 'test_experiment_integer_feature' => [ + 'control' => $this->config->getVariationFromKey('test_experiment_integer_feature', 'control'), + 'variation' => $this->config->getVariationFromKey('test_experiment_integer_feature', 'variation') + ], + 'rollout_1_exp_1' => [ + '177771' => $this->config->getVariationFromKey('rollout_1_exp_1', '177771') + ], + 'rollout_1_exp_2' => [ + '177773' => $this->config->getVariationFromKey('rollout_1_exp_2', '177773') + ], + 'rollout_1_exp_3' => [ + '177778' => $this->config->getVariationFromKey('rollout_1_exp_3', '177778') + ], + 'rollout_2_exp_1' => [ + '177775' => $this->config->getVariationFromKey('rollout_2_exp_1', '177775') + ], + 'rollout_2_exp_2' => [ + '177780' => $this->config->getVariationFromKey('rollout_2_exp_2', '177780') + ], + 'test_experiment_2' => [ + 'test_variation_1' => $this->config->getVariationFromKey('test_experiment_2', 'test_variation_1'), + 'test_variation_2' => $this->config->getVariationFromKey('test_experiment_2', 'test_variation_2') + ], + 'test_experiment_json_feature' => [ + 'json_variation' => $this->config->getVariationFromKey('test_experiment_json_feature', 'json_variation') + ] + ], + $variationKeyMap->getValue($this->config) + ); + + // Check variation ID map + $variationIdMap = new \ReflectionProperty(DatafileProjectConfig::class, '_variationIdMap'); + $variationIdMap->setAccessible(true); + $this->assertEquals( + [ + 'test_experiment' => [ + '7722370027' => $this->config->getVariationFromId('test_experiment', '7722370027'), + '7721010009' => $this->config->getVariationFromId('test_experiment', '7721010009') + ], + 'paused_experiment' => [ + '7722370427' => $this->config->getVariationFromId('paused_experiment', '7722370427'), + '7721010509' => $this->config->getVariationFromId('paused_experiment', '7721010509') + ], + 'group_experiment_1' => [ + '7722260071' => $this->config->getVariationFromId('group_experiment_1', '7722260071'), + '7722360022' => $this->config->getVariationFromId('group_experiment_1', '7722360022') + ], + 'group_experiment_2' => [ + '7713030086' => $this->config->getVariationFromId('group_experiment_2', '7713030086'), + '7725250007' => $this->config->getVariationFromId('group_experiment_2', '7725250007') + ], + 'test_experiment_multivariate' => [ + '122231' => $this->config->getVariationFromId('test_experiment_multivariate', '122231'), + '122232' => $this->config->getVariationFromId('test_experiment_multivariate', '122232'), + '122233' => $this->config->getVariationFromId('test_experiment_multivariate', '122233'), + '122234' => $this->config->getVariationFromId('test_experiment_multivariate', '122234') + ], + 'test_experiment_with_feature_rollout' => [ + '122236' => $this->config->getVariationFromId('test_experiment_with_feature_rollout', '122236'), + '122237' => $this->config->getVariationFromId('test_experiment_with_feature_rollout', '122237') + ], + 'test_experiment_double_feature' => [ + '122239' => $this->config->getVariationFromId('test_experiment_double_feature', '122239'), + '122240' => $this->config->getVariationFromId('test_experiment_double_feature', '122240') + ], + 'test_experiment_integer_feature' => [ + '122242' => $this->config->getVariationFromId('test_experiment_integer_feature', '122242'), + '122243' => $this->config->getVariationFromId('test_experiment_integer_feature', '122243') + ], + 'rollout_1_exp_1' => [ + '177771' => $this->config->getVariationFromId('rollout_1_exp_1', '177771') + ], + 'rollout_1_exp_2' => [ + '177773' => $this->config->getVariationFromId('rollout_1_exp_2', '177773') + ], + 'rollout_1_exp_3' => [ + '177778' => $this->config->getVariationFromId('rollout_1_exp_3', '177778') + ], + 'rollout_2_exp_1' => [ + '177775' => $this->config->getVariationFromId('rollout_2_exp_1', '177775') + ], + 'rollout_2_exp_2' => [ + '177780' => $this->config->getVariationFromId('rollout_2_exp_2', '177780') + ], + 'test_experiment_2' => [ + '151239' => $this->config->getVariationFromId('test_experiment_2', '151239'), + '151240' => $this->config->getVariationFromId('test_experiment_2', '151240') + ], + 'test_experiment_json_feature' => [ + '122246' => $this->config->getVariationFromId('test_experiment_json_feature', '122246') + ] + ], + $variationIdMap->getValue($this->config) + ); - // create ab experiment and variations - $variationA = new OptimizelyVariation('17277380360', 'variation_a', null, []); - $variationB = new OptimizelyVariation('17273501081', 'variation_b', null, []); - $variationsMap = []; - $variationsMap['variation_a'] = $variationA; - $variationsMap['variation_b'] = $variationB; - $abExperiment = new OptimizelyExperiment('17301270474', 'ab_experiment', $variationsMap); + // Check feature flag key map + $featureFlagKeyMap = new \ReflectionProperty(DatafileProjectConfig::class, '_featureKeyMap'); + $featureFlagKeyMap->setAccessible(true); + $this->assertEquals( + [ + 'boolean_feature' => $this->config->getFeatureFlagFromKey('boolean_feature'), + 'double_single_variable_feature' => $this->config->getFeatureFlagFromKey('double_single_variable_feature'), + 'integer_single_variable_feature' => $this->config->getFeatureFlagFromKey('integer_single_variable_feature'), + 'boolean_single_variable_feature' => $this->config->getFeatureFlagFromKey('boolean_single_variable_feature'), + 'string_single_variable_feature' => $this->config->getFeatureFlagFromKey('string_single_variable_feature'), + 'multiple_variables_feature' => $this->config->getFeatureFlagFromKey('multiple_variables_feature'), + 'multi_variate_feature' => $this->config->getFeatureFlagFromKey('multi_variate_feature'), + 'mutex_group_feature' => $this->config->getFeatureFlagFromKey('mutex_group_feature'), + 'empty_feature' => $this->config->getFeatureFlagFromKey('empty_feature') + ], + $featureFlagKeyMap->getValue($this->config) + ); + + + // Check rollout id map + $rolloutIdMap = new \ReflectionProperty(DatafileProjectConfig::class, '_rolloutIdMap'); + $rolloutIdMap->setAccessible(true); + $this->assertEquals( + [ + '166660' => $this->config->getRolloutFromId('166660'), + '166661' => $this->config->getRolloutFromId('166661') + ], + $rolloutIdMap->getValue($this->config) + ); + + + // Check variation entity + $variableUsages = [ + new VariableUsage("155560", "F"), + new VariableUsage("155561", "red") + ]; + $expectedVariation = new Variation("122231", "Fred", true, $variableUsages); + $actualVariation = $this->config->getVariationFromKey("test_experiment_multivariate", "Fred"); + + $this->assertEquals($expectedVariation, $actualVariation); + + // Check Experiment Feature Map + $experimentFeatureMap = new \ReflectionProperty(DatafileProjectConfig::class, '_experimentFeatureMap'); + $experimentFeatureMap->setAccessible(true); + $this->assertEquals( + [ + '111133' => ['155549'], + '122238' => ['155550'], + '122241' => ['155552'], + '122235' => ['155557'], + '122230' => ['155559'], + '7723330021' => ['155562'], + '7718750065' => ['155562'], + '122245' => ['155597'] + ], + $experimentFeatureMap->getValue($this->config) + ); + } - // create group_ab_experiment and variations - $variationA = new OptimizelyVariation('17287500312', 'variation_a', null, []); - $variationB = new OptimizelyVariation('17283640326', 'variation_b', null, []); - $variationsMap = []; - $variationsMap['variation_a'] = $variationA; - $variationsMap['variation_b'] = $variationB; + public function testExceptionThrownForUnsupportedVersion() + { + // Verify that an exception is thrown when given datafile version is unsupported // + $this->setExpectedException( + InvalidDatafileVersionException::class, + 'This version of the PHP SDK does not support the given datafile version: 5.' + ); - $groupExperiment = - new OptimizelyExperiment('17258450439', 'group_ab_experiment', $variationsMap); + $this->config = new DatafileProjectConfig( + UNSUPPORTED_DATAFILE, + $this->loggerMock, + $this->errorHandlerMock + ); + } - // create experiment key map - $this->expectedExpKeyMap = []; - $this->expectedExpKeyMap['ab_experiment'] = $abExperiment; - $this->expectedExpKeyMap['group_ab_experiment'] = $groupExperiment; - $this->expectedExpKeyMap['feat_experiment'] = $featExperiment; + public function testVariationParsingWithoutFeatureEnabledProp() + { + $variables = [ + [ + "id"=> "155556", + "value"=> "true" + ] + ]; + + $data = [ + [ + "id"=> "177771", + "key"=> "my_var", + "variables"=> $variables + ] + ]; + + $variationIdMap = ConfigParser::generateMap($data, 'id', Variation::class); + + $variation = $variationIdMap["177771"]; + $this->assertEquals("177771", $variation->getId()); + $this->assertEquals("my_var", $variation->getKey()); + + $variableUsageMap = ConfigParser::generateMap($variables, null, VariableUsage::class); + $this->assertEquals($variableUsageMap, $variation->getVariables()); + + // assert featureEnabled by default is set to false when property not provided in data file + $this->assertNull($variation->getFeatureEnabled()); + } - // create experiment id map - $this->expectedExpIdMap = []; - $this->expectedExpIdMap['17301270474'] = $abExperiment; - $this->expectedExpIdMap['17258450439'] = $groupExperiment; - $this->expectedExpIdMap['17279300791'] = $featExperiment; + public function testGetAccountId() + { + $this->assertEquals('1592310167', $this->config->getAccountId()); } - protected static function getMethod($name) + public function testGetProjectId() { - $class = new \ReflectionClass('Optimizely\\OptimizelyConfig\\OptimizelyConfigService'); - $method = $class->getMethod($name); - $method->setAccessible(true); - return $method; + $this->assertEquals('7720880029', $this->config->getProjectId()); } - public function testGetVariablesMapReturnsEmptyForAbExpVariation() + public function testGetBotFiltering() { - $abExp = $this->projectConfig->getExperimentFromKey("ab_experiment"); - $abExpVarA = $abExp->getVariations()[0]; + $botFiltering = new \ReflectionProperty(DatafileProjectConfig::class, '_botFiltering'); + $botFiltering->setAccessible(true); + $this->assertSame($botFiltering->getValue($this->config), $this->config->getBotFiltering()); + } - $getVariablesMap = self::getMethod("getVariablesMap"); - $response = $getVariablesMap->invokeArgs($this->optConfigService, array($abExp, $abExpVarA)); + public function testGetRevision() + { + $this->assertEquals('15', $this->config->getRevision()); + } - $this->assertEmpty($response); + public function testGetGroupValidId() + { + $group = $this->config->getGroup('7722400015'); + $this->assertEquals('7722400015', $group->getId()); + $this->assertEquals('random', $group->getPolicy()); } - public function testGetVariablesMapReturnsVariableMapForEnabledVariation() + public function testGetGroupInvalidId() { - $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); - $featureDisabledVar = $featExp->getVariations()[0]; + $this->loggerMock->expects($this->once()) + ->method('log') + ->with(Logger::ERROR, 'Group ID "invalid_id" is not in datafile.'); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidGroupException('Provided group is not in datafile.')); + + $this->assertEquals(new Group(), $this->config->getGroup('invalid_id')); + } - $getVariablesMap = self::getMethod("getVariablesMap"); - $response = $getVariablesMap->invokeArgs($this->optConfigService, array($featExp, $featureDisabledVar)); + public function testGetExperimentValidKey() + { + $experiment = $this->config->getExperimentFromKey('test_experiment'); + $this->assertEquals('test_experiment', $experiment->getKey()); + $this->assertEquals('7716830082', $experiment->getId()); + } - $this->assertEquals($this->expectedVariableKeyMap, $response); + public function testGetExperimentInvalidKey() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with(Logger::ERROR, 'Experiment key "invalid_key" is not in datafile.'); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidExperimentException('Provided experiment is not in datafile.')); + + $this->assertEquals(new Experiment(), $this->config->getExperimentFromKey('invalid_key')); } - public function testGetVariablesMapReturnsVariableMapForDisabledVariation() + public function testGetExperimentValidId() { - $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); - $featureDisabledVar = $featExp->getVariations()[1]; + $experiment = $this->config->getExperimentFromId('7716830082'); + $this->assertEquals('7716830082', $experiment->getId()); + $this->assertEquals('test_experiment', $experiment->getKey()); + } - $getVariablesMap = self::getMethod("getVariablesMap"); - $response = $getVariablesMap->invokeArgs($this->optConfigService, array($featExp, $featureDisabledVar)); + public function testGetExperimentInvalidId() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with(Logger::ERROR, 'Experiment ID "42" is not in datafile.'); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidExperimentException('Provided experiment is not in datafile.')); + + $this->assertEquals(new Experiment(), $this->config->getExperimentFromId('42')); + } - $this->assertEquals($this->expectedDefaultVariableKeyMap, $response); + public function testGetFeatureFlagInvalidKey() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with(Logger::ERROR, 'FeatureFlag Key "42" is not in datafile.'); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidFeatureFlagException('Provided feature flag is not in datafile.')); + + $this->assertEquals(new FeatureFlag(), $this->config->getFeatureFlagFromKey('42')); } - public function testGetVariationsMap() + public function testGetRolloutInvalidId() { - $featExp = $this->projectConfig->getExperimentFromKey("feat_experiment"); + $this->loggerMock->expects($this->once()) + ->method('log') + ->with(Logger::ERROR, 'Rollout with ID "42" is not in the datafile.'); - $getVariationsMap = self::getMethod("getVariationsMap"); - $response = $getVariationsMap->invokeArgs($this->optConfigService, array($featExp)); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidRolloutException('Provided rollout is not in datafile.')); - $this->assertEquals($this->featExpVariationMap, $response); + $this->assertEquals(new Rollout(), $this->config->getRolloutFromId('42')); } - public function testGetExperimentsMaps() + public function testGetEventValidKey() { - $getExperimentsMap = self::getMethod("getExperimentsMaps"); - $response = $getExperimentsMap->invokeArgs($this->optConfigService, array()); + $event = $this->config->getEvent('purchase'); + $this->assertEquals('purchase', $event->getKey()); + $this->assertEquals('7718020063', $event->getId()); + $this->assertEquals(['7716830082', '7723330021', '7718750065', '7716830585'], $event->getExperimentIds()); + } - // assert experiment key map - $this->assertEquals($this->expectedExpKeyMap, $response[0]); + public function testGetEventInvalidKey() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with(Logger::ERROR, 'Event key "invalid_key" is not in datafile.'); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidEventException('Provided event is not in datafile.')); + + $this->assertEquals(new Event(), $this->config->getEvent('invalid_key')); + } - // assert experiment id map - $this->assertEquals($this->expectedExpIdMap, $response[1]); + public function testGetAudienceValidId() + { + $audience = $this->config->getAudience('7718080042'); + $this->assertEquals('7718080042', $audience->getId()); + $this->assertEquals('iPhone users in San Francisco', $audience->getName()); } - public function testGetFeaturesMap() + public function testGetAudiencePrefersTypedAudiencesOverAudiences() { - $getFeaturesMap = self::getMethod("getFeaturesMap"); - $response = $getFeaturesMap->invokeArgs( - $this->optConfigService, - array($this->expectedExpIdMap) + $projectConfig = new DatafileProjectConfig( + DATAFILE_WITH_TYPED_AUDIENCES, + $this->loggerMock, + $this->errorHandlerMock ); - $this->assertEquals(['test_feature' => $this->feature], $response); - } - - public function testGetConfig() - { - $response = $this->optConfigService->getConfig(); - - $this->assertInstanceof(OptimizelyConfig::class, $response); - - // assert revision - $this->assertEquals('16', $response->getRevision()); - - // assert experiments map - $this->assertEquals($this->expectedExpKeyMap, $response->getExperimentsMap()); - - // assert features map - $this->assertEquals(['test_feature' => $this->feature], $response->getFeaturesMap()); - } - - public function testJsonEncodeofOptimizelyConfig() - { - $response = $this->optConfigService->getConfig(); - - $expectedJSON = '{ - "revision": "16", - "experimentsMap": { - "ab_experiment": { - "id": "17301270474", - "key": "ab_experiment", - "variationsMap": { - "variation_a": { - "id": "17277380360", - "key": "variation_a", - "variablesMap": [ - - ] - }, - "variation_b": { - "id": "17273501081", - "key": "variation_b", - "variablesMap": [ - - ] - } - } - }, - "feat_experiment": { - "id": "17279300791", - "key": "feat_experiment", - "variationsMap": { - "variation_a": { - "id": "17289540366", - "key": "variation_a", - "featureEnabled": true, - "variablesMap": { - "boolean_var": { - "id": "17252790456", - "key": "boolean_var", - "type": "boolean", - "value": "true" - }, - "integer_var": { - "id": "17258820367", - "key": "integer_var", - "type": "integer", - "value": "5" - }, - "double_var": { - "id": "17260550714", - "key": "double_var", - "type": "double", - "value": "5.5" - }, - "string_var": { - "id": "17290540010", - "key": "string_var", - "type": "string", - "value": "i am variable value" - }, - "json_var": { - "id": "17260550458", - "key": "json_var", - "type": "json", - "value": "{\"text\": \"variable value\"}" - } - } - }, - "variation_b": { - "id": "17304990114", - "key": "variation_b", - "featureEnabled": false, - "variablesMap": { - "boolean_var": { - "id": "17252790456", - "key": "boolean_var", - "type": "boolean", - "value": "false" - }, - "integer_var": { - "id": "17258820367", - "key": "integer_var", - "type": "integer", - "value": "1" - }, - "double_var": { - "id": "17260550714", - "key": "double_var", - "type": "double", - "value": "0.5" - }, - "string_var": { - "id": "17290540010", - "key": "string_var", - "type": "string", - "value": "i am default value" - }, - "json_var": { - "id": "17260550458", - "key": "json_var", - "type": "json", - "value": "{\"text\": \"default value\"}" - } - } - } - } - }, - "group_ab_experiment": { - "id": "17258450439", - "key": "group_ab_experiment", - "variationsMap": { - "variation_a": { - "id": "17287500312", - "key": "variation_a", - "variablesMap": [ - - ] - }, - "variation_b": { - "id": "17283640326", - "key": "variation_b", - "variablesMap": [ - - ] - } - } - } - }, - "featuresMap": { - "test_feature": { - "id": "17266500726", - "key": "test_feature", - "experimentsMap": { - "feat_experiment": { - "id": "17279300791", - "key": "feat_experiment", - "variationsMap": { - "variation_a": { - "id": "17289540366", - "key": "variation_a", - "featureEnabled": true, - "variablesMap": { - "boolean_var": { - "id": "17252790456", - "key": "boolean_var", - "type": "boolean", - "value": "true" - }, - "integer_var": { - "id": "17258820367", - "key": "integer_var", - "type": "integer", - "value": "5" - }, - "double_var": { - "id": "17260550714", - "key": "double_var", - "type": "double", - "value": "5.5" - }, - "string_var": { - "id": "17290540010", - "key": "string_var", - "type": "string", - "value": "i am variable value" - }, - "json_var": { - "id": "17260550458", - "key": "json_var", - "type": "json", - "value": "{\"text\": \"variable value\"}" - } - } - }, - "variation_b": { - "id": "17304990114", - "key": "variation_b", - "featureEnabled": false, - "variablesMap": { - "boolean_var": { - "id": "17252790456", - "key": "boolean_var", - "type": "boolean", - "value": "false" - }, - "integer_var": { - "id": "17258820367", - "key": "integer_var", - "type": "integer", - "value": "1" - }, - "double_var": { - "id": "17260550714", - "key": "double_var", - "type": "double", - "value": "0.5" - }, - "string_var": { - "id": "17290540010", - "key": "string_var", - "type": "string", - "value": "i am default value" - }, - "json_var": { - "id": "17260550458", - "key": "json_var", - "type": "json", - "value": "{\"text\": \"default value\"}" - } - } - } - } - } - }, - "variablesMap": { - "boolean_var": { - "id": "17252790456", - "key": "boolean_var", - "type": "boolean", - "value": "false" - }, - "integer_var": { - "id": "17258820367", - "key": "integer_var", - "type": "integer", - "value": "1" - }, - "double_var": { - "id": "17260550714", - "key": "double_var", - "type": "double", - "value": "0.5" - }, - "string_var": { - "id": "17290540010", - "key": "string_var", - "type": "string", - "value": "i am default value" - }, - "json_var": { - "id": "17260550458", - "key": "json_var", - "type": "json", - "value": "{\"text\": \"default value\"}" - } - } - } - } - }'; - - $this->assertEquals(json_encode(json_decode($expectedJSON)), json_encode($response)); - } - - public function testGetDatafile() + // test that typedAudience is returned when an audience exists with the same ID. + $audience = $projectConfig->getAudience('3988293898'); + + $this->assertEquals('3988293898', $audience->getId()); + $this->assertEquals('substringString', $audience->getName()); + + $expectedConditions = json_decode('["and", ["or", ["or", {"name": "house", "type": "custom_attribute", + "match": "substring", "value": "Slytherin"}]]]', true); + $this->assertEquals($expectedConditions, $audience->getConditions()); + $this->assertEquals($expectedConditions, $audience->getConditionsList()); + + // test that normal audience is returned if no typedAudience exists with the same ID. + $audience = $projectConfig->getAudience('3468206642'); + + $this->assertEquals('3468206642', $audience->getId()); + $this->assertEquals('exactString', $audience->getName()); + + $expectedConditions = '["and", ["or", ["or", {"name": "house", "type": "custom_attribute", "value": "Gryffindor"}]]]'; + $this->assertEquals($expectedConditions, $audience->getConditions()); + $expectedConditionsList = json_decode($expectedConditions, true); + $this->assertEquals($expectedConditionsList, $audience->getConditionsList()); + } + + public function testGetAudienceInvalidKey() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with(Logger::ERROR, 'Audience ID "invalid_id" is not in datafile.'); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidAudienceException('Provided audience is not in datafile.')); + + $this->assertNull($this->config->getAudience('invalid_id')); + } + + public function testGetAttributeValidKey() + { + $attribute = $this->config->getAttribute('device_type'); + $this->assertEquals('device_type', $attribute->getKey()); + $this->assertEquals('7723280020', $attribute->getId()); + } + + public function testGetAttributeValidKeyWithReservedPrefix() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with( + Logger::WARNING, + 'Attribute $opt_xyz unexpectedly has reserved prefix $opt_; using attribute ID instead of reserved attribute name.' + ); + + $validAttrWithReservedPrefix = new Attribute('7723340006', '$opt_xyz'); + $this->assertEquals($validAttrWithReservedPrefix, $this->config->getAttribute('$opt_xyz')); + } + + public function testGetAttributeInvalidKey() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with(Logger::ERROR, 'Attribute key "invalid_key" is not in datafile.'); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidAttributeException('Provided attribute is not in datafile.')); + + $this->assertNull($this->config->getAttribute('invalid_key')); + } + + public function testGetAttributeMissingKeyWithReservedPrefix() + { + $validAttrWithReservedPrefix = new Attribute('$opt_007', '$opt_007'); + $this->assertEquals($validAttrWithReservedPrefix, $this->config->getAttribute('$opt_007')); + } + + public function testGetAttributeForControlAttributes() + { + # Should return attribute with ID same as given Key + $optUserAgentAttr = new Attribute('$opt_user_agent', '$opt_user_agent'); + $this->assertEquals($optUserAgentAttr, $this->config->getAttribute('$opt_user_agent')); + + $optBucketingIdAttr = new Attribute('$opt_bucketing_id', '$opt_bucketing_id'); + $this->assertEquals($optBucketingIdAttr, $this->config->getAttribute('$opt_bucketing_id')); + } + + public function testGetVariationFromKeyValidExperimentKeyValidVariationKey() + { + $variation = $this->config->getVariationFromKey('test_experiment', 'control'); + $this->assertEquals('7722370027', $variation->getId()); + $this->assertEquals('control', $variation->getKey()); + } + + public function testGetVariationFromKeyValidExperimentKeyInvalidVariationKey() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with( + Logger::ERROR, + 'No variation key "invalid_key" defined in datafile for experiment "test_experiment".' + ); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidVariationException('Provided variation is not in datafile.')); + + $this->assertEquals(new Variation(), $this->config->getVariationFromKey('test_experiment', 'invalid_key')); + } + + public function testGetVariationFromKeyInvalidExperimentKey() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with( + Logger::ERROR, + 'No variation key "control" defined in datafile for experiment "invalid_experiment".' + ); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidVariationException('Provided variation is not in datafile.')); + + $this->assertEquals(new Variation(), $this->config->getVariationFromKey('invalid_experiment', 'control')); + } + + public function testGetVariationFromIdValidExperimentKeyValidVariationId() + { + $variation = $this->config->getVariationFromId('test_experiment', '7722370027'); + $this->assertEquals('control', $variation->getKey()); + $this->assertEquals('7722370027', $variation->getId()); + } + + public function testGetVariationFromIdValidExperimentKeyInvalidVariationId() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with( + Logger::ERROR, + 'No variation ID "invalid_id" defined in datafile for experiment "test_experiment".' + ); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidVariationException('Provided variation is not in datafile.')); + + $this->assertEquals(new Variation(), $this->config->getVariationFromId('test_experiment', 'invalid_id')); + } + + public function testGetVariationFromIdInvalidExperimentKey() + { + $this->loggerMock->expects($this->once()) + ->method('log') + ->with( + Logger::ERROR, + 'No variation ID "7722370027" defined in datafile for experiment "invalid_experiment".' + ); + $this->errorHandlerMock->expects($this->once()) + ->method('handleError') + ->with(new InvalidVariationException('Provided variation is not in datafile.')); + + $this->assertEquals(new Variation(), $this->config->getVariationFromId('invalid_experiment', '7722370027')); + } + + // Test that a true is returned if experiment is a feature test, false otherwise. + public function testIsFeatureExperiment() + { + $experiment = $this->config->getExperimentFromKey('test_experiment'); + $featureExperiment = $this->config->getExperimentFromKey('test_experiment_double_feature'); + + $this->assertTrue($this->config->isFeatureExperiment($featureExperiment->getId())); + $this->assertFalse($this->config->isFeatureExperiment($experiment->getId())); + } + + public function testToDatafile() { $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; - $actualDatafile = $this->optConfigService->getDatafile(); + $this->config = new DatafileProjectConfig($expectedDatafile, $this->loggerMock, $this->errorHandlerMock); + $actualDatafile = $this->config->toDatafile(); + $this->assertEquals($expectedDatafile, $actualDatafile); } } diff --git a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php index fc40cb36..0a1d5ad8 100644 --- a/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyConfigServiceTest.php @@ -465,13 +465,16 @@ public function testJsonEncodeofOptimizelyConfig() } }'; - $this->assertEquals(json_encode(json_decode($expectedJSON)), json_encode($response)); + $optimizelyConfig = json_decode($expectedJSON, true); + $optimizelyConfig['datafile'] = DATAFILE_FOR_OPTIMIZELY_CONFIG; + + $this->assertEquals(json_encode($optimizelyConfig), json_encode($response)); } public function testGetDatafile() { $expectedDatafile = DATAFILE_FOR_OPTIMIZELY_CONFIG; - $actualDatafile = $this->optConfigService->getDatafile(); + $actualDatafile = $this->optConfigService->getConfig()->getDatafile(); $this->assertEquals($expectedDatafile, $actualDatafile); } } diff --git a/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php b/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php index 40b3e3a2..c73bf331 100644 --- a/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php @@ -31,7 +31,8 @@ public function testOptimizelyConfigEntity() $optConfig = new OptimizelyConfig( "20", ["a" => "apple"], - ["o" => "orange"] + ["o" => "orange"], + null ); $this->assertEquals("20", $optConfig->getRevision()); @@ -41,7 +42,8 @@ public function testOptimizelyConfigEntity() $expectedJson = '{ "revision": "20", "experimentsMap" : {"a": "apple"}, - "featuresMap": {"o": "orange"} + "featuresMap": {"o": "orange"}, + "datafile": null }'; $expectedJson = json_encode(json_decode($expectedJson)); From 1363d519994fb32ff29f068d65cba51b06d4b5fc Mon Sep 17 00:00:00 2001 From: Sohail Hussain Date: Thu, 27 Aug 2020 13:21:32 -0700 Subject: [PATCH 09/11] added null default value --- src/Optimizely/OptimizelyConfig/OptimizelyConfig.php | 2 +- tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php index 157a4666..fdd5736c 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfig.php @@ -43,7 +43,7 @@ class OptimizelyConfig implements \JsonSerializable private $datafile; - public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile) + public function __construct($revision, array $experimentsMap, array $featuresMap, $datafile = null) { $this->revision = $revision; $this->experimentsMap = $experimentsMap; diff --git a/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php b/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php index c73bf331..40b3e3a2 100644 --- a/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php @@ -31,8 +31,7 @@ public function testOptimizelyConfigEntity() $optConfig = new OptimizelyConfig( "20", ["a" => "apple"], - ["o" => "orange"], - null + ["o" => "orange"] ); $this->assertEquals("20", $optConfig->getRevision()); @@ -42,8 +41,7 @@ public function testOptimizelyConfigEntity() $expectedJson = '{ "revision": "20", "experimentsMap" : {"a": "apple"}, - "featuresMap": {"o": "orange"}, - "datafile": null + "featuresMap": {"o": "orange"} }'; $expectedJson = json_encode(json_decode($expectedJson)); From ae01873bb846b8123ae49f331af228c37b095cf3 Mon Sep 17 00:00:00 2001 From: Sohail Hussain Date: Thu, 27 Aug 2020 13:47:58 -0700 Subject: [PATCH 10/11] fixed unit test --- tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php b/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php index 40b3e3a2..3551af3f 100644 --- a/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php +++ b/tests/OptimizelyConfigTests/OptimizelyEntitiesTest.php @@ -41,7 +41,8 @@ public function testOptimizelyConfigEntity() $expectedJson = '{ "revision": "20", "experimentsMap" : {"a": "apple"}, - "featuresMap": {"o": "orange"} + "featuresMap": {"o": "orange"}, + "datafile": null }'; $expectedJson = json_encode(json_decode($expectedJson)); From 8f78fcde4cf0a2aec28418692f1127f16ba134ac Mon Sep 17 00:00:00 2001 From: Owais Akbani Date: Fri, 28 Aug 2020 19:26:58 +0500 Subject: [PATCH 11/11] fix: docstring nit --- src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php index 3f0dffd7..eef1829a 100644 --- a/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php +++ b/src/Optimizely/OptimizelyConfig/OptimizelyConfigService.php @@ -38,7 +38,7 @@ class OptimizelyConfigService private $revision; /** - * @var string config datafile. + * @var string String denoting datafile. */ private $datafile;