diff --git a/src/Optimizely/Optimizely.php b/src/Optimizely/Optimizely.php index 43dc7815..e4b798b7 100644 --- a/src/Optimizely/Optimizely.php +++ b/src/Optimizely/Optimizely.php @@ -37,6 +37,7 @@ use Optimizely\Notification\NotificationCenter; use Optimizely\Notification\NotificationType; use Optimizely\OptimizelyConfig\OptimizelyConfigService; +use Optimizely\ProjectConfigManager\HTTPProjectConfigManager; use Optimizely\ProjectConfigManager\ProjectConfigManagerInterface; use Optimizely\ProjectConfigManager\StaticProjectConfigManager; use Optimizely\UserProfile\UserProfileServiceInterface; @@ -96,7 +97,7 @@ class Optimizely /** * @var ProjectConfigManagerInterface */ - private $_projectConfigManager; + public $configManager; /** * @var NotificationCenter @@ -114,6 +115,7 @@ class Optimizely * @param $userProfileService UserProfileServiceInterface * @param $configManager ProjectConfigManagerInterface provides ProjectConfig through getConfig method. * @param $notificationCenter NotificationCenter + * @param $sdkKey string uniquely identifying the datafile corresponding to project and environment combination. Must provide at least one of datafile or sdkKey. */ public function __construct( $datafile, @@ -123,7 +125,8 @@ public function __construct( $skipJsonValidation = false, UserProfileServiceInterface $userProfileService = null, ProjectConfigManagerInterface $configManager = null, - NotificationCenter $notificationCenter = null + NotificationCenter $notificationCenter = null, + $sdkKey = null ) { $this->_isValid = true; $this->_eventDispatcher = $eventDispatcher ?: new DefaultEventDispatcher(); @@ -132,7 +135,15 @@ public function __construct( $this->_eventBuilder = new EventBuilder($this->_logger); $this->_decisionService = new DecisionService($this->_logger, $userProfileService); $this->notificationCenter = $notificationCenter ?: new NotificationCenter($this->_logger, $this->_errorHandler); - $this->_projectConfigManager = $configManager ?: new StaticProjectConfigManager($datafile, $skipJsonValidation, $this->_logger, $this->_errorHandler); + $this->configManager = $configManager; + + if ($this->configManager === null) { + if ($sdkKey) { + $this->configManager = new HTTPProjectConfigManager($sdkKey, null, null, true, $datafile, $skipJsonValidation, $this->_logger, $this->_errorHandler, $this->notificationCenter); + } else { + $this->configManager = new StaticProjectConfigManager($datafile, $skipJsonValidation, $this->_logger, $this->_errorHandler); + } + } } /** @@ -141,7 +152,7 @@ public function __construct( */ protected function getConfig() { - $config = $this->_projectConfigManager->getConfig(); + $config = $this->configManager->getConfig(); return $config instanceof DatafileProjectConfig ? $config : null; } diff --git a/src/Optimizely/OptimizelyFactory.php b/src/Optimizely/OptimizelyFactory.php new file mode 100644 index 00000000..5c1207f0 --- /dev/null +++ b/src/Optimizely/OptimizelyFactory.php @@ -0,0 +1,42 @@ +datafile = DATAFILE; + $this->typedAudiencesDataFile = DATAFILE_WITH_TYPED_AUDIENCES; + } + + public function testDefaultInstance() + { + $optimizelyClient = OptimizelyFactory::createDefaultInstance("some-sdk-key", $this->datafile); + + // client hasn't been mocked yet. Hence, config manager should return config of hardcoded + // datafile. + $this->assertEquals('15', $optimizelyClient->configManager->getConfig()->getRevision()); + + // Mock http client to return a valid datafile + $mock = new MockHandler([ + new Response(200, [], $this->typedAudiencesDataFile) + ]); + + $handler = HandlerStack::create($mock); + + $client = new Client(['handler' => $handler]); + $httpClient = new \ReflectionProperty(HTTPProjectConfigManager::class, 'httpClient'); + $httpClient->setAccessible(true); + $httpClient->setValue($optimizelyClient->configManager, $client); + + /// Fetch datafile + $optimizelyClient->configManager->fetch(); + + $this->assertEquals('3', $optimizelyClient->configManager->getConfig()->getRevision()); + } +} diff --git a/tests/OptimizelyTest.php b/tests/OptimizelyTest.php index 5d82f508..79c8f1de 100644 --- a/tests/OptimizelyTest.php +++ b/tests/OptimizelyTest.php @@ -17,6 +17,11 @@ namespace Optimizely\Tests; use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Middleware; +use GuzzleHttp\Psr7\Response; use Monolog\Logger; use Optimizely\Config\DatafileProjectConfig; use Optimizely\DecisionService\DecisionService; @@ -63,9 +68,7 @@ private function setOptimizelyConfigObject($optimizely, $config, $configManager) $projConfig->setAccessible(true); $projConfig->setValue($configManager, $config); - $projConfigManager = new \ReflectionProperty(Optimizely::class, '_projectConfigManager'); - $projConfigManager->setAccessible(true); - $projConfigManager->setValue($optimizely, $configManager); + $optimizely->configManager = $configManager; } public function setUp() @@ -221,6 +224,92 @@ public function testInitDatafileInvalidFormat() $this->expectOutputRegex('/Provided datafile is in an invalid format./'); } + public function testInitWithSdkKey() + { + $sdkKey = "some-sdk-key"; + $optimizelyClient = new Optimizely( + null, + null, + null, + null, + null, + null, + null, + null, + $sdkKey + ); + + // client hasn't been mocked yet. Hence, activate should return null. + $actualVariation = $optimizelyClient->activate('test_experiment_integer_feature', 'test_user'); + $this->assertNull($actualVariation); + + // Mock http client to return a valid datafile + $mock = new MockHandler([ + new Response(200, [], $this->datafile) + ]); + + $container = []; + $history = Middleware::history($container); + $handler = HandlerStack::create($mock); + $handler->push($history); + + $client = new Client(['handler' => $handler]); + $httpClient = new \ReflectionProperty(HTTPProjectConfigManager::class, 'httpClient'); + $httpClient->setAccessible(true); + $httpClient->setValue($optimizelyClient->configManager, $client); + + // Fetch datafile + $optimizelyClient->configManager->fetch(); + + // activate should return expected variation. + $actualVariation = $optimizelyClient->activate('test_experiment_integer_feature', 'test_user'); + $this->assertEquals('variation', $actualVariation); + + // assert that https call is made to mock as expected. + $transaction = $container[0]; + $this->assertEquals( + 'https://cdn.optimizely.com/datafiles/some-sdk-key.json', + $transaction['request']->getUri() + ); + } + + public function testInitWithBothSdkKeyAndDatafile() + { + $sdkKey = "some-sdk-key"; + $optimizelyClient = new Optimizely( + DATAFILE, + null, + null, + null, + null, + null, + null, + null, + $sdkKey + ); + + // client hasn't been mocked yet. Hence, config manager should return config of hardcoded + // datafile. + $this->assertEquals('15', $optimizelyClient->configManager->getConfig()->getRevision()); + + + // Mock http client to return a valid datafile + $mock = new MockHandler([ + new Response(200, [], $this->typedAudiencesDataFile) + ]); + + $handler = HandlerStack::create($mock); + $client = new Client(['handler' => $handler]); + $httpClient = new \ReflectionProperty(HTTPProjectConfigManager::class, 'httpClient'); + $httpClient->setAccessible(true); + $httpClient->setValue($optimizelyClient->configManager, $client); + + // Fetch datafile + $optimizelyClient->configManager->fetch(); + + $this->assertEquals('3', $optimizelyClient->configManager->getConfig()->getRevision()); + } + public function testActivateInvalidOptimizelyObject() { $optimizelyMock = $this->getMockBuilder(Optimizely::class) @@ -4471,9 +4560,7 @@ public function testGetConfigReturnsDatafileProjectConfigInstance() ->setMethods(array('getConfig')) ->getMock(); - $projectConfigManager = new \ReflectionProperty(Optimizely::class, '_projectConfigManager'); - $projectConfigManager->setAccessible(true); - $projectConfigManager->setValue($optlyObject, $projectConfigManagerMock); + $optlyObject->configManager = $projectConfigManagerMock; $expectedProjectConfig = new DatafileProjectConfig($this->datafile, $this->loggerMock, new NoOpErrorHandler());