diff --git a/composer.json b/composer.json index 6380b9d7e..786884acc 100644 --- a/composer.json +++ b/composer.json @@ -66,7 +66,7 @@ "oat-sa/generis" : ">=15.22", "oat-sa/tao-core": ">=54.0.0", "oat-sa/extension-tao-item" : ">=12.1.0", - "oat-sa/extension-tao-itemqti" : ">=30.0.0", + "oat-sa/extension-tao-itemqti" : ">=30.10.0", "oat-sa/extension-tao-test" : ">=16.0.0", "oat-sa/extension-tao-delivery" : ">=15.0.0", "oat-sa/extension-tao-outcome" : ">=13.0.0", diff --git a/manifest.php b/manifest.php index f6cfc386b..6840e9446 100755 --- a/manifest.php +++ b/manifest.php @@ -21,6 +21,7 @@ use oat\tao\model\user\TaoRoles; use oat\taoQtiTest\model\Container\TestQtiServiceProvider; +use oat\taoQtiTest\models\classes\metadata\MetadataServiceProvider; // phpcs:disable Generic.Files.LineLength use oat\taoQtiTest\models\classes\render\CustomInteraction\ServiceProvider\CustomInteractionPostProcessingServiceProvider; // phpcs:enable Generic.Files.LineLength @@ -182,6 +183,7 @@ CustomInteractionPostProcessingServiceProvider::class, ItemsReferencesServiceProvider::class, TestQtiServiceProvider::class, - TestSessionStateServiceProvider::class + TestSessionStateServiceProvider::class, + MetadataServiceProvider::class ], ]; diff --git a/models/classes/class.QtiTestService.php b/models/classes/class.QtiTestService.php index a3d6339a4..a7841f830 100644 --- a/models/classes/class.QtiTestService.php +++ b/models/classes/class.QtiTestService.php @@ -29,8 +29,12 @@ use oat\taoItems\model\Command\DeleteItemCommand; use oat\taoQtiItem\model\qti\ImportService; use oat\taoQtiItem\model\qti\metadata\importer\MetadataImporter; +use oat\taoQtiItem\model\qti\metadata\imsManifest\MetaMetadataExtractor; +use oat\taoQtiItem\model\qti\metadata\importer\MetaMetadataImportMapper; +use oat\taoQtiItem\model\qti\metadata\importer\PropertyDoesNotExistException; use oat\taoQtiItem\model\qti\metadata\MetadataGuardianResource; use oat\taoQtiItem\model\qti\metadata\MetadataService; +use oat\taoQtiItem\model\qti\metadata\ontology\MappedMetadataInjector; use oat\taoQtiItem\model\qti\Resource; use oat\taoQtiItem\model\qti\Service; use oat\taoQtiTest\models\cat\AdaptiveSectionInjectionException; @@ -50,6 +54,8 @@ use qtism\data\storage\xml\XmlDocument; use qtism\data\storage\xml\XmlStorageException; use taoTests_models_classes_TestsService as TestService; +use oat\oatbox\reporting\Report; +use taoQtiTest_models_classes_import_TestImportForm as TestImportForm; /** * the QTI TestModel service. @@ -349,7 +355,8 @@ public function importMultipleTests( core_kernel_classes_Class $targetClass, $file, bool $overwriteTest = false, - ?string $itemClassUri = null + ?string $itemClassUri = null, + array $form = [] ) { $testClass = $targetClass; $report = new common_report_Report(common_report_Report::TYPE_INFO); @@ -417,7 +424,8 @@ public function importMultipleTests( $folder, $alreadyImportedQtiResources, $overwriteTest, - $itemClassUri + $itemClassUri, + !empty($form[TestImportForm::METADATA_FORM_ELEMENT_NAME]) ?? false ); $report->add($importTestReport); @@ -537,7 +545,8 @@ protected function importTest( $folder, array $ignoreQtiResources = [], bool $overwriteTest = false, - ?string $itemClassUri = null + ?string $itemClassUri = null, + bool $importMetadata = false ) { /** @var ImportService $itemImportService */ $itemImportService = $this->getServiceLocator()->get(ImportService::SERVICE_ID); @@ -585,6 +594,8 @@ protected function importTest( $reportCtx->testMetadata = $metadataValues[$qtiTestResourceIdentifier] ?? []; $reportCtx->createdClasses = []; + + // 'uriResource' key is needed by javascript in tao/views/templates/form/import.tpl $reportCtx->uriResource = $testResource->getUri(); @@ -630,6 +641,15 @@ protected function importTest( $targetItemClass->label = $testLabel; $reportCtx->itemClass = $targetItemClass; + + $mappedProperties = $this->getMappedProperties( + $importMetadata, + $domManifest, + $reportCtx, + $testClass, + $targetItemClass + ); + // -- Load all items related to test. $itemError = false; @@ -679,7 +699,6 @@ protected function importTest( ); } } - // Skip if $qtiFile already imported (multiple assessmentItemRef "hrefing" the same // file). if (array_key_exists($qtiFile, $alreadyImportedTestItemFiles) === false) { @@ -697,7 +716,9 @@ protected function importTest( $this->useMetadataValidators, $this->itemMustExist, $this->itemMustBeOverwritten, - $reportCtx->overwrittenItems + $reportCtx->overwrittenItems, + $mappedProperties['itemProperties'] ?? [], + $importMetadata ); $reportCtx->createdClasses = array_merge( @@ -780,6 +801,11 @@ protected function importTest( // 4. Import metadata for the resource (use same mechanics as item resources). // Metadata will be set as property values. $this->getMetadataImporter()->inject($qtiTestResource->getIdentifier(), $testResource); + $this->getServiceManager()->getContainer()->get(MappedMetadataInjector::class)->inject( + $mappedProperties['testProperties'] ?? [], + $metadataValues[$qtiTestResourceIdentifier], + $testResource + ); // 5. if $targetClass does not contain any instances // (because everything resolved by class lookups), @@ -833,6 +859,9 @@ protected function importTest( $msg = __("Error found in the IMS QTI Test:\n%s", $finalErrorString); $report->add(common_report_Report::createFailure($msg)); + } catch (PropertyDoesNotExistException $e) { + $reportCtx->itemClass = $targetItemClass; + $report->add(Report::createError($e->getMessage())); } catch (CatEngineNotFoundException $e) { $report->add( new common_report_Report( @@ -1411,6 +1440,12 @@ protected function getMetadataImporter() return $this->metadataImporter; } + private function getMetaMetadataExtractor(): MetaMetadataExtractor + { + return $this->getPsrContainer()->get(MetaMetadataExtractor::class); + return $this->getServiceManager()->getContainer()->get(MetaMetadataExtractor::class); + } + private function getSecureResourceService(): SecureResourceServiceInterface { return $this->getServiceLocator()->get(SecureResourceServiceInterface::SERVICE_ID); @@ -1488,4 +1523,26 @@ private function getPsrContainer(): ContainerInterface { return $this->getServiceLocator()->getContainer(); } + + private function getMetaMetadataImporter(): MetaMetadataImportMapper + { + return $this->getServiceManager()->getContainer()->get(MetaMetadataImportMapper::class); + } + + private function getMappedProperties( + bool $importMetadata, + DOMDocument $domManifest, + stdClass $reportCtx, + core_kernel_classes_Class $testClass, + core_kernel_classes_Class $targetItemClass + ): array { + if ($importMetadata === true) { + $metaMetadataValues = $this->getMetaMetadataExtractor()->extract($domManifest); + $reportCtx->metaMetadata = $metaMetadataValues; + return $this->getMetaMetadataImporter() + ->mapMetaMetadataToProperties($metaMetadataValues, $targetItemClass, $testClass); + } + + return []; + } } diff --git a/models/classes/export/AbstractQtiTestExporter.php b/models/classes/export/AbstractQtiTestExporter.php index 0de4e8c55..fbac75b30 100644 --- a/models/classes/export/AbstractQtiTestExporter.php +++ b/models/classes/export/AbstractQtiTestExporter.php @@ -32,6 +32,9 @@ use DOMXPath; use oat\oatbox\reporting\Report; use oat\oatbox\reporting\ReportInterface; +use oat\tao\model\featureFlag\FeatureFlagChecker; +use oat\taoQtiTest\models\classes\metadata\GenericLomOntologyExtractor; +use oat\taoQtiTest\models\classes\metadata\MetadataLomService; use qtism\data\storage\xml\marshalling\MarshallingException; use qtism\data\storage\xml\XmlDocument; use oat\oatbox\filesystem\Directory; @@ -174,6 +177,13 @@ public function export(array $options = []): Report // 3. Export test metadata to manifest $this->getMetadataExporter()->export($this->getItem(), $this->getManifest()); + if ($this->getFeatureFlagChecker()->isEnabled(MetadataLomService::FEATURE_FLAG)) { + $this->genericLomOntologyExtractor()->extract( + array_merge([$this->getItem()], $this->getItems()), + $this->getManifest() + ); + } + // 4. Persist manifest in archive. $this->getZip()->addFromString('imsmanifest.xml', $this->getManifest()->saveXML()); @@ -350,4 +360,14 @@ protected function getServiceManager(): ServiceManager { return ServiceManager::getServiceManager(); } + + private function genericLomOntologyExtractor(): GenericLomOntologyExtractor + { + return $this->getServiceManager()->getContainer()->get(GenericLomOntologyExtractor::class); + } + + private function getFeatureFlagChecker(): FeatureFlagChecker + { + return $this->getServiceManager()->getContainer()->get(FeatureFlagChecker::class); + } } diff --git a/models/classes/import/class.TestImport.php b/models/classes/import/class.TestImport.php index 43b7a0aab..418f9d509 100755 --- a/models/classes/import/class.TestImport.php +++ b/models/classes/import/class.TestImport.php @@ -22,10 +22,14 @@ use oat\oatbox\event\EventManagerAwareTrait; use oat\oatbox\PhpSerializable; use oat\oatbox\PhpSerializeStateless; +use oat\tao\model\featureFlag\FeatureFlagChecker; use oat\tao\model\import\ImportHandlerHelperTrait; use oat\tao\model\import\TaskParameterProviderInterface; +use oat\tao\model\upload\UploadService; +use oat\taoQtiTest\models\classes\metadata\MetadataLomService; use oat\taoQtiTest\models\event\QtiTestImportEvent; use Zend\ServiceManager\ServiceLocatorAwareInterface; +use taoQtiTest_models_classes_import_TestImportForm as TestImportForm; /** * Import handler for QTI packages @@ -44,6 +48,9 @@ class taoQtiTest_models_classes_import_TestImport implements use EventManagerAwareTrait; use ImportHandlerHelperTrait; + public const DISABLED_FIELDS = 'disabledFields'; + public const METADATA_FIELD = 'metadataImport'; + /** * (non-PHPdoc) * @see tao_models_classes_import_ImportHandler::getLabel() @@ -59,7 +66,7 @@ public function getLabel() */ public function getForm() { - $form = new taoQtiTest_models_classes_import_TestImportForm(); + $form = new taoQtiTest_models_classes_import_TestImportForm([], $this->getFormOptions()); return $form->getForm(); } @@ -78,7 +85,8 @@ public function import($class, $form, $userId = null) // The zip extraction is a long process that can exceed the 30s timeout helpers_TimeOutHelper::setTimeOutLimit(helpers_TimeOutHelper::LONG); - $report = taoQtiTest_models_classes_QtiTestService::singleton()->importMultipleTests($class, $uploadedFile); + $report = taoQtiTest_models_classes_QtiTestService::singleton() + ->importMultipleTests($class, $uploadedFile, false, null, $form); helpers_TimeOutHelper::reset(); @@ -93,4 +101,32 @@ public function import($class, $form, $userId = null) return common_report_Report::createFailure($e->getMessage()); } } + public function getTaskParameters(tao_helpers_form_Form $importForm) + { + $file = $this->getUploadService()->getUploadedFlyFile($importForm->getValue('importFile') + ?: $importForm->getValue('source')['uploaded_file']); + + return [ + 'uploaded_file' => $file->getPrefix(), // because of Async, we need the full path of the uploaded file + TestImportForm::METADATA_FORM_ELEMENT_NAME => $importForm->getValue('metadata'), + ]; + } + + private function getFeatureFlagChecker(): FeatureFlagChecker + { + return $this->serviceLocator->getContainer()->get(FeatureFlagChecker::class); + } + + private function getFormOptions(): array + { + $options = []; + if (!$this->getFeatureFlagChecker()->isEnabled(MetadataLomService::FEATURE_FLAG)) { + $options[self::DISABLED_FIELDS] = [self::METADATA_FIELD]; + } + return $options; + } + private function getUploadService() + { + return $this->serviceLocator->get(UploadService::SERVICE_ID); + } } diff --git a/models/classes/import/class.TestImportForm.php b/models/classes/import/class.TestImportForm.php index 4720efda7..a35e355bc 100755 --- a/models/classes/import/class.TestImportForm.php +++ b/models/classes/import/class.TestImportForm.php @@ -31,12 +31,8 @@ */ class taoQtiTest_models_classes_import_TestImportForm extends tao_helpers_form_FormContainer { - // --- ASSOCIATIONS --- + public const METADATA_FORM_ELEMENT_NAME = 'metadata'; - - // --- ATTRIBUTES --- - - // --- OPERATIONS --- /** * (non-PHPdoc) * @see tao_helpers_form_FormContainer::initForm() @@ -99,18 +95,41 @@ public function initElements() ) ]); + $this->form->addElement($fileElt); - /* - $disableValidationElt = tao_helpers_form_FormFactory::getElement("disable_validation", 'Checkbox'); - $disableValidationElt->setDescription(__("Disable validation")); - $disableValidationElt->setOptions(array("on" => "")); - $this->form->addElement($disableValidationElt); - */ - $this->form->createGroup('file', __('Import a QTI/APIP Content Package'), ['source']); + $this->form->createGroup( + 'file', + __('Import a QTI/APIP Content Package'), + [ + 'source', + ] + ); + $this->addMetadataImportElement(); $qtiSentElt = tao_helpers_form_FormFactory::getElement('import_sent_qti', 'Hidden'); $qtiSentElt->setValue(1); $this->form->addElement($qtiSentElt); } + + private function isMetadataDisabled(): bool + { + return isset($this->options[taoQtiTest_models_classes_import_TestImport::DISABLED_FIELDS]) && + in_array( + taoQtiTest_models_classes_import_TestImport::METADATA_FIELD, + $this->options[taoQtiTest_models_classes_import_TestImport::DISABLED_FIELDS] + ); + } + + private function addMetadataImportElement() + { + if (!$this->isMetadataDisabled()) { + $metadataImport = tao_helpers_form_FormFactory::getElement(self::METADATA_FORM_ELEMENT_NAME, 'Checkbox'); + $metadataImport->setOptions([self::METADATA_FORM_ELEMENT_NAME => __('Import metadata or fail')]); + $metadataImport->setDescription(__('Metadata import')); + $metadataImport->setLevel(1); + $this->form->addElement($metadataImport); + $this->form->addToGroup('file', self::METADATA_FORM_ELEMENT_NAME); + } + } } diff --git a/models/classes/metadata/GenericLomOntologyExtractor.php b/models/classes/metadata/GenericLomOntologyExtractor.php new file mode 100644 index 000000000..03b310293 --- /dev/null +++ b/models/classes/metadata/GenericLomOntologyExtractor.php @@ -0,0 +1,86 @@ +ontology = $ontology; + $this->propertyMapper = $propertyMapper; + $this->metadataLomService = $metadataLomService; + } + + /** + * @param Resource[] $resourceCollection + * @throws MetadataExtractionException + */ + public function extract(array $resourceCollection, DOMDocument $manifest): void + { + $properties = []; + + foreach ($resourceCollection as $resource) { + if (!$resource instanceof Resource) { + throw new MetadataExtractionException( + __('The given target is not an instance of core_kernel_classes_Resource') + ); + } + + foreach ($resource->getRdfTriples() as $triple) { + if ($this->mappingRequired($properties, $triple)) { + $properties[] = $this->propertyMapper + ->getMetadataProperties( + $this->ontology->getProperty($triple->predicate) + ); + } + } + } + + $this->metadataLomService->addPropertiesToMetadataBlock($properties, $manifest); + } + + /** + * Mapping action only applies for confirmed properties that are not already mapped + */ + private function mappingRequired(array $properties, Triple $triple): bool + { + return $this->ontology->getProperty($triple->predicate)->isProperty() && + array_filter($properties, function ($property) use ($triple) { + return $property['uri'] === $triple->predicate; + }) === []; + } +} diff --git a/models/classes/metadata/MetadataLomService.php b/models/classes/metadata/MetadataLomService.php new file mode 100644 index 000000000..cc60b2fe5 --- /dev/null +++ b/models/classes/metadata/MetadataLomService.php @@ -0,0 +1,59 @@ +getElementsByTagName('metadata'); + + if ($metadataBlock === null) { + $metadataBlock = $manifest->createElement('metadata'); + $manifest->documentElement->appendChild($metadataBlock); + } + + $metadataBlock->item(0) + ->appendChild($manifest->createElement('imsmd:lom')) + ->appendChild($manifest->createElement('imsmd:metaMetadata')) + ->appendChild($manifest->createElement('extension')) + ->appendChild($manifest->createElement('customProperties')); + + foreach ($properties as $property) { + $propertyNode = $manifest->createElement('property'); + foreach ($property as $key => $value) { + $propertyNode->appendChild($manifest->createElement($key, $value)); + } + $metadataBlock->item(0) + ->getElementsByTagName('customProperties') + ->item(0) + ->appendChild($propertyNode); + } + } +} diff --git a/models/classes/metadata/MetadataServiceProvider.php b/models/classes/metadata/MetadataServiceProvider.php new file mode 100644 index 000000000..c252528ef --- /dev/null +++ b/models/classes/metadata/MetadataServiceProvider.php @@ -0,0 +1,62 @@ +services(); + + $services->set(MetadataLomService::class, MetadataLomService::class); + + $services->set(PropertyMapper::class, PropertyMapper::class) + ->args([ + service(ChecksumGenerator::class), + [ + 'label' => RDFS_LABEL, + 'domain' => RDFS_DOMAIN, + 'alias' => GenerisRdf::PROPERTY_ALIAS, + 'multiple' => GenerisRdf::PROPERTY_MULTIPLE + ] + ]); + + $services + ->set(GenericLomOntologyExtractor::class, GenericLomOntologyExtractor::class) + ->public() + ->args([ + service(Ontology::SERVICE_ID), + service(PropertyMapper::class), + service(MetadataLomService::class) + ]); + } +} diff --git a/models/classes/metadata/metaMetadata/PropertyMapper.php b/models/classes/metadata/metaMetadata/PropertyMapper.php new file mode 100644 index 000000000..af4fb9a19 --- /dev/null +++ b/models/classes/metadata/metaMetadata/PropertyMapper.php @@ -0,0 +1,78 @@ +metaMetadataCollectionToExport = $metaMetadataCollectionToExport; + $this->checksumGenerator = $checksumGenerator; + } + + public function getMetadataProperties(Property $property): array + { + $fields = []; + + foreach ($this->metaMetadataCollectionToExport as $key => $stringProperty) { + $fields['uri'] = $property->getUri(); + $metaProperty = $property->getOnePropertyValue(new Property($stringProperty)); + if ($metaProperty !== null) { + $fields[$key] = $metaProperty instanceof Resource + ? $metaProperty->getUri() + : (string) $metaProperty; + } + } + + if (!$this->isIgnoredForCollectionGathering($property)) { + $fields[self::DATATYPE_CHECKSUM] = $this->checksumGenerator->getRangeChecksum($property); + } + + return $fields; + } + + private function isIgnoredForCollectionGathering(Property $property): bool + { + return in_array($property->getUri(), $this->getIgnoredProperties()); + } + + private function getIgnoredProperties(): array + { + return [ + OntologyRdf::RDF_TYPE, + taoTests_models_classes_TestsService::PROPERTY_TEST_TESTMODEL, + RDFS_LABEL + ]; + } +} diff --git a/test/unit/models/classes/metadata/GenericLomOntologyExtractorTest.php b/test/unit/models/classes/metadata/GenericLomOntologyExtractorTest.php new file mode 100644 index 000000000..10971293a --- /dev/null +++ b/test/unit/models/classes/metadata/GenericLomOntologyExtractorTest.php @@ -0,0 +1,112 @@ +ontologyMock = $this->createMock(Ontology::class); + $this->propertyMapperMock = $this->createMock(PropertyMapper::class); + $this->metadataLomServiceMock = $this->createMock(MetadataLomService::class); + + $this->subject = new GenericLomOntologyExtractor( + $this->ontologyMock, + $this->propertyMapperMock, + $this->metadataLomServiceMock, + ); + } + + /** + * @noinspection PhpParamsInspection + */ + public function testExtractExceptionForCollectionWithNonResourceContent(): void + { + $this->expectException(MetadataExtractionException::class); + $resourceCollection = ['ResourceThatIsAString']; + $manifest = new DOMDocument(); + + $this->subject->extract($resourceCollection, $manifest); + } + + public function testExtract(): void + { + $resourceMock = $this->createMock(Resource::class); + $tripleMock1 = $this->createMock(Triple::class); + $tripleMock2 = $this->createMock(Triple::class); + $tripleMock3 = $this->createMock(Triple::class); + $propertyMock = $this->createMock(Property::class); + $manifest = new DOMDocument(); + $tripleMock1->predicate = 'predicate1'; + $tripleMock2->predicate = 'predicate2'; + $tripleMock3->predicate = 'predicate3'; + + $resourceMock->method('getRdfTriples')->willReturn([ + $tripleMock1, + $tripleMock2, + $tripleMock3 + ]); + + $propertyMock + ->method('isProperty') + ->willReturn(true); + + $this->ontologyMock + ->method('getProperty') + ->willReturn($propertyMock); + + $this->propertyMapperMock + ->method('getMetadataProperties') + ->willReturnOnConsecutiveCalls( + ['uri' => 'predicate1'], + ['uri' => 'predicate2'], + ['uri' => 'predicate3'], + ['uri' => 'predicate1'], + ['uri' => 'predicate2'], + ['uri' => 'predicate3'] + ); + + $this->metadataLomServiceMock + ->expects($this->once()) + ->method('addPropertiesToMetadataBlock') + ->with([ + ['uri' => 'predicate1'], + ['uri' => 'predicate2'], + ['uri' => 'predicate3'], + ], $manifest); + + $this->subject->extract([$resourceMock, $resourceMock], $manifest); + } +} diff --git a/test/unit/models/classes/metadata/MetadataLomServiceTest.php b/test/unit/models/classes/metadata/MetadataLomServiceTest.php new file mode 100644 index 000000000..cb5a697e3 --- /dev/null +++ b/test/unit/models/classes/metadata/MetadataLomServiceTest.php @@ -0,0 +1,62 @@ +metadataLomService = new MetadataLomService(); + } + + public function testAddPropertiesToMetadataBlock(): void + { + $manifest = new DOMDocument(); + $manifest->appendChild($manifest->createElement('metadata')); + + $proerties = [ + [ + 'label' => 'label_example', + 'domain' => 'domain_example', + 'alias' => 'alias_example', + 'multiple' => 'multiple_example', + ], + [ + 'label' => 'label_another_example', + 'domain' => 'domain_another_example', + 'alias' => 'alias_another_example', + 'multiple' => 'multiple_another_example', + ] + ]; + + $this->metadataLomService->addPropertiesToMetadataBlock($proerties, $manifest); + self::assertXmlStringEqualsXmlFile( + __DIR__ . '/imsManifestMetadata.xml', + $manifest->saveXML() + ); + } +} diff --git a/test/unit/models/classes/metadata/imsManifestMetadata.xml b/test/unit/models/classes/metadata/imsManifestMetadata.xml new file mode 100644 index 000000000..5fdf00e69 --- /dev/null +++ b/test/unit/models/classes/metadata/imsManifestMetadata.xml @@ -0,0 +1,23 @@ + + + + + + + + + domain_example + alias_example + multiple_example + + + + domain_another_example + alias_another_example + multiple_another_example + + + + + + diff --git a/test/unit/models/classes/metadata/metaMetadata/PropertyMapperTest.php b/test/unit/models/classes/metadata/metaMetadata/PropertyMapperTest.php new file mode 100644 index 000000000..056e50843 --- /dev/null +++ b/test/unit/models/classes/metadata/metaMetadata/PropertyMapperTest.php @@ -0,0 +1,84 @@ +checksumGeneratorMock = $this->createMock(ChecksumGenerator::class); + $this->metaMetadataCollectionToExport = [ + 'label' => RDFS_LABEL, + 'domain' => RDFS_DOMAIN, + 'alias' => GenerisRdf::PROPERTY_ALIAS, + 'multiple' => GenerisRdf::PROPERTY_MULTIPLE + ]; + + $this->subject = new PropertyMapper($this->checksumGeneratorMock, $this->metaMetadataCollectionToExport); + } + + public function testGetMetadataProperties(): void + { + $property = $this->createMock(Property::class); + $resourceMock = $this->createMock(Resource::class); + $property->method('getUri')->willReturn('uri'); + $resourceMock->method('getUri')->willReturn('resource_uri'); + + $property + ->method('getOnePropertyValue') + ->willReturnOnConsecutiveCalls( + $resourceMock, + 'value', + new core_kernel_classes_Literal('literal_value'), + null + ); + + $this->checksumGeneratorMock + ->method('getRangeChecksum') + ->willReturn('c315a4bd4fa0f4479b1ea4b5998aa548eed3b670'); + + + $result = $this->subject->getMetadataProperties($property); + + $this->assertIsArray($result); + $this->assertArrayHasKey('uri', $result); + $this->assertArrayHasKey('label', $result); + $this->assertArrayHasKey('domain', $result); + $this->assertArrayHasKey('alias', $result); + $this->assertArrayNotHasKey('multiple', $result); + $this->assertArrayHasKey(PropertyMapper::DATATYPE_CHECKSUM, $result); + $this->assertEquals('uri', $result['uri']); + $this->assertEquals('resource_uri', $result['label']); + $this->assertEquals('value', $result['domain']); + $this->assertEquals('literal_value', $result['alias']); + $this->assertEquals('c315a4bd4fa0f4479b1ea4b5998aa548eed3b670', $result['checksum']); + } +}