├── .docheader ├── .env ├── .env.test ├── .github ├── dependabot.yml ├── stale.yml └── workflows │ └── ci.yml ├── .gitignore ├── .mailmap ├── .php-cs-fixer.php ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── composer.json ├── docker-compose.yml ├── phpstan.neon ├── phpunit.xml.dist ├── public └── index.php ├── src └── Kernel.php └── test ├── TogglesEndpointTest.php └── bootstrap.php /.docheader: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the qandidate/toggle-api package. 3 | * 4 | * (c) Qandidate.com 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the latter taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # 13 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 14 | # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration 15 | 16 | APP_ENV=dev 17 | APP_SECRET="I'm on a boat" 18 | APP_DEBUG=true 19 | TOGGLE__ALLOWED_ORIGINS='["127.0.0.1"]' 20 | TOGGLE__REDIS_DSN="tcp://127.0.0.1:6379" 21 | TOGGLE__PREFIX="feature_toggle" 22 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | APP_ENV=test 3 | APP_DEBUG=false 4 | KERNEL_CLASS='Qandidate\Application\Toggle\Kernel' 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: composer 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | - package-ecosystem: github-actions 9 | directory: "/" 10 | schedule: 11 | interval: weekly 12 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # see https://probot.github.io/apps/stale/ 2 | 3 | # inherit settings from https://github.com/qandidate-labs/.github/blob/main/.github/stale.yml 4 | _extends: .github 5 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: "CI" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "master" 7 | push: 8 | branches: 9 | - "master" 10 | schedule: 11 | - cron: "37 13 * * 1" 12 | 13 | jobs: 14 | tests: 15 | name: "Run tests" 16 | runs-on: "ubuntu-20.04" 17 | strategy: 18 | matrix: 19 | php-version: 20 | - "7.4" 21 | - "8.0" 22 | - "8.1" 23 | - "8.2" 24 | services: 25 | redis: 26 | image: "redis:5" 27 | ports: 28 | - "6379:6379" 29 | steps: 30 | - name: "Checkout" 31 | uses: "actions/checkout@v3" 32 | - name: "Install PHP" 33 | uses: "shivammathur/setup-php@v2" 34 | with: 35 | php-version: "${{ matrix.php-version }}" 36 | coverage: "none" 37 | env: 38 | fail-fast: true 39 | - name: "Validate composer.json and composer.lock" 40 | run: "composer validate --strict --no-interaction --ansi" 41 | - name: "Install dependencies with Composer" 42 | uses: "ramsey/composer-install@v2" 43 | - name: "Run tests" 44 | run: "make test" 45 | 46 | coding-standards: 47 | name: "Coding standards" 48 | runs-on: "ubuntu-20.04" 49 | steps: 50 | - name: "Checkout" 51 | uses: "actions/checkout@v3" 52 | - name: "Install PHP" 53 | uses: "shivammathur/setup-php@v2" 54 | with: 55 | php-version: "8.1" 56 | coverage: "none" 57 | - name: "Install dependencies with Composer" 58 | uses: "ramsey/composer-install@v2" 59 | - name: "Check coding standards" 60 | run: "make php-cs-fixer-ci" 61 | - name: Create Pull Request 62 | if: github.ref == 'refs/heads/master' 63 | uses: peter-evans/create-pull-request@v5 64 | with: 65 | commit-message: Apply coding standards 66 | branch: php-cs-fixer 67 | delete-branch: true 68 | title: Apply coding standards 69 | draft: false 70 | base: master 71 | 72 | static-analysis: 73 | name: "Static analysis" 74 | runs-on: "ubuntu-20.04" 75 | steps: 76 | - name: "Checkout" 77 | uses: "actions/checkout@v3" 78 | - name: "Install PHP" 79 | uses: "shivammathur/setup-php@v2" 80 | with: 81 | php-version: "8.1" 82 | coverage: "none" 83 | - name: "Install dependencies with Composer" 84 | uses: "ramsey/composer-install@v2" 85 | - name: "Run PHPStan" 86 | run: "make phpstan" 87 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | phpunit.xml 2 | composer.lock 3 | composer.phar 4 | /vendor/ 5 | /config.json 6 | .php-cs-fixer.cache 7 | .phpunit.result.cache 8 | /var/ 9 | /.env.local 10 | /.env.local.php 11 | /.env.*.local 12 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | othillo 2 | -------------------------------------------------------------------------------- /.php-cs-fixer.php: -------------------------------------------------------------------------------- 1 | setFinder( 6 | \PhpCsFixer\Finder::create() 7 | ->in([ 8 | __DIR__ . '/src', 9 | __DIR__ . '/test', 10 | __DIR__ . '/public', 11 | ]) 12 | ); 13 | 14 | return $config; 15 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog for Qandidate toggle API 2 | 3 | ## 0.3.x 4 | 5 | - Replaced `igorw/config-service-provider` with `vlucas/phpdotenv`. With this method there is [more flexibility to deploy] the API 6 | `.env.dist` gives you the same configuration as `config.json.dist` gave you before. 7 | But you will need to recreate your config if you adjusted the configuration. 8 | Note that `TOGGLE__ALLOWED_ORIGINS` should be a JSON string and the name for the redis connection changed 9 | from `uri` to `dsn` (`TOGGLE__REDIS_DSN`). 10 | 11 | [more flexibility to deploy]: http://12factor.net/config 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Qandidate.com - http://qandidate.com/ 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL:=help 2 | 3 | .PHONY: dependencies 4 | dependencies: 5 | composer install --no-interaction --no-suggest --no-scripts --ansi 6 | 7 | .PHONY: test 8 | test: 9 | vendor/bin/phpunit --testdox --exclude-group=none --colors=always 10 | 11 | .PHONY: qa 12 | qa: php-cs-fixer phpstan 13 | 14 | .PHONY: php-cs-fixer 15 | php-cs-fixer: 16 | vendor/bin/php-cs-fixer fix --no-interaction --allow-risky=yes --diff --verbose 17 | 18 | .PHONY: php-cs-fixer-ci 19 | php-cs-fixer-ci: 20 | vendor/bin/php-cs-fixer fix --no-interaction --allow-risky=yes --diff --verbose 21 | 22 | .PHONY: phpstan 23 | phpstan: 24 | vendor/bin/phpstan analyse --level=max src/ public/ 25 | 26 | .PHONY: changelog 27 | changelog: 28 | git log $$(git describe --abbrev=0 --tags)...HEAD --no-merges --pretty=format:"* [%h](http://github.com/${TRAVIS_REPO_SLUG}/commit/%H) %s (%cN)" 29 | 30 | .PHONY: license 31 | license: 32 | vendor/bin/docheader check --no-interaction --ansi -vvv {app,test,web} 33 | 34 | # Based on https://suva.sh/posts/well-documented-makefiles/ 35 | help: ## Display this help 36 | @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n\nTargets:\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 }' $(MAKEFILE_LIST) 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Toggle API 2 | ========== 3 | 4 | An API for managing your toggles, uses Redis to store the toggle collection. 5 | 6 | ![build status](https://github.com/qandidate-labs/qandidate-toggle-api/actions/workflows/ci.yml/badge.svg) 7 | 8 | ## About 9 | 10 | Read our blog post series about this repository at: 11 | - http://labs.qandidate.com/blog/2014/08/18/a-new-feature-toggling-library-for-php/ 12 | - http://labs.qandidate.com/blog/2014/08/19/open-sourcing-our-feature-toggle-api-and-ui/ 13 | 14 | ## Installation 15 | 16 | Install the dependencies with composer: 17 | 18 | ``` 19 | make dependencies 20 | ``` 21 | 22 | Configuration is determined based on environment variables. See the `.env.*` files. 23 | You can override the values in the file with environment values. 24 | The default configuration is mainly for local development. 25 | 26 | The environment variable `TOGGLE__ALLOWED_ORIGINS` should be valid JSON. This is to allow arrays. 27 | 28 | ## Running the tests 29 | 30 | We use PHPUnit, so to run the tests simply run: 31 | 32 | ``` 33 | docker-compose up -d 34 | make test 35 | ``` 36 | 37 | ## Running the app 38 | 39 | With your favorite webserver (or with `php -S 127.0.0.1:1337 -t public` for local testing) point your document root to the `public` folder. 40 | 41 | ## Endpoints 42 | 43 | #### Retrieve the toggles 44 | 45 | `GET /toggles` 46 | 47 | #### Create or update a toggle 48 | 49 | `PUT /toggles/{name}` 50 | 51 | Example request: 52 | 53 | ``` 54 | { 55 | "conditions" : [ 56 | { 57 | "name" : "operator-condition", 58 | "operator" : { 59 | "name" : "less-than", 60 | "value" : "1337" 61 | }, 62 | "key" : "user_id" 63 | } 64 | ], 65 | "name" : "foo", 66 | "status" : "conditionally-active", 67 | "originalName" : "foo" 68 | } 69 | ``` 70 | 71 | NOTE: PUT doesn't remove the previous toggle if you rename it. So if you want to rename _foo_ to _bar_, you would have to `PUT` _bar_ and `DELETE` _foo_. 72 | 73 | #### Delete a toggle 74 | 75 | `DELETE /toggles/{name}` 76 | 77 | ## License 78 | 79 | MIT, see LICENSE. 80 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "qandidate/toggle-api", 3 | "description": "Api interface for your toggles.", 4 | "keywords": [ 5 | "feature", 6 | "flipping", 7 | "flip", 8 | "switch", 9 | "bits", 10 | "trigger", 11 | "toggle", 12 | "api" 13 | ], 14 | "license": "MIT", 15 | "authors": [ 16 | { 17 | "name": "Alexander", 18 | "email": "iam.asm89@gmail.com" 19 | }, 20 | { 21 | "name": "othillo", 22 | "email": "othillo@othillo.nl" 23 | }, 24 | { 25 | "name": "Willem-Jan", 26 | "email": "wjzijderveld@gmail.com" 27 | }, 28 | { 29 | "name": "Qandidate.com", 30 | "homepage": "http://labs.qandidate.com/" 31 | } 32 | ], 33 | "require": { 34 | "php": ">=7.2", 35 | "ext-json": "*", 36 | "qandidate/toggle": "^2.0", 37 | "asm89/stack-cors": "^2.0", 38 | "symfony/http-foundation": "^5.2", 39 | "symfony/http-kernel": "^5.2", 40 | "symfony/framework-bundle": "^5.2", 41 | "qandidate/toggle-bundle": "^1.4.1", 42 | "symfony/security-bundle": "^5.2", 43 | "predis/predis": "^1.1||^2.0", 44 | "symfony/dotenv": "^5.2", 45 | "symfony/dependency-injection": "^5.2" 46 | }, 47 | "require-dev": { 48 | "symfony/browser-kit": "^5.2", 49 | "phpunit/phpunit": "^8.0", 50 | "broadway/coding-standard": "^1.2", 51 | "phpstan/phpstan": "^1.0" 52 | }, 53 | "autoload": { 54 | "psr-4": { 55 | "Qandidate\\Application\\Toggle\\": "src/" 56 | } 57 | }, 58 | "autoload-dev": { 59 | "psr-4": { 60 | "Qandidate\\Application\\Toggle\\": "test/" 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | services: 3 | redis: 4 | image: redis:5 5 | ports: 6 | - "6379:6379" 7 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | checkMissingIterableValueType: false 3 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 15 | ./test/ 16 | 17 | 18 | 19 | 20 | 21 | ./src/ 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | require_once __DIR__.'/../vendor/autoload.php'; 15 | 16 | use Asm89\Stack\Cors; 17 | use Qandidate\Application\Toggle\Kernel; 18 | use Symfony\Component\Dotenv\Dotenv; 19 | use Symfony\Component\HttpFoundation\Request; 20 | 21 | (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); 22 | 23 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 24 | 25 | $stackedApp = new Cors($kernel, [ 26 | 'allowedOrigins' => $_SERVER['TOGGLE__ALLOWED_ORIGINS'] ? json_decode((string) $_SERVER['TOGGLE__ALLOWED_ORIGINS'], true) : [], 27 | 'allowedMethods' => ['DELETE', 'GET', 'PUT', 'POST'], 28 | 'allowedHeaders' => ['accept', 'content-type', 'origin', 'x-requested-with'], 29 | ]); 30 | 31 | $request = Request::createFromGlobals(); 32 | $response = $stackedApp->handle($request); 33 | 34 | $response->send(); 35 | $kernel->terminate($request, $response); 36 | -------------------------------------------------------------------------------- /src/Kernel.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Qandidate\Application\Toggle; 15 | 16 | use Predis\Client; 17 | use Qandidate\Bundle\ToggleBundle\QandidateToggleBundle; 18 | use Qandidate\Toggle\Serializer\OperatorConditionSerializer; 19 | use Qandidate\Toggle\Serializer\OperatorSerializer; 20 | use Qandidate\Toggle\Serializer\ToggleSerializer; 21 | use Qandidate\Toggle\Toggle; 22 | use Qandidate\Toggle\ToggleManager; 23 | use Symfony\Bundle\FrameworkBundle\FrameworkBundle; 24 | use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait; 25 | use Symfony\Bundle\SecurityBundle\SecurityBundle; 26 | use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator; 27 | use Symfony\Component\HttpFoundation\JsonResponse; 28 | use Symfony\Component\HttpFoundation\Request; 29 | use Symfony\Component\HttpFoundation\Response; 30 | use Symfony\Component\HttpKernel\Kernel as BaseKernel; 31 | use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; 32 | 33 | class Kernel extends BaseKernel 34 | { 35 | use MicroKernelTrait; 36 | 37 | public function registerBundles(): array 38 | { 39 | return [ 40 | new FrameworkBundle(), 41 | new SecurityBundle(), 42 | new QandidateToggleBundle(), 43 | ]; 44 | } 45 | 46 | protected function configureContainer(ContainerConfigurator $c): void 47 | { 48 | $c->extension('framework', [ 49 | 'secret' => '%env(string:APP_SECRET)%', 50 | 'test' => 'test' === $this->environment, 51 | ]); 52 | $c->extension('security', [ 53 | 'enable_authenticator_manager' => true, 54 | 'firewalls' => [ 55 | 'main' => [ 56 | 'lazy' => true, 57 | ], 58 | ], 59 | ]); 60 | $c->extension('qandidate_toggle', [ 61 | 'persistence' => 'redis', 62 | 'redis_namespace' => '%env(string:TOGGLE__PREFIX)%', 63 | 'redis_client' => 'my_redis_client', 64 | ]); 65 | 66 | $c->services()->set('my_redis_client', Client::class) 67 | ->args(['%env(string:TOGGLE__REDIS_DSN)%']) 68 | ->public(); 69 | } 70 | 71 | protected function configureRoutes(RoutingConfigurator $routes): void 72 | { 73 | $routes->add('get_toggles', '/toggles') 74 | ->controller([$this, 'getToggles']) 75 | ->methods(['GET']) 76 | ; 77 | 78 | $routes->add('delete_toggle', '/toggles/{name}') 79 | ->controller([$this, 'deleteToggle']) 80 | ->methods(['DELETE']) 81 | ; 82 | 83 | $routes->add('update_toggle', '/toggles/{name}') 84 | ->controller([$this, 'updateToggle']) 85 | ->methods(['PUT']) 86 | ; 87 | } 88 | 89 | public function getToggles(): JsonResponse 90 | { 91 | /** @var ToggleManager $toggleManager */ 92 | $toggleManager = $this->container->get('qandidate.toggle.manager'); 93 | 94 | $toggleSerializer = new ToggleSerializer(new OperatorConditionSerializer(new OperatorSerializer())); 95 | 96 | $serializedToggles = []; 97 | 98 | /** @var Toggle $toggle */ 99 | foreach ($toggleManager->all() as $toggle) { 100 | $serializedToggles[] = $toggleSerializer->serialize($toggle); 101 | } 102 | 103 | return new JsonResponse($serializedToggles); 104 | } 105 | 106 | public function deleteToggle(string $name): Response 107 | { 108 | /** @var ToggleManager $toggleManager */ 109 | $toggleManager = $this->container->get('qandidate.toggle.manager'); 110 | 111 | $toggleManager->remove($name); 112 | 113 | return new Response('', Response::HTTP_NO_CONTENT); 114 | } 115 | 116 | public function updateToggle(string $name, Request $request): Response 117 | { 118 | /** @var ToggleManager $toggleManager */ 119 | $toggleManager = $this->container->get('qandidate.toggle.manager'); 120 | 121 | $toggleSerializer = new ToggleSerializer(new OperatorConditionSerializer(new OperatorSerializer())); 122 | $toggle = $toggleSerializer->deserialize((array) json_decode((string) $request->getContent(), true)); 123 | 124 | if ($name !== $toggle->getName()) { 125 | return new Response('Name of toggle can not be changed.', Response::HTTP_BAD_REQUEST); 126 | } 127 | 128 | $toggleManager->update($toggle); 129 | 130 | return new Response('', Response::HTTP_NO_CONTENT); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /test/TogglesEndpointTest.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Qandidate\Application\Toggle; 15 | 16 | use Predis\Client; 17 | use Qandidate\Toggle\Operator\LessThan; 18 | use Qandidate\Toggle\OperatorCondition; 19 | use Qandidate\Toggle\Toggle; 20 | use Qandidate\Toggle\ToggleManager; 21 | use Symfony\Bundle\FrameworkBundle\KernelBrowser; 22 | use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; 23 | 24 | class TogglesEndpointTest extends WebTestCase 25 | { 26 | /** @var KernelBrowser */ 27 | private $client; 28 | 29 | protected function setUp(): void 30 | { 31 | parent::setUp(); 32 | 33 | $this->client = static::createClient(); 34 | 35 | $this->loadToggleFixtures($this->client->getContainer()->get('qandidate.toggle.manager')); 36 | } 37 | 38 | protected function tearDown(): void 39 | { 40 | /** @var Client $redisClient */ 41 | $redisClient = $this->client->getContainer()->get('my_redis_client'); 42 | 43 | $namespace = $this->client->getContainer()->getParameter('qandidate.toggle.redis.namespace'); 44 | $keys = $redisClient->keys($namespace.'*'); 45 | 46 | foreach ($keys as $key) { 47 | $redisClient->del($key); 48 | } 49 | 50 | parent::tearDown(); 51 | } 52 | 53 | /** @test */ 54 | public function it_exposes_all_toggles(): void 55 | { 56 | $this->client->request('GET', '/toggles'); 57 | 58 | $this->assertTrue($this->client->getResponse()->isOk()); 59 | $this->assertJsonStringEqualsJsonString(json_encode([ 60 | [ 61 | 'name' => 'toggling', 62 | 'conditions' => [ 63 | [ 64 | 'name' => 'operator-condition', 65 | 'key' => 'user_id', 66 | 'operator' => ['name' => 'less-than', 'value' => 42], 67 | ], 68 | ], 69 | 'status' => 'conditionally-active', 70 | 'strategy' => 'affirmative', 71 | ], 72 | ] 73 | ), 74 | $this->client->getResponse()->getContent() 75 | ); 76 | } 77 | 78 | /** @test */ 79 | public function it_deletes_a_toggle(): void 80 | { 81 | $this->client->request('DELETE', '/toggles/toggling'); 82 | $this->assertTrue($this->client->getResponse()->isEmpty()); 83 | 84 | $this->client->request('GET', '/toggles'); 85 | $this->assertTrue($this->client->getResponse()->isOK()); 86 | 87 | $toggles = json_decode($this->client->getResponse()->getContent(), true); 88 | $this->assertCount(0, $toggles); 89 | } 90 | 91 | /** @test */ 92 | public function it_does_not_error_when_deleting_non_existing_toggle(): void 93 | { 94 | $this->client->request('DELETE', '/toggles/nothere'); 95 | 96 | $this->assertTrue($this->client->getResponse()->isEmpty()); 97 | } 98 | 99 | /** @test */ 100 | public function it_updates_a_toggle(): void 101 | { 102 | $toggleData = [ 103 | 'name' => 'toggling', 104 | 'conditions' => [ 105 | [ 106 | 'name' => 'operator-condition', 107 | 'key' => 'company_id', 108 | 'operator' => ['name' => 'greater-than', 'value' => 42], 109 | ], 110 | ], 111 | 'status' => 'conditionally-active', 112 | 'strategy' => 'affirmative', 113 | ]; 114 | $toggle = json_encode($toggleData); 115 | 116 | $this->client->request('PUT', '/toggles/toggling', [], [], [], $toggle); 117 | 118 | $response = $this->client->getResponse(); 119 | $this->assertTrue($response->isSuccessful()); 120 | 121 | // Check the endpoint! 122 | $this->client->request('GET', '/toggles'); 123 | 124 | $this->assertTrue($this->client->getResponse()->isOk()); 125 | $this->assertJsonStringEqualsJsonString( 126 | json_encode([$toggleData]), 127 | $this->client->getResponse()->getContent() 128 | ); 129 | } 130 | 131 | /** @test */ 132 | public function it_does_not_allow_changing_the_name_of_a_toggle(): void 133 | { 134 | $toggleData = ['name' => 'new-name', 'conditions' => []]; 135 | $toggle = json_encode($toggleData); 136 | 137 | $this->client->request('PUT', '/toggles/toggling', [], [], [], $toggle); 138 | 139 | $response = $this->client->getResponse(); 140 | $this->assertTrue($response->isClientError()); 141 | } 142 | 143 | private function loadToggleFixtures(ToggleManager $manager): void 144 | { 145 | // A toggle that will be active is the user id is less than 42 146 | $toggle = new Toggle('toggling', [ 147 | new OperatorCondition( 148 | 'user_id', 149 | new LessThan(42) 150 | ), 151 | ]); 152 | 153 | $manager->add($toggle); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /test/bootstrap.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * For the full copyright and license information, please view the LICENSE 11 | * file that was distributed with this source code. 12 | */ 13 | 14 | namespace Qandidate\Application\Toggle; 15 | 16 | use Symfony\Component\Dotenv\Dotenv; 17 | 18 | require dirname(__DIR__).'/vendor/autoload.php'; 19 | 20 | (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); 21 | --------------------------------------------------------------------------------