[TASK] Introduce property mapper related functional tests 67/62567/8
authorAlexander Schnitzler <git@alexanderschnitzler.de>
Fri, 6 Dec 2019 18:04:09 +0000 (19:04 +0100)
committerSusanne Moog <look@susi.dev>
Tue, 14 Jan 2020 19:55:21 +0000 (20:55 +0100)
The introduced tests are prepatory work to have a
high coverage before refactoring property mapper
related code parts.

Releases: master
Resolves: #89896
Change-Id: I83cf025a63ea7c0b38b55c0173ca305f423d8a2e
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/62567
Tested-by: TYPO3com <noreply@typo3.com>
Tested-by: Benni Mack <benni@typo3.org>
Tested-by: Susanne Moog <look@susi.dev>
Reviewed-by: Benni Mack <benni@typo3.org>
Reviewed-by: Susanne Moog <look@susi.dev>
24 files changed:
typo3/sysext/extbase/Classes/Property/PropertyMapper.php
typo3/sysext/extbase/Classes/Property/TypeConverter/DateTimeConverter.php
typo3/sysext/extbase/Classes/Property/TypeConverter/ObjectConverter.php
typo3/sysext/extbase/Classes/Property/TypeConverter/PersistentObjectConverter.php
typo3/sysext/extbase/Tests/Functional/Property/Fixtures/Animal.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/Fixtures/Cat.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/Fixtures/DatabaseImports/be_users.xml [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/Fixtures/DatabaseImports/sys_file.xml [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/Fixtures/DatabaseImports/sys_file_storage.xml [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/Fixtures/ExtendedCountableInterface.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/PropertyMapperTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/ArrayConverterTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/BooleanConverterTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/DateTimeConverterTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/FileConverterTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/FloatConverterTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/IntegerConverterTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/ObjectConverterTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/ObjectStorageConverterTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/PersistentObjectConverterTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/StringConverterTest.php [new file with mode: 0644]
typo3/sysext/extbase/Tests/Unit/Property/PropertyMapperTest.php [deleted file]
typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/ObjectConverterTest.php [deleted file]
typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/PersistentObjectConverterTest.php [deleted file]

index f93cf4a..45ba3ca 100644 (file)
@@ -159,6 +159,7 @@ class PropertyMapper implements \TYPO3\CMS\Core\SingletonInterface
         $targetType = $typeConverter->getTargetTypeForSource($source, $targetType, $configuration);
 
         if (!is_object($typeConverter) || !$typeConverter instanceof \TYPO3\CMS\Extbase\Property\TypeConverterInterface) {
+            // todo: this Exception is never thrown as findTypeConverter returns an object or throws an Exception.
             throw new Exception\TypeConverterException(
                 'Type converter for "' . $source . '" -> "' . $targetType . '" not found.',
                 1476045062
@@ -295,6 +296,9 @@ class PropertyMapper implements \TYPO3\CMS\Core\SingletonInterface
         if (isset($convertersForSource['object'])) {
             return $this->findEligibleConverterWithHighestPriority($convertersForSource['object'], $source, $targetClass);
         }
+
+        // todo: this case is impossible because at this point there must be an ObjectConverter
+        // todo: which allowed the processing up to this point.
         return null;
     }
 
@@ -307,6 +311,7 @@ class PropertyMapper implements \TYPO3\CMS\Core\SingletonInterface
     protected function findEligibleConverterWithHighestPriority($converters, $source, $targetType)
     {
         if (!is_array($converters)) {
+            // todo: this case is impossible as initializeObject always defines an array.
             return null;
         }
         krsort($converters, SORT_NUMERIC);
index 6f54380..439dc55 100644 (file)
@@ -95,6 +95,7 @@ class DateTimeConverter extends \TYPO3\CMS\Extbase\Property\TypeConverter\Abstra
     public function canConvertFrom($source, string $targetType): bool
     {
         if (!is_callable([$targetType, 'createFromFormat'])) {
+            // todo: this check does not make sense as this converter is only called on \DateTime targets
             return false;
         }
         if (is_array($source)) {
@@ -145,6 +146,7 @@ class DateTimeConverter extends \TYPO3\CMS\Extbase\Property\TypeConverter\Abstra
             return null;
         }
         if (ctype_digit($dateAsString) && $configuration === null && (!is_array($source) || !isset($source['dateFormat']))) {
+            // todo: type converters are never called without a property mapping configuration
             $dateFormat = 'U';
         }
         if (is_array($source) && isset($source['timezone']) && (string)$source['timezone'] !== '') {
@@ -190,6 +192,7 @@ class DateTimeConverter extends \TYPO3\CMS\Extbase\Property\TypeConverter\Abstra
     protected function getDefaultDateFormat(\TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface $configuration = null): string
     {
         if ($configuration === null) {
+            // todo: type converters are never called without a property mapping configuration
             return self::DEFAULT_DATE_FORMAT;
         }
         $dateFormat = $configuration->getConfigurationValue(DateTimeConverter::class, self::CONFIGURATION_DATE_FORMAT);
index f0020cb..733b2ec 100644 (file)
@@ -192,6 +192,7 @@ class ObjectConverter extends AbstractTypeConverter
             $targetType = $source['__type'];
 
             if ($configuration === null) {
+                // todo: this is impossible to achieve since this methods is always called via (convert -> doMapping -> getTargetTypeForSource) and convert and doMapping create configuration objects if missing.
                 throw new \InvalidArgumentException('A property mapping configuration must be given, not NULL.', 1326277369);
             }
             if ($configuration->getConfigurationValue(\TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter::class, self::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED) !== true) {
index 1c9ee06..d4bf379 100644 (file)
@@ -156,6 +156,7 @@ class PersistentObjectConverter extends ObjectConverter
             }
             $object = $this->fetchObjectFromPersistence($source, $targetType);
         } else {
+            // todo: this case is impossible as this converter is never called with a source that is not an integer, a string or an array
             throw new \InvalidArgumentException('Only integers, strings and arrays are accepted.', 1305630314);
         }
         foreach ($convertedChildProperties as $propertyName => $propertyValue) {
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/Animal.php b/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/Animal.php
new file mode 100644 (file)
index 0000000..dcaf8d0
--- /dev/null
@@ -0,0 +1,41 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\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!
+ */
+
+class Animal
+{
+    /**
+     * @var string
+     */
+    private $name;
+
+    /**
+     * @return string
+     */
+    public function getName(): string
+    {
+        return $this->name;
+    }
+
+    /**
+     * @param string $name
+     */
+    public function setName(string $name): void
+    {
+        $this->name = $name;
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/Cat.php b/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/Cat.php
new file mode 100644 (file)
index 0000000..bd10310
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\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!
+ */
+
+class Cat extends Animal
+{
+    /**
+     * @var string|null
+     */
+    protected $color;
+
+    /**
+     * @var int|null
+     */
+    protected $height;
+
+    /**
+     * @var int|null
+     */
+    protected $numberOfEars;
+
+    /**
+     * @return string|null
+     */
+    public function getColor(): ?string
+    {
+        return $this->color;
+    }
+
+    /**
+     * @param string|null $color
+     */
+    public function setColor(?string $color): void
+    {
+        $this->color = $color;
+    }
+
+    /**
+     * @return int|null
+     */
+    public function getHeight(): ?int
+    {
+        return $this->height;
+    }
+
+    /**
+     * @param int|null $height
+     */
+    public function setHeight(?int $height): void
+    {
+        $this->height = $height;
+    }
+
+    /**
+     * @return int|null
+     */
+    public function getNumberOfEars(): ?int
+    {
+        return $this->numberOfEars;
+    }
+
+    /**
+     * @param int|null $numberOfEars
+     */
+    public function setNumberOfEars(?int $numberOfEars): void
+    {
+        $this->numberOfEars = $numberOfEars;
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/DatabaseImports/be_users.xml b/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/DatabaseImports/be_users.xml
new file mode 100644 (file)
index 0000000..325b729
--- /dev/null
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+    <be_users>
+        <uid>1</uid>
+        <pid>0</pid>
+        <username>admin</username>
+        <deleted>0</deleted>
+        <admin>1</admin>
+        <disableIPlock>1</disableIPlock>
+    </be_users>
+</dataset>
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/DatabaseImports/sys_file.xml b/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/DatabaseImports/sys_file.xml
new file mode 100644 (file)
index 0000000..079f823
--- /dev/null
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <sys_file>
+               <uid>1</uid>
+               <pid>0</pid>
+               <storage>1</storage>
+               <type>2</type>
+               <identifier>/user_upload/typo3_image2.jpg</identifier>
+               <identifier_hash>f90bb9a35622f35b5279195e324eddbaec8164b2</identifier_hash>
+               <folder_hash>19669f1e02c2f16705ec7587044c66443be70725</folder_hash>
+               <extension>jpg</extension>
+               <mime_type>image/jpeg</mime_type>
+               <name>typo3_image2.jpg</name>
+               <sha1>da9acdf1e105784a57bbffec9520969578287797</sha1>
+               <size>7958</size>
+               <creation_date>1389878273</creation_date>
+               <modification_date>1389878273</modification_date>
+       </sys_file>
+</dataset>
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/DatabaseImports/sys_file_storage.xml b/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/DatabaseImports/sys_file_storage.xml
new file mode 100644 (file)
index 0000000..b9a212e
--- /dev/null
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<dataset>
+       <sys_file_storage>
+               <uid>1</uid>
+               <pid>0</pid>
+               <name>fileadmin/ (auto-created)</name>
+               <driver>Local</driver>
+               <configuration><![CDATA[<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
+<T3FlexForms>
+       <data>
+               <sheet index="sDEF">
+                       <language index="lDEF">
+                               <field index="basePath">
+                                       <value index="vDEF">fileadmin/</value>
+                               </field>
+                               <field index="pathType">
+                                       <value index="vDEF">relative</value>
+                               </field>
+                               <field index="caseSensitive">
+                                       <value index="vDEF">1</value>
+                               </field>
+                       </language>
+               </sheet>
+       </data>
+</T3FlexForms>]]></configuration>
+               <is_browsable>1</is_browsable>
+               <is_public>1</is_public>
+               <is_writable>1</is_writable>
+               <is_online>1</is_online>
+       </sys_file_storage>
+</dataset>
\ No newline at end of file
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/ExtendedCountableInterface.php b/typo3/sysext/extbase/Tests/Functional/Property/Fixtures/ExtendedCountableInterface.php
new file mode 100644 (file)
index 0000000..2fb1509
--- /dev/null
@@ -0,0 +1,21 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\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!
+ */
+
+interface ExtendedCountableInterface extends \Countable
+{
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/PropertyMapperTest.php b/typo3/sysext/extbase/Tests/Functional/Property/PropertyMapperTest.php
new file mode 100644 (file)
index 0000000..a93cbc9
--- /dev/null
@@ -0,0 +1,418 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property;
+
+/*
+ * 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\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Domain\Model\BackendUser;
+use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
+use TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration;
+use TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface;
+use TYPO3\CMS\Extbase\Property\TypeConverter\AbstractTypeConverter;
+use TYPO3\CMS\Extbase\Property\TypeConverter\ArrayConverter;
+use TYPO3\CMS\Extbase\Property\TypeConverter\IntegerConverter;
+use TYPO3\CMS\Extbase\Tests\Functional\Property\Fixtures\Animal;
+use TYPO3\CMS\Extbase\Tests\Functional\Property\Fixtures\Cat;
+use TYPO3\CMS\Extbase\Tests\Functional\Property\Fixtures\ExtendedCountableInterface;
+use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class PropertyMapperTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function initializeObjectThrowsDuplicateTypeConverterException()
+    {
+        static::expectExceptionCode(1297951378);
+
+        $class = new class extends ArrayConverter {
+        };
+        ExtensionUtility::registerTypeConverter(get_class($class));
+        GeneralUtility::getContainer()->get(PropertyMapper::class);
+    }
+
+    /**
+     * @test
+     */
+    public function convertCreatesAPropertyMappingConfigurationIfNotGiven()
+    {
+        // This test just increases the test coverage
+        GeneralUtility::getContainer()->get(PropertyMapper::class)
+            ->convert('string', 'string');
+    }
+
+    /**
+     * @test
+     */
+    public function convertReturnsNullIfDoMappingReturnsAnError()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        self::assertNull($propertyMapper->convert('string', 'integer'));
+        self::assertNotEmpty($propertyMapper->getMessages());
+    }
+
+    /**
+     * @test
+     */
+    public function convertThrowsATargetNotFoundException()
+    {
+        static::expectException(TargetNotFoundException::class);
+        static::expectExceptionCode(1297933823);
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapper->convert(9999, BackendUser::class);
+    }
+
+    /**
+     * @test
+     */
+    public function convertThrowsAnExceptionIfNoTypeConverterCanBeFoundForTheConversionOfSimpleTypes()
+    {
+        static::expectException(\Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": No converter found which can be used to convert from "integer" to "boolean"');
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapper->convert(9999, 'boolean');
+    }
+
+    /**
+     * @test
+     */
+    public function convertThrowsAnExceptionIfTargetTypeIsNotAString()
+    {
+        static::expectException(\Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": The target type was no string, but of type "NULL"');
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapper->convert(9999, null);
+    }
+
+    /**
+     * @test
+     */
+    public function convertInternallyConvertsANullSourceToAnEmptyString()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        self::assertSame('', $propertyMapper->convert(null, 'string'));
+    }
+
+    /**
+     * @test
+     */
+    public function convertThrowsAnExceptionIfTargetTypeIsANonExistingClass()
+    {
+        static::expectException(\Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": Could not find a suitable type converter for "NonExistingClass" because no such class or interface exists.');
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapper->convert(1, 'NonExistingClass');
+    }
+
+    /**
+     * @test
+     */
+    public function convertThrowsAnExceptionIfAtLeastTwoConvertersAreRegisteredThatHandleTheConversionToTheSameInterface()
+    {
+        static::expectException(\Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('There exist at least two converters which handle the conversion to an interface with priority "10"');
+
+        $converterOne = new class extends AbstractTypeConverter {
+            protected $priority = 10;
+            protected $sourceTypes = ['integer'];
+            protected $targetType = \Countable::class;
+
+            public function convertFrom($source, string $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
+            {
+                return true;
+            }
+        };
+
+        $converterTwo = new class extends AbstractTypeConverter {
+            protected $priority = 10;
+            protected $sourceTypes = ['integer'];
+            protected $targetType = ExtendedCountableInterface::class;
+
+            public function convertFrom($source, string $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
+            {
+                return true;
+            }
+        };
+
+        $counter = new class implements ExtendedCountableInterface {
+            public function count()
+            {
+                return 1;
+            }
+        };
+
+        ExtensionUtility::registerTypeConverter(get_class($converterOne));
+        ExtensionUtility::registerTypeConverter(get_class($converterTwo));
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapper->convert(1, get_class($counter));
+    }
+
+    /**
+     * @test
+     */
+    public function doMappingReturnsTheSourceIfItIsAlreadyTheDesiredTypeWithoutCallingAConverter()
+    {
+        $objectStorage = new ObjectStorage();
+
+        $result = GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(
+            $objectStorage,
+            '\TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\BackendUser>'
+        );
+
+        self::assertSame($objectStorage, $result);
+    }
+
+    /**
+     * @test
+     */
+    public function findTypeConverterReturnsTheConverterFromThePropertyMappingConfiguration()
+    {
+        $class = new class extends IntegerConverter {
+            public function convertFrom($source, string $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
+            {
+                return 1575648246;
+            }
+        };
+
+        $propertyMappingConfiguration = new PropertyMappingConfiguration();
+        $propertyMappingConfiguration->setTypeConverter($class);
+
+        $result = GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(
+            1,
+            'integer',
+            $propertyMappingConfiguration
+        );
+
+        self::assertSame(1575648246, $result);
+    }
+
+    /**
+     * @test
+     */
+    public function determineSourceTypeThrowsInvalidSourceExceptionForNonSupportedTypes()
+    {
+        static::expectException(\Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('The source is not of type string, array, float, integer or boolean, but of type "object"');
+
+        $generator = function () {
+            return 'string';
+        };
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapper->convert($generator, 'string');
+    }
+
+    /**
+     * @test
+     */
+    public function findFirstEligibleTypeConverterInObjectHierarchyReturnsNullIfNoTypeConvertersExistForTheSourceType()
+    {
+        static::expectException(\Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": No converter found which can be used to convert from "integer" to "TYPO3\CMS\Extbase\Tests\Functional\Property\Fixtures\Cat"');
+
+        $GLOBALS['TYPO3_CONF_VARS']['EXTCONF']['extbase']['typeConverters'] = [];
+
+        $result = GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(1, Cat::class);
+        self::assertNull($result);
+    }
+
+    /**
+     * @test
+     */
+    public function findFirstEligibleTypeConverterInObjectHierarchyFindsConverterFromStringToObject()
+    {
+        $converter = new class extends AbstractTypeConverter {
+            protected $priority = 10;
+            protected $sourceTypes = ['string'];
+            protected $targetType = Cat::class;
+
+            public function convertFrom($source, string $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
+            {
+                return new Cat();
+            }
+        };
+
+        ExtensionUtility::registerTypeConverter(get_class($converter));
+
+        $result = GeneralUtility::getContainer()->get(PropertyMapper::class)->convert('tigger', Cat::class);
+        self::assertInstanceOf(Cat::class, $result);
+    }
+
+    /**
+     * @test
+     */
+    public function findFirstEligibleTypeConverterInObjectHierarchyReturnsConverterForParentClass()
+    {
+        $converter = new class extends AbstractTypeConverter {
+            protected $priority = 10;
+            protected $sourceTypes = ['string'];
+            protected $targetType = Animal::class;
+
+            public function convertFrom($source, string $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
+            {
+                return new Animal();
+            }
+        };
+
+        ExtensionUtility::registerTypeConverter(get_class($converter));
+
+        $result = GeneralUtility::getContainer()->get(PropertyMapper::class)->convert('tigger', Cat::class);
+        self::assertInstanceOf(Animal::class, $result);
+    }
+
+    /**
+     * @test
+     */
+    public function findFirstEligibleTypeConverterInObjectHierarchyReturnsConverterForInterfaces()
+    {
+        $converter = new class extends AbstractTypeConverter {
+            protected $priority = 10;
+            protected $sourceTypes = ['integer'];
+            protected $targetType = \Countable::class;
+
+            public function convertFrom($source, string $targetType, array $convertedChildProperties = [], PropertyMappingConfigurationInterface $configuration = null)
+            {
+                return [];
+            }
+        };
+
+        $counter = new class implements \Countable {
+            public function count()
+            {
+                return 1;
+            }
+        };
+
+        ExtensionUtility::registerTypeConverter(get_class($converter));
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $result = $propertyMapper->convert(1, get_class($counter));
+
+        self::assertSame([], $result);
+    }
+
+    /**
+     * @test
+     */
+    public function defaultPropertyMappingConfiguration()
+    {
+        $source = [
+            'color' => 'black',
+        ];
+
+        $propertyMappingConfiguration = new PropertyMappingConfiguration();
+        $propertyMappingConfiguration->allowAllProperties();
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        /** @var Cat $result */
+        $result = $propertyMapper->convert(
+            $source,
+            Cat::class,
+            $propertyMappingConfiguration
+        );
+
+        self::assertInstanceOf(Cat::class, $result);
+        self::assertSame('black', $result->getColor());
+    }
+
+    /**
+     * @test
+     */
+    public function skipPropertiesConfiguration()
+    {
+        $source = [
+            'color' => 'black',
+        ];
+
+        $propertyMappingConfiguration = new PropertyMappingConfiguration();
+        $propertyMappingConfiguration->skipProperties('color');
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        /** @var Cat $result */
+        $result = $propertyMapper->convert(
+            $source,
+            Cat::class,
+            $propertyMappingConfiguration
+        );
+
+        self::assertInstanceOf(Cat::class, $result);
+        self::assertNull($result->getColor());
+    }
+
+    /**
+     * @test
+     */
+    public function allowAllPropertiesExceptConfiguration()
+    {
+        static::expectException(\Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('It is not allowed to map property "color". You need to use $propertyMappingConfiguration->allowProperties(\'color\') to enable mapping of this property.');
+
+        $source = [
+            'color' => 'black',
+        ];
+
+        $propertyMappingConfiguration = new PropertyMappingConfiguration();
+        $propertyMappingConfiguration->allowAllPropertiesExcept('color');
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapper->convert(
+            $source,
+            Cat::class,
+            $propertyMappingConfiguration
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function allowAllPropertiesExceptWithSkipUnknownPropertiesConfiguration()
+    {
+        $source = [
+            'color' => 'black',
+        ];
+
+        $propertyMappingConfiguration = new PropertyMappingConfiguration();
+        $propertyMappingConfiguration->allowAllPropertiesExcept('color');
+        $propertyMappingConfiguration->skipUnknownProperties();
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        /** @var Cat $result */
+        $result = $propertyMapper->convert(
+            $source,
+            Cat::class,
+            $propertyMappingConfiguration
+        );
+
+        self::assertInstanceOf(Cat::class, $result);
+        self::assertNull($result->getColor());
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/ArrayConverterTest.php b/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/ArrayConverterTest.php
new file mode 100644 (file)
index 0000000..2bc03c3
--- /dev/null
@@ -0,0 +1,37 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\TypeConverter;
+
+/*
+ * 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\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class ArrayConverterTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function convertToArray()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        self::assertSame([], $propertyMapper->convert([], 'array'));
+
+        self::assertSame([], $propertyMapper->convert('', 'array'));
+        // todo: ArrayConverter can only convert empty strings to array. Adjust tests once that has been fixed.
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/BooleanConverterTest.php b/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/BooleanConverterTest.php
new file mode 100644 (file)
index 0000000..2751271
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\TypeConverter;
+
+/*
+ * 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\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class BooleanConverterTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function convertToBoolean()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        self::assertTrue($propertyMapper->convert(true, 'boolean'));
+        self::assertFalse($propertyMapper->convert(false, 'boolean'));
+
+        self::assertTrue($propertyMapper->convert('true', 'boolean'));
+        self::assertTrue($propertyMapper->convert('false', 'boolean'));
+        self::assertTrue($propertyMapper->convert('1', 'boolean'));
+        self::assertFalse($propertyMapper->convert('0', 'boolean'));
+        self::assertTrue($propertyMapper->convert('string', 'boolean'));
+        self::assertFalse($propertyMapper->convert('', 'boolean'));
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/DateTimeConverterTest.php b/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/DateTimeConverterTest.php
new file mode 100644 (file)
index 0000000..c28b21d
--- /dev/null
@@ -0,0 +1,353 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\TypeConverter;
+
+/*
+ * 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\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Property\Exception;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration;
+use TYPO3\CMS\Extbase\Property\TypeConverter\DateTimeConverter;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class DateTimeConverterTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function convertFromReturnsAnErrorWhenConvertingIntegersToDateTime()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert(0, \DateTime::class);
+
+        self::assertNull($dateTime);
+        self::assertTrue($propertyMapper->getMessages()->hasErrors());
+        self::assertSame(
+            'The date "%s" was not recognized (for format "%s").',
+            $propertyMapper->getMessages()->getFirstError()->getMessage()
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function convertFromReturnsNullIfSourceIsAnEmptyString()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert('', \DateTime::class);
+
+        self::assertNull($dateTime);
+        self::assertFalse($propertyMapper->getMessages()->hasErrors());
+    }
+
+    /**
+     * @test
+     */
+    public function convertDefaultDateFormatString()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert('2019-12-07T19:07:02+00:00', \DateTime::class);
+
+        self::assertInstanceOf(\DateTime::class, $dateTime);
+        self::assertSame(1575745622, $dateTime->getTimestamp());
+    }
+
+    /**
+     * @test
+     */
+    public function convertCustomDateFormatString()
+    {
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->setTypeConverterOption(
+            DateTimeConverter::class,
+            DateTimeConverter::CONFIGURATION_DATE_FORMAT,
+            \DateTime::RFC7231
+        );
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert(
+            'Sat, 07 Dec 2019 19:15:45 GMT',
+            \DateTime::class,
+            $propertyMapperConfiguration
+        );
+
+        self::assertInstanceOf(\DateTime::class, $dateTime);
+        self::assertSame(1575746145, $dateTime->getTimestamp());
+    }
+
+    /**
+     * @test
+     */
+    public function convertThrowsInvalidPropertyMappingConfigurationExceptionIfDateFormatIsNotAString()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": CONFIGURATION_DATE_FORMAT must be of type string, "array" given');
+
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->setTypeConverterOption(
+            DateTimeConverter::class,
+            DateTimeConverter::CONFIGURATION_DATE_FORMAT,
+            []
+        );
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $propertyMapper->convert(
+            'Sat, 07 Dec 2019 19:15:45 GMT',
+            \DateTime::class,
+            $propertyMapperConfiguration
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function convertWithArraySourceWithStringDate()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert(
+            [
+                'date' => '2019-12-07T19:07:02+00:00'
+            ],
+            \DateTime::class
+        );
+
+        self::assertInstanceOf(\DateTime::class, $dateTime);
+        self::assertSame(1575745622, $dateTime->getTimestamp());
+    }
+
+    /**
+     * @test
+     */
+    public function convertWithArraySourceWithIntegerDate()
+    {
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->setTypeConverterOption(
+            DateTimeConverter::class,
+            DateTimeConverter::CONFIGURATION_DATE_FORMAT,
+            'U'
+        );
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert(
+            [
+                'date' => 1575745622
+            ],
+            \DateTime::class,
+            $propertyMapperConfiguration
+        );
+
+        self::assertInstanceOf(\DateTime::class, $dateTime);
+        self::assertSame(1575745622, $dateTime->getTimestamp());
+    }
+
+    /**
+     * @test
+     */
+    public function convertWithArraySourceWithDayMonthAndYearSet()
+    {
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->setTypeConverterOption(
+            DateTimeConverter::class,
+            DateTimeConverter::CONFIGURATION_DATE_FORMAT,
+            'Y-m-d'
+        );
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert(
+            [
+                'day' => '12',
+                'month' => '12',
+                'year' => '2019',
+            ],
+            \DateTime::class,
+            $propertyMapperConfiguration
+        );
+
+        self::assertInstanceOf(\DateTime::class, $dateTime);
+        self::assertSame('2019-12-12', $dateTime->format('Y-m-d'));
+    }
+
+    /**
+     * @test
+     */
+    public function convertWithArraySourceWithDayMonthYearAndDateFormatSet()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert(
+            [
+                'day' => '12',
+                'month' => '12',
+                'year' => '2019',
+                'dateFormat' => 'Y-m-d',
+            ],
+            \DateTime::class
+        );
+
+        self::assertInstanceOf(\DateTime::class, $dateTime);
+        self::assertSame('2019-12-12', $dateTime->format('Y-m-d'));
+    }
+
+    /**
+     * @test
+     */
+    public function convertWithArraySourceWithDayMonthYearHourMinuteAndSecondSet()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert(
+            [
+                'day' => '12',
+                'month' => '12',
+                'year' => '2019',
+                'hour' => '15',
+                'minute' => '5',
+                'second' => '54',
+                'dateFormat' => 'Y-m-d',
+            ],
+            \DateTime::class
+        );
+
+        self::assertInstanceOf(\DateTime::class, $dateTime);
+        self::assertSame('2019-12-12 15:05:54', $dateTime->format('Y-m-d H:i:s'));
+        self::assertSame(1576163154, $dateTime->getTimestamp());
+    }
+
+    /**
+     * @test
+     */
+    public function convertWithArraySourceWithDayMonthYearAndTimeZoneSetWithDateThatIncludesTimezone()
+    {
+        // Hint:
+        // The timezone parameter and the current timezone are ignored when the time parameter
+        // either contains a UNIX timestamp (e.g. 946684800) or specifies a timezone (e.g. 2010-01-28T15:00:00+02:00).
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert(
+            [
+                'date' => '2019-12-07T19:07:02+00:00',
+                'timezone' => 'Pacific/Midway',
+            ],
+            \DateTime::class
+        );
+
+        self::assertInstanceOf(\DateTime::class, $dateTime);
+        self::assertSame('2019-12-07T19:07:02+00:00', $dateTime->format(\DateTime::W3C));
+        self::assertSame(1575745622, $dateTime->getTimestamp());
+    }
+
+    /**
+     * @test
+     */
+    public function convertWithArraySourceWithDayMonthYearAndTimeZoneSet()
+    {
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->setTypeConverterOption(
+            DateTimeConverter::class,
+            DateTimeConverter::CONFIGURATION_DATE_FORMAT,
+            'Y-m-d H:i:s'
+        );
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert(
+            [
+                'date' => '2019-12-07 19:07:02',
+                'timezone' => 'Pacific/Midway',
+            ],
+            \DateTime::class,
+            $propertyMapperConfiguration
+        );
+
+        self::assertInstanceOf(\DateTime::class, $dateTime);
+        self::assertSame('2019-12-07T19:07:02-11:00', $dateTime->format(\DateTime::W3C));
+        self::assertSame(1575785222, $dateTime->getTimestamp());
+    }
+
+    /**
+     * @test
+     */
+    public function convertFromReturnsErrorIfSourceIsAnArrayAndEitherDayMonthOrYearAreLowerThanOne()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $dateTime = $propertyMapper->convert(
+            [
+                'day' => '0',
+                'month' => '1',
+                'year' => '1',
+            ],
+            \DateTime::class
+        );
+
+        self::assertNull($dateTime);
+        self::assertTrue($propertyMapper->getMessages()->hasErrors());
+        self::assertSame(
+            'Could not convert the given date parts into a DateTime object because one or more parts were 0.',
+            $propertyMapper->getMessages()->getFirstError()->getMessage()
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function convertFromThrowsTypeConverterExceptionIfSourceIsAnInvalidArraySource()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": Could not convert the given source into a DateTime object because it was not an array with a valid date as a string');
+
+        GeneralUtility::getContainer()->get(PropertyMapper::class)->convert([], \DateTime::class);
+    }
+
+    /**
+     * @test
+     */
+    public function convertFromThrowsTypeConverterExceptionIfGivenDateTimeZoneIsInvalid()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": The specified timezone "foo" is invalid.');
+
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->setTypeConverterOption(
+            DateTimeConverter::class,
+            DateTimeConverter::CONFIGURATION_DATE_FORMAT,
+            'Y-m-d H:i:s'
+        );
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $propertyMapper->convert(
+            [
+                'date' => '2019-12-07 19:07:02',
+                'timezone' => 'foo',
+            ],
+            \DateTime::class,
+            $propertyMapperConfiguration
+        );
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/FileConverterTest.php b/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/FileConverterTest.php
new file mode 100644 (file)
index 0000000..db06c79
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\TypeConverter;
+
+/*
+ * 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\Authentication\BackendUserAuthentication;
+use TYPO3\CMS\Core\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Domain\Model\File;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class FileConverterTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function convertReturnsFileObject()
+    {
+        $GLOBALS['BE_USER'] = new BackendUserAuthentication();
+        $GLOBALS['BE_USER']->user = ['admin' => true];
+
+        $this->importDataSet(__DIR__ . '/../Fixtures/DatabaseImports/sys_file.xml');
+        $this->importDataSet(__DIR__ . '/../Fixtures/DatabaseImports/sys_file_storage.xml');
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        /** @var File $file */
+        $file = $propertyMapper->convert(
+            1,
+            File::class
+        );
+
+        self::assertInstanceOf(File::class, $file);
+        self::assertSame(1, $file->getOriginalResource()->getUid());
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/FloatConverterTest.php b/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/FloatConverterTest.php
new file mode 100644 (file)
index 0000000..2429aaa
--- /dev/null
@@ -0,0 +1,42 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\TypeConverter;
+
+/*
+ * 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\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class FloatConverterTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function convertToFloat()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        self::assertSame(10.1, $propertyMapper->convert(10.1, 'float'));
+
+        self::assertSame(10.0, $propertyMapper->convert(10, 'float'));
+
+        self::assertSame(10.0, $propertyMapper->convert('10', 'float'));
+        self::assertSame(10.5, $propertyMapper->convert('10.5', 'float'));
+        self::assertSame(10000.0, $propertyMapper->convert('10E3', 'float'));
+        self::assertNull($propertyMapper->convert('string', 'float'));
+        self::assertNull($propertyMapper->convert('', 'float'));
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/IntegerConverterTest.php b/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/IntegerConverterTest.php
new file mode 100644 (file)
index 0000000..21264d5
--- /dev/null
@@ -0,0 +1,40 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\TypeConverter;
+
+/*
+ * 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\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class IntegerConverterTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function convertToInteger()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        self::assertSame(10, $propertyMapper->convert(10, 'integer'));
+
+        self::assertSame(10, $propertyMapper->convert('10', 'integer'));
+        self::assertSame(10, $propertyMapper->convert('10.5', 'integer'));
+        self::assertSame(10000, $propertyMapper->convert('10E3', 'integer'));
+        self::assertNull($propertyMapper->convert('string', 'integer'));
+        self::assertNull($propertyMapper->convert('', 'integer'));
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/ObjectConverterTest.php b/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/ObjectConverterTest.php
new file mode 100644 (file)
index 0000000..c0ab835
--- /dev/null
@@ -0,0 +1,347 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\TypeConverter;
+
+/*
+ * 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\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
+use TYPO3\CMS\Extbase\Property\Exception;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration;
+use TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter;
+use TYPO3\CMS\Extbase\Tests\Functional\Property\Fixtures\Animal;
+use TYPO3\CMS\Extbase\Tests\Functional\Property\Fixtures\Cat;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class ObjectConverterTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function convertToObject()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $model = new class extends AbstractEntity {
+            /**
+             * @var string
+             */
+            protected $name;
+
+            public function setName(string $name)
+            {
+                $this->name = $name;
+            }
+        };
+
+        /** @var \TYPO3\CMS\Extbase\DomainObject\DomainObjectInterface $object */
+        $object = $propertyMapper->convert(['name' => 'John Doe'], get_class($model));
+
+        self::assertInstanceOf(get_class($model), $object);
+        self::assertSame('John Doe', $object->_getProperty('name'));
+    }
+
+    /**
+     * @test
+     */
+    public function convertToObjectViaTypeInArray()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->allowAllProperties();
+        $propertyMapperConfiguration->setTypeConverterOption(
+            ObjectConverter::class,
+            ObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED,
+            true
+        );
+
+        /** @var Cat $object */
+        $object = $propertyMapper->convert(
+            ['name' => 'John Doe', '__type' => Cat::class],
+            Animal::class,
+            $propertyMapperConfiguration
+        );
+
+        self::assertInstanceOf(Cat::class, $object);
+        self::assertSame('John Doe', $object->getName());
+    }
+
+    /**
+     * @test
+     */
+    public function getTypeOfChildPropertyReturnsTypeDefinedByPropertyMappingConfiguration()
+    {
+        $class = new class {
+            public $name;
+        };
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->allowAllProperties();
+        $propertyMapperConfiguration
+            ->forProperty('name')
+            ->setTypeConverterOption(
+                ObjectConverter::class,
+                ObjectConverter::CONFIGURATION_TARGET_TYPE,
+                'string'
+            )
+        ;
+
+        $result = $propertyMapper->convert(
+            ['name' => 'foo'],
+            get_class($class),
+            $propertyMapperConfiguration
+        );
+
+        self::assertSame('foo', $result->name);
+    }
+
+    /**
+     * @test
+     */
+    public function getTypeOfChildPropertyReturnsTypeDefinedByConstructorArgument()
+    {
+        $class = new class('') {
+            private $name;
+            public function __construct(string $name)
+            {
+                $this->name = $name;
+            }
+            public function getName(): string
+            {
+                return $this->name;
+            }
+        };
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->allowAllProperties();
+
+        $result = $propertyMapper->convert(
+            ['name' => 'foo'],
+            get_class($class),
+            $propertyMapperConfiguration
+        );
+
+        self::assertSame('foo', $result->getName());
+    }
+
+    /**
+     * @test
+     */
+    public function getTypeOfChildPropertyReturnsTypeDefinedBySetter()
+    {
+        $class = new class {
+            private $name;
+            public function setName(string $name)
+            {
+                $this->name = $name;
+            }
+            public function getName(): string
+            {
+                return $this->name;
+            }
+        };
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->allowAllProperties();
+
+        $result = $propertyMapper->convert(
+            ['name' => 'foo'],
+            get_class($class),
+            $propertyMapperConfiguration
+        );
+
+        self::assertSame('foo', $result->getName());
+    }
+
+    /**
+     * @test
+     */
+    public function getTypeOfChildPropertyThrowsInvalidTargetExceptionIfPropertyIsNotAccessible()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": Property "name" had no setter or constructor argument in target object of type "');
+
+        $class = new class {
+        };
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->allowAllProperties();
+
+        $propertyMapper->convert(
+            ['name' => 'foo'],
+            get_class($class),
+            $propertyMapperConfiguration
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function getTypeOfChildPropertyThrowsInvalidTargetExceptionIfPropertySetterDoesNotDefineAType()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": Setter for property "name" had no type hint or documentation in target object of type "');
+
+        $class = new class {
+            public function setName($name)
+            {
+            }
+        };
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->allowAllProperties();
+
+        $propertyMapper->convert(
+            ['name' => 'foo'],
+            get_class($class),
+            $propertyMapperConfiguration
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function convertFromThrowsInvalidTargetExceptionIfPropertiesCannotBeSet()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": Property "name" having a value of type "string" could not be set in target object of type "');
+
+        $class = new class {
+            private $name;
+        };
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->allowAllProperties();
+        $propertyMapperConfiguration
+            ->forProperty('name')
+            ->setTypeConverterOption(
+                ObjectConverter::class,
+                ObjectConverter::CONFIGURATION_TARGET_TYPE,
+                'string'
+            )
+        ;
+
+        $propertyMapper->convert(
+            ['name' => 'foo'],
+            get_class($class),
+            $propertyMapperConfiguration
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function buildObjectUsesDefaultValueOfOptionalConstructorArguments()
+    {
+        $class = new class('', '') {
+            public $name;
+            public $color;
+            public function __construct(string $name, ?string $color = 'red')
+            {
+                $this->name = $name;
+                $this->color = $color;
+            }
+        };
+
+        $result = GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(
+            ['name' => 'foo'],
+            get_class($class)
+        );
+
+        self::assertSame('foo', $result->name);
+        self::assertSame('red', $result->color);
+    }
+
+    /**
+     * @test
+     */
+    public function buildObjectThrowsInvalidTargetExceptionIfMandatoryConstructorArgumentIsMissing()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": Missing constructor argument "color" for object of type "');
+
+        $class = new class('', '') {
+            public $name;
+            public $color;
+            public function __construct(string $name, string $color)
+            {
+                $this->name = $name;
+                $this->color = $color;
+            }
+        };
+
+        GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(
+            ['name' => 'foo'],
+            get_class($class)
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function getTargetTypeForSourceThrowsInvalidPropertyMappingConfigurationExceptionIfTargetTypeOverridingIsNotAllowed()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": Override of target type not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED" to TRUE.');
+
+        $class = new class {
+        };
+
+        GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(
+            ['__type' => Animal::class],
+            get_class($class)
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function getTargetTypeForSourceThrowsInvalidDataTypeExceptionIfOverriddenTargetTypeIsNotASubtypeOfOriginalTargetType()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": The given type "TYPO3\CMS\Extbase\Tests\Functional\Property\Fixtures\Animal" is not a subtype of "');
+
+        $class = new class {
+        };
+
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->allowAllProperties();
+        $propertyMapperConfiguration->setTypeConverterOption(
+            ObjectConverter::class,
+            ObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED,
+            true
+        );
+
+        GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(
+            ['__type' => Animal::class],
+            get_class($class),
+            $propertyMapperConfiguration
+        );
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/ObjectStorageConverterTest.php b/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/ObjectStorageConverterTest.php
new file mode 100644 (file)
index 0000000..5b885c7
--- /dev/null
@@ -0,0 +1,74 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\TypeConverter;
+
+/*
+ * 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\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration;
+use TYPO3\CMS\Extbase\Tests\Functional\Property\Fixtures\Cat;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class ObjectStorageConverterTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function convertReturnsObjectStorage()
+    {
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->allowAllProperties();
+        $propertyMapperConfiguration->forProperty('foo')->allowAllProperties();
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $objectStorage = $propertyMapper->convert(
+            [
+                'foo' => ['color' => 'black']
+            ],
+            ObjectStorage::class . '<' . Cat::class . '>',
+            $propertyMapperConfiguration
+        );
+
+        self::assertInstanceOf(ObjectStorage::class, $objectStorage);
+        self::assertCount(1, $objectStorage);
+
+        $cat = $objectStorage->current();
+        self::assertInstanceOf(Cat::class, $cat);
+        self::assertSame('black', $cat->getColor());
+    }
+
+    /**
+     * @test
+     */
+    public function getSourceChildPropertiesToBeConvertedReturnsEmptyArrayIfSourceIsAString()
+    {
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->forProperty('foo')->allowAllProperties();
+
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $objectStorage = $propertyMapper->convert(
+            'foo',
+            ObjectStorage::class . '<' . Cat::class . '>',
+            $propertyMapperConfiguration
+        );
+
+        self::assertInstanceOf(ObjectStorage::class, $objectStorage);
+        self::assertCount(0, $objectStorage);
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/PersistentObjectConverterTest.php b/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/PersistentObjectConverterTest.php
new file mode 100644 (file)
index 0000000..fa4e893
--- /dev/null
@@ -0,0 +1,183 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\TypeConverter;
+
+/*
+ * 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\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Domain\Model\BackendUser;
+use TYPO3\CMS\Extbase\Property\Exception;
+use TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration;
+use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class PersistentObjectConverterTest extends FunctionalTestCase
+{
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->importDataSet(__DIR__ . '/../Fixtures/DatabaseImports/be_users.xml');
+    }
+
+    /**
+     * @test
+     */
+    public function converterReturnsNullWithEmptyStringsOrIntegers()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        self::assertNull($propertyMapper->convert(0, BackendUser::class));
+        self::assertNull($propertyMapper->convert('', BackendUser::class));
+    }
+
+    /**
+     * @test
+     */
+    public function fetchObjectFromPersistenceThrowsInvalidSourceExceptionIfSourceIANonNumericString()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": The identity property "foo" is no UID.');
+
+        GeneralUtility::getContainer()->get(PropertyMapper::class)->convert('foo', BackendUser::class);
+    }
+
+    /**
+     * @test
+     */
+    public function fetchObjectFromPersistenceThrowsTargetNotFoundExceptionIfObjectIsNotToBeFoundInThePersistence()
+    {
+        static::expectException(TargetNotFoundException::class);
+        static::expectExceptionCode(1297933823);
+        static::expectExceptionMessage('Object of type TYPO3\CMS\Extbase\Domain\Model\BackendUser with identity "2" not found.');
+
+        GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(2, BackendUser::class);
+    }
+
+    /**
+     * @test
+     */
+    public function converterFetchesObjectFromPersistenceIfSourceIsAnInteger()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $backendUser = $propertyMapper->convert(1, BackendUser::class);
+
+        self::assertInstanceOf(BackendUser::class, $backendUser);
+        self::assertSame(1, $backendUser->getUid());
+    }
+
+    /**
+     * @test
+     */
+    public function converterFetchesObjectFromPersistenceIfSourceIsANumericString()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $backendUser = $propertyMapper->convert('1', BackendUser::class);
+
+        self::assertInstanceOf(BackendUser::class, $backendUser);
+        self::assertSame(1, $backendUser->getUid());
+    }
+
+    /**
+     * @test
+     */
+    public function converterBuildsEmptyObjectIfSourceIsAnEmptyArray()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $backendUser = $propertyMapper->convert([], BackendUser::class);
+
+        self::assertInstanceOf(BackendUser::class, $backendUser);
+        self::assertNull($backendUser->getUid());
+    }
+
+    /**
+     * @test
+     */
+    public function converterFetchesObjectFromPersistenceIfSourceIsAnArrayWithIdentityKey()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $backendUser = $propertyMapper->convert(['__identity' => 1], BackendUser::class);
+
+        self::assertInstanceOf(BackendUser::class, $backendUser);
+        self::assertSame(1, $backendUser->getUid());
+    }
+
+    /**
+     * @test
+     */
+    public function handleArrayDataThrowsInvalidPropertyMappingConfigurationExceptionIfCreationOfObjectsIsNotAllowed()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": Creation of objects not allowed. To enable this, you need to set the PropertyMappingConfiguration Value "CONFIGURATION_CREATION_ALLOWED" to TRUE');
+
+        $propertyMapperConfiguration = new PropertyMappingConfiguration();
+        $propertyMapperConfiguration->setTypeConverterOption(
+            PersistentObjectConverter::class,
+            PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED,
+            false
+        );
+
+        GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(
+            [],
+            BackendUser::class,
+            $propertyMapperConfiguration
+        );
+    }
+
+    /**
+     * @test
+     */
+    public function converterRespectsAndSetsProperties()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        $backendUser = $propertyMapper->convert(['userName' => 'johndoe'], BackendUser::class);
+
+        self::assertInstanceOf(BackendUser::class, $backendUser);
+        self::assertNull($backendUser->getUid());
+        self::assertSame('johndoe', $backendUser->getUserName());
+    }
+
+    /**
+     * @test
+     */
+    public function getTypeOfChildPropertyThrowsInvalidTargetExceptionIfPropertyIsNonExistant()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": Property "nonExistant" was not found in target object of type "TYPO3\CMS\Extbase\Domain\Model\BackendUser".');
+
+        GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(['nonExistant' => 'johndoe'], BackendUser::class);
+    }
+
+    /**
+     * @test
+     */
+    public function convertFromthrowsInvalidTargetExceptionIfSourceContainsANonSettableProperty()
+    {
+        static::expectException(Exception::class);
+        static::expectExceptionCode(1297759968);
+        static::expectExceptionMessage('Exception while property mapping at property path "": Property "uid" having a value of type "integer" could not be set in target object of type "TYPO3\CMS\Extbase\Domain\Model\BackendUser"');
+
+        GeneralUtility::getContainer()->get(PropertyMapper::class)->convert(['uid' => 7], BackendUser::class);
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/StringConverterTest.php b/typo3/sysext/extbase/Tests/Functional/Property/TypeConverter/StringConverterTest.php
new file mode 100644 (file)
index 0000000..84d020f
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+declare(strict_types = 1);
+
+namespace TYPO3\CMS\Extbase\Tests\Functional\Property\TypeConverter;
+
+/*
+ * 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\Utility\GeneralUtility;
+use TYPO3\CMS\Extbase\Property\PropertyMapper;
+use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
+
+class StringConverterTest extends FunctionalTestCase
+{
+    /**
+     * @test
+     */
+    public function convertToString()
+    {
+        $propertyMapper = GeneralUtility::getContainer()->get(PropertyMapper::class);
+
+        self::assertSame('string', $propertyMapper->convert('string', 'string'));
+        self::assertSame('10', $propertyMapper->convert(10, 'string'));
+    }
+}
diff --git a/typo3/sysext/extbase/Tests/Unit/Property/PropertyMapperTest.php b/typo3/sysext/extbase/Tests/Unit/Property/PropertyMapperTest.php
deleted file mode 100644 (file)
index 2b82be0..0000000
+++ /dev/null
@@ -1,446 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extbase\Tests\Unit\Property;
-
-/*
- * 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\Extbase\Persistence\ObjectStorage;
-use TYPO3\CMS\Extbase\Property\Exception\InvalidSourceException;
-use TYPO3\CMS\Extbase\Property\PropertyMapper;
-use TYPO3\CMS\Extbase\Tests\Unit\Property\Fixtures\DataProviderOneInterface;
-use TYPO3\CMS\Extbase\Tests\Unit\Property\Fixtures\DataProviderThree;
-use TYPO3\CMS\Extbase\Tests\Unit\Property\Fixtures\DataProviderThreeInterface;
-use TYPO3\CMS\Extbase\Tests\Unit\Property\Fixtures\DataProviderTwo;
-use TYPO3\CMS\Extbase\Tests\Unit\Property\Fixtures\DataProviderTwoInterface;
-use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
-
-/**
- * Test case
- */
-class PropertyMapperTest extends UnitTestCase
-{
-    /**
-     * @var \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationBuilder|\PHPUnit\Framework\MockObject\MockObject
-     */
-    protected $mockConfigurationBuilder;
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface|\PHPUnit\Framework\MockObject\MockObject
-     */
-    protected $mockConfiguration;
-
-    /**
-     * Sets up this test case
-     */
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->mockConfigurationBuilder = $this->createMock(\TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationBuilder::class);
-        $this->mockConfiguration = $this->createMock(\TYPO3\CMS\Extbase\Property\PropertyMappingConfigurationInterface::class);
-    }
-
-    /**
-     * @return array
-     */
-    public function validSourceTypes()
-    {
-        return [
-            ['someString', 'string'],
-            [42, 'integer'],
-            [3.5, 'float'],
-            [true, 'boolean'],
-            [[], 'array']
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider validSourceTypes
-     * @param mixed $source
-     * @param mixed $sourceType
-     */
-    public function sourceTypeCanBeCorrectlyDetermined($source, $sourceType)
-    {
-        /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit\Framework\MockObject\MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
-        $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, ['dummy']);
-        self::assertEquals($sourceType, $propertyMapper->_call('determineSourceType', $source));
-    }
-
-    /**
-     * @return array
-     */
-    public function invalidSourceTypes()
-    {
-        return [
-            [null],
-            [new \stdClass()],
-            [new \ArrayObject()]
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider invalidSourceTypes
-     * @param mixed $source
-     */
-    public function sourceWhichIsNoSimpleTypeThrowsException($source)
-    {
-        $this->expectException(InvalidSourceException::class);
-        $this->expectExceptionCode(1297773150);
-        /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit\Framework\MockObject\MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
-        $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, ['dummy']);
-        $propertyMapper->_call('determineSourceType', $source);
-    }
-
-    /**
-     * @param string $name
-     * @param bool $canConvertFrom
-     * @param array $properties
-     * @param string $typeOfSubObject
-     * @return \PHPUnit\Framework\MockObject\MockObject
-     */
-    protected function getMockTypeConverter($name = '', $canConvertFrom = true, $properties = [], $typeOfSubObject = '')
-    {
-        $mockTypeConverter = $this->createMock(\TYPO3\CMS\Extbase\Property\TypeConverterInterface::class);
-        $mockTypeConverter->_name = $name;
-        $mockTypeConverter->expects(self::any())->method('canConvertFrom')->willReturn($canConvertFrom);
-        $mockTypeConverter->expects(self::any())->method('convertFrom')->willReturn($name);
-        $mockTypeConverter->expects(self::any())->method('getSourceChildPropertiesToBeConverted')->willReturn($properties);
-        $mockTypeConverter->expects(self::any())->method('getTypeOfChildProperty')->willReturn($typeOfSubObject);
-        return $mockTypeConverter;
-    }
-
-    /**
-     * @test
-     */
-    public function findTypeConverterShouldReturnTypeConverterFromConfigurationIfItIsSet()
-    {
-        $mockTypeConverter = $this->getMockTypeConverter();
-        $this->mockConfiguration->expects(self::any())->method('getTypeConverter')->willReturn($mockTypeConverter);
-        /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit\Framework\MockObject\MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
-        $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, ['dummy']);
-        self::assertSame($mockTypeConverter, $propertyMapper->_call('findTypeConverter', 'someSource', 'someTargetType', $this->mockConfiguration));
-    }
-
-    /**
-     * Simple type conversion
-     * @return array
-     */
-    public function dataProviderForFindTypeConverter()
-    {
-        return [
-            ['someStringSource', 'string', [
-                'string' => [
-                    'string' => [
-                        10 => $this->getMockTypeConverter('string2string,prio10'),
-                        1 => $this->getMockTypeConverter('string2string,prio1'),
-                    ]
-                ]], 'string2string,prio10'
-            ],
-            [['some' => 'array'], 'string', [
-                'array' => [
-                    'string' => [
-                        10 => $this->getMockTypeConverter('array2string,prio10'),
-                        1 => $this->getMockTypeConverter('array2string,prio1'),
-                    ]
-                ]], 'array2string,prio10'
-            ],
-            ['someStringSource', 'bool', [
-                'string' => [
-                    'boolean' => [
-                        10 => $this->getMockTypeConverter('string2boolean,prio10'),
-                        1 => $this->getMockTypeConverter('string2boolean,prio1'),
-                    ]
-                ]], 'string2boolean,prio10'
-            ],
-            ['someStringSource', 'int', [
-                'string' => [
-                    'integer' => [
-                        10 => $this->getMockTypeConverter('string2integer,prio10'),
-                        1 => $this->getMockTypeConverter('string2integer,prio1'),
-                    ],
-                ]], 'string2integer,prio10'
-            ]
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider dataProviderForFindTypeConverter
-     * @param mixed $source
-     * @param mixed $targetType
-     * @param mixed $typeConverters
-     * @param mixed $expectedTypeConverter
-     */
-    public function findTypeConverterShouldReturnHighestPriorityTypeConverterForSimpleType($source, $targetType, $typeConverters, $expectedTypeConverter)
-    {
-        $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, ['dummy']);
-        $propertyMapper->_set('typeConverters', $typeConverters);
-        $actualTypeConverter = $propertyMapper->_call('findTypeConverter', $source, $targetType, $this->mockConfiguration);
-        self::assertSame($expectedTypeConverter, $actualTypeConverter->_name);
-    }
-
-    /**
-     * @return array
-     */
-    public function dataProviderForObjectTypeConverters()
-    {
-        $data = [];
-
-        $className2 = DataProviderTwo::class;
-        $className3 = DataProviderThree::class;
-
-        $interfaceName1 = DataProviderOneInterface::class;
-        $interfaceName2 = DataProviderTwoInterface::class;
-        $interfaceName3 = DataProviderThreeInterface::class;
-
-        // The most specific converter should win
-        $data[] = [
-            'target' => $className3,
-            'expectedConverter' => 'Class3Converter',
-            'typeConverters' => [
-                $className2 => [0 => $this->getMockTypeConverter('Class2Converter')],
-                $className3 => [0 => $this->getMockTypeConverter('Class3Converter')],
-
-                $interfaceName1 => [0 => $this->getMockTypeConverter('Interface1Converter')],
-                $interfaceName2 => [0 => $this->getMockTypeConverter('Interface2Converter')],
-                $interfaceName3 => [0 => $this->getMockTypeConverter('Interface3Converter')],
-            ]
-        ];
-
-        // In case the most specific converter does not want to handle this conversion, the second one is taken.
-        $data[] = [
-            'target' => $className3,
-            'expectedConverter' => 'Class2Converter',
-            'typeConverters' => [
-                $className2 => [0 => $this->getMockTypeConverter('Class2Converter')],
-                $className3 => [0 => $this->getMockTypeConverter('Class3Converter', false)],
-
-                $interfaceName1 => [0 => $this->getMockTypeConverter('Interface1Converter')],
-                $interfaceName2 => [0 => $this->getMockTypeConverter('Interface2Converter')],
-                $interfaceName3 => [0 => $this->getMockTypeConverter('Interface3Converter')],
-            ]
-        ];
-
-        // In case there is no most-specific-converter, we climb ub the type hierarchy
-        $data[] = [
-            'target' => $className3,
-            'expectedConverter' => 'Class2Converter-HighPriority',
-            'typeConverters' => [
-                $className2 => [0 => $this->getMockTypeConverter('Class2Converter'), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority')]
-            ]
-        ];
-
-        // If no parent class converter wants to handle it, we ask for all interface converters.
-        $data[] = [
-            'target' => $className3,
-            'expectedConverter' => 'Interface1Converter',
-            'typeConverters' => [
-                $className2 => [0 => $this->getMockTypeConverter('Class2Converter', false), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', false)],
-
-                $interfaceName1 => [4 => $this->getMockTypeConverter('Interface1Converter')],
-                $interfaceName2 => [1 => $this->getMockTypeConverter('Interface2Converter')],
-                $interfaceName3 => [2 => $this->getMockTypeConverter('Interface3Converter')],
-            ]
-        ];
-
-        // If two interface converters have the same priority, an exception is thrown.
-        $data[] = [
-            'target' => $className3,
-            'expectedConverter' => 'Interface1Converter',
-            'typeConverters' => [
-                $className2 => [0 => $this->getMockTypeConverter('Class2Converter', false), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', false)],
-
-                $interfaceName1 => [4 => $this->getMockTypeConverter('Interface1Converter')],
-                $interfaceName2 => [2 => $this->getMockTypeConverter('Interface2Converter')],
-                $interfaceName3 => [2 => $this->getMockTypeConverter('Interface3Converter')],
-            ],
-            'shouldFailWithException' => \TYPO3\CMS\Extbase\Property\Exception\DuplicateTypeConverterException::class
-        ];
-
-        // If no interface converter wants to handle it, a converter for "object" is looked up.
-        $data[] = [
-            'target' => $className3,
-            'expectedConverter' => 'GenericObjectConverter-HighPriority',
-            'typeConverters' => [
-                $className2 => [0 => $this->getMockTypeConverter('Class2Converter', false), 10 => $this->getMockTypeConverter('Class2Converter-HighPriority', false)],
-
-                $interfaceName1 => [4 => $this->getMockTypeConverter('Interface1Converter', false)],
-                $interfaceName2 => [3 => $this->getMockTypeConverter('Interface2Converter', false)],
-                $interfaceName3 => [2 => $this->getMockTypeConverter('Interface3Converter', false)],
-                'object' => [1 => $this->getMockTypeConverter('GenericObjectConverter'), 10 => $this->getMockTypeConverter('GenericObjectConverter-HighPriority')]
-            ],
-        ];
-
-        // If the target is no valid class name and no simple type, an exception is thrown
-        $data[] = [
-            'target' => 'SomeNotExistingClassName',
-            'expectedConverter' => 'GenericObjectConverter-HighPriority',
-            'typeConverters' => [],
-            'shouldFailWithException' => \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException::class
-        ];
-
-        // if the type converter is not found, we expect an exception
-        $data[] = [
-            'target' => $className3,
-            'expectedConverter' => 'Class3Converter',
-            'typeConverters' => [],
-            'shouldFailWithException' => \TYPO3\CMS\Extbase\Property\Exception\TypeConverterException::class
-        ];
-
-        // If The target type is no string, we expect an exception.
-        $data[] = [
-            'target' => new \stdClass(),
-            'expectedConverter' => '',
-            'typeConverters' => [],
-            'shouldFailWithException' => \TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException::class
-        ];
-        return $data;
-    }
-
-    /**
-     * @test
-     * @dataProvider dataProviderForObjectTypeConverters
-     * @param mixed $targetClass
-     * @param mixed $expectedTypeConverter
-     * @param mixed $typeConverters
-     * @param bool $shouldFailWithException
-     * @throws \Exception
-     */
-    public function findTypeConverterShouldReturnConverterForTargetObjectIfItExists($targetClass, $expectedTypeConverter, $typeConverters, $shouldFailWithException = false)
-    {
-        $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, ['dummy']);
-        $propertyMapper->_set('typeConverters', ['string' => $typeConverters]);
-        try {
-            $actualTypeConverter = $propertyMapper->_call('findTypeConverter', 'someSourceString', $targetClass, $this->mockConfiguration);
-            if ($shouldFailWithException) {
-                self::fail('Expected exception ' . $shouldFailWithException . ' which was not thrown.');
-            }
-            self::assertSame($expectedTypeConverter, $actualTypeConverter->_name);
-        } catch (\Exception $e) {
-            if ($shouldFailWithException === false) {
-                throw $e;
-            }
-            self::assertInstanceOf($shouldFailWithException, $e);
-        }
-    }
-
-    /**
-     * @test
-     */
-    public function convertShouldAskConfigurationBuilderForDefaultConfiguration()
-    {
-        $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, ['dummy']);
-        $propertyMapper->injectConfigurationBuilder($this->mockConfigurationBuilder);
-
-        $this->mockConfigurationBuilder->expects(self::once())->method('build')->willReturn($this->mockConfiguration);
-
-        $converter = $this->getMockTypeConverter('string2string');
-        $typeConverters = [
-            'string' => [
-                'string' => [10 => $converter]
-            ]
-        ];
-
-        $propertyMapper->_set('typeConverters', $typeConverters);
-        self::assertEquals('string2string', $propertyMapper->convert('source', 'string'));
-    }
-
-    /**
-     * @test
-     */
-    public function findFirstEligibleTypeConverterInObjectHierarchyShouldReturnNullIfSourceTypeIsUnknown()
-    {
-        /** @var \TYPO3\CMS\Extbase\Property\PropertyMapper|\PHPUnit\Framework\MockObject\MockObject|\TYPO3\TestingFramework\Core\AccessibleObjectInterface */
-        $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, ['dummy']);
-        self::assertNull($propertyMapper->_call('findFirstEligibleTypeConverterInObjectHierarchy', 'source', 'unknownSourceType', \TYPO3\CMS\Extbase\Core\Bootstrap::class));
-    }
-
-    /**
-     * @test
-     */
-    public function doMappingReturnsSourceUnchangedIfAlreadyConverted()
-    {
-        $source = new ObjectStorage();
-        $targetType = ObjectStorage::class;
-        $propertyPath = '';
-        $propertyMapper = new PropertyMapper();
-        $mockConf = $this->mockConfiguration;
-        $mock = \Closure::bind(static function (PropertyMapper $propertyMapper) use ($source, $targetType, $mockConf, &$propertyPath) {
-            return $propertyMapper->doMapping($source, $targetType, $mockConf, $propertyPath);
-        }, null, PropertyMapper::class);
-
-        self::assertSame($source, $mock($propertyMapper));
-    }
-
-    /**
-     * @test
-     */
-    public function doMappingReturnsSourceUnchangedIfAlreadyConvertedToCompositeType()
-    {
-        $source = new ObjectStorage();
-        $targetType = ObjectStorage::class . '<SomeEntity>';
-        $propertyPath = '';
-        $propertyMapper = new PropertyMapper();
-        $mockConf = $this->mockConfiguration;
-        $mock = \Closure::bind(static function (PropertyMapper $propertyMapper) use ($source, $targetType, $mockConf, &$propertyPath) {
-            return $propertyMapper->doMapping($source, $targetType, $mockConf, $propertyPath);
-        }, null, PropertyMapper::class);
-
-        self::assertSame($source, $mock($propertyMapper));
-    }
-
-    /**
-     * @test
-     */
-    public function convertSkipsPropertiesIfConfiguredTo()
-    {
-        $source = ['firstProperty' => 1, 'secondProperty' => 2];
-        $typeConverters = [
-            'array' => [
-                'stdClass' => [10 => $this->getMockTypeConverter('array2object', true, $source, 'integer')]
-            ],
-            'integer' => [
-                'integer' => [10 => $this->getMockTypeConverter('integer2integer')]
-            ]
-        ];
-        $configuration = new \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration();
-
-        $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, ['dummy']);
-        $propertyMapper->_set('typeConverters', $typeConverters);
-
-        $propertyMapper->convert($source, 'stdClass', $configuration->allowProperties('firstProperty')->skipProperties('secondProperty'));
-    }
-
-    /**
-     * @test
-     */
-    public function convertSkipsUnknownPropertiesIfConfiguredTo()
-    {
-        $source = ['firstProperty' => 1, 'secondProperty' => 2];
-        $typeConverters = [
-            'array' => [
-                'stdClass' => [10 => $this->getMockTypeConverter('array2object', true, $source, 'integer')]
-            ],
-            'integer' => [
-                'integer' => [10 => $this->getMockTypeConverter('integer2integer')]
-            ]
-        ];
-        $configuration = new \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration();
-
-        $propertyMapper = $this->getAccessibleMock(\TYPO3\CMS\Extbase\Property\PropertyMapper::class, ['dummy']);
-        $propertyMapper->_set('typeConverters', $typeConverters);
-
-        $propertyMapper->convert($source, 'stdClass', $configuration->allowProperties('firstProperty')->skipUnknownProperties());
-    }
-}
diff --git a/typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/ObjectConverterTest.php b/typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/ObjectConverterTest.php
deleted file mode 100644 (file)
index c035c25..0000000
+++ /dev/null
@@ -1,131 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter;
-
-/*
- * 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\Extbase\Property\PropertyMappingConfiguration;
-use TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter;
-use TYPO3\CMS\Extbase\Reflection\ClassSchema;
-use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
-
-/**
- * Test case
- */
-class ObjectConverterTest extends UnitTestCase
-{
-    /**
-     * @var ObjectConverter
-     */
-    protected $converter;
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService|\PHPUnit\Framework\MockObject\MockObject
-     */
-    protected $mockReflectionService;
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface|\PHPUnit\Framework\MockObject\MockObject
-     */
-    protected $mockObjectManager;
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Object\Container\Container|\PHPUnit\Framework\MockObject\MockObject
-     */
-    protected $mockContainer;
-
-    /**
-     * @throws \InvalidArgumentException
-     * @throws \RuntimeException
-     */
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->mockReflectionService = $this->createMock(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class);
-        $this->mockObjectManager = $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
-        $this->mockContainer = $this->createMock(\TYPO3\CMS\Extbase\Object\Container\Container::class);
-
-        $this->converter = new ObjectConverter();
-        $this->converter->injectReflectionService($this->mockReflectionService);
-        $this->converter->injectObjectManager($this->mockObjectManager);
-        $this->converter->injectObjectContainer($this->mockContainer);
-    }
-
-    /**
-     * @test
-     */
-    public function checkMetadata()
-    {
-        self::assertEquals(['array'], $this->converter->getSupportedSourceTypes(), 'Source types do not match');
-        self::assertEquals('object', $this->converter->getSupportedTargetType(), 'Target type does not match');
-        self::assertEquals(10, $this->converter->getPriority(), 'Priority does not match');
-    }
-
-    /**
-     * @return array
-     */
-    public function dataProviderForCanConvert()
-    {
-        return [
-            // Is entity => cannot convert
-            [\TYPO3\CMS\Extbase\Tests\Fixture\Entity::class, false],
-            // Is valueobject => cannot convert
-            [\TYPO3\CMS\Extbase\Tests\Fixture\ValueObject::class, false],
-            // Is no entity and no value object => can convert
-            ['stdClass', true]
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider dataProviderForCanConvert
-     * @param $className
-     * @param $expected
-     */
-    public function canConvertFromReturnsTrueIfClassIsTaggedWithEntityOrValueObject($className, $expected)
-    {
-        self::assertEquals($expected, $this->converter->canConvertFrom('myInputData', $className));
-    }
-
-    /**
-     * @test
-     */
-    public function getTypeOfChildPropertyShouldUseReflectionServiceToDetermineType(): void
-    {
-        $classSchemaMock = $this->createMock(ClassSchema::class);
-        $classSchemaMock->expects(self::any())->method('getMethod')->with('__construct')->willReturn(new ClassSchema\Method(
-            '__construct',
-            [
-                'params' => [
-                    'thePropertyName' => [
-                        'type' => 'TheTypeOfSubObject',
-                        'elementType' => null
-                    ]
-                ]
-            ],
-            get_class($classSchemaMock)
-        ));
-
-        $this->mockReflectionService
-            ->expects(self::any())
-            ->method('getClassSchema')
-            ->with('TheTargetType')
-            ->willReturn($classSchemaMock);
-
-        $this->mockContainer->expects(self::any())->method('getImplementationClassName')->willReturn('TheTargetType');
-
-        $configuration = new PropertyMappingConfiguration();
-        $configuration->setTypeConverterOptions(\TYPO3\CMS\Extbase\Property\TypeConverter\ObjectConverter::class, []);
-        self::assertEquals('TheTypeOfSubObject', $this->converter->getTypeOfChildProperty('TheTargetType', 'thePropertyName', $configuration));
-    }
-}
diff --git a/typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/PersistentObjectConverterTest.php b/typo3/sysext/extbase/Tests/Unit/Property/TypeConverter/PersistentObjectConverterTest.php
deleted file mode 100644 (file)
index 42ad627..0000000
+++ /dev/null
@@ -1,520 +0,0 @@
-<?php
-namespace TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter;
-
-/*
- * 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\Extbase\Property\Exception\DuplicateObjectException;
-use TYPO3\CMS\Extbase\Property\Exception\InvalidPropertyMappingConfigurationException;
-use TYPO3\CMS\Extbase\Property\Exception\InvalidTargetException;
-use TYPO3\CMS\Extbase\Property\Exception\TargetNotFoundException;
-use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter;
-use TYPO3\CMS\Extbase\Reflection\ClassSchema;
-use TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter\Fixtures\PersistentObjectEntityFixture;
-use TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter\Fixtures\PersistentObjectFixture;
-use TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter\Fixtures\PersistentObjectValueObjectFixture;
-use TYPO3\TestingFramework\Core\Unit\UnitTestCase;
-
-/**
- * Test case
- */
-class PersistentObjectConverterTest extends UnitTestCase
-{
-    /**
-     * @var \TYPO3\CMS\Extbase\Property\TypeConverterInterface|\PHPUnit\Framework\MockObject\MockObject
-     */
-    protected $converter;
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Reflection\ReflectionService|\PHPUnit\Framework\MockObject\MockObject
-     */
-    protected $mockReflectionService;
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface|\PHPUnit\Framework\MockObject\MockObject
-     */
-    protected $mockPersistenceManager;
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface|\PHPUnit\Framework\MockObject\MockObject
-     */
-    protected $mockObjectManager;
-
-    /**
-     * @var \TYPO3\CMS\Extbase\Object\Container\Container|\PHPUnit\Framework\MockObject\MockObject
-     */
-    protected $mockContainer;
-
-    /**
-     * @throws \InvalidArgumentException
-     * @throws \PHPUnit\Framework\Exception
-     * @throws \RuntimeException
-     */
-    protected function setUp(): void
-    {
-        parent::setUp();
-        $this->converter = new PersistentObjectConverter();
-        $this->mockReflectionService = $this->createMock(\TYPO3\CMS\Extbase\Reflection\ReflectionService::class);
-        $this->converter->injectReflectionService($this->mockReflectionService);
-
-        $this->mockPersistenceManager = $this->createMock(\TYPO3\CMS\Extbase\Persistence\PersistenceManagerInterface::class);
-        $this->converter->injectPersistenceManager($this->mockPersistenceManager);
-
-        $this->mockObjectManager = $this->createMock(\TYPO3\CMS\Extbase\Object\ObjectManagerInterface::class);
-        $this->mockObjectManager->expects(self::any())
-            ->method('get')
-            ->willReturnCallback(function ($className, ...$arguments) {
-                $reflectionClass = new \ReflectionClass($className);
-                if (empty($arguments)) {
-                    return $reflectionClass->newInstance();
-                }
-                return $reflectionClass->newInstanceArgs($arguments);
-            });
-        $this->converter->injectObjectManager($this->mockObjectManager);
-
-        $this->mockContainer = $this->createMock(\TYPO3\CMS\Extbase\Object\Container\Container::class);
-        $this->converter->injectObjectContainer($this->mockContainer);
-    }
-
-    /**
-     * @test
-     */
-    public function checkMetadata()
-    {
-        self::assertEquals(['integer', 'string', 'array'], $this->converter->getSupportedSourceTypes(), 'Source types do not match');
-        self::assertEquals('object', $this->converter->getSupportedTargetType(), 'Target type does not match');
-        self::assertEquals(20, $this->converter->getPriority(), 'Priority does not match');
-    }
-
-    /**
-     * @return array
-     */
-    public function dataProviderForCanConvert()
-    {
-        return [
-            [true, false, true],
-            // is entity => can convert
-            [false, true, true],
-            // is valueobject => can convert
-            [false, false, false],
-            // is no entity and no value object => can not convert
-        ];
-    }
-
-    /**
-     * @test
-     * @dataProvider dataProviderForCanConvert
-     */
-    public function canConvertFromReturnsTrueIfClassIsTaggedWithEntityOrValueObject($isEntity, $isValueObject, $expected)
-    {
-        $className = PersistentObjectFixture::class;
-
-        if ($isEntity) {
-            $className = PersistentObjectEntityFixture::class;
-        } elseif ($isValueObject) {
-            $className = PersistentObjectValueObjectFixture::class;
-        }
-        self::assertEquals($expected, $this->converter->canConvertFrom('myInputData', $className));
-    }
-
-    /**
-     * @test
-     */
-    public function getSourceChildPropertiesToBeConvertedReturnsAllPropertiesExceptTheIdentityProperty()
-    {
-        $source = [
-            'k1' => 'v1',
-            '__identity' => 'someIdentity',
-            'k2' => 'v2'
-        ];
-        $expected = [
-            'k1' => 'v1',
-            'k2' => 'v2'
-        ];
-        self::assertEquals($expected, $this->converter->getSourceChildPropertiesToBeConverted($source));
-    }
-
-    /**
-     * @test
-     */
-    public function getTypeOfChildPropertyShouldUseReflectionServiceToDetermineType()
-    {
-        $mockSchema = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class)->disableOriginalConstructor()->getMock();
-        $this->mockReflectionService->expects(self::any())->method('getClassSchema')->with('TheTargetType')->willReturn($mockSchema);
-
-        $this->mockContainer->expects(self::any())->method('getImplementationClassName')->willReturn('TheTargetType');
-        $mockSchema->expects(self::any())->method('hasProperty')->with('thePropertyName')->willReturn(true);
-        $mockSchema->expects(self::any())->method('getProperty')->with('thePropertyName')->willReturn(new ClassSchema\Property(
-            'thePropertyName',
-            [
-                'propertyCharacteristicsBit' => 0,
-                't' => 'TheTypeOfSubObject',
-                'e' => null,
-            ]
-        ));
-        $configuration = $this->buildConfiguration([]);
-        self::assertEquals('TheTypeOfSubObject', $this->converter->getTypeOfChildProperty('TheTargetType', 'thePropertyName', $configuration));
-    }
-
-    /**
-     * @test
-     */
-    public function getTypeOfChildPropertyShouldUseConfiguredTypeIfItWasSet()
-    {
-        $this->mockReflectionService->expects(self::never())->method('getClassSchema');
-        $this->mockContainer->expects(self::any())->method('getImplementationClassName')->willReturn('foo');
-
-        $configuration = $this->buildConfiguration([]);
-        $configuration->forProperty('thePropertyName')->setTypeConverterOption(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_TARGET_TYPE, 'Foo\Bar');
-        self::assertEquals('Foo\Bar', $this->converter->getTypeOfChildProperty('foo', 'thePropertyName', $configuration));
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldFetchObjectFromPersistenceIfUuidStringIsGiven()
-    {
-        $identifier = '17';
-        $object = new \stdClass();
-
-        $this->mockPersistenceManager->expects(self::any())->method('getObjectByIdentifier')->with($identifier)->willReturn($object);
-        self::assertSame($object, $this->converter->convertFrom($identifier, 'MySpecialType'));
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldFetchObjectFromPersistenceIfuidStringIsGiven()
-    {
-        $identifier = '17';
-        $object = new \stdClass();
-
-        $this->mockPersistenceManager->expects(self::any())->method('getObjectByIdentifier')->with($identifier)->willReturn($object);
-        self::assertSame($object, $this->converter->convertFrom($identifier, 'MySpecialType'));
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldFetchObjectFromPersistenceIfOnlyIdentityArrayGiven()
-    {
-        $identifier = '12345';
-        $object = new \stdClass();
-
-        $source = [
-            '__identity' => $identifier
-        ];
-        $this->mockPersistenceManager->expects(self::any())->method('getObjectByIdentifier')->with($identifier)->willReturn($object);
-        self::assertSame($object, $this->converter->convertFrom($source, 'MySpecialType'));
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldThrowExceptionIfObjectNeedsToBeModifiedButConfigurationIsNotSet()
-    {
-        $this->expectException(InvalidPropertyMappingConfigurationException::class);
-        $this->expectExceptionCode(1297932028);
-        $identifier = '12345';
-        $object = new \stdClass();
-        $object->someProperty = 'asdf';
-
-        $source = [
-            '__identity' => $identifier,
-            'foo' => 'bar'
-        ];
-        $this->mockPersistenceManager->expects(self::any())->method('getObjectByIdentifier')->with($identifier)->willReturn($object);
-        $this->converter->convertFrom($source, 'MySpecialType');
-    }
-
-    /**
-     * @param array $typeConverterOptions
-     * @return \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration
-     */
-    protected function buildConfiguration($typeConverterOptions)
-    {
-        $configuration = new \TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration();
-        $configuration->setTypeConverterOptions(\TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter::class, $typeConverterOptions);
-        return $configuration;
-    }
-
-    /**
-     * @param int $numberOfResults
-     * @param Matcher $howOftenIsGetFirstCalled
-     * @return \stdClass
-     */
-    public function setupMockQuery($numberOfResults, $howOftenIsGetFirstCalled)
-    {
-        $mockClassSchema = $this->getMockBuilder(\TYPO3\CMS\Extbase\Reflection\ClassSchema::class)
-            ->setConstructorArgs([\TYPO3\CMS\Extbase\Tests\Unit\Property\TypeConverter\Fixtures\Query::class])
-            ->getMock();
-        $this->mockReflectionService->expects(self::any())->method('getClassSchema')->with('SomeType')->willReturn($mockClassSchema);
-
-        $mockConstraint = $this->getMockBuilder(\TYPO3\CMS\Extbase\Persistence\Generic\Qom\Comparison::class)->disableOriginalConstructor()->getMock();
-
-        $mockObject = new \stdClass();
-        $mockQuery = $this->createMock(\TYPO3\CMS\Extbase\Persistence\QueryInterface::class);
-        $mockQueryResult = $this->createMock(\TYPO3\CMS\Extbase\Persistence\QueryResultInterface::class);
-        $mockQueryResult->expects(self::any())->method('count')->willReturn($numberOfResults);
-        $mockQueryResult->expects($howOftenIsGetFirstCalled)->method('getFirst')->willReturn($mockObject);
-        $mockQuery->expects(self::any())->method('equals')->with('key1', 'value1')->willReturn($mockConstraint);
-        $mockQuery->expects(self::any())->method('matching')->with($mockConstraint)->willReturn($mockQuery);
-        $mockQuery->expects(self::any())->method('execute')->willReturn($mockQueryResult);
-
-        $this->mockPersistenceManager->expects(self::any())->method('createQueryForType')->with('SomeType')->willReturn($mockQuery);
-
-        return $mockObject;
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldReturnExceptionIfNoMatchingObjectWasFound()
-    {
-        $this->expectException(TargetNotFoundException::class);
-        $this->expectExceptionCode(1297933823);
-        $this->setupMockQuery(0, self::never());
-        $this->mockReflectionService->expects(self::never())->method('getClassSchema');
-
-        $source = [
-            '__identity' => 123
-        ];
-        $actual = $this->converter->convertFrom($source, 'SomeType');
-        self::assertNull($actual);
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldThrowExceptionIfMoreThanOneObjectWasFound()
-    {
-        $this->expectException(DuplicateObjectException::class);
-        // @TODO expectExceptionCode is 0
-        $this->setupMockQuery(2, self::never());
-
-        $source = [
-            '__identity' => 666
-        ];
-        $this->mockPersistenceManager
-            ->expects(self::any())
-            ->method('getObjectByIdentifier')
-            ->with(666)
-            ->will(self::throwException(new DuplicateObjectException('testing', 1476107580)));
-        $this->converter->convertFrom($source, 'SomeType');
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldThrowExceptionIfObjectNeedsToBeCreatedButConfigurationIsNotSet()
-    {
-        $this->expectException(InvalidPropertyMappingConfigurationException::class);
-        // @TODO expectExceptionCode is 0
-        $source = [
-            'foo' => 'bar'
-        ];
-        $this->converter->convertFrom($source, 'MySpecialType');
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldCreateObject()
-    {
-        $source = [
-            'propertyX' => 'bar'
-        ];
-        $convertedChildProperties = [
-            'property1' => 'bar'
-        ];
-        $expectedObject = new \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters();
-        $expectedObject->property1 = 'bar';
-
-        $configuration = $this->buildConfiguration([PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true]);
-        $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters::class, $convertedChildProperties, $configuration);
-        self::assertEquals($expectedObject, $result);
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldThrowExceptionIfPropertyOnTargetObjectCouldNotBeSet()
-    {
-        $this->expectException(InvalidTargetException::class);
-        $this->expectExceptionCode(1297935345);
-        $source = [
-            'propertyX' => 'bar'
-        ];
-        $object = new \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters();
-        $convertedChildProperties = [
-            'propertyNotExisting' => 'bar'
-        ];
-        $this->mockObjectManager->expects(self::any())->method('get')->with(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters::class)->willReturn($object);
-        $configuration = $this->buildConfiguration([PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true]);
-        $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSetters::class, $convertedChildProperties, $configuration);
-        self::assertSame($object, $result);
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldCreateObjectWhenThereAreConstructorParameters(): void
-    {
-        $classSchemaMock = $this->createMock(ClassSchema::class);
-        $classSchemaMock
-            ->expects(self::any())
-            ->method('getMethod')
-            ->with('__construct')
-            ->willReturn(
-                new ClassSchema\Method(
-                    '__construct',
-                    [
-                        'params' => [
-                            'property1' => ['optional' => false]
-                        ]
-                    ],
-                    get_class($classSchemaMock)
-                )
-            );
-
-        $classSchemaMock
-            ->expects(self::any())
-            ->method('hasConstructor')
-            ->willReturn(true);
-
-        $this->mockReflectionService
-            ->expects(self::any())
-            ->method('getClassSchema')
-            ->with(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class)
-            ->willReturn($classSchemaMock);
-
-        $source = [
-            'propertyX' => 'bar'
-        ];
-        $convertedChildProperties = [
-            'property1' => 'param1',
-            'property2' => 'bar'
-        ];
-        $expectedObject = new \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor('param1');
-        $expectedObject->setProperty2('bar');
-
-        $this->mockContainer->expects(self::any())->method('getImplementationClassName')->willReturn(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class);
-        $configuration = $this->buildConfiguration([PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true]);
-        $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class, $convertedChildProperties, $configuration);
-        self::assertEquals($expectedObject, $result);
-        self::assertEquals('bar', $expectedObject->getProperty2());
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldCreateObjectWhenThereAreOptionalConstructorParameters()
-    {
-        $classSchemaMock = $this->createMock(ClassSchema::class);
-        $classSchemaMock
-            ->expects(self::any())
-            ->method('getMethod')
-            ->with('__construct')
-            ->willReturn(
-                new ClassSchema\Method(
-                    '__construct',
-                    [
-                        'params' => [
-                            'property1' => ['optional' => true, 'defaultValue' => 'thisIsTheDefaultValue']
-                        ]
-                    ],
-                    get_class($classSchemaMock)
-                )
-            );
-
-        $classSchemaMock
-            ->expects(self::any())
-            ->method('hasConstructor')
-            ->willReturn(true);
-
-        $this->mockReflectionService
-            ->expects(self::any())
-            ->method('getClassSchema')
-            ->with(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class)
-            ->willReturn($classSchemaMock);
-
-        $source = [
-            'propertyX' => 'bar'
-        ];
-        $expectedObject = new \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor('thisIsTheDefaultValue');
-
-        $this->mockContainer->expects(self::any())->method('getImplementationClassName')->willReturn(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class);
-        $configuration = $this->buildConfiguration([PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true]);
-        $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class, [], $configuration);
-        self::assertEquals($expectedObject, $result);
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldThrowExceptionIfRequiredConstructorParameterWasNotFound(): void
-    {
-        $classSchemaMock = $this->createMock(ClassSchema::class);
-        $classSchemaMock
-            ->expects(self::any())
-            ->method('getMethod')
-            ->with('__construct')
-            ->willReturn(
-                new ClassSchema\Method(
-                    '__construct',
-                    [
-                        'params' => [
-                            'property1' => ['optional' => false]
-                        ]
-                    ],
-                    get_class($classSchemaMock)
-                )
-            );
-
-        $classSchemaMock
-            ->expects(self::any())
-            ->method('hasConstructor')
-            ->willReturn(true);
-
-        $this->mockReflectionService
-            ->expects(self::any())
-            ->method('getClassSchema')
-            ->with(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class)
-            ->willReturn($classSchemaMock);
-
-        $this->expectException(InvalidTargetException::class);
-        $this->expectExceptionCode(1268734872);
-        $source = [
-            'propertyX' => 'bar'
-        ];
-        $object = new \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor('param1');
-        $convertedChildProperties = [
-            'property2' => 'bar'
-        ];
-
-        $this->mockContainer->expects(self::any())->method('getImplementationClassName')->willReturn(\TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class);
-        $configuration = $this->buildConfiguration([PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED => true]);
-        $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class, $convertedChildProperties, $configuration);
-        self::assertSame($object, $result);
-    }
-
-    /**
-     * @test
-     */
-    public function convertFromShouldReturnNullForEmptyString()
-    {
-        $source = '';
-        $result = $this->converter->convertFrom($source, \TYPO3\CMS\Extbase\Tests\Fixture\ClassWithSettersAndConstructor::class);
-        self::assertNull($result);
-    }
-}