...
 
Commits (6)
......@@ -80,7 +80,7 @@ review:start:
image: minds/helm-eks:latest
script:
- aws eks update-kubeconfig --name=sandbox
- git clone --branch=epic/SSR https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/minds/helm-charts.git
- git clone --branch=master https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.com/minds/helm-charts.git
- echo "Upgrading helm for pipeline ${CI_PIPELINE_ID}"
- echo "Setting to image ${CI_REGISTRY_IMAGE}"
- "helm upgrade \
......
File added
......@@ -37,12 +37,28 @@ class Features extends Cli\Controller implements Interfaces\CliControllerInterfa
$manager = Di::_()->get('Features\Manager');
$ttl = $this->getOpt('ttl') ?: 300;
$environmentList = array_filter(explode(',', $this->getOpt('environment') ?: ''));
if (!$environmentList) {
throw new CliException('Specify an environment');
}
while (true /* Forever running task */) {
$this->out([date('c'), "TTL: {$ttl}"], static::OUTPUT_PRE);
foreach ($environmentList as $environment) {
$this->out([
date('c'),
"TTL: {$ttl}",
"Environment: {$environment}"
], static::OUTPUT_PRE);
$sync = $manager
->setEnvironment($environment)
->sync($ttl);
foreach ($manager->sync($ttl) as $key => $output) {
$this->out(sprintf("Sync %s: %s", $key, $output));
foreach ($sync as $key => $output) {
$this->out(sprintf("Sync %s: %s", $key, $output));
}
}
if (!$this->getOpt('forever')) {
......
......@@ -45,9 +45,9 @@ class register implements Interfaces\Api, Interfaces\ApiIgnorePam
}
try {
$captcha = Core\Di\Di::_()->get('Security\ReCaptcha');
$captcha->setAnswer($_POST['captcha']);
if (isset($_POST['captcha']) && !$captcha->validate()) {
$captcha = Core\Di\Di::_()->get('Captcha\Manager');
if (isset($_POST['captcha']) && !$captcha->verifyFromClientJson($_POST['captcha'])) {
throw new \Exception('Captcha failed');
}
......
<?php
/**
* features
*
* @author edgebal
*/
namespace Minds\Controllers\api\v2\admin;
use Exception;
use Minds\Api\Factory;
use Minds\Core\Features\Manager;
use Minds\Core\Session;
use Minds\Entities\User;
use Minds\Interfaces;
use Minds\Core\Di\Di;
class features implements Interfaces\Api, Interfaces\ApiAdminPam
{
/**
* @inheritDoc
*/
public function get($pages)
{
$for = null;
if (isset($_GET['for'])) {
try {
$for = new User(strtolower($_GET['for']));
if (!$for || !$for->guid) {
$for = null;
}
} catch (Exception $e) {
$for = null;
}
}
/** @var Manager $manager */
$manager = Di::_()->get('Features\Manager');
return Factory::response(
$manager->breakdown($for)
);
}
/**
* @inheritDoc
*/
public function post($pages)
{
return Factory::response([]);
}
/**
* @inheritDoc
*/
public function put($pages)
{
return Factory::response([]);
}
/**
* @inheritDoc
*/
public function delete($pages)
{
return Factory::response([]);
}
}
<?php
/**
* API for returning a captcha
*/
namespace Minds\Controllers\api\v2;
use Minds\Api\Factory;
use Minds\Common\Cookie;
use Minds\Core\Di\Di;
use Minds\Core\Config;
use Minds\Core\Session;
use Minds\Interfaces;
class captcha implements Interfaces\Api
{
public function get($pages)
{
$captchaManager = Di::_()->get('Captcha\Manager');
$captcha = $captchaManager->build();
return Factory::response(
$captcha->export()
);
}
public function post($pages)
{
return Factory::response([]);
}
public function put($pages)
{
return Factory::response([]);
}
public function delete($pages)
{
return Factory::response([]);
}
}
<?php
/**
* Captcha Model
*/
namespace Minds\Core\Captcha;
use Minds\Traits\MagicAttributes;
class Captcha
{
use MagicAttributes;
/** @var string */
private $jwtToken;
/** @var string */
private $clientText;
/** @var string */
private $base64Image;
/**
* Export the captcha
* @param array $extras
* @return array
*/
public function export(array $extras = []): array
{
return [
'jwt_token' => $this->jwtToken,
'base64_image' => $this->base64Image,
];
}
}
<?php
namespace Minds\Core\Captcha;
class ImageGenerator
{
/** @var int */
protected $width = 250;
/** @var int */
protected $height = 100;
/** @var string */
protected $text;
/**
* Set the width
* @param int $width
* @return self
*/
public function setWidth(int $width): self
{
$this->width = $width;
return $this;
}
/**
* Set the height
* @param int $height
* @return self
*/
public function setHeight(int $height): self
{
$this->height = $height;
return $this;
}
/**
* Set the text to output
* @param string $text
* @return self
*/
public function setText(string $text): self
{
$this->text = $text;
return $this;
}
/**
* Outputs the captcha image
* @return string
*/
public function build(): string
{
$image = imagecreatetruecolor($this->width, $this->height);
// Slight grey background
$backgroundColor = imagecolorallocate($image, 240, 240, 240);
// Builds the image background
imagefilledrectangle($image, 0, 0, $this->width, $this->height, $backgroundColor);
// Set the line thickness
imagesetthickness($image, 3);
// Dark grey lines
$lineColor = imagecolorallocate($image, 74, 74, 74);
$numberOfLines = rand(4, 10);
for ($i = 0; $i < $numberOfLines; $i++) {
imagesetthickness($image, rand(1, 3));
imageline($image, 0, rand() % $this->height, $this->width, rand() % $this->height, $lineColor);
}
for ($i = 0; $i< $this->width * 4; $i++) {
$pixelColor = imagecolorallocate($image, rand(0, 255), rand(0, 255), rand(0, 255));
imagesetpixel($image, rand() % $this->width, rand() % $this->height, $pixelColor);
}
$font = __MINDS_ROOT__ . '/Assets/fonts/Roboto-Medium.ttf';
$angle = rand(-6, 6);
$size = rand($this->height * 0.25, $this->height * 0.55);
$x = 10;
$y = ($this->height / 2) + ($size / 2);
$color = imagecolorallocate($image, 64, 64, 64);
// Write the text to the image
imagettftext($image, $size, $angle, $x, $y, $color, $font, $this->text);
ob_start();
imagepng($image);
$imagedata = ob_get_clean();
$base64 = base64_encode($imagedata);
imagedestroy($image);
return "data:image/png;base64,$base64";
}
}
<?php
namespace Minds\Core\Captcha;
use Minds\Common\Jwt;
use Minds\Core\Config;
use Minds\Core\Di\Di;
class Manager
{
/** @var ImageGenerator */
private $imageGenerator;
/** @var JWT */
private $jwt;
/** @var string */
private $secret;
public function __construct($imageGenerator = null, $jwt = null, $config = null)
{
$this->imageGenerator = $imageGenerator ?? new ImageGenerator;
$this->jwt = $jwt ?? new Jwt();
$config = $config ?? Di::_()->get('Config');
$this->secret = $config->get('captcha') ? $config->get('captcha')['jwt_secret'] : 'todo';
$this->jwt->setKey($this->secret);
}
/**
* Verify from client json
* @param string $json
* @return bool
*/
public function verifyFromClientJson(string $json): bool
{
$data = json_decode($json, true);
$captcha = new Captcha();
$captcha->setJwtToken($data['jwtToken'])
->setClientText($data['clientText']);
return $this->verify($captcha);
}
/**
* Verify if a captcha is valid
* @param Captcha $captcha
* @return bool
*/
public function verify(Captcha $captcha): bool
{
$jwtToken = $captcha->getJwtToken();
$decodedJwtToken = $this->jwt->decode($jwtToken);
$salt = $decodedJwtToken['salt'];
$hash = $decodedJwtToken['public_hash'];
// This is what the client has said the captcha image has
$clientText = $captcha->getClientText();
// Now convert this back to our hash
$clientHash = $this->buildCaptchaHash($clientText, $salt);
return $clientHash === $hash;
}
/**
* Output the captcha
* @param string $forcedText
* @return void
*/
public function build(string $forcedText = ''): Captcha
{
$text = $forcedText ?: $this->getRandomText(6);
$now = time();
$expires = $now + 300; // Captcha are good for 5 minutes
$salt = $this->jwt->randomString();
$jwtToken = $this->jwt
->setKey($this->secret)
->encode([
'public_hash' => $this->buildCaptchaHash($text, $salt),
'salt' => $salt,
], $expires, $now);
$image = $this->imageGenerator
->setText($text)
->build();
$captcha = new Captcha();
$captcha->setBase64Image($image)
->setJwtToken($jwtToken);
return $captcha;
}
/**
* Get the random text
* @param int $length
* @return sdtring
*/
protected function getRandomText(int $length): string
{
$chars = array_merge(
range(1, 9),
range('A', 'N'),
range('P', 'Z'), // We don't want O's or 0s
range('a', 'n'),
range('p', 'z')
);
shuffle($chars);
$text="";
for ($i = 0; $i < $length; $i++) {
$text .= $chars[array_rand($chars)];
}
return $text;
}
/**
* Return hash based on text and salt with a secret
* @param string $text
* @param string $salt
* @return string
*/
protected function buildCaptchaHash(string $text, string $salt): string
{
return hash('sha1', $text . $this->secret . $salt);
}
}
<?php
/**
* Captcha Module
*/
namespace Minds\Core\Captcha;
use Minds\Interfaces\ModuleInterface;
class Module implements ModuleInterface
{
/**
* OnInit.
*/
public function onInit()
{
$provider = new Provider();
$provider->register();
}
}
<?php
/**
* Minds Captcha Provider.
*/
namespace Minds\Core\Captcha;
use Minds\Core\Di\Provider as DiProvider;
class Provider extends DiProvider
{
public function register()
{
$this->di->bind('Captcha\Manager', function ($di) {
return new Manager();
}, ['useFactory' => true]);
}
}
......@@ -12,6 +12,7 @@ use Exception;
use Minds\Core\Di\Di;
use Minds\Core\Features\Exceptions\FeatureNotImplementedException;
use Minds\Core\Sessions\ActiveSession;
use Minds\Entities\User;
/**
* Features Manager
......@@ -28,17 +29,23 @@ class Manager
/** @var string[] */
protected $featureKeys;
/** @var string */
protected $environment;
/**
* Manager constructor.
* @param string $environment
* @param Services\ServiceInterface[] $services
* @param ActiveSession $activeSession
* @param string[] $features
*/
public function __construct(
$environment = null,
$services = null,
$activeSession = null,
array $features = null
) {
$this->environment = $environment;
$this->services = $services ?: [
new Services\Config(),
new Services\Unleash(),
......@@ -48,6 +55,26 @@ class Manager
$this->featureKeys = ($features ?? Di::_()->get('Features\Keys')) ?: [];
}
/**
* Sets the current environment
* @param string $environment
* @return Manager
*/
public function setEnvironment(string $environment): Manager
{
$this->environment = $environment;
return $this;
}
/**
* Gets the current environment based on overrides or environment variables
* @return string
*/
public function getEnvironment()
{
return $this->environment ?: getenv('MINDS_ENV') ?: 'development';
}
/**
* Synchronizes all services using their respective mechanisms
* @param int $ttl
......@@ -57,7 +84,9 @@ class Manager
{
foreach ($this->services as $service) {
try {
$output = $service->sync($ttl) ? 'OK' : 'NOT SYNC\'D';
$output = $service
->setEnvironment($this->getEnvironment())
->sync($ttl);
} catch (Exception $e) {
$output = $e;
}
......@@ -105,6 +134,7 @@ class Manager
$features = array_merge(
$features,
$service
->setEnvironment($this->getEnvironment())
->setUser($this->activeSession->getUser())
->fetch($this->featureKeys)
);
......@@ -114,4 +144,64 @@ class Manager
return $features;
}
/**
* Breakdown for services, features and its individual values for certain user.
* Used by admin interface.
* @param User|null $for
* @return array
*/
public function breakdown(?User $for = null)
{
$env = $this->getEnvironment();
$output = [
'environment' => $env,
'for' => $for ? (string) $for->username : null,
'services' => [
'Default'
],
'features' => [],
];
$cache = [];
foreach ($this->featureKeys as $feature) {
$cache[$feature] = [
'Default' => false,
];
foreach ($this->services as $service) {
$cache[$feature][$service->getReadableName()] = null;
}
}
foreach ($this->services as $service) {
$output['services'][] = $service->getReadableName();
$features = [];
$features = array_merge(
$features,
$service
->setUser($for)
->setEnvironment($env)
->fetch($this->featureKeys)
);
foreach ($features as $feature => $value) {
$cache[$feature][$service->getReadableName()] = $value;
}
}
foreach ($cache as $name => $services) {
$output['features'][] = compact('name', 'services');
}
usort($output['features'], function ($a, $b) {
return $a['name'] <=> $b['name'];
});
return $output;
}
}
......@@ -49,10 +49,10 @@ class Provider extends DiProvider
$this->di->bind('Features\Manager', function ($di) {
return new Manager();
}, [ 'useFactory'=> true ]);
}, [ 'useFactory' => true ]);
$this->di->bind('Features\Canary', function ($di) {
return new Canary();
}, [ 'useFactory'=> true ]);
}, [ 'useFactory' => true ]);
}
}
......@@ -15,9 +15,23 @@ use Minds\Entities\User;
*/
abstract class BaseService implements ServiceInterface
{
/** @var string */
protected $environment;
/** @var User */
protected $user;
/**
* @inheritDoc
* @param string $environment
* @return ServiceInterface
*/
public function setEnvironment(string $environment): ServiceInterface
{
$this->environment = $environment;
return $this;
}
/**
* @inheritDoc
* @param User|null $user
......
......@@ -30,6 +30,14 @@ class Config extends BaseService
$this->config = $config ?: Di::_()->get('Config');
}
/**
* @inheritDoc
*/
public function getReadableName(): string
{
return '$CONFIG';
}
/**
* @inheritDoc
*/
......
......@@ -16,6 +16,14 @@ class Environment extends BaseService
/** @var array|null */
protected $global = null;
/**
* @inheritDoc
*/
public function getReadableName(): string
{
return 'EnvVars';
}
/**
* @param array $global
* @return Environment
......
......@@ -11,6 +11,19 @@ use Minds\Entities\User;
interface ServiceInterface
{
/**
* Readable name. Used for admin interface.
* @return string
*/
public function getReadableName(): string;
/**
* Sets the current interface to sync/fetch
* @param string $environment
* @return ServiceInterface
*/
public function setEnvironment(string $environment): ServiceInterface;
/**
* Sets the current user to calculate context values
* @param User|null $user
......
......@@ -20,7 +20,6 @@ use Minds\UnleashClient\Exceptions\NoContextException;
use Minds\UnleashClient\Factories\FeatureArrayFactory as UnleashFeatureArrayFactory;
use Minds\UnleashClient\Entities\Context;
use Minds\UnleashClient\Unleash as UnleashResolver;
use Minds\UnleashClient\Http\Client as UnleashClient;
/**
* Unleash server (GitLab FF) feature flags service
......@@ -40,8 +39,8 @@ class Unleash extends BaseService
/** @var UnleashFeatureArrayFactory */
protected $unleashFeatureArrayFactory;
/** @var UnleashClient */
protected $unleashClient;
/** @var Unleash\ClientFactory */
protected $unleashClientFactory;
/**
* Unleash constructor.
......@@ -49,20 +48,28 @@ class Unleash extends BaseService
* @param Repository $repository
* @param UnleashResolver $unleashResolver
* @param UnleashFeatureArrayFactory $unleashFeatureArrayFactory
* @param UnleashClient $unleashClient
* @param Unleash\ClientFactory $unleashClientFactory
*/
public function __construct(
$config = null,
$repository = null,
$unleashResolver = null,
$unleashFeatureArrayFactory = null,
$unleashClient = null
$unleashClientFactory = null
) {
$this->config = $config ?: Di::_()->get('Config');
$this->repository = $repository ?: new Repository();
$this->unleashResolver = $unleashResolver ?: new UnleashResolver(Di::_()->get('Logger\Singleton'));
$this->unleashFeatureArrayFactory = $unleashFeatureArrayFactory ?: new UnleashFeatureArrayFactory();
$this->unleashClient = $unleashClient ?: (new Unleash\ClientFactory($this->config, Di::_()->get('Logger\Singleton')))->build();
$this->unleashClientFactory = $unleashClientFactory ?: new Unleash\ClientFactory($this->config, Di::_()->get('Logger\Singleton'));
}
/**
* @inheritDoc
*/
public function getReadableName(): string
{
return 'GitLab';
}
/**
......@@ -71,19 +78,23 @@ class Unleash extends BaseService
*/
public function sync(int $ttl): bool
{
$registered = $this->unleashClient->register();
$client = $this->unleashClientFactory
->build($this->environment);
$registered = $client->register();
if (!$registered) {
throw new Exception('Could not register Unleash client');
}
$now = time();
$features = $this->unleashClient->fetch();
$features = $client->fetch();
foreach ($features as $feature) {
$entity = new Entity();
$entity
->setId($feature['name'])
->setEnvironment($this->environment)
->setFeatureName($feature['name'])
->setData($feature)
->setCreatedAt($now)
->setStaleAt($now + $ttl);
......@@ -104,6 +115,7 @@ class Unleash extends BaseService
* @throws InvalidFeaturesArrayException
* @throws InvalidStrategyImplementationException
* @throws NoContextException
* @throws Exception
*/
public function fetch(array $keys): array
{
......@@ -123,7 +135,9 @@ class Unleash extends BaseService
$features = $this->unleashFeatureArrayFactory
->build(
$this->repository
->getAllData()
->getAllData([
'environment' => $this->environment,
])
->toArray()
);
......
......@@ -36,16 +36,21 @@ class ClientFactory
/**
* Builds an Unleash Client using environment configuration
* @param string $environment
* @return Client
*/
public function build(): Client
public function build(?string $environment): Client
{
$environment = $environment ?: ($configValues['applicationName'] ?? 'development');
$this->logger->info(sprintf("Building Unleash Client for %s", $environment));
$configValues = $this->config->get('unleash');
$config = new Config(
getenv('UNLEASH_API_URL') ?: ($configValues['apiUrl'] ?? null),
getenv('UNLEASH_INSTANCE_ID') ?: ($configValues['instanceId'] ?? null),
getenv('MINDS_ENV') ?: ($configValues['applicationName'] ?? 'development'),
$environment,
$configValues['pollingIntervalSeconds'] ?? null,
$configValues['metricsIntervalSeconds'] ?? null
);
......
......@@ -12,8 +12,10 @@ use Minds\Traits\MagicAttributes;
/**
* Entity for cached feature flags
* @package Minds\Core\Features\Services\Unleash
* @method string getId()
* @method Entity setId(string $id)
* @method string getEnvironment()
* @method Entity setEnvironment(string $environment)
* @method string getFeatureName()
* @method Entity setFeatureName(string $featureName)
* @method array getData()
* @method Entity setData(array $data)
* @method int getCreatedAt()
......@@ -26,7 +28,10 @@ class Entity
use MagicAttributes;
/** @var string */
protected $id;
protected $environment;
/** @var string */
protected $featureName;
/** @var array */
protected $data;
......
......@@ -33,14 +33,27 @@ class Repository
/**
* Returns a list of all feature toggles cached in Cassandra
* @param array $opts
* @return Response
* @throws Exception
*/
public function getList(): Response
public function getList(array $opts = []): Response
{
$cql = "SELECT * FROM feature_toggles_cache";
$opts = array_merge([
'environment' => null,
], $opts);
if (!$opts['environment']) {
throw new Exception('Specify an environment');
}
$cql = "SELECT * FROM feature_toggles_cache_ns WHERE environment = ?";
$values = [
(string) $opts['environment']
];
$prepared = new Custom();
$prepared->query($cql);
$prepared->query($cql, $values);
$response = new Response();
......@@ -50,7 +63,8 @@ class Repository
foreach ($rows ?: [] as $row) {
$entity = new Entity();
$entity
->setId($row['id'])
->setEnvironment($row['environment'])
->setFeatureName($row['feature_name'])
->setData(json_decode($row['data'], true))
->setCreatedAt($row['created_at']->time())
->setStaleAt($row['stale_at']->time());
......@@ -65,13 +79,17 @@ class Repository
/**
* Shortcut method that casts all the data from getList() entities
* @param array $opts getList() opts
* @return Response
* @throws Exception
*/
public function getAllData(): Response
public function getAllData(array $opts = []): Response
{
return $this->getList()->map(function (Entity $entity) {
return $entity->getData();
});
return $this
->getList($opts)
->map(function (Entity $entity) {
return $entity->getData();
});
}
/**
......@@ -82,13 +100,18 @@ class Repository
*/
public function add(Entity $entity): bool
{
if (!$entity->getId()) {
throw new Exception('Invalid Unleash entity name');
if (!$entity->getEnvironment()) {
throw new Exception('Invalid Unleash entity namespace');
}
if (!$entity->getFeatureName()) {
throw new Exception('Invalid Unleash entity feature name');
}
$cql = "INSERT INTO feature_toggles_cache (id, data, created_at, stale_at) VALUES (?, ?, ?, ?)";
$cql = "INSERT INTO feature_toggles_cache_ns (environment, feature_name, data, created_at, stale_at) VALUES (?, ?, ?, ?, ?)";
$values = [
(string) $entity->getId(),
(string) $entity->getEnvironment(),
(string) $entity->getFeatureName(),
(string) json_encode($entity->getData()),
new Timestamp($entity->getCreatedAt()),
new Timestamp($entity->getStaleAt())
......
......@@ -33,6 +33,7 @@ class Minds extends base
VideoChat\Module::class,
Feeds\Module::class,
Front\Module::class,
Captcha\Module::class,
];
/**
......
......@@ -1569,9 +1569,11 @@ CREATE TABLE minds.video_transcodes (
ALTER TABLE minds.video_transcodes ADD failure_reason text;
CREATE TABLE minds.feature_toggles_cache (
id text PRIMARY KEY,
created_at timestamp,
CREATE TABLE minds.feature_toggles_cache_ns (
environment text,
feature_name text,
data text,
stale_at timestamp
created_at timestamp,
stale_at timestamp,
PRIMARY KEY (environment, feature_name)
);
<?php
namespace Spec\Minds\Core\Captcha;
use Minds\Core\Captcha\ImageGenerator;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ImageGeneratorSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(ImageGenerator::class);
}
}
<?php
namespace Spec\Minds\Core\Captcha;
use Minds\Core\Captcha\Manager;
use Minds\Core\Captcha\Captcha as CaptchaModel;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
class ManagerSpec extends ObjectBehavior
{
public function it_is_initializable()
{
$this->shouldHaveType(Manager::class);
}
public function it_should_build_a_captcha()
{
$captcha = $this->build();
$jwtToken = $captcha->getJwtToken();
$jwtToken->shouldBeString();
}
public function it_should_verify_a_captcha()
{
$captcha = $this->build('abfu21')->getWrappedObject();
$captcha->setClientText('abfu21');
$this->verify($captcha)
->shouldBe(true);
}
public function it_should_verify_a_captcha_with_fail()
{
$captcha = $this->build();
$captcha->setClientText('abfu21');
$this->verify($captcha)
->shouldBe(false);
}
}
......@@ -31,6 +31,7 @@ class ManagerSpec extends ObjectBehavior
$this->activeSession = $activeSession;
$this->beConstructedWith(
'phpspec',
[ $service1, $service2 ],
$activeSession,
['feature1', 'feature2', 'feature3']
......@@ -44,10 +45,18 @@ class ManagerSpec extends ObjectBehavior
public function it_should_sync()
{
$this->service1->setEnvironment('phpspec')
->shouldBeCalled()
->willReturn($this->service1);
$this->service1->sync(30)
->shouldBeCalled()
->willReturn(true);
$this->service2->setEnvironment('phpspec')
->shouldBeCalled()
->willReturn($this->service2);
$this->service2->sync(30)
->shouldBeCalled()
->willReturn(false);
......@@ -55,8 +64,8 @@ class ManagerSpec extends ObjectBehavior
$this
->sync(30)
->shouldBeAnIterator([
get_class($this->service1->getWrappedObject()) => 'OK',
get_class($this->service2->getWrappedObject()) => 'NOT SYNC\'D',
get_class($this->service1->getWrappedObject()) => true,
get_class($this->service2->getWrappedObject()) => false,
]);
}
......@@ -67,6 +76,10 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn($user);
$this->service1->setEnvironment('phpspec')
->shouldBeCalled()
->willReturn($this->service1);
$this->service1->setUser($user)
->shouldBeCalled()
->willReturn($this->service1);
......@@ -78,6 +91,10 @@ class ManagerSpec extends ObjectBehavior
'feature2' => false,
]);
$this->service2->setEnvironment('phpspec')
->shouldBeCalled()
->willReturn($this->service2);
$this->service2->setUser($user)
->shouldBeCalled()
->willReturn($this->service2);
......@@ -100,6 +117,10 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn($user);
$this->service1->setEnvironment('phpspec')
->shouldBeCalled()
->willReturn($this->service1);
$this->service1->setUser($user)
->shouldBeCalled()
->willReturn($this->service1);
......@@ -111,6 +132,10 @@ class ManagerSpec extends ObjectBehavior
'feature2' => false,
]);
$this->service2->setEnvironment('phpspec')
->shouldBeCalled()
->willReturn($this->service2);
$this->service2->setUser($user)
->shouldBeCalled()
->willReturn($this->service2);
......@@ -134,6 +159,10 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn($user);
$this->service1->setEnvironment('phpspec')
->shouldBeCalled()
->willReturn($this->service1);
$this->service1->setUser($user)
->shouldBeCalled()
->willReturn($this->service1);
......@@ -145,6 +174,10 @@ class ManagerSpec extends ObjectBehavior
'feature2' => false,
]);
$this->service2->setEnvironment('phpspec')
->shouldBeCalled()
->willReturn($this->service2);
$this->service2->setUser($user)
->shouldBeCalled()
->willReturn($this->service2);
......@@ -168,6 +201,10 @@ class ManagerSpec extends ObjectBehavior
->shouldBeCalled()
->willReturn($user);
$this->service1->setEnvironment('phpspec')
->shouldBeCalled()
->willReturn($this->service1);
$this->service1->setUser($user)
->shouldBeCalled()
->willReturn($this->service1);
......@@ -179,6 +216,10 @@ class ManagerSpec extends ObjectBehavior
'feature2' => false,
]);
$this->service2->setEnvironment('phpspec')
->shouldBeCalled()
->willReturn($this->service2);
$this->service2->setUser($user)
->shouldBeCalled()
->willReturn($this->service2);
......
......@@ -28,28 +28,28 @@ class UnleashSpec extends ObjectBehavior
/** @var UnleashFeatureArrayFactory */
protected $unleashFeatureArrayFactory;
/** @var UnleashClient */
protected $unleashClient;
/** @var Unleash\ClientFactory */
protected $unleashClientFactory;
public function let(
Config $config,
Repository $repository,
UnleashResolver $unleashResolver,
UnleashFeatureArrayFactory $unleashFeatureArrayFactory,
UnleashClient $unleashClient
Unleash\ClientFactory $unleashClientFactory
) {
$this->config = $config;
$this->repository = $repository;
$this->unleashResolver = $unleashResolver;
$this->unleashFeatureArrayFactory = $unleashFeatureArrayFactory;
$this->unleashClient = $unleashClient;
$this->unleashClientFactory = $unleashClientFactory;
$this->beConstructedWith(
$config,
$repository,
$unleashResolver,
$unleashFeatureArrayFactory,
$unleashClient
$unleashClientFactory
);
}
......@@ -58,13 +58,18 @@ class UnleashSpec extends ObjectBehavior
$this->shouldHaveType(Unleash::class);
}
public function it_should_sync()
{
$this->unleashClient->register()
public function it_should_sync(
UnleashClient $client
) {
$this->unleashClientFactory->build('phpspec')
->shouldBeCalled()
->willReturn($client);
$client->register()
->shouldBeCalled()
->willReturn(true);
$this->unleashClient->fetch()
$client->fetch()
->shouldBeCalled()
->willReturn([
['name' => 'feature1'],
......@@ -76,6 +81,7 @@ class UnleashSpec extends ObjectBehavior
->willReturn(true);
$this
->setEnvironment('phpspec')
->sync(30)
->shouldReturn(true);
}
......@@ -86,7 +92,9 @@ class UnleashSpec extends ObjectBehavior
$featuresMock = ['featuresMock' . mt_rand()];
$resolvedFeaturesMock = ['resolvedFeaturesMock' . mt_rand()];
$this->repository->getAllData()
$this->repository->getAllData([
'environment' => 'phpspec'
])
->shouldBeCalled()
->willReturn($response);
......@@ -124,6 +132,7 @@ class UnleashSpec extends ObjectBehavior
]);
$this
->setEnvironment('phpspec')
->fetch([
'feature1',
'feature2',
......@@ -149,7 +158,9 @@ class UnleashSpec extends ObjectBehavior
$featuresMock = ['featuresMock' . mt_rand()];
$resolvedFeaturesMock = ['resolvedFeaturesMock' . mt_rand()];
$this->repository->getAllData()
$this->repository->getAllData([
'environment' => 'phpspec'
])
->shouldBeCalled()
->willReturn($response);
......@@ -207,6 +218,7 @@ class UnleashSpec extends ObjectBehavior
]);
$this
->setEnvironment('phpspec')
->setUser($user)
->fetch([
'feature1',
......
......@@ -626,3 +626,8 @@ $CONFIG->set('unleash', [
'pollingIntervalSeconds' => 300,
'metricsIntervalSeconds' => 15
]);
$CONFIG->set('captcha', [
'jwt_secret' => '{{site-secret}}',
]);