From 70265090dd303bf817c7a986f9b289773a27a483 Mon Sep 17 00:00:00 2001 From: Tim Otten Date: Fri, 28 Mar 2014 16:35:26 -0700 Subject: [PATCH] CRM-14370 - API Kernel - Add test for event lifecycle --- Civi/API/Events.php | 13 +++ Civi/API/Kernel.php | 18 +++- Civi/API/Provider/AdhocProvider.php | 132 ++++++++++++++++++++++++++ tests/phpunit/Civi/API/KernelTest.php | 108 +++++++++++++++++++++ 4 files changed, 270 insertions(+), 1 deletion(-) create mode 100644 Civi/API/Provider/AdhocProvider.php create mode 100644 tests/phpunit/Civi/API/KernelTest.php diff --git a/Civi/API/Events.php b/Civi/API/Events.php index 4cfbd11dc9..6bbc41cc76 100644 --- a/Civi/API/Events.php +++ b/Civi/API/Events.php @@ -88,4 +88,17 @@ class Events { * Weight - Late */ const W_LATE = -100; + + /** + * @return array + */ + public static function allEvents() { + return array( + self::AUTHORIZE, + self::EXCEPTION, + self::PREPARE, + self::RESOLVE, + self::RESPOND, + ); + } } diff --git a/Civi/API/Kernel.php b/Civi/API/Kernel.php index 685c5bbfc7..d9efbf697e 100644 --- a/Civi/API/Kernel.php +++ b/Civi/API/Kernel.php @@ -67,7 +67,7 @@ class Kernel { * * @return array|int */ - public function run($entity, $action, $params, $extra) { + public function run($entity, $action, $params, $extra = NULL) { /** * @var $apiProvider \Civi\API\Provider\ProviderInterface|NULL */ @@ -320,9 +320,23 @@ class Kernel { /** * @param array $apiProviders + * @return Kernel */ public function setApiProviders($apiProviders) { $this->apiProviders = $apiProviders; + return $this; + } + + /** + * @param ProviderInterface $apiProvider + * @return Kernel + */ + public function registerApiProvider($apiProvider) { + $this->apiProviders[] = $apiProvider; + if ($apiProvider instanceof \Symfony\Component\EventDispatcher\EventSubscriberInterface) { + $this->getDispatcher()->addSubscriber($apiProvider); + } + return $this; } /** @@ -334,8 +348,10 @@ class Kernel { /** * @param \Symfony\Component\EventDispatcher\EventDispatcher $dispatcher + * @return Kernel */ public function setDispatcher($dispatcher) { $this->dispatcher = $dispatcher; + return $this; } } \ No newline at end of file diff --git a/Civi/API/Provider/AdhocProvider.php b/Civi/API/Provider/AdhocProvider.php new file mode 100644 index 0000000000..47e0cde494 --- /dev/null +++ b/Civi/API/Provider/AdhocProvider.php @@ -0,0 +1,132 @@ + array( + array('onApiResolve', Events::W_MIDDLE), + ), + Events::AUTHORIZE => array( + array('onApiAuthorize', Events::W_MIDDLE), + ), + ); + } + + /** + * @var array (string $name => array('perm' => string, 'callback' => callable)) + */ + private $actions = array(); + + /** + * @var string + */ + private $entity; + + /** + * @var int + */ + private $version; + + /** + * @param int $version + * @param string $entity + */ + public function __construct($version, $entity) { + $this->entity = $entity; + $this->version = $version; + } + + /** + * @param string $name + * @param string $perm + * @param callable $callback + * @return ReflectionProvider + */ + public function addAction($name, $perm, $callback) { + $this->actions[strtolower($name)] = array( + 'perm' => $perm, + 'callback' => $callback, + ); + return $this; + } + + public function onApiResolve(\Civi\API\Event\ResolveEvent $event) { + $apiRequest = $event->getApiRequest(); + if ($this->matchesRequest($apiRequest)) { + $event->setApiRequest($apiRequest); + $event->setApiProvider($this); + $event->stopPropagation(); + } + } + + public function onApiAuthorize(\Civi\API\Event\AuthorizeEvent $event) { + $apiRequest = $event->getApiRequest(); + if ($this->matchesRequest($apiRequest) && \CRM_Core_Permission::check($this->actions[strtolower($apiRequest['action'])]['perm'])) { + $event->authorize(); + $event->stopPropagation(); + } + } + + /** + * {inheritdoc} + */ + public function invoke($apiRequest) { + return call_user_func($this->actions[strtolower($apiRequest['action'])]['callback'], $apiRequest); + } + + /** + * {inheritdoc} + */ + function getEntityNames($version) { + return array($this->entity); + } + + /** + * {inheritdoc} + */ + function getActionNames($version, $entity) { + if ($version == $this->version && $entity == $this->entity) { + return array_keys($this->actions); + } + else { + return array(); + } + } + + public function matchesRequest($apiRequest) { + return $apiRequest['entity'] == $this->entity && $apiRequest['version'] == $this->version && isset($this->actions[strtolower($apiRequest['action'])]); + } +} \ No newline at end of file diff --git a/tests/phpunit/Civi/API/KernelTest.php b/tests/phpunit/Civi/API/KernelTest.php new file mode 100644 index 0000000000..945655b800 --- /dev/null +++ b/tests/phpunit/Civi/API/KernelTest.php @@ -0,0 +1,108 @@ + array('name' => string $eventName, 'type' => string $className)) + */ + var $actualEventSequence; + + /** + * @var EventDispatcher + */ + var $dispatcher; + + /** + * @var Kernel + */ + var $kernel; + + protected function setUp() { + parent::setUp(); + $this->actualEventSequence = array(); + $this->dispatcher = new EventDispatcher(); + $this->monitorEvents(Events::allEvents()); + $this->kernel = new Kernel($this->dispatcher); + } + + function testNormalEvents() { + $this->kernel->registerApiProvider($this->createWidgetFrobnicateProvider()); + $result = $this->kernel->run('Widget', 'frobnicate', array( + 'version' => self::MOCK_VERSION, + )); + + $expectedEventSequence = array( + array('name' => Events::RESOLVE, 'class' => 'Civi\API\Event\ResolveEvent'), + array('name' => Events::AUTHORIZE, 'class' => 'Civi\API\Event\AuthorizeEvent'), + array('name' => Events::PREPARE, 'class' => 'Civi\API\Event\PrepareEvent'), + array('name' => Events::RESPOND, 'class' => 'Civi\API\Event\RespondEvent'), + ); + $this->assertEquals($expectedEventSequence, $this->actualEventSequence); + $this->assertEquals('frob', $result['values'][98]); + } + + function testResolveException() { + $test = $this; + $this->dispatcher->addListener(Events::RESOLVE, function () { + throw new \API_Exception('Oh My God', 'omg', array('the' => 'badzes')); + }, Events::W_EARLY); + $this->dispatcher->addListener(Events::EXCEPTION, function(\Civi\API\Event\ExceptionEvent $event) use ($test) { + $test->assertEquals('Oh My God', $event->getException()->getMessage()); + }); + + $this->kernel->registerApiProvider($this->createWidgetFrobnicateProvider()); + $result = $this->kernel->run('Widget', 'frobnicate', array( + 'version' => self::MOCK_VERSION, + )); + + $expectedEventSequence = array( + array('name' => Events::RESOLVE, 'class' => 'Civi\API\Event\ResolveEvent'), + array('name' => Events::EXCEPTION, 'class' => 'Civi\API\Event\ExceptionEvent'), + ); + $this->assertEquals($expectedEventSequence, $this->actualEventSequence); + $this->assertEquals('Oh My God', $result['error_message']); + $this->assertEquals('omg', $result['error_code']); + $this->assertEquals('badzes', $result['the']); + } + + // TODO testAuthorizeException, testPrepareException, testRespondException, testExceptionException + + /** + * Create an API provider for entity "Widget" with action "frobnicate". + * + * @return Provider\ProviderInterface + */ + public function createWidgetFrobnicateProvider() { + $provider = new \Civi\API\Provider\AdhocProvider(self::MOCK_VERSION, 'Widget'); + $provider->addAction('frobnicate', 'access CiviCRM', function ($apiRequest) { + return civicrm_api3_create_success(array(98 => 'frob')); + }); + return $provider; + } + + /** + * Add listeners to $this->dispatcher which record each invocation of $monitoredEvents + * in $this->actualEventSequence. + * + * @param EventDispatcher $this->dispatcher + * @param array $monitoredEvents list of event names + */ + public function monitorEvents($monitoredEvents) { + foreach ($monitoredEvents as $monitoredEvent) { + $test = $this; + $this->dispatcher->addListener($monitoredEvent, function ($event) use ($monitoredEvent, &$test) { + $test->actualEventSequence[] = array( + 'name' => $monitoredEvent, + 'class' => get_class($event), + ); + }, 2 * Events::W_EARLY); + } + } +} \ No newline at end of file -- 2.25.1