diff --git a/src/Application/Controller/Admin/AdminOrder.php b/src/Application/Controller/Admin/AdminOrder.php index d9d70c3..34a5b86 100644 --- a/src/Application/Controller/Admin/AdminOrder.php +++ b/src/Application/Controller/Admin/AdminOrder.php @@ -21,13 +21,19 @@ use D3\Linkmobility4OXID\Application\Model\MessageTypes\Sms; use D3\Linkmobility4OXID\Application\Model\OrderRecipients; use D3\LinkmobilityClient\Response\ResponseInterface; use D3\LinkmobilityClient\ValueObject\Recipient; -use Exception; +use D3\TestingTools\Production\IsMockable; +use InvalidArgumentException; use OxidEsales\Eshop\Application\Controller\Admin\AdminController; use OxidEsales\Eshop\Application\Model\Order; +use OxidEsales\Eshop\Core\Language; use OxidEsales\Eshop\Core\Registry; +use OxidEsales\Eshop\Core\Request; +use OxidEsales\Eshop\Core\UtilsView; class AdminOrder extends AdminController { + use IsMockable; + protected $_sThisTemplate = 'd3adminorder.tpl'; /** @@ -42,12 +48,13 @@ class AdminOrder extends AdminController public function __construct() { - $this->order = $order = oxNew(Order::class); + $this->order = $order = $this->d3GetMockableOxNewObject(Order::class); $order->load($this->getEditObjectId()); $this->addTplParam('recipient', $this->getRecipientFromCurrentOrder()); parent::__construct(); + } /** @@ -56,48 +63,82 @@ class AdminOrder extends AdminController public function getRecipientFromCurrentOrder() { try { - return oxNew(OrderRecipients::class, $this->order)->getSmsRecipient(); + return $this->d3GetMockableOxNewObject(OrderRecipients::class, $this->order)->getSmsRecipient(); } catch (noRecipientFoundException $e) { /** @var string $message */ - $message = Registry::getLang()->translateString($e->getMessage()); - Registry::getUtilsView()->addErrorToDisplay($message); + $message = $this->d3GetMockableRegistryObject(Language::class)->translateString($e->getMessage()); + $this->d3GetMockableRegistryObject(UtilsView::class)->addErrorToDisplay($message); } return false; } /** * @return void - * @throws Exception */ public function send(): void { - $messageBody = Registry::getRequest()->getRequestEscapedParameter('messagebody'); - - if (false === is_string($messageBody) || strlen($messageBody) <= 1) { - /** @var string $message */ - $message = Registry::getLang()->translateString('D3LM_EXC_MESSAGE_NO_LENGTH'); - Registry::getUtilsView()->addErrorToDisplay($message); - return; - } - - $order = oxNew(Order::class); - $order->load($this->getEditObjectId()); + $utilsView = $this->d3GetMockableRegistryObject(UtilsView::class); try { - $sms = oxNew(Sms::class, $messageBody); - if ($sms->sendOrderMessage($order)) { - $smsCount = $sms->getResponse() ? $sms->getResponse()->getSmsCount() : 0; - Registry::getUtilsView()->addErrorToDisplay( - oxNew(successfullySentException::class, $smsCount) - ); - } else { - $errorMsg = $sms->getResponse() instanceof ResponseInterface ? $sms->getResponse()->getErrorMessage() : 'no response'; - /** @var string $format */ - $format = Registry::getLang()->translateString('D3LM_EXC_MESSAGE_UNEXPECTED_ERR_SEND'); - Registry::getUtilsView()->addErrorToDisplay(sprintf($format, $errorMsg)); - } - } catch (noRecipientFoundException $e) { - Registry::getUtilsView()->addErrorToDisplay($e); + $utilsView->addErrorToDisplay($this->sendMessage()); + } catch (noRecipientFoundException|InvalidArgumentException $e) { + $utilsView->addErrorToDisplay($e); } } + + /** + * @return string + * @throws InvalidArgumentException + */ + protected function getMessageBody(): string + { + $messageBody = $this->d3GetMockableRegistryObject(Request::class) + ->getRequestEscapedParameter('messagebody'); + + if (false === is_string($messageBody) || strlen(trim($messageBody)) <= 1) { + throw $this->d3GetMockableOxNewObject( + InvalidArgumentException::class, + Registry::getLang()->translateString('D3LM_EXC_MESSAGE_NO_LENGTH') + ); + } + + return $messageBody; + } + + /** + * @return string + * @throws noRecipientFoundException + */ + protected function sendMessage(): string + { + $order = $this->d3GetMockableOxNewObject(Order::class); + $order->load($this->getEditObjectId()); + + $sms = $this->d3GetMockableOxNewObject(Sms::class, $this->getMessageBody()); + return $sms->sendOrderMessage($order) ? + (string) $this->getSuccessSentMessage($sms) : + $this->getUnsuccessfullySentMessage($sms); + } + + /** + * @param Sms $sms + * @return successfullySentException + */ + protected function getSuccessSentMessage(Sms $sms): successfullySentException + { + $smsCount = $sms->getResponse() ? $sms->getResponse()->getSmsCount() : 0; + return $this->d3GetMockableOxNewObject(successfullySentException::class, $smsCount); + } + + /** + * @param Sms $sms + * @return string + */ + protected function getUnsuccessfullySentMessage(Sms $sms): string + { + $errorMsg = $sms->getResponse() instanceof ResponseInterface ? $sms->getResponse()->getErrorMessage() : 'no response'; + /** @var string $format */ + $format = Registry::getLang()->translateString('D3LM_EXC_MESSAGE_UNEXPECTED_ERR_SEND'); + return sprintf($format, $errorMsg); + } } diff --git a/src/tests/.gitignore b/src/tests/.gitignore new file mode 100644 index 0000000..817366c --- /dev/null +++ b/src/tests/.gitignore @@ -0,0 +1,2 @@ +.phpunit.result.cache +reports diff --git a/src/tests/README.md b/src/tests/README.md new file mode 100644 index 0000000..bb1c169 --- /dev/null +++ b/src/tests/README.md @@ -0,0 +1,52 @@ +# D3 Linkmobility4OXID Tests + +## Requirements + +Both unit and acceptance tests require OXID Testing Library installed. +See https://github.com/OXID-eSales/testing_library. + +### Configuration + +Please install the packages listed in the composer.json in "require-dev". Unfortunately Composer does not provide an automatic installation. + +Here is an example of Testing Library configuration file `oxideshop/test_config.yml` + +``` +# This file is auto-generated during the composer install +mandatory_parameters: + shop_path: /var/www/oxideshop/source + shop_tests_path: /var/www/oxideshop/tests + partial_module_paths: d3/linkmobility +optional_parameters: + shop_url: null + shop_serial: '' + enable_varnish: false + is_subshop: false + install_shop: false + remote_server_dir: null + shop_setup_path: null + restore_shop_after_tests_suite: false + test_database_name: null + restore_after_acceptance_tests: false + restore_after_unit_tests: false + tmp_path: /tmp/oxid_test_library/ + database_restoration_class: DatabaseRestorer + activate_all_modules: false + run_tests_for_shop: false + run_tests_for_modules: true + screen_shots_path: null + screen_shots_url: null + browser_name: firefox + selenium_server_ip: 127.0.0.1 + selenium_server_port: '4444' + additional_test_paths: null +``` + +## Unit Tests + +To execute unit tests run the following: + +``` +cd /var/www/oxideshop/ +PHPBIN=/usr/bin/php7.4 ./vendor/bin/runtests +``` diff --git a/src/tests/additional.inc.php b/src/tests/additional.inc.php new file mode 100644 index 0000000..c82ae12 --- /dev/null +++ b/src/tests/additional.inc.php @@ -0,0 +1,25 @@ + + * @link https://www.oxidmodule.com + */ + +declare(strict_types=1); + +namespace D3\Linkmobility4OXID\tests; + +use D3\ModCfg\Tests\additional_abstract; +use OxidEsales\Eshop\Core\Exception\StandardException; + +class additional extends additional_abstract +{ +} + +oxNew(additional::class); diff --git a/src/tests/phpunit.xml b/src/tests/phpunit.xml new file mode 100644 index 0000000..1bdc0ce --- /dev/null +++ b/src/tests/phpunit.xml @@ -0,0 +1,28 @@ + + + + ../Application + ../Modules + ../Setup + + ../Application/views + + + + + + + diff --git a/src/tests/unit/Application/Controller/Admin/AdminOrderTest.php b/src/tests/unit/Application/Controller/Admin/AdminOrderTest.php new file mode 100644 index 0000000..c1e7843 --- /dev/null +++ b/src/tests/unit/Application/Controller/Admin/AdminOrderTest.php @@ -0,0 +1,503 @@ + + * @link https://www.oxidmodule.com + */ + +declare(strict_types=1); + +namespace D3\Linkmobility4OXID\tests\unit\Application\Controller\Admin; + +use D3\Linkmobility4OXID\Application\Controller\Admin\AdminOrder; +use D3\Linkmobility4OXID\Application\Model\Exceptions\noRecipientFoundException; +use D3\Linkmobility4OXID\Application\Model\Exceptions\successfullySentException; +use D3\Linkmobility4OXID\Application\Model\MessageTypes\Sms; +use D3\Linkmobility4OXID\Application\Model\OrderRecipients; +use D3\LinkmobilityClient\SMS\Response; +use D3\LinkmobilityClient\ValueObject\Recipient; +use D3\TestingTools\Development\CanAccessRestricted; +use InvalidArgumentException; +use OxidEsales\Eshop\Application\Model\Order; +use OxidEsales\Eshop\Core\Registry; +use OxidEsales\Eshop\Core\Request; +use OxidEsales\Eshop\Core\UtilsView; +use OxidEsales\TestingLibrary\UnitTestCase; +use PHPUnit\Framework\MockObject\MockObject; +use ReflectionException; + +class AdminOrderTest extends UnitTestCase +{ + use CanAccessRestricted; + + /** + * @test + * @return void + * @throws ReflectionException + * @covers \D3\Linkmobility4OXID\Application\Controller\Admin\AdminOrder::__construct + */ + public function canConstruct() + { + /** @var Order|MockObject $orderMock */ + $orderMock = $this->getMockBuilder(Order::class) + ->onlyMethods(['load']) + ->getMock(); + $orderMock->method('load')->willReturn(true); + + /** @var Recipient|MockObject $recipientMock */ + $recipientMock = $this->getMockBuilder(Recipient::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var AdminOrder|MockObject $sut */ + $sut = $this->getMockBuilder(AdminOrder::class) + ->disableOriginalConstructor() + ->onlyMethods(['d3GetMockableOxNewObject', 'getEditObjectId', 'getRecipientFromCurrentOrder']) + ->getMock(); + $sut->method('d3GetMockableOxNewObject')->willReturnCallback( + function () use ($orderMock) { + $args = func_get_args(); + switch ($args[0]) { + case Order::class: + return $orderMock; + default: + return call_user_func_array("oxNew", $args); + } + } + ); + $sut->method('getEditObjectId')->willReturn('editObjId'); + $sut->method('getRecipientFromCurrentOrder')->willReturn($recipientMock); + + $this->callMethod( + $sut, + '__construct' + ); + + $this->assertSame( + $orderMock, + $this->getValue( + $sut, + 'order' + ) + ); + + $this->assertSame( + $recipientMock, + $this->callMethod( + $sut, + 'getViewDataElement', + ['recipient'] + ) + ); + } + + /** + * @test + * @return void + * @throws ReflectionException + * @covers \D3\Linkmobility4OXID\Application\Controller\Admin\AdminOrder::getRecipientFromCurrentOrder + */ + public function canGetRecipientFromCurrentOrderPassed() + { + /** @var Recipient|MockObject $recipientMock */ + $recipientMock = $this->getMockBuilder(Recipient::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var OrderRecipients|MockObject $orderRecipientsMock */ + $orderRecipientsMock = $this->getMockBuilder(OrderRecipients::class) + ->disableOriginalConstructor() + ->onlyMethods(['getSmsRecipient']) + ->getMock(); + $orderRecipientsMock->method('getSmsRecipient')->willReturn($recipientMock); + + /** @var AdminOrder|MockObject $sut */ + $sut = $this->getMockBuilder(AdminOrder::class) + ->onlyMethods(['d3GetMockableOxNewObject']) + ->disableOriginalConstructor() + ->getMock(); + $sut->method('d3GetMockableOxNewObject')->willReturnCallback( + function () use ($orderRecipientsMock) { + $args = func_get_args(); + switch ($args[0]) { + case OrderRecipients::class: + return $orderRecipientsMock; + default: + return call_user_func_array("oxNew", $args); + } + } + ); + $this->setValue( + $sut, + 'order', + oxNew(Order::class) + ); + + $this->assertSame( + $recipientMock, + $this->callMethod( + $sut, + 'getRecipientFromCurrentOrder' + ) + ); + } + + /** + * @test + * @return void + * @throws ReflectionException + * @covers \D3\Linkmobility4OXID\Application\Controller\Admin\AdminOrder::getRecipientFromCurrentOrder + */ + public function canGetRecipientFromCurrentOrderThrowsException() + { + /** @var UtilsView|MockObject $utilsViewMock */ + $utilsViewMock = $this->getMockBuilder(UtilsView::class) + ->onlyMethods(['addErrorToDisplay']) + ->getMock(); + $utilsViewMock->expects($this->once())->method('addErrorToDisplay'); + + /** @var OrderRecipients|MockObject $orderRecipientsMock */ + $orderRecipientsMock = $this->getMockBuilder(OrderRecipients::class) + ->disableOriginalConstructor() + ->onlyMethods(['getSmsRecipient']) + ->getMock(); + $orderRecipientsMock->method('getSmsRecipient')->willThrowException( + oxNew(noRecipientFoundException::class) + ); + + /** @var AdminOrder|MockObject $sut */ + $sut = $this->getMockBuilder(AdminOrder::class) + ->onlyMethods(['d3GetMockableOxNewObject', 'd3GetMockableRegistryObject']) + ->disableOriginalConstructor() + ->getMock(); + $sut->method('d3GetMockableOxNewObject')->willReturnCallback( + function () use ($orderRecipientsMock) { + $args = func_get_args(); + switch ($args[0]) { + case OrderRecipients::class: + return $orderRecipientsMock; + default: + return call_user_func_array("oxNew", $args); + } + } + ); + $sut->method('d3GetMockableRegistryObject')->willReturnCallback( + function () use ($utilsViewMock) { + $args = func_get_args(); + switch ($args[0]) { + case UtilsView::class: + return $utilsViewMock; + default: + return Registry::get($args[0]); + } + } + ); + $this->setValue( + $sut, + 'order', + oxNew(Order::class) + ); + + $this->assertFalse( + $this->callMethod( + $sut, + 'getRecipientFromCurrentOrder' + ) + ); + } + + /** + * @test + * @param $throwsException + * @return void + * @throws ReflectionException + * @covers \D3\Linkmobility4OXID\Application\Controller\Admin\AdminOrder::send + * @dataProvider canSendDataProvider + */ + public function canSend($throwsException) + { + /** @var UtilsView|MockObject $utilsViewMock */ + $utilsViewMock = $this->getMockBuilder(UtilsView::class) + ->onlyMethods(['addErrorToDisplay']) + ->getMock(); + $utilsViewMock->expects($this->once())->method('addErrorToDisplay'); + + /** @var AdminOrder|MockObject $sut */ + $sut = $this->getMockBuilder(AdminOrder::class) + ->onlyMethods(['d3GetMockableRegistryObject', 'sendMessage']) + ->disableOriginalConstructor() + ->getMock(); + $sut->method('d3GetMockableRegistryObject')->willReturnCallback( + function () use ($utilsViewMock) { + $args = func_get_args(); + switch ($args[0]) { + case UtilsView::class: + return $utilsViewMock; + default: + return Registry::get($args[0]); + } + } + ); + $sut->method('sendMessage')->will( + $throwsException ? + $this->throwException(oxNew(noRecipientFoundException::class)) : + $this->returnValue('successfully sent message') + ); + + $this->callMethod( + $sut, + 'send' + ); + } + + /** + * @return array + */ + public function canSendDataProvider(): array + { + return [ + 'can send message' => [false], + 'can not send message' => [true], + ]; + } + + /** + * @param $message + * @param $expectException + * @param $expected + * @test + * @return void + * @throws ReflectionException + * @covers \D3\Linkmobility4OXID\Application\Controller\Admin\AdminOrder::getMessageBody + * @dataProvider canGetMessageBodyDataProvider + */ + public function canGetMessageBody($message, $expectException, $expected) + { + /** @var Request|MockObject $requestMock */ + $requestMock = $this->getMockBuilder(Request::class) + ->onlyMethods(['getRequestEscapedParameter']) + ->getMock(); + $requestMock->method('getRequestEscapedParameter')->willReturn($message); + + /** @var Request|MockObject $sut */ + $sut = $this->getMockBuilder(AdminOrder::class) + ->onlyMethods(['d3GetMockableRegistryObject']) + ->disableOriginalConstructor() + ->getMock(); + $sut->method('d3GetMockableRegistryObject')->willReturnCallback( + function () use ($requestMock) { + $args = func_get_args(); + switch ($args[0]) { + case Request::class: + return $requestMock; + default: + return Registry::get($args[0]); + } + } + ); + + if ($expectException) { + $this->expectException(InvalidArgumentException::class); + } + + $this->assertSame( + $expected, + $this->callMethod( + $sut, + 'getMessageBody' + ) + ); + } + + /** + * @return array[] + */ + public function canGetMessageBodyDataProvider(): array + { + return [ + 'message not string' => [[], true, ''], + 'message empty string' => ['', true, ''], + 'message whitespace string' => [' ', true, ''], + 'message right string' => ['messagefixture', false, 'messagefixture'], + ]; + } + + /** + * @test + * @param $canSendOrderMessage + * @return void + * @throws ReflectionException + * @covers \D3\Linkmobility4OXID\Application\Controller\Admin\AdminOrder::sendMessage + * @dataProvider canSendMessageDataProvider + */ + public function canSendMessage($canSendOrderMessage) + { + /** @var Order|MockObject $orderMock */ + $orderMock = $this->getMockBuilder(Order::class) + ->onlyMethods(['load']) + ->getMock(); + $orderMock->method('load')->willReturn(true); + + /** @var Sms|MockObject $smsMock */ + $smsMock = $this->getMockBuilder(Sms::class) + ->disableOriginalConstructor() + ->onlyMethods(['sendOrderMessage']) + ->getMock(); + $smsMock->expects($this->once())->method('sendOrderMessage')->willReturn($canSendOrderMessage); + + /** @var AdminOrder|MockObject $sut */ + $sut = $this->getMockBuilder(AdminOrder::class) + ->disableOriginalConstructor() + ->onlyMethods(['d3GetMockableOxNewObject', 'getMessageBody', 'getSuccessSentMessage', 'getUnsuccessfullySentMessage']) + ->getMock(); + $sut->method('d3GetMockableOxNewObject')->willReturnCallback( + function () use ($orderMock, $smsMock) { + $args = func_get_args(); + switch ($args[0]) { + case Order::class: + return $orderMock; + case Sms::class: + return $smsMock; + default: + return call_user_func_array("oxNew", $args); + } + } + ); + $sut->method('getMessageBody')->willReturn('messageBodyFixture'); + $sut->expects($this->exactly((int) $canSendOrderMessage))->method('getSuccessSentMessage') + ->willReturn(oxNew(successfullySentException::class, 'expectedReturn')); + $sut->expects($this->exactly((int) !$canSendOrderMessage))->method('getUnsuccessfullySentMessage') + ->willReturn('expectedReturn'); + + $this->assertIsString( + $this->callMethod( + $sut, + 'sendMessage' + ) + ); + } + + /** + * @return array + */ + public function canSendMessageDataProvider(): array + { + return [ + 'send order message' => [true], + 'dont send order message' => [false] + ]; + } + + /** + * @test + * @param $hasResponse + * @return void + * @throws ReflectionException + * @covers \D3\Linkmobility4OXID\Application\Controller\Admin\AdminOrder::getSuccessSentMessage + * @dataProvider canGetSuccessSentMessageDataProvider + */ + public function canGetSuccessSentMessage($hasResponse) + { + /** @var successfullySentException|MockObject $successfullySendExceptionMock */ + $successfullySendExceptionMock = $this->getMockBuilder(successfullySentException::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var Response|MockObject $resonseMock */ + $resonseMock = $this->getMockBuilder(Response::class) + ->onlyMethods(['getSmsCount']) + ->disableOriginalConstructor() + ->getMock(); + $resonseMock->expects($hasResponse ? $this->once() : $this->never())->method('getSmsCount') + ->willReturn(20); + + /** @var Sms|MockObject $smsMock */ + $smsMock = $this->getMockBuilder(Sms::class) + ->disableOriginalConstructor() + ->onlyMethods(['getResponse']) + ->getMock(); + $smsMock->method('getResponse')->willReturn($hasResponse ? $resonseMock : null); + + /** @var AdminOrder|MockObject $sut */ + $sut = $this->getMockBuilder(AdminOrder::class) + ->disableOriginalConstructor() + ->onlyMethods(['d3GetMockableOxNewObject']) + ->getMock(); + $sut->method('d3GetMockableOxNewObject')->willReturnCallback( + function () use ($successfullySendExceptionMock) { + $args = func_get_args(); + switch ($args[0]) { + case successfullySentException::class: + return $successfullySendExceptionMock; + default: + return call_user_func_array("oxNew", $args); + } + } + ); + + $this->assertSame( + $successfullySendExceptionMock, + $this->callMethod( + $sut, + 'getSuccessSentMessage', + [$smsMock] + ) + ); + } + + /** + * @return array + */ + public function canGetSuccessSentMessageDataProvider(): array + { + return [ + 'has response' => [true], + 'has no response' => [false], + ]; + } + + /** + * @test + * @param $hasResponse + * @return void + * @throws ReflectionException + * @covers \D3\Linkmobility4OXID\Application\Controller\Admin\AdminOrder::getUnsuccessfullySentMessage + * @dataProvider canGetSuccessSentMessageDataProvider + */ + public function canGetUnsuccessfullySentMessage($hasResponse) + { + /** @var Response|MockObject $resonseMock */ + $resonseMock = $this->getMockBuilder(Response::class) + ->onlyMethods(['getErrorMessage']) + ->disableOriginalConstructor() + ->getMock(); + $resonseMock->expects($hasResponse ? $this->once() : $this->never())->method('getErrorMessage') + ->willReturn('errorMessage'); + + /** @var Sms|MockObject $smsMock */ + $smsMock = $this->getMockBuilder(Sms::class) + ->disableOriginalConstructor() + ->onlyMethods(['getResponse']) + ->getMock(); + $smsMock->method('getResponse')->willReturn($hasResponse ? $resonseMock : null); + + /** @var AdminOrder|MockObject $sut */ + $sut = $this->getMockBuilder(AdminOrder::class) + ->disableOriginalConstructor() + ->getMock(); + + $this->assertIsString( + $this->callMethod( + $sut, + 'getUnsuccessfullySentMessage', + [$smsMock] + ) + ); + } +} \ No newline at end of file