[BUGFIX] Keep existing validation errors for recursive domain relations
[Packages/TYPO3.CMS.git] / typo3 / sysext / extbase / Tests / Unit / Validation / Validator / GenericObjectValidatorTest.php
1 <?php
2 namespace TYPO3\CMS\Extbase\Tests\Unit\Validation\Validator;
3
4 /* *
5 * This script belongs to the Extbase framework. *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License as published by the *
9 * Free Software Foundation, either version 3 of the License, or (at your *
10 * option) any later version. *
11 * *
12 * This script is distributed in the hope that it will be useful, but *
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
14 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
15 * General Public License for more details. *
16 * *
17 * You should have received a copy of the GNU Lesser General Public *
18 * License along with the script. *
19 * If not, see http://www.gnu.org/licenses/lgpl.html *
20 * *
21 * The TYPO3 project - inspiring people to share! *
22 * */
23
24 use TYPO3\CMS\Extbase\Error\Error;
25 use TYPO3\CMS\Extbase\Error\Result;
26 use TYPO3\CMS\Extbase\Validation\Validator\GenericObjectValidator;
27 use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
28
29 /**
30 * Testcase for the Generic Object Validator
31 *
32 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
33 */
34 class GenericObjectValidatorTest extends AbstractValidatorTestcase
35 {
36 /**
37 * @var string
38 */
39 protected $validatorClassName = GenericObjectValidator::class;
40
41 protected function setUp()
42 {
43 parent::setUp();
44 }
45
46 /**
47 * @test
48 */
49 public function validatorShouldReturnErrorsIfTheValueIsNoObjectAndNotNull()
50 {
51 $this->assertTrue($this->validator->validate('foo')->hasErrors());
52 }
53
54 /**
55 * @test
56 */
57 public function validatorShouldReturnNoErrorsIfTheValueIsNull()
58 {
59 $this->assertFalse($this->validator->validate(null)->hasErrors());
60 }
61
62 /**
63 * @return array
64 */
65 public function dataProviderForValidator()
66 {
67 $error1 = new \TYPO3\CMS\Extbase\Error\Error('error1', 1);
68 $error2 = new \TYPO3\CMS\Extbase\Error\Error('error2', 2);
69 $emptyResult1 = new \TYPO3\CMS\Extbase\Error\Result();
70 $emptyResult2 = new \TYPO3\CMS\Extbase\Error\Result();
71 $resultWithError1 = new \TYPO3\CMS\Extbase\Error\Result();
72 $resultWithError1->addError($error1);
73 $resultWithError2 = new \TYPO3\CMS\Extbase\Error\Result();
74 $resultWithError2->addError($error2);
75 $classNameForObjectWithPrivateProperties = $this->getUniqueId('B');
76 eval('class ' . $classNameForObjectWithPrivateProperties . '{ protected $foo = \'foovalue\'; protected $bar = \'barvalue\'; }');
77 $objectWithPrivateProperties = new $classNameForObjectWithPrivateProperties();
78 return [
79 // If no errors happened, this is shown
80 [$objectWithPrivateProperties, $emptyResult1, $emptyResult2, []],
81 // If errors on two properties happened, they are merged together.
82 [$objectWithPrivateProperties, $resultWithError1, $resultWithError2, ['foo' => [$error1], 'bar' => [$error2]]]
83 ];
84 }
85
86 /**
87 * @test
88 * @dataProvider dataProviderForValidator
89 * @param mixed $mockObject
90 * @param mixed $validationResultForFoo
91 * @param mixed $validationResultForBar
92 * @param mixed $errors
93 */
94 public function validateChecksAllPropertiesForWhichAPropertyValidatorExists($mockObject, $validationResultForFoo, $validationResultForBar, $errors)
95 {
96 $validatorForFoo = $this->getMockBuilder(ValidatorInterface::class)
97 ->setMethods(['validate', 'getOptions'])
98 ->getMock();
99 $validatorForFoo->expects($this->once())->method('validate')->with('foovalue')->will($this->returnValue($validationResultForFoo));
100 $validatorForBar = $this->getMockBuilder(ValidatorInterface::class)
101 ->setMethods(['validate', 'getOptions'])
102 ->getMock();
103 $validatorForBar->expects($this->once())->method('validate')->with('barvalue')->will($this->returnValue($validationResultForBar));
104 $this->validator->addPropertyValidator('foo', $validatorForFoo);
105 $this->validator->addPropertyValidator('bar', $validatorForBar);
106 $this->assertEquals($errors, $this->validator->validate($mockObject)->getFlattenedErrors());
107 }
108
109 /**
110 * @test
111 */
112 public function validateCanHandleRecursiveTargetsWithoutEndlessLooping()
113 {
114 $classNameA = $this->getUniqueId('B');
115 eval('class ' . $classNameA . '{ public $b; }');
116 $classNameB = $this->getUniqueId('B');
117 eval('class ' . $classNameB . '{ public $a; }');
118 $A = new $classNameA();
119 $B = new $classNameB();
120 $A->b = $B;
121 $B->a = $A;
122
123 $aValidator = new GenericObjectValidator([]);
124 $bValidator = new GenericObjectValidator([]);
125
126 $aValidator->addPropertyValidator('b', $bValidator);
127 $bValidator->addPropertyValidator('a', $aValidator);
128 $this->assertFalse($aValidator->validate($A)->hasErrors());
129 }
130
131 /**
132 * @test
133 */
134 public function validateDetectsFailuresInRecursiveTargetsI()
135 {
136 $classNameA = $this->getUniqueId('A');
137 eval('class ' . $classNameA . '{ public $b; }');
138 $classNameB = $this->getUniqueId('B');
139 eval('class ' . $classNameB . '{ public $a; public $uuid = 0xF; }');
140 $A = new $classNameA();
141 $B = new $classNameB();
142 $A->b = $B;
143 $B->a = $A;
144 $aValidator = $this->getValidator();
145 $bValidator = $this->getValidator();
146
147 $aValidator->addPropertyValidator('b', $bValidator);
148 $bValidator->addPropertyValidator('a', $aValidator);
149 $error = new \TYPO3\CMS\Extbase\Error\Error('error1', 123);
150 $result = new \TYPO3\CMS\Extbase\Error\Result();
151 $result->addError($error);
152 $mockUuidValidator = $this->getMockBuilder(ValidatorInterface::class)
153 ->setMethods(['validate', 'getOptions'])
154 ->getMock();
155 $mockUuidValidator->expects($this->any())->method('validate')->with(15)->will($this->returnValue($result));
156 $bValidator->addPropertyValidator('uuid', $mockUuidValidator);
157 $this->assertSame(['b.uuid' => [$error]], $aValidator->validate($A)->getFlattenedErrors());
158 }
159
160 /**
161 * @test
162 */
163 public function validateDetectsFailuresInRecursiveTargetsII()
164 {
165 $classNameA = $this->getUniqueId('A');
166 eval('class ' . $classNameA . '{ public $b; public $uuid = 0xF; }');
167 $classNameB = $this->getUniqueId('B');
168 eval('class ' . $classNameB . '{ public $a; public $uuid = 0xF; }');
169 $A = new $classNameA();
170 $B = new $classNameB();
171 $A->b = $B;
172 $B->a = $A;
173 $aValidator = $this->getValidator();
174 $bValidator = $this->getValidator();
175
176 $aValidator->addPropertyValidator('b', $bValidator);
177 $bValidator->addPropertyValidator('a', $aValidator);
178 $error1 = new \TYPO3\CMS\Extbase\Error\Error('error1', 123);
179 $result1 = new \TYPO3\CMS\Extbase\Error\Result();
180 $result1->addError($error1);
181 $mockUuidValidator = $this->getMockBuilder(ValidatorInterface::class)
182 ->setMethods(['validate', 'getOptions'])
183 ->getMock();
184 $mockUuidValidator->expects($this->any())->method('validate')->with(15)->will($this->returnValue($result1));
185 $aValidator->addPropertyValidator('uuid', $mockUuidValidator);
186 $bValidator->addPropertyValidator('uuid', $mockUuidValidator);
187 $this->assertSame(['b.uuid' => [$error1], 'uuid' => [$error1]], $aValidator->validate($A)->getFlattenedErrors());
188 }
189
190 /**
191 * @test
192 */
193 public function validateDetectsFailuresInRecursiveTargetsIII()
194 {
195 // Create to test-entities. Use the same uuid to make the same validator trigger on both objects
196 $A = new class() {
197 public $b;
198 public $uuid = 0xF;
199 };
200
201 $B = new class() {
202 public $a;
203 public $uuid = 0xF;
204 };
205
206 $A->b = $B;
207 $B->a = $A;
208 $aValidator = new GenericObjectValidator();
209 $bValidator = new GenericObjectValidator();
210
211 $error1 = new Error('error1', 123);
212 $result1 = new Result();
213 $result1->addError($error1);
214
215 /** @var ValidatorInterface|\PHPUnit_Framework_MockObject_MockObject $mockValidatorUuidNot0xF */
216 $mockValidatorUuidNot0xF = $this->getMockBuilder(ValidatorInterface::class)
217 ->setMethods(['validate', 'getOptions'])
218 ->getMock();
219 $mockValidatorUuidNot0xF->expects($this->any())
220 ->method('validate')->with(0xF)->will($this->returnValue($result1));
221
222 $aValidator->addPropertyValidator('uuid', $mockValidatorUuidNot0xF);
223 $bValidator->addPropertyValidator('uuid', $mockValidatorUuidNot0xF);
224 $aValidator->addPropertyValidator('b', $bValidator);
225 $bValidator->addPropertyValidator('a', $aValidator);
226
227 // assert that the validation error is being reported for both objects
228 $this->assertSame(
229 ['uuid' => [$error1], 'b.uuid' => [$error1], 'b.a.uuid' => [$error1]],
230 $aValidator->validate($A)->getFlattenedErrors()
231 );
232 }
233 }