From 26dd1a07320a2b58b3a44e2caa9a38be3df28130 Mon Sep 17 00:00:00 2001 From: Owais Akbani <oakbani@folio3.com> Date: Fri, 24 Jan 2020 15:33:55 +0500 Subject: [PATCH 1/3] feat: Add init with sdk key --- src/Optimizely/Optimizely.php | 17 ++++++-- tests/OptimizelyTest.php | 79 ++++++++++++++++++++++++++++++++--- 2 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/Optimizely/Optimizely.php b/src/Optimizely/Optimizely.php index 43dc7815..b9cb7ed4 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 @@ -112,6 +113,7 @@ class Optimizely * @param $errorHandler ErrorHandlerInterface * @param $skipJsonValidation boolean representing whether JSON schema validation needs to be performed. * @param $userProfileService UserProfileServiceInterface + * @param $sdkKey string uniquely identifying the datafile corresponding to project and environment combination. Must provide at least one of datafile or sdkKey. * @param $configManager ProjectConfigManagerInterface provides ProjectConfig through getConfig method. * @param $notificationCenter NotificationCenter */ @@ -122,6 +124,7 @@ public function __construct( ErrorHandlerInterface $errorHandler = null, $skipJsonValidation = false, UserProfileServiceInterface $userProfileService = null, + $sdkKey = null, ProjectConfigManagerInterface $configManager = null, NotificationCenter $notificationCenter = null ) { @@ -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/tests/OptimizelyTest.php b/tests/OptimizelyTest.php index 5d82f508..531e37cf 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,72 @@ 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, $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, $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 +4540,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()); From f56015a4d21a24b423ff6f8b7b0dcdfe450fb788 Mon Sep 17 00:00:00 2001 From: Owais Akbani <owais.akbani92@gmail.com> Date: Fri, 20 Mar 2020 14:50:58 +0500 Subject: [PATCH 2/3] refact: Make sdkKey last param of constructor --- src/Optimizely/Optimizely.php | 6 +++--- tests/OptimizelyTest.php | 24 ++++++++++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/Optimizely/Optimizely.php b/src/Optimizely/Optimizely.php index b9cb7ed4..e4b798b7 100644 --- a/src/Optimizely/Optimizely.php +++ b/src/Optimizely/Optimizely.php @@ -113,9 +113,9 @@ class Optimizely * @param $errorHandler ErrorHandlerInterface * @param $skipJsonValidation boolean representing whether JSON schema validation needs to be performed. * @param $userProfileService UserProfileServiceInterface - * @param $sdkKey string uniquely identifying the datafile corresponding to project and environment combination. Must provide at least one of datafile or sdkKey. * @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, @@ -124,9 +124,9 @@ public function __construct( ErrorHandlerInterface $errorHandler = null, $skipJsonValidation = false, UserProfileServiceInterface $userProfileService = null, - $sdkKey = null, ProjectConfigManagerInterface $configManager = null, - NotificationCenter $notificationCenter = null + NotificationCenter $notificationCenter = null, + $sdkKey = null ) { $this->_isValid = true; $this->_eventDispatcher = $eventDispatcher ?: new DefaultEventDispatcher(); diff --git a/tests/OptimizelyTest.php b/tests/OptimizelyTest.php index 531e37cf..79c8f1de 100644 --- a/tests/OptimizelyTest.php +++ b/tests/OptimizelyTest.php @@ -227,7 +227,17 @@ public function testInitDatafileInvalidFormat() public function testInitWithSdkKey() { $sdkKey = "some-sdk-key"; - $optimizelyClient = new Optimizely(null, null, null, null, null, null, $sdkKey); + $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'); @@ -266,7 +276,17 @@ public function testInitWithSdkKey() public function testInitWithBothSdkKeyAndDatafile() { $sdkKey = "some-sdk-key"; - $optimizelyClient = new Optimizely(DATAFILE, null, null, null, null, null, $sdkKey); + $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. From 1b8d6edd2615afa836dbdace04ef6058f140c4a6 Mon Sep 17 00:00:00 2001 From: Owais Akbani <owais.akbani92@gmail.com> Date: Fri, 20 Mar 2020 15:52:10 +0500 Subject: [PATCH 3/3] feat: Add defaultinstance in OptimizelyFactory --- src/Optimizely/OptimizelyFactory.php | 42 +++++++++++++++++++ tests/OptimizelyFactoryTest.php | 60 ++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 src/Optimizely/OptimizelyFactory.php create mode 100644 tests/OptimizelyFactoryTest.php 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 @@ +<?php +/** + * Copyright 2020, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Optimizely; + +use Optimizely\Optimizely; + +/** + * Class OptimizelyFactory + * + * @package Optimizely + */ +class OptimizelyFactory +{ + public static function createDefaultInstance($sdkKey, $fallbackDatafile = null) + { + return new Optimizely( + $fallbackDatafile, + null, + null, + null, + null, + null, + null, + null, + $sdkKey + ); + } +} diff --git a/tests/OptimizelyFactoryTest.php b/tests/OptimizelyFactoryTest.php new file mode 100644 index 00000000..e0413030 --- /dev/null +++ b/tests/OptimizelyFactoryTest.php @@ -0,0 +1,60 @@ +<?php +/** + * Copyright 2020, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +namespace Optimizely\Tests; + +use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Psr7\Response; +use Optimizely\OptimizelyFactory; +use Optimizely\ProjectConfigManager\HTTPProjectConfigManager; + +class OptimizelyFactoryTest extends \PHPUnit_Framework_TestCase +{ + public function setUp() + { + $this->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()); + } +}