Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/Optimizely/Optimizely.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -96,7 +97,7 @@ class Optimizely
/**
* @var ProjectConfigManagerInterface
*/
private $_projectConfigManager;
public $configManager;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this have to be public now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HTTPProjectConfigManager exposes a public method fetch, which the user can use to get the latest datafile from cdn. Previously, we were expecting the user to create it's own config manager and pass. Now, we will be creating httpconfigmanager given the sdk key so the user must be able to access it to fetch the latest datafile.


/**
* @var NotificationCenter
Expand All @@ -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,
Expand All @@ -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();
Expand All @@ -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);
}
}
}

/**
Expand All @@ -141,7 +152,7 @@ public function __construct(
*/
protected function getConfig()
{
$config = $this->_projectConfigManager->getConfig();
$config = $this->configManager->getConfig();
return $config instanceof DatafileProjectConfig ? $config : null;
}

Expand Down
42 changes: 42 additions & 0 deletions src/Optimizely/OptimizelyFactory.php
Original file line number Diff line number Diff line change
@@ -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
);
}
}
60 changes: 60 additions & 0 deletions tests/OptimizelyFactoryTest.php
Original file line number Diff line number Diff line change
@@ -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());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should mock it anyway, to prevent it from ever making a real HTTP request. Even if it won't because of hard coded datafile.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the way PHP Unit allows to use doubles, I don't think we can mock the first http call that a config manager makes when it's initialized inside Optly client. I can only mock and set a double, after the Optimizely client has been created and in that case, it has already made a real HTTP request.


// 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());
}
}
99 changes: 93 additions & 6 deletions tests/OptimizelyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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());

Expand Down