From f7a0fedd700059b3a2286723b48cb80ba0561fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 28 Jun 2016 16:35:17 +0200 Subject: [PATCH 1/4] EZP-25280: write test case --- .../Limitation/ParentDepthLimitationTest.php | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/eZ/Publish/API/Repository/Tests/Values/User/Limitation/ParentDepthLimitationTest.php b/eZ/Publish/API/Repository/Tests/Values/User/Limitation/ParentDepthLimitationTest.php index f551407be1a..c7ff82082c6 100644 --- a/eZ/Publish/API/Repository/Tests/Values/User/Limitation/ParentDepthLimitationTest.php +++ b/eZ/Publish/API/Repository/Tests/Values/User/Limitation/ParentDepthLimitationTest.php @@ -29,8 +29,10 @@ class ParentDepthLimitationTest extends BaseLimitationTest * * @see \eZ\Publish\API\Repository\Values\User\Limitation\ContentTypeLimitation * @see \eZ\Publish\API\Repository\Values\User\Limitation\ParentDepthLimitation + * + * @expectedException \eZ\Publish\API\Repository\Exceptions\UnauthorizedException */ - public function testParentDepthLimitationAllow() + public function testParentDepthLimitationForbid() { $repository = $this->getRepository(); @@ -45,7 +47,7 @@ public function testParentDepthLimitationAllow() $policyCreate = $roleService->newPolicyCreateStruct('content', 'create'); $policyCreate->addLimitation( new ParentDepthLimitation( - array('limitationValues' => array(2)) + array('limitationValues' => array(3)) ) ); $policyCreate->addLimitation( @@ -74,9 +76,8 @@ public function testParentDepthLimitationAllow() * * @see \eZ\Publish\API\Repository\Values\User\Limitation\ContentTypeLimitation * @see \eZ\Publish\API\Repository\Values\User\Limitation\ParentDepthLimitation - * @expectedException \eZ\Publish\API\Repository\Exceptions\UnauthorizedException */ - public function testParentDepthLimitationForbid() + public function testParentDepthLimitationAllow() { $repository = $this->getRepository(); @@ -91,7 +92,7 @@ public function testParentDepthLimitationForbid() $policyCreate = $roleService->newPolicyCreateStruct('content', 'create'); $policyCreate->addLimitation( new ParentDepthLimitation( - array('limitationValues' => array(1, 3, 4)) + array('limitationValues' => array(1, 2, 3, 4)) ) ); $policyCreate->addLimitation( @@ -109,4 +110,50 @@ public function testParentDepthLimitationForbid() $this->createWikiPageDraft(); /* END: Use Case */ } + + /** + * Tests a combination of ParentDepthLimitation and ContentTypeLimitation. + * + * @depends testParentDepthLimitationAllow + * + * @see \eZ\Publish\API\Repository\Values\User\Limitation\ContentTypeLimitation + * @see \eZ\Publish\API\Repository\Values\User\Limitation\ParentDepthLimitation + */ + public function testParentDepthLimitationAllowPublish() + { + $repository = $this->getRepository(); + + $contentTypeId = $this->generateId('contentType', 22); + /* BEGIN: Use Case */ + $user = $this->createUserVersion1(); + + $roleService = $repository->getRoleService(); + + $role = $roleService->loadRoleByIdentifier('Editor'); + + $policyCreate = $roleService->newPolicyCreateStruct('content', 'create'); + $policyCreate->addLimitation( + new ParentDepthLimitation( + array('limitationValues' => array(1, 2, 3, 4)) + ) + ); + $policyCreate->addLimitation( + new ContentTypeLimitation( + array('limitationValues' => array($contentTypeId)) + ) + ); + + $role = $roleService->addPolicy($role, $policyCreate); + + $roleService->assignRoleToUser($role, $user); + + $repository->setCurrentUser($user); + + $draft = $this->createWikiPageDraft(); + + $contentService = $repository->getContentService(); + + $content = $contentService->publishVersion($draft->versionInfo); + /* END: Use Case */ + } } From 476866931841928741a56fc7a9aa6713f0f2641e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 28 Jun 2016 18:50:42 +0200 Subject: [PATCH 2/4] EZP-25280: ParentDepthLimitationType handles missing targets (publish) --- .../Limitation/ParentDepthLimitationType.php | 75 +++++++++++++++++-- 1 file changed, 70 insertions(+), 5 deletions(-) diff --git a/eZ/Publish/Core/Limitation/ParentDepthLimitationType.php b/eZ/Publish/Core/Limitation/ParentDepthLimitationType.php index 821af29343d..1df5f660100 100644 --- a/eZ/Publish/Core/Limitation/ParentDepthLimitationType.php +++ b/eZ/Publish/Core/Limitation/ParentDepthLimitationType.php @@ -12,8 +12,12 @@ use eZ\Publish\API\Repository\Values\ValueObject; use eZ\Publish\API\Repository\Values\User\UserReference as APIUserReference; +use eZ\Publish\API\Repository\Values\Content\Content; +use eZ\Publish\API\Repository\Values\Content\ContentCreateStruct; +use eZ\Publish\API\Repository\Values\Content\ContentInfo; use eZ\Publish\API\Repository\Values\Content\Location; use eZ\Publish\API\Repository\Values\Content\LocationCreateStruct; +use eZ\Publish\API\Repository\Values\Content\VersionInfo; use eZ\Publish\Core\Base\Exceptions\InvalidArgumentException; use eZ\Publish\Core\Base\Exceptions\InvalidArgumentType; use eZ\Publish\API\Repository\Values\User\Limitation\ParentDepthLimitation as APIParentDepthLimitation; @@ -102,23 +106,84 @@ public function evaluate(APILimitationValue $value, APIUserReference $currentUse throw new InvalidArgumentException('$value', 'Must be of type: APIParentDepthLimitation'); } - // Parent Limitations are usually used by content/create where target is specified, so we return false if not provided. + if ($object instanceof ContentCreateStruct) { + return $this->evaluateForContentCreateStruct($value, $targets); + } elseif ($object instanceof Content) { + $object = $object->getVersionInfo()->getContentInfo(); + } elseif ($object instanceof VersionInfo) { + $object = $object->getContentInfo(); + } elseif (!$object instanceof ContentInfo) { + throw new InvalidArgumentException( + '$object', + 'Must be of type: ContentCreateStruct, Content, VersionInfo or ContentInfo' + ); + } + + // Load locations if no specific placement was provided + if (empty($targets)) { + if ($object->published) { + $targets = $this->persistence->locationHandler()->loadLocationsByContent($object->id); + } else { + // @todo Need support for draft locations to to work correctly + $targets = $this->persistence->locationHandler()->loadParentLocationsForDraftContent($object->id); + } + } + + // Parent Limitations are usually used by content/create where target is specified, + // so we return false if not provided. if (empty($targets)) { return false; } foreach ($targets as $target) { - if ($target instanceof LocationCreateStruct) { - $depth = $this->persistence->locationHandler()->load($target->parentLocationId)->depth; - } elseif ($target instanceof Location || $target instanceof SPILocation) { + if ($target instanceof Location || $target instanceof SPILocation) { $depth = $target->depth; } else { throw new InvalidArgumentException( '$targets', - 'Must contain objects of type: Location or LocationCreateStruct' + 'Must contain objects of type: Location' + ); + } + + // All placements must match + if (!in_array($depth, $value->limitationValues)) { + return false; + } + } + + return true; + } + + /** + * Evaluate permissions for ContentCreateStruct against LocationCreateStruct placements. + * + * @throws \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException If $targets does not contain + * objects of type LocationCreateStruct + * + * @param \eZ\Publish\API\Repository\Values\User\Limitation $value + * @param array|null $targets + * + * @return bool + */ + protected function evaluateForContentCreateStruct(APILimitationValue $value, array $targets = null) + { + // If targets is empty/null return false as user does not have access + // to content w/o location with this limitation + if (empty($targets)) { + return false; + } + + foreach ($targets as $target) { + if (!$target instanceof LocationCreateStruct) { + throw new InvalidArgumentException( + '$targets', + 'If $object is ContentCreateStruct must contain objects of type: LocationCreateStruct' ); } + $depth = $this->persistence->locationHandler()->load($target->parentLocationId)->depth; + + // All placements must match if (!in_array($depth, $value->limitationValues)) { return false; } From 9c725e79527fe8fc979f5a85aed30224b6146cdf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Tue, 28 Jun 2016 18:51:33 +0200 Subject: [PATCH 3/4] EZP-25280: add unit tests for ParentDepthLimitationType --- .../Tests/ParentDepthLimitationTypeTest.php | 435 ++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 eZ/Publish/Core/Limitation/Tests/ParentDepthLimitationTypeTest.php diff --git a/eZ/Publish/Core/Limitation/Tests/ParentDepthLimitationTypeTest.php b/eZ/Publish/Core/Limitation/Tests/ParentDepthLimitationTypeTest.php new file mode 100644 index 00000000000..067df5b551f --- /dev/null +++ b/eZ/Publish/Core/Limitation/Tests/ParentDepthLimitationTypeTest.php @@ -0,0 +1,435 @@ +locationHandlerMock = $this->getMock( + 'eZ\\Publish\\SPI\\Persistence\\Content\\Location\\Handler', + [], + [], + '', + false + ); + } + + /** + * Tear down Location Handler mock. + */ + public function tearDown() + { + unset($this->locationHandlerMock); + parent::tearDown(); + } + + /** + * @return \eZ\Publish\Core\Limitation\ParentDepthLimitationType + */ + public function testConstruct() + { + return new ParentDepthLimitationType($this->getPersistenceMock()); + } + + /** + * @return array + */ + public function providerForTestAcceptValue() + { + return [ + [new ParentDepthLimitation()], + [new ParentDepthLimitation([])], + [new ParentDepthLimitation(['limitationValues' => [0, 1, 2, PHP_INT_MAX]])], + ]; + } + + /** + * @dataProvider providerForTestAcceptValue + * @depends testConstruct + * + * @param \eZ\Publish\API\Repository\Values\User\Limitation\ParentDepthLimitation $limitation + * @param \eZ\Publish\Core\Limitation\ParentDepthLimitationType $limitationType + */ + public function testAcceptValue(ParentDepthLimitation $limitation, ParentDepthLimitationType $limitationType) + { + $limitationType->acceptValue($limitation); + } + + /** + * @return array + */ + public function providerForTestAcceptValueException() + { + return [ + [new ObjectStateLimitation()], + [new ParentDepthLimitation(['limitationValues' => [true]])], + ]; + } + + /** + * @dataProvider providerForTestAcceptValueException + * @depends testConstruct + * @expectedException \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException + * + * @param \eZ\Publish\API\Repository\Values\User\Limitation $limitation + * @param \eZ\Publish\Core\Limitation\ParentDepthLimitationType $limitationType + */ + public function testAcceptValueException(Limitation $limitation, ParentDepthLimitationType $limitationType) + { + $limitationType->acceptValue($limitation); + } + + /** + * @return array + */ + public function providerForTestValidatePass() + { + return [ + [new ParentDepthLimitation()], + [new ParentDepthLimitation([])], + [new ParentDepthLimitation(['limitationValues' => [2]])], + ]; + } + + /** + * @dataProvider providerForTestValidatePass + * @depends testConstruct + * + * @param \eZ\Publish\API\Repository\Values\User\Limitation\ParentDepthLimitation $limitation + */ + public function testValidatePass(ParentDepthLimitation $limitation, ParentDepthLimitationType $limitationType) + { + $validationErrors = $limitationType->validate($limitation); + self::assertEmpty($validationErrors); + } + + /** + * @depends testConstruct + * + * @param \eZ\Publish\Core\Limitation\ParentDepthLimitationType $limitationType + */ + public function testBuildValue(ParentDepthLimitationType $limitationType) + { + $expected = [2, 7]; + $value = $limitationType->buildValue($expected); + + self::assertInstanceOf( + '\eZ\Publish\API\Repository\Values\User\Limitation\ParentDepthLimitation', + $value + ); + self::assertInternalType('array', $value->limitationValues); + self::assertEquals($expected, $value->limitationValues); + } + + /** + * @return array + */ + public function providerForTestEvaluate() + { + // Mocks for testing Content & VersionInfo objects, should only be used once because of expect rules. + $contentMock = $this->getMock( + 'eZ\\Publish\\API\\Repository\\Values\\Content\\Content', + [], + [], + '', + false + ); + + $versionInfoMock = $this->getMock( + 'eZ\\Publish\\API\\Repository\\Values\\Content\\VersionInfo', + [], + [], + '', + false + ); + + $contentMock + ->expects($this->once()) + ->method('getVersionInfo') + ->will($this->returnValue($versionInfoMock)); + + $versionInfoMock + ->expects($this->once()) + ->method('getContentInfo') + ->will($this->returnValue(new ContentInfo(['published' => true]))); + + $versionInfoMock2 = $this->getMock( + 'eZ\\Publish\\API\\Repository\\Values\\Content\\VersionInfo', + [], + [], + '', + false + ); + + $versionInfoMock2 + ->expects($this->once()) + ->method('getContentInfo') + ->will($this->returnValue(new ContentInfo(['published' => true]))); + + return [ + // ContentInfo, with targets, no access + [ + 'limitation' => new ParentDepthLimitation(), + 'object' => new ContentInfo(['published' => true]), + 'targets' => [new Location()], + 'persistence' => [], + 'expected' => false, + ], + // ContentInfo, with targets, no access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2]]), + 'object' => new ContentInfo(['published' => true]), + 'targets' => [new Location(['depth' => 55])], + 'persistence' => [], + 'expected' => false, + ], + // ContentInfo, with targets, with access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2]]), + 'object' => new ContentInfo(['published' => true]), + 'targets' => [new Location(['depth' => 2])], + 'persistence' => [], + 'expected' => true, + ], + // ContentInfo, no targets, with access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2]]), + 'object' => new ContentInfo(['published' => true]), + 'targets' => null, + 'persistence' => [new Location(['depth' => 2])], + 'expected' => true, + ], + // ContentInfo, no targets, no access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2, 43]]), + 'object' => new ContentInfo(['published' => true]), + 'targets' => null, + 'persistence' => [new Location(['depth' => 55])], + 'expected' => false, + ], + // ContentInfo, no targets, un-published, with access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2]]), + 'object' => new ContentInfo(['published' => false]), + 'targets' => null, + 'persistence' => [new Location(['depth' => 2])], + 'expected' => true, + ], + // ContentInfo, no targets, un-published, no access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2, 43]]), + 'object' => new ContentInfo(['published' => false]), + 'targets' => null, + 'persistence' => [new Location(['depth' => 55])], + 'expected' => false, + ], + // Content, with targets, with access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2]]), + 'object' => $contentMock, + 'targets' => [new Location(['depth' => 2])], + 'persistence' => [], + 'expected' => true, + ], + // VersionInfo, with targets, with access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2]]), + 'object' => $versionInfoMock2, + 'targets' => [new Location(['depth' => 2])], + 'persistence' => [], + 'expected' => true, + ], + // ContentCreateStruct, no targets, no access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2]]), + 'object' => new ContentCreateStruct(), + 'targets' => [], + 'persistence' => [], + 'expected' => false, + ], + // ContentCreateStruct, with targets, no access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2, 43]]), + 'object' => new ContentCreateStruct(), + 'targets' => [new LocationCreateStruct(['parentLocationId' => 55])], + 'persistence' => [ + 55 => new Location(['depth' => 42]), + ], + 'expected' => false, + ], + // ContentCreateStruct, with targets, with access + [ + 'limitation' => new ParentDepthLimitation(['limitationValues' => [2, 42]]), + 'object' => new ContentCreateStruct(), + 'targets' => [new LocationCreateStruct(['parentLocationId' => 43])], + 'persistence' => [ + 43 => new Location(['depth' => 42]), + ], + 'expected' => true, + ], + ]; + } + + /** + * @dataProvider providerForTestEvaluate + */ + public function testEvaluate( + ParentDepthLimitation $limitation, + ValueObject $object, + $targets, + array $persistenceLocations, + $expected + ) { + // Need to create inline instead of depending on testConstruct() to get correct mock instance + $limitationType = $this->testConstruct(); + + $userMock = $this->getUserMock(); + $userMock + ->expects($this->never()) + ->method($this->anything()); + + $persistenceMock = $this->getPersistenceMock(); + if (empty($persistenceLocations)) { + $persistenceMock + ->expects($this->never()) + ->method($this->anything()); + } elseif ($object instanceof ContentCreateStruct) { + $this->getPersistenceMock() + ->expects($this->once()) + ->method('locationHandler') + ->will($this->returnValue($this->locationHandlerMock)); + + foreach ($targets as $target) { + $this->locationHandlerMock + ->expects($this->once()) + ->method('load') + ->with($target->parentLocationId) + ->will($this->returnValue($persistenceLocations[$target->parentLocationId])); + } + } else { + $this->getPersistenceMock() + ->expects($this->once()) + ->method('locationHandler') + ->will($this->returnValue($this->locationHandlerMock)); + + $this->locationHandlerMock + ->expects($this->once()) + ->method( + $object instanceof ContentInfo && $object->published ? + 'loadLocationsByContent' : + 'loadParentLocationsForDraftContent' + ) + ->with($object->id) + ->will($this->returnValue($persistenceLocations)); + } + + $value = $limitationType->evaluate( + $limitation, + $userMock, + $object, + $targets + ); + + self::assertInternalType('boolean', $value); + self::assertEquals($expected, $value); + } + + /** + * @return array + */ + public function providerForTestEvaluateInvalidArgument() + { + return [ + // invalid limitation + [ + 'limitation' => new ObjectStateLimitation(), + 'object' => new ContentInfo(), + 'targets' => [new Location()], + 'persistence' => [], + ], + // invalid object + [ + 'limitation' => new ParentDepthLimitation(), + 'object' => new ObjectStateLimitation(), + 'targets' => [new Location()], + 'persistence' => [], + ], + // invalid target + [ + 'limitation' => new ParentDepthLimitation(), + 'object' => new ContentInfo(), + 'targets' => [new ObjectStateLimitation()], + 'persistence' => [], + ], + // invalid target when using ContentCreateStruct + [ + 'limitation' => new ParentDepthLimitation(), + 'object' => new ContentCreateStruct(), + 'targets' => [new Location()], + 'persistence' => [], + ], + ]; + } + + /** + * @dataProvider providerForTestEvaluateInvalidArgument + * @expectedException \eZ\Publish\API\Repository\Exceptions\InvalidArgumentException + */ + public function testEvaluateInvalidArgument( + Limitation $limitation, + ValueObject $object, + $targets, + array $persistenceLocations + ) { + // Need to create inline instead of depending on testConstruct() to get correct mock instance + $limitationType = $this->testConstruct(); + + $userMock = $this->getUserMock(); + $userMock + ->expects($this->never()) + ->method($this->anything()); + + $persistenceMock = $this->getPersistenceMock(); + $persistenceMock + ->expects($this->never()) + ->method($this->anything()); + + $v = $limitationType->evaluate( + $limitation, + $userMock, + $object, + $targets + ); + var_dump($v);// intentional, debug in case no exception above + } +} From 7ae302b98dc44f08a65f788891eebc43950bcb70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petar=20=C5=A0panja?= Date: Fri, 1 Jul 2016 09:29:07 +0200 Subject: [PATCH 4/4] [travis] version 6.4 requires Solr search engine 1.0 --- bin/.travis/prepare_unittest.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/.travis/prepare_unittest.sh b/bin/.travis/prepare_unittest.sh index 881d3b9001e..5f5085acd0f 100755 --- a/bin/.travis/prepare_unittest.sh +++ b/bin/.travis/prepare_unittest.sh @@ -35,7 +35,7 @@ COMPOSER_UPDATE="" # solr package search API integration tests if [ "$TEST_CONFIG" = "phpunit-integration-legacy-solr.xml" ] ; then echo "> Require ezsystems/ezplatform-solr-search-engine:dev-master" - composer require --no-update ezsystems/ezplatform-solr-search-engine:dev-master + composer require --no-update ezsystems/ezplatform-solr-search-engine:^1.0.0@dev COMPOSER_UPDATE="true" fi