diff --git a/README.md b/README.md index 9548d8d..20acd78 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,10 @@ Chaos Monkey for Symfony applications. Try to attack your running Symfony App. - Repository (not implemented) - Service (not implemented) +## Activators + + - "Query param" - attack only if given query param is present (default `chaos`) + ## Symfony ## How to use @@ -62,10 +66,12 @@ chaos_monkey: request: enabled: true priority: 0 + activators: + query_param: false # if true then chaos monkey will be called only if given query param exist (with any value) + query_param_name: 'chaos' ``` ## Roadmap - - [ ] Query param activator - [ ] Flex recipe - [ ] Metrics (for example `chaos_monkey_request_count_assaulted`) - [ ] Assault profiles - each profile can contain different assaults diff --git a/src/Activator/QueryParamActivator.php b/src/Activator/QueryParamActivator.php new file mode 100644 index 0000000..6a7c576 --- /dev/null +++ b/src/Activator/QueryParamActivator.php @@ -0,0 +1,23 @@ +enabled) { + return true; + } + + return $request->query->has($this->paramName); + } +} diff --git a/src/DependencyInjection/ChaosMonkeyExtension.php b/src/DependencyInjection/ChaosMonkeyExtension.php index 662462a..cd56f07 100644 --- a/src/DependencyInjection/ChaosMonkeyExtension.php +++ b/src/DependencyInjection/ChaosMonkeyExtension.php @@ -21,6 +21,10 @@ * }, * watchers: array{ * request: array{enabled: bool, priority: int} + * }, + * activators: array{ + * query_param: bool, + * query_param_name: string * } * } */ @@ -39,6 +43,7 @@ public function load(array $configs, ContainerBuilder $container): void $config = $this->processConfiguration($configuration, $configs); $this->setChaosMonkeySettings($container, $config); + $this->setActivators($container, $config); $this->enableWatchers($container, $config); } @@ -77,4 +82,16 @@ private function enableWatchers(ContainerBuilder $container, array $config): voi ]); } } + + /** + * @param ConfigArray $config + */ + private function setActivators(ContainerBuilder $container, array $config): void + { + $queryParam = $container->getDefinition('chaos_monkey.activator.query_param'); + $queryParam->setArguments([ + '$enabled' => $config['activators']['query_param'], + '$paramName' => $config['activators']['query_param_name'], + ]); + } } diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index c940b5f..e19b23e 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -65,6 +65,13 @@ public function getConfigTreeBuilder(): TreeBuilder ->end() ->end() ->end() + ->arrayNode('activators') + ->addDefaultsIfNotSet() + ->children() + ->booleanNode('query_param')->defaultFalse()->end() + ->scalarNode('query_param_name')->defaultValue('chaos')->end() + ->end() + ->end() ->end(); return $treeBuilder; diff --git a/src/Resources/config/services.xml b/src/Resources/config/services.xml index ebf6787..95f1375 100644 --- a/src/Resources/config/services.xml +++ b/src/Resources/config/services.xml @@ -14,6 +14,10 @@ + + + + diff --git a/src/Watcher/RequestWatcher.php b/src/Watcher/RequestWatcher.php index 228521c..08597e0 100644 --- a/src/Watcher/RequestWatcher.php +++ b/src/Watcher/RequestWatcher.php @@ -5,15 +5,15 @@ namespace Chaos\Monkey\Symfony\Watcher; use Chaos\Monkey\ChaosMonkey; +use Chaos\Monkey\Symfony\Activator\QueryParamActivator; use Symfony\Component\HttpKernel\Event\RequestEvent; -class RequestWatcher +final class RequestWatcher { - private ChaosMonkey $chaosMonkey; - - public function __construct(ChaosMonkey $chaosMonkey) - { - $this->chaosMonkey = $chaosMonkey; + public function __construct( + private readonly ChaosMonkey $chaosMonkey, + private readonly QueryParamActivator $queryParamActivator + ) { } public function onKernelRequest(RequestEvent $event): void @@ -22,6 +22,8 @@ public function onKernelRequest(RequestEvent $event): void return; } - $this->chaosMonkey->call(); + if ($this->queryParamActivator->inChaos($event->getRequest())) { + $this->chaosMonkey->call(); + } } } diff --git a/tests/Activator/QueryParamActivatorTest.php b/tests/Activator/QueryParamActivatorTest.php new file mode 100644 index 0000000..ae48104 --- /dev/null +++ b/tests/Activator/QueryParamActivatorTest.php @@ -0,0 +1,33 @@ +inChaos(new Request())); + } + + public function testItIsNoInChaosIfEnabledAndParamMissing(): void + { + $activator = new QueryParamActivator(true); + + self::assertFalse($activator->inChaos(new Request())); + } + + public function testItIsInChaosIfEnabledAndParamExists(): void + { + $activator = new QueryParamActivator(true, 'bye'); + + self::assertTrue($activator->inChaos(new Request(['bye' => true]))); + } +} diff --git a/tests/Controller/SymfonyControllerTest.php b/tests/Controller/SymfonyControllerTest.php index d4fcdf8..c6760ec 100644 --- a/tests/Controller/SymfonyControllerTest.php +++ b/tests/Controller/SymfonyControllerTest.php @@ -5,9 +5,11 @@ namespace Chaos\Monkey\Symfony\Tests\Controller; use Chaos\Monkey\Settings; +use Chaos\Monkey\Symfony\Activator\QueryParamActivator; use Chaos\Monkey\Symfony\Tests\Symfony\Kernel; use PHPUnit\Framework\TestCase; use Symfony\Bundle\FrameworkBundle\KernelBrowser; +use Symfony\Component\HttpKernel\Exception\LockedHttpException; use Symfony\Component\Stopwatch\Stopwatch; class SymfonyControllerTest extends TestCase @@ -49,7 +51,22 @@ public function testRequestExceptionAttack(): void $this->enableExceptionAssault(); $this->client->request('GET', '/hello'); - self::assertEquals(500, $this->client->getResponse()->getStatusCode()); + self::assertEquals(423, $this->client->getResponse()->getStatusCode()); + + $this->disableExceptionAssault(); + } + + public function testRequestExceptionAttackWithQueryParamActivatorEnabled(): void + { + $this->enableQueryParamActivator(); + $this->enableExceptionAssault(); + $this->client->request('GET', '/hello'); + + self::assertEquals(200, $this->client->getResponse()->getStatusCode()); + + $this->client->request('GET', '/hello?chaos=true'); + + self::assertEquals(423, $this->client->getResponse()->getStatusCode()); $this->disableExceptionAssault(); } @@ -58,6 +75,7 @@ private function enableExceptionAssault(): void { $this->chaosMonkeySettings()->setEnabled(true); $this->chaosMonkeySettings()->setExceptionActive(true); + $this->chaosMonkeySettings()->setExceptionClass(LockedHttpException::class); $this->chaosMonkeySettings()->setProbability(100); } @@ -84,4 +102,9 @@ private function chaosMonkeySettings(): Settings { return $this->client->getContainer()->get('chaos_monkey')->settings(); } + + private function enableQueryParamActivator(): void + { + $this->client->getContainer()->get('test.service_container')->set('chaos_monkey.activator.query_param', new QueryParamActivator(true)); + } } diff --git a/tests/Symfony/Kernel.php b/tests/Symfony/Kernel.php index c2ba8ed..7aade88 100644 --- a/tests/Symfony/Kernel.php +++ b/tests/Symfony/Kernel.php @@ -29,6 +29,7 @@ protected function configureContainer(ContainerConfigurator $container): void { $container->extension('framework', [ 'secret' => 'S0ME_SECRET', + 'test' => true, ]); $container->services()->set('logger', NullLogger::class);