diff --git a/core/Context/AbstractContext.php b/core/Context/AbstractContext.php new file mode 100644 index 000000000..97a5b0e4e --- /dev/null +++ b/core/Context/AbstractContext.php @@ -0,0 +1,71 @@ + $parameterValue) { + $this->setParameter($parameter, $parameterValue); + } + } + + public function getParameter(string $parameter, $default = null) + { + $this->checkParameterSupport($parameter); + + return $this->parameters[$parameter] ?? $default; + } + + public function setParameter(string $parameter, $parameterValue): void + { + $this->checkParameterSupport($parameter); + $this->validateParameter($parameter, $parameterValue); + + $this->parameters[$parameter] = $parameterValue; + } + + abstract protected function getSupportedParameters(): array; + + /** + * @param mixed $parameterValue + */ + abstract protected function validateParameter(string $parameter, $parameterValue): void; + + private function checkParameterSupport(string $parameter): void + { + if (!in_array($parameter, $this->getSupportedParameters(), true)) { + throw new InvalidArgumentException( + sprintf( + 'Context parameter %s is not supported.', + $parameter + ) + ); + } + } +} diff --git a/core/Context/ContextInterface.php b/core/Context/ContextInterface.php new file mode 100644 index 000000000..ef15256c2 --- /dev/null +++ b/core/Context/ContextInterface.php @@ -0,0 +1,38 @@ +class = $class; @@ -44,4 +51,41 @@ public function getClass(): core_kernel_classes_Class { return $this->class; } + + public function setSelectedClass(?core_kernel_classes_Class $class): self + { + $this->selectedClass = $class; + + return $this; + } + + public function setParentClass(?core_kernel_classes_Class $class): self + { + $this->parentClass = $class; + + return $this; + } + + public function jsonSerialize(): array + { + $data = [ + 'uri' => $this->class->getUri(), + ]; + + if ($this->selectedClass !== null && $this->selectedClass !== $this->class) { + $data['selectedClass'] = [ + 'uri' => $this->selectedClass->getUri(), + 'label' => $this->selectedClass->getLabel(), + ]; + } + + if ($this->parentClass !== null) { + $data['parentClass'] = [ + 'uri' => $this->parentClass->getUri(), + 'label' => $this->parentClass->getLabel(), + ]; + } + + return $data; + } } diff --git a/core/data/event/ResourceDeleted.php b/core/data/event/ResourceDeleted.php index d2ac8a06b..724626043 100755 --- a/core/data/event/ResourceDeleted.php +++ b/core/data/event/ResourceDeleted.php @@ -15,23 +15,29 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * - * Copyright (c) 2018 (original work) Open Assessment Technologies SA; - * - * + * Copyright (c) 2018-2021 (original work) Open Assessment Technologies SA. */ +declare(strict_types=1); + namespace oat\generis\model\data\event; +use JsonSerializable; use oat\oatbox\event\Event; +use core_kernel_classes_Class; +use core_kernel_classes_Resource; -/** - * Class ResourceDeleted - * @package oat\generis\model\data\event - */ -class ResourceDeleted implements Event +class ResourceDeleted implements Event, JsonSerializable { + /** @var string */ private $uri; + /** @var core_kernel_classes_Class|null */ + private $selectedClass; + + /** @var core_kernel_classes_Resource|null */ + private $parentClass; + /** * @param string $uri */ @@ -40,21 +46,53 @@ function __construct($uri) $this->uri = $uri; } - /** - * Return the URI of the deleted resource - * @return string - */ - function getId() + function getId(): string { return $this->uri; } /** - * (non-PHPdoc) - * @see \oat\oatbox\event\Event::getName() + * {@inheritdoc} */ function getName() { return __CLASS__; } + + public function setSelectedClass(?core_kernel_classes_Class $class): self + { + $this->selectedClass = $class; + + return $this; + } + + public function setParentClass(?core_kernel_classes_Class $class): self + { + $this->parentClass = $class; + + return $this; + } + + public function jsonSerialize(): array + { + $data = [ + 'uri' => $this->uri, + ]; + + if ($this->selectedClass !== null) { + $data['selectedClass'] = [ + 'uri' => $this->selectedClass->getUri(), + 'label' => $this->selectedClass->getLabel(), + ]; + } + + if ($this->parentClass !== null) { + $data['parentClass'] = [ + 'uri' => $this->parentClass->getUri(), + 'label' => $this->parentClass->getLabel(), + ]; + } + + return $data; + } } diff --git a/core/kernel/classes/class.Class.php b/core/kernel/classes/class.Class.php index 9b29a0266..8f069f9b1 100644 --- a/core/kernel/classes/class.Class.php +++ b/core/kernel/classes/class.Class.php @@ -18,27 +18,27 @@ * Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg (under the project TAO & TAO2); * 2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung (under the project TAO-TRANSFER); * 2009-2012 (update and modification) Public Research Centre Henri Tudor (under the project TAO-SUSTAIN & TAO-DEV); - * 2017 (update and modification) Open Assessment Technologies SA (under the project TAO-PRODUCT); + * 2017-2021 (update and modification) Open Assessment Technologies SA. */ -use oat\generis\model\data\event\ClassDeletedEvent; -use oat\generis\model\data\event\ResourceCreated; use oat\generis\model\OntologyRdf; -use oat\generis\model\resource\ResourceCollection; use oat\oatbox\event\EventManager; use oat\oatbox\event\EventManagerAwareTrait; +use oat\generis\model\data\event\ResourceCreated; +use oat\generis\model\resource\ResourceCollection; +use oat\generis\model\resource\Repository\ClassRepository; +use oat\generis\model\resource\Context\ResourceRepositoryContext; +use oat\generis\model\resource\Contract\ResourceRepositoryInterface; /** * The class of rdfs:classes. It implements basic tests like isSubClassOf(Class * instances, properties and subclasses retrieval, but also enable to edit it * setSubClassOf setProperty, etc. * - * * @author patrick.plichart@tudor.lu - * @package generis + * * @see http://www.w3.org/RDF/ * @see http://www.w3.org/TR/rdf-schema/ - * */ class core_kernel_classes_Class extends core_kernel_classes_Resource { @@ -425,22 +425,30 @@ public function deleteInstances($resources, $deleteReference = false) } /** - * Short description of method delete + * @deprecated Use \oat\generis\model\resource\Repository\ClassRepository::delete() instead * - * @access public * @author Jerome Bogaerts, - * @param boolean deleteReference - * @return boolean + * + * @param bool deleteReference + * + * @return bool */ public function delete($deleteReference = false) { - $delete = (bool)$this->getImplementation()->delete($this, $deleteReference); - - if ($delete) { - $this->getEventManager()->trigger(new ClassDeletedEvent($this)); + try { + $this->getClassRepository()->delete( + new ResourceRepositoryContext( + [ + ResourceRepositoryContext::PARAM_CLASS => $this, + ResourceRepositoryContext::PARAM_DELETE_REFERENCE => $deleteReference, + ] + ) + ); + + return true; + } catch (Throwable $exception) { + return false; } - - return $delete; } /** @@ -458,4 +466,9 @@ public function exists() // we know that the class exists. return (bool) (count($this->getParentClasses(false)) > 0); } + + private function getClassRepository(): ResourceRepositoryInterface + { + return $this->getServiceManager()->getContainer()->get(ClassRepository::class); + } } diff --git a/core/kernel/classes/class.Resource.php b/core/kernel/classes/class.Resource.php index 3007d5c8d..e29bcb459 100644 --- a/core/kernel/classes/class.Resource.php +++ b/core/kernel/classes/class.Resource.php @@ -18,7 +18,7 @@ * Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg (under the project TAO & TAO2); * 2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung (under the project TAO-TRANSFER); * 2009-2012 (update and modification) Public Research Centre Henri Tudor (under the project TAO-SUSTAIN & TAO-DEV); - * 2017 (update and modification) Open Assessment Technologies SA (under the project TAO-PRODUCT); + * 2017-2021 (update and modification) Open Assessment Technologies SA; */ use oat\generis\model\OntologyRdf; @@ -26,22 +26,20 @@ use oat\oatbox\event\EventAggregator; use oat\oatbox\service\ServiceManager; use oat\generis\model\OntologyAwareTrait; -use oat\oatbox\event\EventManager; +use Zend\ServiceManager\ServiceLocatorInterface; use oat\generis\model\data\event\ResourceUpdated; -use oat\generis\model\data\event\ResourceDeleted; use Zend\ServiceManager\ServiceLocatorAwareInterface; -use Zend\ServiceManager\ServiceLocatorInterface; +use oat\generis\model\resource\Repository\ResourceRepository; +use oat\generis\model\resource\Context\ResourceRepositoryContext; +use oat\generis\model\resource\Contract\ResourceRepositoryInterface; /** * Resource implements rdf:resource container identified by an uri (a string). * Methods enable meta data management for this resource * - * @access public * @author patrick.plichart@tudor.lu - * @package generis + * * @see http://www.w3.org/RDF/ - - * @version v1.0 */ class core_kernel_classes_Resource extends core_kernel_classes_Container { @@ -569,19 +567,32 @@ public function duplicate($excludedProperties = []) } /** - * remove any assignation made to this resource, the uri is consequently + * Remove any assignation made to this resource, the uri is consequently + * + * @deprecated Use \oat\generis\model\resource\Repository\ResourceRepository::delete() instead * - * @access public * @author patrick.plichart@tudor.lu - * @param boolean deleteReference set deleteRefence to true when you need that all reference to this resource are removed. - * @return boolean + * + * @param bool deleteReference set deleteReference to true when you need that all reference to this resource are removed. + * + * @return bool */ public function delete($deleteReference = false) { - $returnValue = $this->getImplementation()->delete($this, $deleteReference); - $eventManager = $this->getServiceManager()->get(EventManager::SERVICE_ID); - $eventManager->trigger(new ResourceDeleted($this->getUri())); - return (bool) $returnValue; + try { + $this->getResourceRepository()->delete( + new ResourceRepositoryContext( + [ + ResourceRepositoryContext::PARAM_RESOURCE => $this, + ResourceRepositoryContext::PARAM_DELETE_REFERENCE => $deleteReference, + ] + ) + ); + + return true; + } catch (Throwable $exception) { + return false; + } } @@ -768,4 +779,9 @@ private function onUpdate(): void $eventAggregator = $this->getServiceManager()->get(EventAggregator::SERVICE_ID); $eventAggregator->put($this->getUri(), new ResourceUpdated($this)); } + + private function getResourceRepository(): ResourceRepositoryInterface + { + return $this->getServiceManager()->getContainer()->get(ResourceRepository::class); + } } diff --git a/core/resource/Context/ResourceRepositoryContext.php b/core/resource/Context/ResourceRepositoryContext.php new file mode 100644 index 000000000..08d97307b --- /dev/null +++ b/core/resource/Context/ResourceRepositoryContext.php @@ -0,0 +1,73 @@ +ontology = $ontology; + $this->eventManager = $eventManager; + } + + public function delete(ContextInterface $context): void + { + /** @var core_kernel_classes_Class|null $class */ + $class = $context->getParameter(ResourceRepositoryContext::PARAM_CLASS); + + if ($class === null) { + throw new InvalidArgumentException('Class was not provided for deletion.'); + } + + $deleteReference = $context->getParameter( + ResourceRepositoryContext::PARAM_DELETE_REFERENCE, + false + ); + /** @var core_kernel_classes_Class $parentClass */ + $parentClass = $context->getParameter( + ResourceRepositoryContext::PARAM_PARENT_CLASS, + current($class->getParentClasses()) ?: null + ); + + if (!$this->getImplementation()->delete($class, $deleteReference)) { + throw new RuntimeException( + sprintf( + 'Class "%s" ("%s") was not deleted.', + $class->getLabel(), + $class->getUri() + ) + ); + } + + /** @var core_kernel_classes_Class|null $selectedClass */ + $selectedClass = $context->getParameter(ResourceRepositoryContext::PARAM_SELECTED_CLASS); + $classDeletedEvent = (new ClassDeletedEvent($class)) + ->setSelectedClass($selectedClass) + ->setParentClass($parentClass); + $this->eventManager->trigger($classDeletedEvent); + } + + private function getImplementation(): core_kernel_persistence_ClassInterface + { + return $this->ontology->getRdfsInterface()->getClassImplementation(); + } +} diff --git a/core/resource/Repository/ResourceRepository.php b/core/resource/Repository/ResourceRepository.php new file mode 100644 index 000000000..a613dcd39 --- /dev/null +++ b/core/resource/Repository/ResourceRepository.php @@ -0,0 +1,90 @@ +ontology = $ontology; + $this->eventManager = $eventManager; + } + + public function delete(ContextInterface $context): void + { + /** @var core_kernel_classes_Resource|null $resource */ + $resource = $context->getParameter(ResourceRepositoryContext::PARAM_RESOURCE); + + if ($resource === null) { + throw new InvalidArgumentException('Resource was not provided for deletion.'); + } + + $deleteReference = $context->getParameter( + ResourceRepositoryContext::PARAM_DELETE_REFERENCE, + false + ); + + if (!$this->getImplementation()->delete($resource, $deleteReference)) { + throw new RuntimeException( + sprintf( + 'Resource "%s" ("%s") was not deleted.', + $resource->getLabel(), + $resource->getUri() + ) + ); + } + + /** @var core_kernel_classes_Class|null $selectedClass */ + $selectedClass = $context->getParameter(ResourceRepositoryContext::PARAM_SELECTED_CLASS); + /** @var core_kernel_classes_Class|null $selectedClass */ + $parentClass = $context->getParameter(ResourceRepositoryContext::PARAM_PARENT_CLASS); + + $resourceDeletedEvent = (new ResourceDeleted($resource->getUri())) + ->setSelectedClass($selectedClass) + ->setParentClass($parentClass); + $this->eventManager->trigger($resourceDeletedEvent); + } + + private function getImplementation(): core_kernel_persistence_ResourceInterface + { + return $this->ontology->getRdfsInterface()->getResourceImplementation(); + } +} diff --git a/core/resource/ResourceServiceProvider.php b/core/resource/ResourceServiceProvider.php new file mode 100644 index 000000000..1f94b0452 --- /dev/null +++ b/core/resource/ResourceServiceProvider.php @@ -0,0 +1,70 @@ +services(); + + $services + ->set(ClassRepository::class, ClassRepository::class) + ->public() + ->args( + [ + service(Ontology::SERVICE_ID), + service(EventManager::SERVICE_ID), + ] + ); + + $services + ->set(ResourceRepository::class, ResourceRepository::class) + ->public() + ->args( + [ + service(Ontology::SERVICE_ID), + service(EventManager::SERVICE_ID), + ] + ); + + $services + ->set(ResourceDeleter::class, ResourceDeleter::class) + ->public() + ->args( + [ + service(ResourceRepository::class), + ] + ); + } +} diff --git a/core/resource/Service/ResourceDeleter.php b/core/resource/Service/ResourceDeleter.php new file mode 100644 index 000000000..7347fcfa7 --- /dev/null +++ b/core/resource/Service/ResourceDeleter.php @@ -0,0 +1,76 @@ +resourceRepository = $resourceRepository; + } + + public function delete(core_kernel_classes_Resource $resource): void + { + try { + $context = new ResourceRepositoryContext([ResourceRepositoryContext::PARAM_RESOURCE => $resource]); + $parentClass = $this->getParentClass($resource); + + if ($parentClass !== null) { + $context->setParameter(ResourceRepositoryContext::PARAM_PARENT_CLASS, $parentClass); + } + + $this->resourceRepository->delete($context); + } catch (Throwable $exception) { + throw new ResourceDeletionException( + sprintf( + 'Unable to delete resource "%s::%s" (%s).', + $resource->getLabel(), + $resource->getUri(), + $exception->getMessage() + ), + __('Unable to delete the selected resource') + ); + } + } + + private function getParentClass(core_kernel_classes_Resource $resource): ?core_kernel_classes_Class + { + $parentClassUri = $resource->getOnePropertyValue($resource->getProperty(OntologyRdf::RDF_TYPE)); + + return $parentClassUri !== null + ? $resource->getClass($parentClassUri) + : null; + } +} diff --git a/core/resource/exception/ResourceDeletionException.php b/core/resource/exception/ResourceDeletionException.php new file mode 100644 index 000000000..43d2143b2 --- /dev/null +++ b/core/resource/exception/ResourceDeletionException.php @@ -0,0 +1,45 @@ +userMessage = $userMessage; + + parent::__construct($message, $code, $throwable); + } + + public function getUserMessage(): string + { + return $this->userMessage; + } +} diff --git a/manifest.php b/manifest.php index 921b63fc2..e36fede70 100755 --- a/manifest.php +++ b/manifest.php @@ -1,7 +1,5 @@ [ TaskQueue::class, SetupDefaultKvPersistence::class, - RegisterServices::class + RegisterServices::class, ], ], 'update' => Updater::class, 'containerServiceProviders' => [ ContainerServiceProvider::class, LogServiceProvider::class, - ] + ResourceServiceProvider::class, + ], ]; diff --git a/test/unit/core/kernel/persistence/OntologyRdfsTest.php b/test/unit/core/kernel/persistence/OntologyRdfsTest.php index e11cb4827..12f5f744e 100755 --- a/test/unit/core/kernel/persistence/OntologyRdfsTest.php +++ b/test/unit/core/kernel/persistence/OntologyRdfsTest.php @@ -109,19 +109,6 @@ public function testDuplicateInstance(Ontology $model) $this->assertNotEquals($resource, $resourceClone); } - /** - * @dataProvider getOntologies - */ - public function testDeleteInstance(Ontology $model) - { - $class = $model->getClass('http://testing#class'); - $resource = $class->createInstance('sample'); - $this->assertInstanceOf(\core_kernel_classes_Resource::class, $resource); - $this->assertTrue($resource->exists()); - $resource->delete(); - $this->assertFalse($resource->exists()); - } - public function getOntologies() { return [ diff --git a/test/unit/core/resource/Repository/ClassRepositoryTest.php b/test/unit/core/resource/Repository/ClassRepositoryTest.php new file mode 100644 index 000000000..d7c98cdc2 --- /dev/null +++ b/test/unit/core/resource/Repository/ClassRepositoryTest.php @@ -0,0 +1,189 @@ +classImplementation = $this->createMock(core_kernel_persistence_ClassInterface::class); + + $this->rdfsInterface = $this->createMock(RdfsInterface::class); + $this->rdfsInterface + ->method('getClassImplementation') + ->willReturn($this->classImplementation); + + $this->ontology = $this->createMock(Ontology::class); + $this->ontology + ->method('getRdfsInterface') + ->willReturn($this->rdfsInterface); + + $this->eventManager = $this->createMock(EventManager::class); + + $this->sut = new ClassRepository($this->ontology, $this->eventManager); + } + + public function testDeleteSuccess(): void + { + $this->ontology + ->expects($this->once()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->once()) + ->method('getClassImplementation'); + $this->classImplementation + ->expects($this->once()) + ->method('delete') + ->willReturn(true); + $this->eventManager + ->expects($this->once()) + ->method('trigger'); + + $context = $this->createContext(4, $this->createClass()); + $this->sut->delete($context); + } + + public function testDeleteWithoutResource(): void + { + $this->ontology + ->expects($this->never()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->never()) + ->method('getClassImplementation'); + $this->classImplementation + ->expects($this->never()) + ->method('delete'); + $this->eventManager + ->expects($this->never()) + ->method('trigger'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Class was not provided for deletion.'); + + $context = $this->createContext(1, null); + $this->sut->delete($context); + } + + public function testDeleteFailure(): void + { + $this->ontology + ->expects($this->once()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->once()) + ->method('getClassImplementation'); + $this->classImplementation + ->expects($this->once()) + ->method('delete') + ->willReturn(false); + $this->eventManager + ->expects($this->never()) + ->method('trigger'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Class "classLabel" ("classUri") was not deleted.'); + + $context = $this->createContext(3, $this->createClass('classUri', 'classLabel')); + $this->sut->delete($context); + } + + private function createClass(string $uri = null, string $label = null): core_kernel_classes_Class + { + $class = $this->createMock(core_kernel_classes_Class::class); + $class + ->expects($uri !== null ? $this->once() : $this->never()) + ->method('getUri') + ->willReturn($uri); + $class + ->expects($label !== null ? $this->once() : $this->never()) + ->method('getLabel') + ->willReturn($label); + $class + ->expects($this->once()) + ->method('getParentClasses') + ->willReturn([]); + + return $class; + } + + private function createContext(int $expects, ?core_kernel_classes_Class $class): ContextInterface + { + $context = $this->createMock(ContextInterface::class); + $context + ->expects($this->exactly($expects)) + ->method('getParameter') + ->willReturnCallback( + function (string $param) use ($class) { + if ($param === self::PARAM_CLASS) { + return $class; + } + + if ($param === self::PARAM_DELETE_REFERENCE) { + return false; + } + + if (in_array($param, [self::PARAM_SELECTED_CLASS, self::PARAM_PARENT_CLASS], true)) { + return $this->createMock(core_kernel_classes_Class::class); + } + + return null; + } + ); + + return $context; + } +} diff --git a/test/unit/core/resource/Repository/ResourceRepositoryTest.php b/test/unit/core/resource/Repository/ResourceRepositoryTest.php new file mode 100644 index 000000000..4c96f1d7e --- /dev/null +++ b/test/unit/core/resource/Repository/ResourceRepositoryTest.php @@ -0,0 +1,186 @@ +resourceImplementation = $this->createMock(core_kernel_persistence_ResourceInterface::class); + + $this->rdfsInterface = $this->createMock(RdfsInterface::class); + $this->rdfsInterface + ->method('getResourceImplementation') + ->willReturn($this->resourceImplementation); + + $this->ontology = $this->createMock(Ontology::class); + $this->ontology + ->method('getRdfsInterface') + ->willReturn($this->rdfsInterface); + + $this->eventManager = $this->createMock(EventManager::class); + + $this->sut = new ResourceRepository($this->ontology, $this->eventManager); + } + + public function testDeleteSuccess(): void + { + $this->ontology + ->expects($this->once()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->once()) + ->method('getResourceImplementation'); + $this->resourceImplementation + ->expects($this->once()) + ->method('delete') + ->willReturn(true); + $this->eventManager + ->expects($this->once()) + ->method('trigger'); + + $context = $this->createContext(4, $this->createResource('resourceUri')); + $this->sut->delete($context); + } + + public function testDeleteWithoutResource(): void + { + $this->ontology + ->expects($this->never()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->never()) + ->method('getResourceImplementation'); + $this->resourceImplementation + ->expects($this->never()) + ->method('delete'); + $this->eventManager + ->expects($this->never()) + ->method('trigger'); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Resource was not provided for deletion.'); + + $context = $this->createContext(1, null); + $this->sut->delete($context); + } + + public function testDeleteFailure(): void + { + $this->ontology + ->expects($this->once()) + ->method('getRdfsInterface'); + $this->rdfsInterface + ->expects($this->once()) + ->method('getResourceImplementation'); + $this->resourceImplementation + ->expects($this->once()) + ->method('delete') + ->willReturn(false); + $this->eventManager + ->expects($this->never()) + ->method('trigger'); + + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Resource "resourceLabel" ("resourceUri") was not deleted.'); + + $context = $this->createContext(2, $this->createResource('resourceUri', 'resourceLabel')); + $this->sut->delete($context); + } + + private function createResource(string $uri, string $label = null): core_kernel_classes_Resource + { + $class = $this->createMock(core_kernel_classes_Resource::class); + $class + ->expects($this->once()) + ->method('getUri') + ->willReturn($uri); + $class + ->expects($label !== null ? $this->once() : $this->never()) + ->method('getLabel') + ->willReturn($label); + + return $class; + } + + private function createContext(int $expects, ?core_kernel_classes_Resource $resource): ContextInterface + { + $context = $this->createMock(ContextInterface::class); + $context + ->expects($this->exactly($expects)) + ->method('getParameter') + ->willReturnCallback( + function (string $param) use ($resource) { + if ($param === self::PARAM_RESOURCE) { + return $resource; + } + + if ($param === self::PARAM_DELETE_REFERENCE) { + return false; + } + + if (in_array($param, [self::PARAM_SELECTED_CLASS, self::PARAM_PARENT_CLASS], true)) { + return $this->createMock(core_kernel_classes_Class::class); + } + + return null; + } + ); + + return $context; + } +} diff --git a/test/unit/core/resource/Service/ResourceDeleterTest.php b/test/unit/core/resource/Service/ResourceDeleterTest.php new file mode 100644 index 000000000..ae33a4616 --- /dev/null +++ b/test/unit/core/resource/Service/ResourceDeleterTest.php @@ -0,0 +1,149 @@ +resourceRepository = $this->createMock(ResourceRepositoryInterface::class); + $this->sut = new ResourceDeleter($this->resourceRepository); + + $this->resource = $this->createMock(core_kernel_classes_Resource::class); + $this->resource + ->expects($this->once()) + ->method('getProperty') + ->willReturn($this->createMock(core_kernel_classes_Property::class)); + } + + public function testDeleteSuccessWithParentClass(): void + { + $this->resourceRepository + ->expects($this->once()) + ->method('delete'); + + $this->resource + ->expects($this->once()) + ->method('getOnePropertyValue') + ->willReturn('parentClassUri'); + $this->resource + ->expects($this->once()) + ->method('getClass') + ->willReturn($this->createMock(core_kernel_classes_Class::class)); + + $this->resource + ->expects($this->never()) + ->method('getLabel'); + $this->resource + ->expects($this->never()) + ->method('getUri'); + + $this->sut->delete($this->resource); + } + + public function testDeleteSuccessWithoutParentClass(): void + { + $this->resourceRepository + ->expects($this->once()) + ->method('delete'); + + $this->resource + ->expects($this->once()) + ->method('getOnePropertyValue') + ->willReturn(null); + $this->resource + ->expects($this->never()) + ->method('getClass'); + + $this->resource + ->expects($this->never()) + ->method('getLabel'); + $this->resource + ->expects($this->never()) + ->method('getUri'); + + $this->sut->delete($this->resource); + } + + public function testDeleteWithRepositoryError(): void + { + $resourceRepositoryContext = $this->createMock(ContextInterface::class); + $resourceRepositoryContext + ->expects($this->once()) + ->method('getParameter') + ->with(self::PARAM_RESOURCE) + ->willReturn(null); + + $this->resourceRepository + ->expects($this->once()) + ->with($resourceRepositoryContext) + ->method('delete'); + + $this->resource + ->expects($this->once()) + ->method('getOnePropertyValue') + ->willReturn('parentClassUri'); + $this->resource + ->expects($this->once()) + ->method('getClass') + ->willReturn($this->createMock(core_kernel_classes_Class::class)); + + $this->resource + ->expects($this->once()) + ->method('getLabel') + ->willReturn('resourceLabel'); + $this->resource + ->expects($this->once()) + ->method('getUri') + ->willReturn('resourceUri'); + + $this->expectException(ResourceDeletionException::class); + $this->expectExceptionMessage( + 'Unable to delete resource "resourceLabel::resourceUri" (Resource was not provided for deletion.).' + ); + + $this->sut->delete($this->resource); + } +} diff --git a/test/unit/model/data/event/ClassDeletedEventTest.php b/test/unit/model/data/event/ClassDeletedEventTest.php new file mode 100644 index 000000000..cce61a1dc --- /dev/null +++ b/test/unit/model/data/event/ClassDeletedEventTest.php @@ -0,0 +1,162 @@ +class = $this->createMock(core_kernel_classes_Class::class); + + $this->sut = new ClassDeletedEvent($this->class); + } + + public function testGetName(): void + { + $this->assertEquals(ClassDeletedEvent::class, $this->sut->getName()); + } + + public function testGetClass(): void + { + $this->assertEquals($this->class, $this->sut->getClass()); + } + + public function testJsonSerializeWithoutAnyData(): void + { + $this->class + ->expects($this->once()) + ->method('getUri') + ->willReturn(self::CLASS_URI); + + $this->assertEquals(['uri' => self::CLASS_URI], $this->sut->jsonSerialize()); + } + + public function testJsonSerializeWithSelectedClass(): void + { + $this->class + ->expects($this->once()) + ->method('getUri') + ->willReturn(self::CLASS_URI); + + $this->sut->setSelectedClass($this->createClass('selectedClassUri', 'selectedClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::CLASS_URI, + 'selectedClass' => [ + 'uri' => 'selectedClassUri', + 'label' => 'selectedClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithParentClass(): void + { + $this->class + ->expects($this->once()) + ->method('getUri') + ->willReturn(self::CLASS_URI); + + $this->sut->setParentClass($this->createClass('parentClassUri', 'parentClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::CLASS_URI, + 'parentClass' => [ + 'uri' => 'parentClassUri', + 'label' => 'parentClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithSelectedAndParentClasses(): void + { + $this->class + ->expects($this->once()) + ->method('getUri') + ->willReturn(self::CLASS_URI); + + $this->sut->setSelectedClass($this->createClass('selectedClassUri', 'selectedClassLabel')); + $this->sut->setParentClass($this->createClass('parentClassUri', 'parentClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::CLASS_URI, + 'selectedClass' => [ + 'uri' => 'selectedClassUri', + 'label' => 'selectedClassLabel', + ], + 'parentClass' => [ + 'uri' => 'parentClassUri', + 'label' => 'parentClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithNullableSelectedAndParentClasses(): void + { + $this->class + ->expects($this->once()) + ->method('getUri') + ->willReturn(self::CLASS_URI); + + $this->sut->setSelectedClass(null); + $this->sut->setParentClass(null); + + $this->assertEquals(['uri' => self::CLASS_URI], $this->sut->jsonSerialize()); + } + + private function createClass(string $uri, string $label): core_kernel_classes_Class + { + $class = $this->createMock(core_kernel_classes_Class::class); + $class + ->expects($this->once()) + ->method('getUri') + ->willReturn($uri); + $class + ->expects($this->once()) + ->method('getLabel') + ->willReturn($label); + + return $class; + } +} diff --git a/test/unit/model/data/event/ResourceDeletedTest.php b/test/unit/model/data/event/ResourceDeletedTest.php new file mode 100644 index 000000000..698eb682a --- /dev/null +++ b/test/unit/model/data/event/ResourceDeletedTest.php @@ -0,0 +1,131 @@ +sut = new ResourceDeleted(self::RESOURCE_URI); + } + + public function testGetName(): void + { + $this->assertEquals(ResourceDeleted::class, $this->sut->getName()); + } + + public function testGetId(): void + { + $this->assertEquals(self::RESOURCE_URI, $this->sut->getId()); + } + + public function testJsonSerializeWithoutAnyData(): void + { + $this->assertEquals(['uri' => self::RESOURCE_URI], $this->sut->jsonSerialize()); + } + + public function testJsonSerializeWithSelectedClass(): void + { + $this->sut->setSelectedClass($this->createClass('selectedClassUri', 'selectedClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::RESOURCE_URI, + 'selectedClass' => [ + 'uri' => 'selectedClassUri', + 'label' => 'selectedClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithParentClass(): void + { + $this->sut->setParentClass($this->createClass('parentClassUri', 'parentClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::RESOURCE_URI, + 'parentClass' => [ + 'uri' => 'parentClassUri', + 'label' => 'parentClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithSelectedAndParentClasses(): void + { + $this->sut->setSelectedClass($this->createClass('selectedClassUri', 'selectedClassLabel')); + $this->sut->setParentClass($this->createClass('parentClassUri', 'parentClassLabel')); + + $this->assertEquals( + [ + 'uri' => self::RESOURCE_URI, + 'selectedClass' => [ + 'uri' => 'selectedClassUri', + 'label' => 'selectedClassLabel', + ], + 'parentClass' => [ + 'uri' => 'parentClassUri', + 'label' => 'parentClassLabel', + ], + ], + $this->sut->jsonSerialize() + ); + } + + public function testJsonSerializeWithNullableSelectedAndParentClasses(): void + { + $this->sut->setSelectedClass(null); + $this->sut->setParentClass(null); + + $this->assertEquals(['uri' => self::RESOURCE_URI], $this->sut->jsonSerialize()); + } + + private function createClass(string $uri, string $label): core_kernel_classes_Class + { + $class = $this->createMock(core_kernel_classes_Class::class); + $class + ->expects($this->once()) + ->method('getUri') + ->willReturn($uri); + $class + ->expects($this->once()) + ->method('getLabel') + ->willReturn($label); + + return $class; + } +}