From fe526326b6a4f9c6d63a7d7a9bbe794beed3680a Mon Sep 17 00:00:00 2001 From: Mathias Gelhausen Date: Wed, 17 Oct 2018 12:49:59 +0200 Subject: [PATCH] Implements console action 'update-crawler' (refs #14) --- config/module.config.php | 33 ++- .../UpdateCrawlerConsoleController.php | 161 ++++++++++++ .../UpdateCrawlerConsoleControllerFactory.php | 53 ++++ src/Filter/IdToEntity.php | 120 +++++++++ src/Filter/IdToEntityFactory.php | 56 +++++ src/InputFilter/CrawlerInputFilter.php | 26 +- src/Validator/EntityExists.php | 112 +++++++++ .../UpdateCrawlerConsoleControllerTest.php | 230 ++++++++++++++++++ .../UpdateCrawlerConsoleControllerTest.php | 96 ++++++++ .../Filter/IdToEntityFactoryTest.php | 89 +++++++ .../Filter/IdToEntityTest.php | 181 ++++++++++++++ .../InputFilter/CrawlerInputFilterTest.php | 147 +++++++---- .../Validator/EntityExistsTest.php | 128 ++++++++++ 13 files changed, 1372 insertions(+), 60 deletions(-) create mode 100644 src/Controller/UpdateCrawlerConsoleController.php create mode 100644 src/Factory/Controller/UpdateCrawlerConsoleControllerFactory.php create mode 100644 src/Filter/IdToEntity.php create mode 100644 src/Filter/IdToEntityFactory.php create mode 100644 src/Validator/EntityExists.php create mode 100644 test/SimpleImportTest/Controller/UpdateCrawlerConsoleControllerTest.php create mode 100644 test/SimpleImportTest/Factory/Controller/UpdateCrawlerConsoleControllerTest.php create mode 100644 test/SimpleImportTest/Filter/IdToEntityFactoryTest.php create mode 100644 test/SimpleImportTest/Filter/IdToEntityTest.php create mode 100644 test/SimpleImportTest/Validator/EntityExistsTest.php diff --git a/config/module.config.php b/config/module.config.php index b2a19c9..23a89cc 100755 --- a/config/module.config.php +++ b/config/module.config.php @@ -45,6 +45,7 @@ 'factories' => [ 'SimpleImport/ConsoleController' => Factory\Controller\ConsoleControllerFactory::class, Controller\DeleteCrawlerConsoleController::class => Factory\Controller\DeleteCrawlerConsoleControllerFactory::class, + Controller\UpdateCrawlerConsoleController::class => Factory\Controller\UpdateCrawlerConsoleControllerFactory::class, ] ], 'controller_plugins' => [ @@ -88,8 +89,26 @@ ], ], ], - ] - ] + 'simpleimport-info' => [ + 'options' => [ + 'route' => 'simpleimport info [--id] []', + 'defaults' => [ + 'controller' => Controller\UpdateCrawlerConsoleController::class, + 'action' => 'index' + ], + ], + ], + 'simpleimport-update-crawler' => [ + 'options' => [ + 'route' => 'simpleimport update-crawler [--id] [--rename=] [--feed-uri=] [--rundelay=] [--type=] [--jobInitialState=] [--organization=]', + 'defaults' => [ + 'controller' => Controller\UpdateCrawlerConsoleController::class, + 'action' => 'update' + ], + ], + ], + ], + ], ], 'log' => [ 'SimpleImport/Log' => [ @@ -108,11 +127,17 @@ InputFilter\CrawlerInputFilter::class => InvokableFactory::class ] ], + 'filters' => [ + 'factories' => [ + Filter\IdToEntity::class => Filter\IdToEntityFactory::class, + ], + ], 'validators' => [ 'factories' => [ 'SimpleImportOrganizationExists' => Factory\Validator\OrganizationExistsFactory::class, - Validator\CrawlerOptions::class => InvokableFactory::class - ] + Validator\CrawlerOptions::class => InvokableFactory::class, + Validator\EntityExists::class => InvokableFactory::class, + ], ], 'simple_import_crawler_processor_manager' => [ 'factories' => [ diff --git a/src/Controller/UpdateCrawlerConsoleController.php b/src/Controller/UpdateCrawlerConsoleController.php new file mode 100644 index 0000000..8ce4b4a --- /dev/null +++ b/src/Controller/UpdateCrawlerConsoleController.php @@ -0,0 +1,161 @@ + + */ + +/** */ +namespace SimpleImport\Controller; + +use SimpleImport\InputFilter\CrawlerInputFilter; +use Zend\Console\ColorInterface; +use Zend\InputFilter\InputFilter; +use Zend\Mvc\Console\Controller\AbstractConsoleController; + +/** + * Update crawler configuration or displays crawler information. + * + * @author Mathias Gelhausen + */ +class UpdateCrawlerConsoleController extends AbstractConsoleController +{ + /** + * @var CrawlerInputFilter + */ + private $inputFilter; + + /** + * @param CrawlerInputFilter $inputFilter + * + * @return self + */ + public function setInputFilter(CrawlerInputFilter $inputFilter) + { + $this->inputFilter = $inputFilter; + + return $this; + } + + /** + * displays list of crawlers or information about one crawler + * + * @return string|null + */ + public function indexAction() + { + /* @var \SimpleImport\Controller\Plugin\LoadCrawler $loader */ + + $loader = $this->plugin('siLoadCrawler'); + + if ($this->params('name')) { + $crawler = $loader(); + + return $this->info($crawler); + } + + $set = $loader->loadAll(); + + echo PHP_EOL; + foreach ($set as $crawler) { + printf("%'.-40s (%s)" . PHP_EOL, $crawler->getName(), $crawler->getId()); + } + } + + /** + * generates cralwer information message. + * + * @param \SimpleImport\Entity\Crawler $crawler + * + * @return string + */ + private function info(\SimpleImport\Entity\Crawler $crawler) + { + return sprintf(<<getName(), $crawler->getId(), $crawler->getType(), + $crawler->getOrganization()->getOrganizationName()->getName(), + $crawler->getOrganization()->getId(), + $crawler->getFeedUri(), + $crawler->getRunDelay(), + $crawler->getOptions()->getInitialState(), + $crawler->getDateLastRun()->format('d.m.Y H:i:s') + ); + + } + + + /** + * update the configuration of a crawler + * + * @return string + */ + public function updateAction() + { + /* @var \SimpleImport\Entity\Crawler $crawler */ + + $loader = $this->plugin('siLoadCrawler'); + $crawler = $loader(); + $values = $this->validateInput($crawler); + $console = $this->getConsole(); + + $crawler->setName($values['name']); + /** @noinspection PhpParamsInspection */ + $crawler->setOrganization($values['organization']); + $crawler->setFeedUri($values['feedUri']); + $crawler->setRunDelay((int) $values['runDelay']); + $crawler->setType($values['type']); + $crawler->setOptionsFromArray((array) $values['options']); + + $loader->store($crawler); + + $console->writeLine('Crawler updated.', ColorInterface::GREEN); + return $this->info($crawler); + } + + /** + * Validates rhe command line arguments. + * + * @param \SimpleImport\Entity\Crawler $crawler + * + * @return array + * @throws \RuntimeException if validation fails. + */ + private function validateInput(\SimpleImport\Entity\Crawler $crawler) + { + $params = $this->plugin('params'); + + $this->inputFilter->setData([ + 'name' => $params('rename', $crawler->getName()), + 'feedUri' => $params('feed-uri', $crawler->getFeedUri()), + 'organization' => $params('organization', $crawler->getOrganization()), + 'runDelay' => $params('rundelay', $crawler->getRunDelay()), + 'type' => $params('type', $crawler->getType()), + 'options' => ['initialState' => $params('jobInitialState', $crawler->getOptions()->getInitialState())] + ]); + + if (!$this->inputFilter->isValid()) { + $message = 'Invalid parameters!' . PHP_EOL . PHP_EOL; + foreach ($this->inputFilter->getMessages() as $name => $messages) { + $message .= sprintf(' - %s: %s', $name, join(', ', $messages)) . PHP_EOL; + } + + throw new \RuntimeException($message); + } + + $values = array_filter($this->inputFilter->getValues(), function($i) { return !empty($i); }); + return $values; + } + +} diff --git a/src/Factory/Controller/UpdateCrawlerConsoleControllerFactory.php b/src/Factory/Controller/UpdateCrawlerConsoleControllerFactory.php new file mode 100644 index 0000000..4260b99 --- /dev/null +++ b/src/Factory/Controller/UpdateCrawlerConsoleControllerFactory.php @@ -0,0 +1,53 @@ + + */ + +/** */ +namespace SimpleImport\Factory\Controller; + +use SimpleImport\Controller\UpdateCrawlerConsoleController; +use Interop\Container\ContainerInterface; +use SimpleImport\InputFilter\CrawlerInputFilter; +use Zend\ServiceManager\Factory\FactoryInterface; + +/** + * Factory for \SimpleImport\Controller\UpdateCrawlerConsoleController + * + * @author Mathias Gelhausen + * @todo write test + */ +class UpdateCrawlerConsoleControllerFactory implements FactoryInterface +{ + + /** + * @param ContainerInterface $container + * @param string $requestedName + * @param array|null $options + * + * @return UpdateCrawlerConsoleController + */ + public function __invoke(ContainerInterface $container, $requestedName, array $options = null) + { + /* @var CrawlerInputFilter $filter + * @var \Zend\Router\RouteMatch $routeMatch + */ + + $controller = new UpdateCrawlerConsoleController(); + $application = $container->get('Application'); + $routeMatch = $application->getMvcEvent()->getRouteMatch(); + + if ('index' != $routeMatch->getParam('action')) { + $filters = $container->get('InputFilterManager'); + $filter = $filters->get(CrawlerInputFilter::class); + + $controller->setInputFilter($filter); + } + + return $controller; + } +} diff --git a/src/Filter/IdToEntity.php b/src/Filter/IdToEntity.php new file mode 100644 index 0000000..392e943 --- /dev/null +++ b/src/Filter/IdToEntity.php @@ -0,0 +1,120 @@ + + */ + +/** */ +namespace SimpleImport\Filter; + +use Doctrine\ODM\MongoDB\DocumentRepository; +use Zend\Filter\FilterInterface; + +/** + * Filter to load an entity by its id. + * + * @author Mathias Gelhausen + */ +class IdToEntity implements FilterInterface +{ + /** + * @var DocumentRepository + */ + private $repository; + + /** + * Custom value to return if no entity is found. + * + * @var mixed + */ + private $notFoundValue; + + /** + * IdToEntity constructor. + * + * @param DocumentRepository $repository + */ + public function __construct(DocumentRepository $repository) + { + $this->repository = $repository; + } + + /** + * Allows direct invokation. + * + * Proxies to {@łink filter()} + * + * @param mixed $value + * + * @return mixed|null + * @see filter + */ + public function __invoke($value) + { + return $this->filter($value); + } + + /** + * @param mixed $notFoundValue + * + * @return self + */ + public function setNotFoundValue($notFoundValue) + { + $this->notFoundValue = $notFoundValue; + + return $this; + } + + /** + * @param $value + * + * @return mixed + */ + private function getNotFoundValue($value) + { + return $this->notFoundValue ?: $value; + } + + /** + * Filters an id to an entity instance. + * + * If empty($value) is true, returns null. + * + * The id can be given as a string or as an instance of \MongoId. + * If an entity is passed, and it is of the type managed by {@link repository}, it is + * returned as is. + * + * If no entity with the given Id is found, the {@link notFoundValue} is returned + * which defaults to $value + * + * If $value is not an entity that {@link repository} manages, and $value is not a string + * or an instance of \MongoId, an exception is thrown. + * + * @param mixed $value + * + * @return mixed|null|object + * @throws \InvalidArgumentException + */ + public function filter($value) + { + if (empty($value)) { return null; } + + if (is_string($value) || $value instanceOf \MongoId) { + return $this->repository->find($value) ?: $this->getNotFoundValue($value); + } + + if (!is_a($value, $this->repository->getDocumentName())) { + throw new \InvalidArgumentException(sprintf( + 'Value must either be a string or an instance of \MongoId or %s', + $this->repository->getDocumentName()) + ); + } + + return $value; + } + +} diff --git a/src/Filter/IdToEntityFactory.php b/src/Filter/IdToEntityFactory.php new file mode 100644 index 0000000..d190ef3 --- /dev/null +++ b/src/Filter/IdToEntityFactory.php @@ -0,0 +1,56 @@ + + */ + +/** */ +namespace SimpleImport\Filter; + +use Interop\Container\ContainerInterface; +use Zend\ServiceManager\Exception\ServiceNotCreatedException; +use Zend\ServiceManager\Factory\FactoryInterface; + +/** + * Factory for \SimpleImport\Filter\IdToEntity + * + * @author Mathias Gelhausen + * @todo write test + */ +class IdToEntityFactory implements FactoryInterface +{ + /** + * Creates an IdToEntity filter. + * + * Requires the key 'document' in the options array which should be the key to load the + * entity repository from the repositories plugin manager. + * + * Optionally sets the notFoundValue, if the key 'not_found_value' is set in the options array. + * + * @param ContainerInterface $container + * @param string $requestedName + * @param array|null $options + * + * @return IdToEntity + */ + public function __invoke(ContainerInterface $container, $requestedName, array $options = null) + { + if (!isset($options['document'])) { + throw new ServiceNotCreatedException('Missing option "document".'); + } + + $repositories = $container->get('repositories'); + $repository = $repositories->get($options['document']); + + $service = new IdToEntity($repository); + + if (isset($options['not_found_value'])) { + $service->setNotFoundValue($options['not_found_value']); + } + + return $service; + } +} diff --git a/src/InputFilter/CrawlerInputFilter.php b/src/InputFilter/CrawlerInputFilter.php index 6fdc82f..38c514f 100644 --- a/src/InputFilter/CrawlerInputFilter.php +++ b/src/InputFilter/CrawlerInputFilter.php @@ -32,14 +32,20 @@ public function init() 'name' => 'organization', 'filters' => [ [ - 'name' => 'StringTrim' - ] + 'name' => \SimpleImport\Filter\IdToEntity::class, + 'options' => [ + 'document' => 'Organizations', + ], + ], ], 'validators' => [ [ - 'name' => 'SimpleImportOrganizationExists', - ] - ] + 'name' => \SimpleImport\Validator\EntityExists::class, + 'options' => [ + 'entityClass' => \Organizations\Entity\Organization::class, + ], + ], + ], ])->add([ 'name' => 'feedUri', 'filters' => [ @@ -92,13 +98,7 @@ public function init() [ 'name' => 'Callback', 'options' => [ - 'callback' => function ($array) - { - return array_filter((array)$array, function ($value) - { - return !is_null($value); - }); - } + 'callback' => 'array_filter' ] ] ], @@ -109,4 +109,4 @@ public function init() ] ]); } -} \ No newline at end of file +} diff --git a/src/Validator/EntityExists.php b/src/Validator/EntityExists.php new file mode 100644 index 0000000..252fcb7 --- /dev/null +++ b/src/Validator/EntityExists.php @@ -0,0 +1,112 @@ + + */ + +/** */ +namespace SimpleImport\Validator; + +use Zend\Validator\AbstractValidator; +use Organizations\Repository\Organization as OrganizationRepository; + +/** + * Validates, if the value is an object of a specific type. + * + * This validator is meant to be used with {@link \SimpleImport\Filter\IdToEntity} in an InputFilter. + * It does not make much sense outside of this context. + * + * The filter loads an entity or returns the given value (which should be an entity id). + * So, if anything else than an object of the set type is given to this validator, it assumes + * that the entity could not be loaded and thus, does not exist. + * + * @author Mathias Gelhausen + */ +class EntityExists extends AbstractValidator +{ + + /** + * @var string + */ + const NOT_EXIST = 'notExist'; + + /** + * @var array + */ + protected $messageTemplates = [ + self::NOT_EXIST => "%entityClass% with ID '%value%' does not exist", + ]; + + /** + * @var array + */ + protected $messageVariables = [ + 'entityClass' => 'entityClass', + ]; + + /** + * @var string + */ + protected $entityClass; + + /** + * @param OrganizationRepository $repository + */ + public function __construct($entityOrOptions = null) + { + if (null !== $entityOrOptions && !is_array($entityOrOptions)) { + $entityOrOptions = [ 'entityClass' => $entityOrOptions ]; + } + + parent::__construct($entityOrOptions); + } + + /** + * @param string|object $entityClass + * + * @return self + * @throws \InvalidArgumentException if anything else than a string or an object is passed. + */ + public function setEntityClass($entityClass) + { + if (is_object($entityClass)) { + $entityClass = get_class($entityClass); + } + + if (!is_string($entityClass)) { + throw new \InvalidArgumentException('Entity class must be given as string or object'); + } + + $this->entityClass = $entityClass; + + return $this; + } + + /** + * @param object|mixed $value + * + * @returns bool + * @throws \RuntimeException if no entity class is set prior to the call. + */ + public function isValid($value) + { + if (!$this->entityClass) { + throw new \RuntimeException('An entity class must be set prior to use this validator.'); + } + + if (!$value instanceOf $this->entityClass) { + if (is_object($value)) { + $value = method_exists($value, '__toString') ? (string) $value : '[object]'; + } + $this->setValue($value); + $this->error(self::NOT_EXIST); + + return false; + } + + return true; + } +} diff --git a/test/SimpleImportTest/Controller/UpdateCrawlerConsoleControllerTest.php b/test/SimpleImportTest/Controller/UpdateCrawlerConsoleControllerTest.php new file mode 100644 index 0000000..145c007 --- /dev/null +++ b/test/SimpleImportTest/Controller/UpdateCrawlerConsoleControllerTest.php @@ -0,0 +1,230 @@ + + */ + +/** */ +namespace SimpleImportTest\Controller; + +use CoreTestUtils\TestCase\TestInheritanceTrait; +use CoreTestUtils\TestCase\TestSetterGetterTrait; +use Organizations\Entity\Organization; +use Organizations\Entity\OrganizationName; +use SimpleImport\Controller\Plugin\LoadCrawler; +use SimpleImport\Controller\UpdateCrawlerConsoleController; +use SimpleImport\Entity\Crawler; +use SimpleImport\InputFilter\CrawlerInputFilter; +use Zend\Mvc\Console\Controller\AbstractConsoleController; + +/** + * Tests for \SimpleImport\Controller\UpdateCrawlerConsoleController + * + * @covers \SimpleImport\Controller\UpdateCrawlerConsoleController + * @author Mathias Gelhausen + * + */ +class UpdateCrawlerConsoleControllerTest extends \PHPUnit_Framework_TestCase +{ + use TestInheritanceTrait, TestSetterGetterTrait; + + /** + * + * + * @var array|\PHPUnit_Framework_MockObject_MockObject|UpdateCrawlerConsoleController|\ReflectionClass + */ + private $target = [ + UpdateCrawlerConsoleController::class, + 'mock' => [ 'plugin', 'params' ], + '@testInheritance' => [ 'as_reflection' => true ], + '@testSetterAndGetter' => [ + 'mock' => null, + ], + ]; + + private $inheritance = [ AbstractConsoleController::class ]; + + public function propertiesProvider() { + $filter = new CrawlerInputFilter(); + + return [ + ['inputFilter', ['value' => $filter, 'expect_property' => $filter]] + ]; + } + + public function testIndexActionPrintsSingleCrawlerInfo() + { + $date = new \DateTime(); + $org = new Organization(); + $crawler = new Crawler(); + + $org->setOrganizationName(new OrganizationName('TestCompany')); + $org->setId('TestOrgId'); + + $crawler->setId('TestId'); + $crawler->setType(Crawler::TYPE_JOB); + $crawler->setName('TestCrawler'); + $crawler->setFeedUri('TestUri'); + $crawler->setRunDelay(1234); + $crawler->getOptions()->setInitialState('active'); + $crawler->setDateLastRun($date); + $crawler->setOrganization($org); + + $loader = $this->getMockBuilder(LoadCrawler::class)->disableOriginalConstructor()->setMethods(['__invoke'])->getMock(); + $loader->expects($this->once())->method('__invoke')->willReturn($crawler); + + $this->target->expects($this->once())->method('params')->with('name')->willReturn('TestCrawler'); + $this->target->expects($this->once())->method('plugin')->with('siLoadCrawler')->willReturn($loader); + + + $output = $this->target->indexAction(); + + $this->assertContains('TestCrawler (TestId) [' . Crawler::TYPE_JOB . ']', $output); + $this->assertContains('Organization: TestCompany (TestOrgId)', $output); + $this->assertContains('TestUri', $output); + $this->assertContains('1234', $output); + $this->assertContains('Jobs initial state: active', $output); + $this->assertContains($date->format('d.m.Y H:i:s'), $output); + } + + public function testIndexActionPrintsListOfCrawlers() + { + $this->target->expects($this->once())->method('params')->with('name')->willReturn(null); + + $crawler1 = new Crawler(); + $crawler1->setName('Crawler1'); + $crawler1->setId('Id1'); + + $crawler2 = new Crawler(); + $crawler2->setName('Crawler2'); + $crawler2->setId('Id2'); + + $set = [ $crawler1, $crawler2 ]; + + $loader = $this->getMockBuilder(LoadCrawler::class)->disableOriginalConstructor()->setMethods(['loadAll'])->getMock(); + $loader->expects($this->once())->method('loadAll')->willReturn($set); + + $this->target->expects($this->once())->method('plugin')->with('siLoadCrawler')->willReturn($loader); + + $this->expectOutputRegex('~Crawler1\.* \(Id1\)' . PHP_EOL . 'Crawler2\.* \(Id2\)~'); + + $this->target->indexAction(); + + } + + private function setupUpdateActionTest($inputFilterValid) + { + $return = []; + $params = $this->getMockBuilder(\Zend\Mvc\Controller\Plugin\Params::class) + ->disableOriginalConstructor()->setMethods(['__invoke'])->getMock(); + + $params->expects($this->exactly(6))->method('__invoke') + ->with($this->callback(function($arg) { + return in_array($arg, ['rename', 'feed-uri', 'organization', 'rundelay', 'type', 'jobInitialState']); + })) + ->willReturn(null); + + $crawler = new Crawler(); + $crawler->setType(Crawler::TYPE_JOB); + + $return['crawler'] = $crawler; + + + $loader = $this->getMockBuilder(LoadCrawler::class)->disableOriginalConstructor() + ->setMethods(['__invoke', 'store'])->getMock(); + + $return['loader'] = $loader; + + $loader->expects($this->once())->method('__invoke')->willReturn($crawler); + + $inputFilter = $this->getMockBuilder(CrawlerInputFilter::class) + ->disableOriginalConstructor() + ->setMethods(['setData', 'isValid', 'getMessages', 'getValues']) + ->getMock(); + + $inputFilter->expects($this->once())->method('setData')->with([ + 'name' => null, + 'feedUri' => null, + 'organization' => null, + 'runDelay' => null, + 'type' => null, + 'options' => ['initialState' => null] + ]); + + $inputFilter->expects($this->once())->method('isValid')->willReturn($inputFilterValid + ); + if ($inputFilterValid) { + $org = $return['org'] = new Organization(); + $inputFilter->expects($this->never())->method('getMessages'); + $inputFilter->expects($this->once())->method('getValues')->willReturn( + [ + 'name' => 'UpdateName', + 'feedUri' => 'UpdateFeedUri', + 'organization' => $org, + 'runDelay' => '4321', + 'type' => Crawler::TYPE_JOB, + 'options' => [ 'initialState' => 'active' ], + ] + ); + } else { + + $inputFilter->expects($this->once())->method('getMessages')->willReturn( + [ + 'field1' => ['empty' => 'Test error message 1', 'Message1.1'], + 'field2' => ['error' => 'Error Message 2'] + + ] + ); + $inputFilter->expects($this->never())->method('getValues'); + } + + $this->target->expects($this->exactly(2))->method('plugin')->will($this->returnValueMap( + [ + ['params', null, $params], + ['siLoadCrawler', null, $loader], + ] + )); + + $this->target->setInputFilter($inputFilter); + + return $return; + } + + public function testUpdateActionInvalidParametersThrowsException() + { + $this->setupUpdateActionTest(false); + + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessageRegExp('~^Invalid parameter.*field1.*Test error message 1, Message1\.1.*field2.*Error Message 2~s'); + + $this->target->updateAction(); + + } + + public function testUpdateAction() + { + /* @var Organization $org + * @var Crawler $crawler + */ + $mocks = $this->setupUpdateActionTest(true); + extract($mocks); + + $org->setOrganizationName(new OrganizationName('CompanyName')); + $crawler->setDateLastRun(new \DateTime()); + + $console = $this->getMockBuilder(\Zend\Console\Adapter\AdapterInterface::class) + ->getMock(); + + $console->expects($this->once())->method('writeLine')->with($this->stringContains('Crawler updated')); + + $this->target->setConsole($console); + + $this->target->updateAction(); + + $this->assertSame($org, $crawler->getOrganization()); + $this->assertEquals('UpdateName', $crawler->getName()); + } +} diff --git a/test/SimpleImportTest/Factory/Controller/UpdateCrawlerConsoleControllerTest.php b/test/SimpleImportTest/Factory/Controller/UpdateCrawlerConsoleControllerTest.php new file mode 100644 index 0000000..10978b9 --- /dev/null +++ b/test/SimpleImportTest/Factory/Controller/UpdateCrawlerConsoleControllerTest.php @@ -0,0 +1,96 @@ + + */ + +/** */ +namespace SimpleImportTest\Factory\Controller; + +use CoreTestUtils\TestCase\ServiceManagerMockTrait; +use CoreTestUtils\TestCase\TestInheritanceTrait; +use SimpleImport\Controller\UpdateCrawlerConsoleController; +use SimpleImport\Factory\Controller\UpdateCrawlerConsoleControllerFactory; +use SimpleImport\InputFilter\CrawlerInputFilter; +use Zend\Mvc\Console\Router\RouteMatch; +use Zend\Mvc\MvcEvent; +use Zend\ServiceManager\Factory\FactoryInterface; + +/** + * Tests for \SimpleImport\Factory\Controller\UpdateCrawlerConsoleControllerFactory + * + * @covers \SimpleImport\Factory\Controller\UpdateCrawlerConsoleControllerFactory + * @author Mathias Gelhausen + * + */ +class UpdateCrawlerConsoleControllerTest extends \PHPUnit_Framework_TestCase +{ + use TestInheritanceTrait, ServiceManagerMockTrait; + + private $target = UpdateCrawlerConsoleControllerFactory::class; + + private $inheritance = [ FactoryInterface::class ]; + + private function setupContainer($isIndexRoute) + { + $application = $this->getMockBuilder(\Zend\Mvc\Application::class) + ->disableOriginalConstructor() + ->setMethods(['getMvcEvent']) + ->getMock(); + + $event = new MvcEvent(); + $routeMatch = new RouteMatch([]); + $routeMatch->setParam('action', $isIndexRoute ? 'index' : 'update'); + $event->setRouteMatch($routeMatch); + + $application->expects($this->once())->method('getMvcEvent')->willReturn($event); + + if ($isIndexRoute) { + $container = $this->getServiceManagerMock([ + 'Application' => ['service' => $application, 'count_get' => 1], + 'InputFilterManager' => ['service' => null, 'count_get' => 0], + ]); + + return $container; + + } + + $container = $this->getServiceManagerMock([ + 'Application' => ['service' => $application, 'count_get' => 1], + ]); + + $filter = new CrawlerInputFilter(); + $filters = $this->createPluginManagerMock([ + CrawlerInputFilter::class => ['service' => $filter, 'count_get' => 1] + ], $container); + + $container->setService('InputFilterManager', $filters); + $container->setExpectedCallCount('get', 'InputFilterManager', 1); + + return [$container, $filter]; + + } + + public function testCreatesServiceWithoutInjectingInputFilter() + { + $container = $this->setupContainer(true); + + $controller = $this->target->__invoke($container, 'irrelevant'); + + $this->assertInstanceOf(UpdateCrawlerConsoleController::class, $controller); + $this->assertAttributeEmpty('inputFilter', $controller); + } + + public function testCreatesServiceWithInputFilterInjection() + { + list($container, $filter) = $this->setupContainer(false); + + $controller = $this->target->__invoke($container, 'irrelevant'); + + $this->assertAttributeSame($filter, 'inputFilter', $controller); + } + +} diff --git a/test/SimpleImportTest/Filter/IdToEntityFactoryTest.php b/test/SimpleImportTest/Filter/IdToEntityFactoryTest.php new file mode 100644 index 0000000..c338bd0 --- /dev/null +++ b/test/SimpleImportTest/Filter/IdToEntityFactoryTest.php @@ -0,0 +1,89 @@ + + */ + +/** */ +namespace SimpleImportTest\Filter; + +use CoreTestUtils\TestCase\ServiceManagerMockTrait; +use CoreTestUtils\TestCase\TestInheritanceTrait; +use Doctrine\ODM\MongoDB\DocumentRepository; +use SimpleImport\Filter\IdToEntity; +use SimpleImport\Filter\IdToEntityFactory; +use Zend\ServiceManager\AbstractPluginManager; +use Zend\ServiceManager\Exception\ServiceNotCreatedException; +use Zend\ServiceManager\Factory\FactoryInterface; +use Zend\ServiceManager\ServiceManager; + +/** + * Tests for \SimpleImport\Filter\IdToEntityFactory + * + * @covers \SimpleImport\Filter\IdToEntityFactory + * @author Mathias Gelhausen + * + */ +class IdToEntityFactoryTest extends \PHPUnit_Framework_TestCase +{ + use TestInheritanceTrait, ServiceManagerMockTrait; + + /** + * @var string|IdToEntityFactory + */ + private $target = IdToEntityFactory::class; + + private $inheritance = [ FactoryInterface::class ]; + + public function testInvokationThrowsExceptionIfDocumentNameIsMissing() + { + $container = $this->getServiceManagerMock(); + + $this->expectException(ServiceNotCreatedException::class); + $this->expectExceptionMessage('Missing option "document"'); + + $this->target->__invoke($container, 'irrelevant'); + } + + public function notFoundValueProvider() + { + return [ + [null], + ['customNotFoundValue'], + ]; + + } + + /** + * @dataProvider notFoundValueProvider + * + * @param $notFoundValue + */ + public function testInvokationCreatesService($notFoundValue) + { + $documentName = 'TestEntityClass'; + $repository = $this->getMockBuilder(DocumentRepository::class)->disableOriginalConstructor()->getMock(); + + $container = $this->getServiceManagerMock(); + + $repositories = $this->getPluginManagerMock([ + $documentName => $repository, + ], $container); + + $container->setService('repositories', $repositories); + $container->setExpectedCallCount('get', 'repositories', 1); + + $options = [ + 'document' => $documentName, + 'not_found_value' => $notFoundValue + ]; + + $actual = $this->target->__invoke($container, 'irrelevant', $options); + + $this->assertInstanceOf(IdToEntity::class, $actual); + $this->assertAttributeEquals($notFoundValue, 'notFoundValue', $actual); + } +} diff --git a/test/SimpleImportTest/Filter/IdToEntityTest.php b/test/SimpleImportTest/Filter/IdToEntityTest.php new file mode 100644 index 0000000..1b952c7 --- /dev/null +++ b/test/SimpleImportTest/Filter/IdToEntityTest.php @@ -0,0 +1,181 @@ + + */ + +/** */ +namespace SimpleImportTest\Filter; + +use CoreTestUtils\TestCase\TestInheritanceTrait; +use CoreTestUtils\TestCase\TestSetterGetterTrait; +use Doctrine\ODM\MongoDB\DocumentRepository; +use SimpleImport\Filter\IdToEntity; +use Zend\Filter\FilterInterface; + +/** + * Tests for \SimpleImport\Filter\IdToEntity + * + * @covers \SimpleImport\Filter\IdToEntity + * @author Mathias Gelhausen + * + */ +class IdToEntityTest extends \PHPUnit_Framework_TestCase +{ + use TestInheritanceTrait, TestSetterGetterTrait; + + /** + * + * + * @var array|\PHPUnit_Framework_MockObject_MockObject|IdToEntity|\ReflectionClass + */ + private $target = [ + IdToEntity::class, + 'setupMocks', + '@testInheritance' => ['as_reflection' => true], + '@testSetterAndGetter' => [ 'mock' => ['__invoke'], 'args' => false ], + '@testInvokation' => [ 'mock' => ['filter'], 'args' => false ] + ]; + + private $inheritance = [ FilterInterface::class ]; + + private $properties = [ + [ 'notFoundValue', ['value' => 'customNotFoundValue', 'expect_property' => 'customNotFoundValue' ]], + ]; + + /** + * + * + * @var \PHPUnit_Framework_MockObject_MockObject|DocumentRepository + */ + private $repositoryMock; + + private function setupMocks() + { + $this->repositoryMock = $this->getMockBuilder(DocumentRepository::class) + ->disableOriginalConstructor() + ->setMethods(['find', 'getDocumentName']) + ->getMock(); + + return [$this->repositoryMock]; + } + /** + * @testdox Invokation proxies to filter + */ + public function testInvokation() + { + $value = 'testId'; + + $this->target->expects($this->once())->method('filter')->with($value); + + $this->target->__invoke($value); + } + + public function nullValuesProvider() + { + return [ + [null], + [[]], + [false], + [''], + [0], + ]; + } + + /** + * @dataProvider nullValuesProvider + * + * @param $value + */ + public function testFilterReturnsNullOnEmptyValues($value) + { + $this->repositoryMock->expects($this->never())->method('find'); + $this->repositoryMock->expects($this->never())->method('getDocumentName'); + + $this->assertNull($this->target->filter($value)); + } + + public function idValuesProvider() + { + return [ + ['testId'], + [new \MongoId()] + ]; + } + + /** + * @dataProvider idValuesProvider + * + * @param $value + */ + public function testFilterReturnsEntity($value) + { + $entity = new \stdClass; + + $this->repositoryMock->expects($this->once())->method('find')->with($value)->willReturn($entity); + $this->repositoryMock->expects($this->never())->method('getDocumentName'); + + $this->assertSame($entity, $this->target->filter($value)); + } + + /** + * @dataProvider idValuesProvider + * + * @param $value + */ + public function testFilterReturnsNotFoundValue($value) + { + if (!is_string($value)) { + $notFoundValue = 'testNotFoundValue'; + $this->target->setNotFoundValue($notFoundValue); + } else { + $notFoundValue = $value; + } + + $this->repositoryMock->expects($this->once())->method('find')->with($value)->willReturn(null); + + $this->assertEquals($notFoundValue, $this->target->filter($value)); + } + + public function testFilterReturnsValueIfItsAnEntity() + { + $entity = new \stdClass; + + $this->repositoryMock->expects($this->once())->method('getDocumentName')->willReturn(\stdClass::class); + $this->repositoryMock->expects($this->never())->method('find'); + + $this->assertSame($entity, $this->target->filter($entity)); + + } + + public function invalidValuesProvider() + { + $invalidObject = new \DateTime(); + + return [ + [['invalid']], + [$invalidObject], + [true], + [1234], + ]; + } + + /** + * @dataProvider invalidValuesProvider + * + * @param $value + */ + public function testFilterThrowsExceptionOnInvalidValues($value) + { + $this->repositoryMock->expects($this->never())->method('find'); + $this->repositoryMock->expects($this->exactly(2))->method('getDocumentName')->willReturn('testEntityClass'); + + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Value must either be a string or an instance of \MongoId or testEntityClass'); + + $this->target->filter($value); + } +} diff --git a/test/SimpleImportTest/InputFilter/CrawlerInputFilterTest.php b/test/SimpleImportTest/InputFilter/CrawlerInputFilterTest.php index 6d4674c..560366d 100644 --- a/test/SimpleImportTest/InputFilter/CrawlerInputFilterTest.php +++ b/test/SimpleImportTest/InputFilter/CrawlerInputFilterTest.php @@ -12,6 +12,7 @@ namespace SimpleImportTest\Hydrator; use CoreTestUtils\TestCase\TestInheritanceTrait; +use SimpleImport\Entity\Crawler; use SimpleImport\InputFilter\CrawlerInputFilter; use Zend\InputFilter\InputFilter; use Zend\Validator\ValidatorPluginManager; @@ -27,7 +28,11 @@ class CrawlerInputFilterTest extends \PHPUnit_Framework_TestCase /** * @var CrawlerInputFilter */ - private $target = CrawlerInputFilter::class; + private $target = [ + CrawlerInputFilter::class, + 'args' => false, + 'mock' => ['add'], + ]; /** * @see TestInheritanceTrait @@ -40,55 +45,111 @@ class CrawlerInputFilterTest extends \PHPUnit_Framework_TestCase * {@inheritDoc} * @see \PHPUnit_Framework_TestCase::setUp() */ - protected function setUp() - { - $this->setupTargetInstance(); - - $validatorMock = $this->getMockBuilder(AbstractValidator::class) - ->getMock(); - - $validatorPluginManager = $this->getMockBuilder(ValidatorPluginManager::class) - ->disableOriginalConstructor() - ->getMock(); - $validatorPluginManager->method('get') - ->will($this->returnValue($validatorMock)); - - $this->target->getFactory() - ->getDefaultValidatorChain() - ->setPluginManager($validatorPluginManager); - } - - /** - * @covers ::init() - */ - public function testHasInputFilters() - { - $this->target->init(); - - $this->assertTrue($this->target->has('name')); - $this->assertTrue($this->target->has('organization')); - $this->assertTrue($this->target->has('feedUri')); - $this->assertTrue($this->target->has('runDelay')); - $this->assertTrue($this->target->has('type')); - $this->assertTrue($this->target->has('options')); - } /** * @covers ::init() */ - public function testOptionsFilter() + public function testInitiatesCorrectInputs() { + $this->target->expects($this->exactly(6))->method('add') + ->withConsecutive( + [[ + 'name' => 'name', + 'filters' => [ + [ + 'name' => 'StringTrim' + ] + ] + ]], + [[ + 'name' => 'organization', + 'filters' => [ + [ + 'name' => \SimpleImport\Filter\IdToEntity::class, + 'options' => [ + 'document' => 'Organizations', + ], + ], + ], + 'validators' => [ + [ + 'name' => \SimpleImport\Validator\EntityExists::class, + 'options' => [ + 'entityClass' => \Organizations\Entity\Organization::class, + ], + ], + ], + ]], + [[ + 'name' => 'feedUri', + 'filters' => [ + [ + 'name' => 'StringTrim' + ] + ], + 'validators' => [ + [ + 'name' => 'Uri', + 'options' => [ + 'allowRelative' => false + ] + ] + ] + ]], + [[ + 'name' => 'runDelay', + 'required' => false, + 'validators' => [ + [ + 'name' => 'Digits', + ], + [ + 'name' => 'GreaterThan', + 'options' => [ + 'min' => 0, + 'inclusive' => true + ] + ] + ] + ]], + [[ + 'name' => 'type', + 'filters' => [ + [ + 'name' => 'StringTrim' + ] + ], + 'validators' => [ + [ + 'name' => 'InArray', + 'options' => [ + 'haystack' => Crawler::validTypes() + ] + ] + ] + ]], + [[ + 'name' => 'options', + 'required' => false, + 'filters' => [ + [ + 'name' => 'Callback', + 'options' => [ + 'callback' => 'array_filter' + ] + ] + ], + 'validators' => [ + [ + 'name' => \SimpleImport\Validator\CrawlerOptions::class, + ] + ] + ]] + ) + ->will($this->returnSelf()); $this->target->init(); - $options = [ - 'someKey' => 'someValue' - ]; - $this->target->setData([ - 'options' => array_merge($options, [ - 'emptyValue' => null - ]) - ]); - $this->assertSame($options, $this->target->getValue('options')); + } } diff --git a/test/SimpleImportTest/Validator/EntityExistsTest.php b/test/SimpleImportTest/Validator/EntityExistsTest.php new file mode 100644 index 0000000..1f94b34 --- /dev/null +++ b/test/SimpleImportTest/Validator/EntityExistsTest.php @@ -0,0 +1,128 @@ + + */ + +/** */ +namespace SimpleImportTest\Validator; + +use CoreTestUtils\TestCase\TestInheritanceTrait; +use CoreTestUtils\TestCase\TestSetterGetterTrait; +use SimpleImport\Validator\EntityExists; +use Zend\Validator\AbstractValidator; + +/** + * Tests for \SimpleImport\Validator\EntityExists + * + * @covers \SimpleImport\Validator\EntityExists + * @author Mathias Gelhausen + * + */ +class EntityExistsTest extends \PHPUnit_Framework_TestCase +{ + use TestInheritanceTrait, TestSetterGetterTrait; + + /** + * + * + * @var array|\ReflectionClass|EntityExists|null + */ + private $target = [ + EntityExists::class, + '@testInheritance' => [ 'as_reflection' => true ], + '@testConstruct' => false, + ]; + + private $inheritance = [ AbstractValidator::class ]; + + public function propertiesProvider() + { + return [ + ['entityClass', ['value' => ['invalid'], 'setter_exception' => [\InvalidArgumentException::class, 'Entity class must be given']]], + ['entityClass', ['value' => new \stdClass, 'expect_property' => \stdClass::class]], + ['entityClass', ['value' => 'entityClass', 'expect_property' => 'entityClass']], + ]; + } + + public function optionsProvider() + { + return [ + [null], + ['entityClass'], + [['entityClass' => 'entityClass']], + ]; + } + + /** + * @dataProvider optionsProvider + * + * @param $options + * @covers \SimpleImport\Validator\EntityExists::__construct + */ + public function testConstruct($options) + { + $actual = new EntityExists($options); + + $this->assertInstanceOf(EntityExists::class, $actual); + } + + public function testIsValidThrowsExceptionIfNoEntityClassIsSet() + { + $this->expectException(\RuntimeException::class); + $this->expectExceptionMessage('entity class must be set'); + + $this->target->isValid('testId'); + } + + public function testIsValidReturnsTrue() + { + $this->target->setEntityClass(\stdClass::class); + + $this->assertTrue($this->target->isValid(new \stdClass)); + } + + public function invalidValuesProvider() + { + return [ + [1234], + [new \stdClass], + [new EntityWithToStringMethod], + ]; + } + + /** + * @dataProvider invalidValuesProvider + * + * @param string|object $value + */ + public function testIsValidReturnsFalseAndSetErrorMessage($value = 'Fehler') + { + $this->target->setEntityClass('NonExistentClass'); + + if (is_object($value)) { + $expectedValue = method_exists($value, '__toString') ? (string) $value : '[object]'; + } else { + $expectedValue = (string) $value; + } + + $this->assertFalse($this->target->isValid($value)); + $messages = $this->target->getMessages(); + + $this->assertArrayHasKey(EntityExists::NOT_EXIST, $messages); + $this->assertContains("NonExistentClass with ID '$expectedValue'", $messages[EntityExists::NOT_EXIST]); + } + + +} + +class EntityWithToStringMethod +{ + public function __toString() + { + return 'string'; + } +}