From 952fa853391e240cc97b0868e18a2fe96b5c875c Mon Sep 17 00:00:00 2001 From: Oliver Hader <oliver@typo3.org> Date: Mon, 27 Mar 2017 18:28:02 +0200 Subject: [PATCH] [TASK] Integrate tests for DataHandler hook invocations Change-Id: I69beabad3e4b9419778d1ed7d1b181349089b139 Resolves: #80494 Releases: master Reviewed-on: https://review.typo3.org/52190 Tested-by: TYPO3com <no-reply@typo3.com> Reviewed-by: Thomas Hohn <thomas@hohn.dk> Tested-by: Thomas Hohn <thomas@hohn.dk> Reviewed-by: Wouter Wolters <typo3@wouterwolters.nl> Tested-by: Wouter Wolters <typo3@wouterwolters.nl> --- .../DataSet/LiveDefaultElements.csv | 25 ++ .../DataHandler/DataSet/LiveDefaultPages.csv | 6 + .../DataHandler/Fixtures/HookFixture.php | 189 +++++++++ .../DataHandling/DataHandler/HookTest.php | 363 ++++++++++++++++++ .../DataHandling/Framework/ActionService.php | 4 +- 5 files changed, 586 insertions(+), 1 deletion(-) create mode 100644 typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/DataSet/LiveDefaultElements.csv create mode 100644 typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/DataSet/LiveDefaultPages.csv create mode 100644 typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/Fixtures/HookFixture.php create mode 100644 typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/HookTest.php diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/DataSet/LiveDefaultElements.csv b/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/DataSet/LiveDefaultElements.csv new file mode 100644 index 000000000000..77811d69fb31 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/DataSet/LiveDefaultElements.csv @@ -0,0 +1,25 @@ +"sys_language" +,"uid","pid","hidden","title","flag" +,1,0,0,"Dansk","dk" +,2,0,0,"Deutsch","de" +"sys_category" +,"uid","pid","sorting","deleted","sys_language_uid","l10n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parent","items","l10n_diffsource","description" +,28,0,256,0,0,0,0,0,0,0,0,0,"Category A",0,0,, +,29,0,512,0,0,0,0,0,0,0,0,0,"Category B",0,0,, +,30,0,768,0,0,0,0,0,0,0,0,0,"Category C",0,0,, +,31,0,1024,0,0,0,0,0,0,0,0,0,"Category A.A",28,0,, +"sys_category_record_mm" +,"uid_local","uid_foreign","tablenames","sorting","sorting_foreign","fieldname" +,28,297,"tt_content",0,1,"categories" +,29,297,"tt_content",0,2,"categories" +,29,298,"tt_content",0,1,"categories" +,30,298,"tt_content",0,2,"categories" +"tt_content" +,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","header","image","categories","tx_irretutorial_1nff_hotels" +,297,89,256,0,0,0,0,0,0,0,0,0,"Regular Element #1",0,2,0 +,298,89,512,0,0,0,0,0,0,0,0,0,"Regular Element #2",0,2,0 +"tx_irretutorial_1nff_hotel" +,"uid","pid","sorting","deleted","sys_language_uid","l18n_parent","l18n_diffsource","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title","parentid","parenttable","parentidentifier","offers" +,3,89,1,0,0,0,,0,0,0,0,0,0,"Hotel #1",297,"tt_content",,0 +,4,89,2,0,0,0,,0,0,0,0,0,0,"Hotel #2",297,"tt_content",,0 +,5,89,1,0,0,0,,0,0,0,0,0,0,"Hotel #1",298,"tt_content",,0 diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/DataSet/LiveDefaultPages.csv b/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/DataSet/LiveDefaultPages.csv new file mode 100644 index 000000000000..1d91e2c6c795 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/DataSet/LiveDefaultPages.csv @@ -0,0 +1,6 @@ +"pages" +,"uid","pid","sorting","deleted","t3_origuid","t3ver_wsid","t3ver_state","t3ver_stage","t3ver_oid","t3ver_move_id","title" +,1,0,256,0,0,0,0,0,0,0,"FunctionalTest" +,88,1,256,0,0,0,0,0,0,0,"DataHandlerTest" +,89,88,256,0,0,0,0,0,0,0,"Relations" +,90,88,512,0,0,0,0,0,0,0,"Target" diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/Fixtures/HookFixture.php b/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/Fixtures/HookFixture.php new file mode 100644 index 000000000000..f1ed327dc195 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/Fixtures/HookFixture.php @@ -0,0 +1,189 @@ +<?php +declare(strict_types=1); +namespace TYPO3\CMS\Core\Tests\Functional\DataHandling\DataHandler\Fixtures; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Core\DataHandling\DataHandler; +use TYPO3\CMS\Core\SingletonInterface; + +/** + * Class for testing execution of DataHandler hook invocations. + */ +class HookFixture implements SingletonInterface +{ + /** + * @var array[] + */ + protected $invocations = []; + + /** + * Purges the state of this singleton instance + */ + public function purge() + { + $this->invocations = []; + } + + /** + * @param string $methodName + * @return array|null + */ + public function findInvocationsByMethodName(string $methodName) + { + return $this->invocations[$methodName] ?? null; + } + + /** + * @param DataHandler $dataHandler + */ + public function processDatamap_beforeStart(DataHandler $dataHandler) + { + $this->invocations[__FUNCTION__][] = true; + } + + /** + * @param array $fieldArray + * @param string $table + * @param string|int $id + * @param DataHandler $dataHandler + */ + public function processDatamap_preProcessFieldArray(array $fieldArray, string $table, $id, DataHandler $dataHandler) + { + $this->invocations[__FUNCTION__][] = [ + 'fieldArray' => $fieldArray, + 'table' => $table, + 'id' => $id, + ]; + } + + /** + * @param string $status + * @param string $table + * @param string|int $id + * @param array $fieldArray + * @param DataHandler $dataHandler + */ + public function processDatamap_postProcessFieldArray(string $status, string $table, $id, array $fieldArray, DataHandler $dataHandler) + { + $this->invocations[__FUNCTION__][] = [ + 'status' => $status, + 'table' => $table, + 'id' => $id, + 'fieldArray' => $fieldArray, + ]; + } + + /** + * @param string $status + * @param string $table + * @param string|int $id + * @param array $fieldArray + * @param DataHandler $dataHandler + */ + public function processDatamap_afterDatabaseOperations(string $status, string $table, $id, array $fieldArray, DataHandler $dataHandler) + { + $this->invocations[__FUNCTION__][] = [ + 'status' => $status, + 'table' => $table, + 'id' => $id, + 'fieldArray' => $fieldArray, + ]; + } + + /** + * @param DataHandler $dataHandler + */ + public function processDatamap_afterAllOperations(DataHandler $dataHandler) + { + $this->invocations[__FUNCTION__][] = true; + } + + /** + * @param DataHandler $dataHandler + */ + public function processCmdmap_beforeStart(DataHandler $dataHandler) + { + $this->invocations[__FUNCTION__][] = true; + } + + /** + * @param string $command + * @param string $table + * @param string|int $id + * @param mixed $value + * @param DataHandler $dataHandler + * @param bool|string $pasteUpdate + */ + public function processCmdmap_preProcess(string $command, string $table, $id, $value, DataHandler $dataHandler, $pasteUpdate) + { + $this->invocations[__FUNCTION__][] = [ + 'command' => $command, + 'table' => $table, + 'id' => $id, + 'value' => $value, + 'pasteUpdate' => $pasteUpdate, + ]; + } + + /** + * @param string $command + * @param string $table + * @param string|int $id + * @param mixed $value + * @param bool $commandIsProcessed + * @param DataHandler $dataHandler + * @param bool|string $pasteUpdate + */ + public function processCmdmap(string $command, string $table, $id, $value, bool $commandIsProcessed, DataHandler $dataHandler, $pasteUpdate) + { + $this->invocations[__FUNCTION__][] = [ + 'command' => $command, + 'table' => $table, + 'id' => $id, + 'value' => $value, + 'commandIsProcessed' => $commandIsProcessed, + 'pasteUpdate' => $pasteUpdate, + ]; + } + + /** + * @param string $command + * @param string $table + * @param string|int $id + * @param mixed $value + * @param DataHandler $dataHandler + * @param bool|string $pasteUpdate + * @param bool|string $pasteDatamap + */ + public function processCmdmap_postProcess(string $command, string $table, $id, $value, DataHandler $dataHandler, $pasteUpdate, $pasteDatamap) + { + $this->invocations[__FUNCTION__][] = [ + 'command' => $command, + 'table' => $table, + 'id' => $id, + 'value' => $value, + 'pasteUpdate' => $pasteUpdate, + 'pasteDatamap' => $pasteDatamap, + ]; + } + + /** + * @param DataHandler $dataHandler + */ + public function processCmdmap_afterFinish(DataHandler $dataHandler) + { + $this->invocations[__FUNCTION__][] = true; + } +} diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/HookTest.php b/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/HookTest.php new file mode 100644 index 000000000000..6c72ea72ea49 --- /dev/null +++ b/typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/HookTest.php @@ -0,0 +1,363 @@ +<?php +declare(strict_types=1); +namespace TYPO3\CMS\Core\Tests\Functional\DataHandling\DataHandler; + +/* + * This file is part of the TYPO3 CMS project. + * + * It is free software; you can redistribute it and/or modify it under + * the terms of the GNU General Public License, either version 2 + * of the License, or any later version. + * + * For the full copyright and license information, please read the + * LICENSE.txt file that was distributed with this source code. + * + * The TYPO3 project - inspiring people to share! + */ + +use TYPO3\CMS\Core\Tests\Functional\DataHandling\AbstractDataHandlerActionTestCase; +use TYPO3\CMS\Core\Tests\Functional\DataHandling\DataHandler\Fixtures\HookFixture; +use TYPO3\CMS\Core\Utility\GeneralUtility; +use TYPO3\CMS\Core\Utility\StringUtility; + +/** + * Tests triggering hook execution in DataHandler. + */ +class HookTest extends AbstractDataHandlerActionTestCase +{ + const VALUE_PageId = 89; + const VALUE_ContentId = 297; + const TABLE_Content = 'tt_content'; + const TABLE_Hotel = 'tx_irretutorial_1nff_hotel'; + const TABLE_Category = 'sys_category'; + const FIELD_ContentHotel = 'tx_irretutorial_1nff_hotels'; + const FIELD_Categories = 'categories'; + + /** + * @var HookFixture + */ + protected $hookFixture; + + /** + * @var string + */ + protected $scenarioDataSetDirectory = 'typo3/sysext/core/Tests/Functional/DataHandling/DataHandler/DataSet/'; + + protected function setUp() + { + parent::setUp(); + $this->importScenarioDataSet('LiveDefaultPages'); + $this->importScenarioDataSet('LiveDefaultElements'); + $this->backendUser->workspace = 0; + + $this->hookFixture = GeneralUtility::makeInstance(HookFixture::class); + $this->hookFixture->purge(); + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][__CLASS__] = HookFixture::class; + $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][__CLASS__] = HookFixture::class; + } + + protected function tearDown() + { + parent::tearDown(); + + unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass'][__CLASS__]); + unset($GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processCmdmapClass'][__CLASS__]); + unset($this->hookFixture); + } + + /** + * @test + */ + public function hooksAreExecutedForNewRecords() + { + $newTableIds = $this->actionService->createNewRecord( + self::TABLE_Content, + self::VALUE_PageId, + ['header' => 'Testing #1'] + ); + $this->recordIds['newContentId'] = $newTableIds[self::TABLE_Content][0]; + + $this->assertHookInvocationsCount([ + 'processDatamap_beforeStart', + 'processDatamap_afterAllOperations' + ], 1); + + $this->assertHookInvocationsPayload([ + 'processDatamap_preProcessFieldArray', + 'processDatamap_postProcessFieldArray', + 'processDatamap_afterDatabaseOperations', + ], [ + [ + 'table' => self::TABLE_Content, + 'fieldArray' => [ 'header' => 'Testing #1', 'pid' => self::VALUE_PageId ] + ] + ]); + } + + /** + * @test + */ + public function hooksAreExecutedForExistingRecords() + { + $this->actionService->modifyRecord( + self::TABLE_Content, + self::VALUE_ContentId, + ['header' => 'Testing #1'] + ); + + $this->assertHookInvocationsCount([ + 'processDatamap_beforeStart', + 'processDatamap_afterAllOperations' + ], 1); + + $this->assertHookInvocationsPayload([ + 'processDatamap_preProcessFieldArray', + 'processDatamap_postProcessFieldArray', + 'processDatamap_afterDatabaseOperations', + ], [ + [ + 'table' => self::TABLE_Content, + 'fieldArray' => [ 'header' => 'Testing #1' ] + ] + ]); + } + + /** + * @test + */ + public function hooksAreExecutedForNewRelations() + { + $contentNewId = StringUtility::getUniqueId('NEW'); + $hotelNewId = StringUtility::getUniqueId('NEW'); + $categoryNewId = StringUtility::getUniqueId('NEW'); + + $this->actionService->modifyRecords( + self::VALUE_PageId, + [ + self::TABLE_Content => [ + 'uid' => $contentNewId, + 'header' => 'Testing #1', + self::FIELD_ContentHotel => $hotelNewId, + self::FIELD_Categories => $categoryNewId, + ], + self::TABLE_Hotel => [ + 'uid' => $hotelNewId, + 'title' => 'Hotel #1', + ], + self::TABLE_Category => [ + 'uid' => $categoryNewId, + 'title' => 'Category #1', + ], + ] + ); + + $this->assertHookInvocationsCount([ + 'processDatamap_beforeStart', + 'processDatamap_afterAllOperations' + ], 1); + + $this->assertHookInvocationPayload( + 'processDatamap_preProcessFieldArray', + [ + [ + 'table' => self::TABLE_Content, + 'fieldArray' => [ + 'header' => 'Testing #1', + self::FIELD_ContentHotel => $hotelNewId, + self::FIELD_Categories => $categoryNewId, + ], + ], + [ + 'table' => self::TABLE_Hotel, + 'fieldArray' => [ 'title' => 'Hotel #1' ], + ], + [ + 'table' => self::TABLE_Category, + 'fieldArray' => [ 'title' => 'Category #1' ], + ], + ] + ); + + $this->assertHookInvocationPayload( + 'processDatamap_postProcessFieldArray', + [ + [ + 'table' => self::TABLE_Content, + 'fieldArray' => [ 'header' => 'Testing #1' ], + ], + [ + 'table' => self::TABLE_Hotel, + 'fieldArray' => [ 'title' => 'Hotel #1' ], + ], + [ + 'table' => self::TABLE_Category, + 'fieldArray' => [ 'title' => 'Category #1' ], + ], + ] + ); + + $this->assertHookInvocationPayload( + 'processDatamap_afterDatabaseOperations', + [ + [ + 'table' => self::TABLE_Content, + 'fieldArray' => [ + 'header' => 'Testing #1', + self::FIELD_ContentHotel => 1, + ], + ], + // @todo Fix the double invocation for this tt_content record + [ + 'table' => self::TABLE_Content, + 'fieldArray' => [ + 'header' => 'Testing #1', + self::FIELD_Categories => 1, + ], + ], + [ + 'table' => self::TABLE_Hotel, + 'fieldArray' => [ 'title' => 'Hotel #1' ], + ], + [ + 'table' => self::TABLE_Category, + 'fieldArray' => [ 'title' => 'Category #1' ], + ], + ] + ); + } + + /** + * @test + */ + public function hooksAreExecutedForExistingRelations() + { + $this->actionService->modifyRecord( + self::TABLE_Content, + self::VALUE_ContentId, + [ + 'header' => 'Testing #1', + self::FIELD_ContentHotel => '3,4,5', + self::FIELD_Categories => '28,29,30', + ] + ); + + $this->assertHookInvocationsCount([ + 'processDatamap_beforeStart', + 'processDatamap_afterAllOperations' + ], 1); + + $this->assertHookInvocationPayload( + 'processDatamap_preProcessFieldArray', + [ + [ + 'table' => self::TABLE_Content, + 'fieldArray' => [ + 'header' => 'Testing #1', + self::FIELD_ContentHotel => '3,4,5', + self::FIELD_Categories => '28,29,30', + ] + ] + ] + ); + + $this->assertHookInvocationsPayload([ + 'processDatamap_postProcessFieldArray', + 'processDatamap_afterDatabaseOperations', + ], [ + [ + 'table' => self::TABLE_Content, + 'fieldArray' => [ + 'header' => 'Testing #1', + self::FIELD_ContentHotel => 3, + self::FIELD_Categories => 3, + ] + ] + ]); + } + + /** + * @param string[] $methodNames + * @param int $count + */ + protected function assertHookInvocationsCount(array $methodNames, int $count) + { + $message = 'Unexpected invocations of method "%s"'; + foreach ($methodNames as $methodName) { + $invocations = $this->hookFixture->findInvocationsByMethodName($methodName); + $this->assertCount( + $count, + $invocations, + sprintf($message, $methodName) + ); + } + } + + /** + * @param string[] $methodNames + * @param array $assertions + */ + protected function assertHookInvocationsPayload(array $methodNames, array $assertions) + { + foreach ($methodNames as $methodName) { + $this->assertHookInvocationPayload($methodName, $assertions); + } + } + + /** + * @param string $methodName + * @param array $assertions + */ + protected function assertHookInvocationPayload(string $methodName, array $assertions) + { + $message = 'Unexpected hook payload amount found for method "%s"'; + $invocations = $this->hookFixture->findInvocationsByMethodName($methodName); + $this->assertNotNull($invocations); + + foreach ($assertions as $assertion) { + $indexes = $this->findAllArrayValuesInHaystack($invocations, $assertion); + $this->assertCount( + 1, + $indexes, + sprintf($message, $methodName) + ); + $index = $indexes[0]; + unset($invocations[$index]); + } + } + + /** + * @param array $haystack + * @param array $assertion + * @return int[] + */ + protected function findAllArrayValuesInHaystack(array $haystack, array $assertion) + { + $found = []; + foreach ($haystack as $index => $item) { + if ($this->equals($assertion, $item)) { + $found[] = $index; + } + } + return $found; + } + + /** + * @param array $left + * @param array $right + * @return bool + */ + protected function equals(array $left, array $right) + { + foreach ($left as $key => $leftValue) { + $rightValue = $right[$key] ?? null; + if (!is_array($leftValue) && (string)$leftValue !== (string)$rightValue) { + return false; + } elseif (is_array($leftValue)) { + if (!$this->equals($leftValue, $rightValue)) { + return false; + } + } + } + return true; + } +} diff --git a/typo3/sysext/core/Tests/Functional/DataHandling/Framework/ActionService.php b/typo3/sysext/core/Tests/Functional/DataHandling/Framework/ActionService.php index 8e634b11c893..e5cf2baa0bde 100644 --- a/typo3/sysext/core/Tests/Functional/DataHandling/Framework/ActionService.php +++ b/typo3/sysext/core/Tests/Functional/DataHandling/Framework/ActionService.php @@ -138,9 +138,11 @@ class ActionService $recordData = $this->resolvePreviousUid($recordData, $currentUid); $currentUid = $recordData['uid']; if ($recordData['uid'] === '__NEW') { - $recordData['pid'] = $pageId; $currentUid = StringUtility::getUniqueId('NEW'); } + if (strpos($currentUid, 'NEW') === 0) { + $recordData['pid'] = $pageId; + } unset($recordData['uid']); $dataMap[$tableName][$currentUid] = $recordData; if ($previousTableName !== null && $previousUid !== null) { -- GitLab