From ec02130d8ffab722e61233c28b6e7f4462beac56 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Fri, 5 Dec 2025 17:34:06 +0000 Subject: [PATCH 01/18] implement version checks --- .../five-safes-crate/must/15_metadata_file.py | 57 +++++++++++++++++++ .../must/15_metadata_file.ttl | 39 +++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py create mode 100644 rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.ttl diff --git a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py new file mode 100644 index 00000000..fb5b7d92 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py @@ -0,0 +1,57 @@ +# Copyright (c) 2024-2025 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any +import re + +import rocrate_validator.log as logging +from rocrate_validator.models import Severity, ValidationContext +from rocrate_validator.requirements.python import (PyFunctionCheck, check, + requirement) +from rocrate_validator.utils import HttpRequester + +# set up logging +logger = logging.getLogger(__name__) + + +@requirement(name="RO-Crate context version") +class FileDescriptorExistence(PyFunctionCheck): + """The RO-Crate metadata file MUST include the RO-Crate context version 1.2 (or later minor version) in `@context`""" + + @check(name="RO-Crate context version", severity=Severity.REQUIRED) + def test_existence(self, context: ValidationContext) -> bool: + """ + The RO-Crate metadata file MUST include the RO-Crate context version 1.2 (or later minor version) in `@context` + """ + try: + json_dict = context.ro_crate.metadata.as_dict() + context_value = json_dict["@context"] + pattern = re.compile(r"https://w3id\.org/ro/crate/1\.[2-9](-DRAFT)?/context") + passed = True + if isinstance(context_value, list): + if not any(pattern.match(item) for item in context_value if isinstance(item, str)): + passed = False + else: + if not pattern.match(context_value): + passed = False + if not passed: + context.result.add_issue( + f"The RO-Crate metadata file MUST include the RO-Crate context " + "version 1.2 (or later minor version) in `@context`", self) + return passed + + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return True diff --git a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.ttl b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.ttl new file mode 100644 index 00000000..c8a3b918 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.ttl @@ -0,0 +1,39 @@ +# Copyright (c) 2025 eScience Lab, The University of Manchester +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . +@prefix dct: . + +five-safes-crate:MetadataFileDescriptorProperties a sh:NodeShape ; + sh:name "RO-Crate conforms to 1.2 or later minor version" ; + sh:description """The RO-Crate metadata file descriptor MUST have a `conformsTo` property with RO-Crate specification version 1.2 or later minor version"""; + sh:targetClass ro-crate:ROCrateMetadataFileDescriptor ; + sh:property [ + a sh:PropertyShape ; + sh:name "RO-Crate conforms to 1.2 or later minor version" ; + sh:description "The RO-Crate metadata file descriptor MUST have a `conformsTo` property with RO-Crate specification version 1.2 or later minor version" ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path dct:conformsTo ; + sh:pattern "https://w3id\\.org/ro/crate/(1\\.[2-9](-DRAFT)?)" ; + sh:severity sh:Violation; + sh:message "The RO-Crate metadata file descriptor MUST have a `conformsTo` property with RO-Crate specification version 1.2 or later minor version" ; + ] . From fd7046d0208df3d926db66fada4f5f7cfdd6586d Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Fri, 5 Dec 2025 17:34:32 +0000 Subject: [PATCH 02/18] add extra crates to check context --- .../ro-crate-metadata.json | 169 ++++++++++++++++++ .../ro-crate-metadata.json | 169 ++++++++++++++++++ .../ro-crate-metadata.json | 6 +- tests/ro_crates.py | 19 +- 4 files changed, 350 insertions(+), 13 deletions(-) create mode 100644 tests/data/crates/invalid/five_safes_crate/context_multiple_wrong_version/ro-crate-metadata.json create mode 100644 tests/data/crates/invalid/five_safes_crate/context_single_wrong_version/ro-crate-metadata.json diff --git a/tests/data/crates/invalid/five_safes_crate/context_multiple_wrong_version/ro-crate-metadata.json b/tests/data/crates/invalid/five_safes_crate/context_multiple_wrong_version/ro-crate-metadata.json new file mode 100644 index 00000000..79733fa7 --- /dev/null +++ b/tests/data/crates/invalid/five_safes_crate/context_multiple_wrong_version/ro-crate-metadata.json @@ -0,0 +1,169 @@ +{ + "@context": ["https://w3id.org/ro/crate/1.1/context", "http://schema.org", {"test": "http://schema.org/test"}], + "@graph": [ + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "about": { + "@id": "./" + }, + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.2" + } + }, + { + "@id": "./", + "@type": "Dataset", + "name": "5-Safe RO-Crate Request", + "description": "example 5-Safe RO-Crate request metadata for testing", + "license": "Apache-2.0", + "datePublished": "2025-09-20T14:38:00+00:00", + "conformsTo": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "hasPart": [ + { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + { + "@id": "input1.txt" + } + ], + "mainEntity": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "mentions": { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538" + }, + "sourceOrganization": { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + }, + { + "@id": "https://w3id.org/5s-crate/0.4", + "@type": "Profile", + "name": "Five Safes RO-Crate profile" + }, + { + "@id": "https://workflowhub.eu/workflows/289?version=1", + "@type": "Dataset", + "name": "CWL Protein MD Setup tutorial with mutations", + "conformsTo": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + }, + "distribution": { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1" + } + }, + { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1", + "@type": "DataDownload", + "conformsTo": { + "@id": "https://w3id.org/ro/crate" + }, + "encodingFormat": "application/zip" + }, + { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538", + "@type": "CreateAction", + "actionStatus": "http://schema.org/PotentialActionStatus", + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "instrument": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "name": "Execute query 12389 on workflow ", + "object": [ + { + "@id": "input1.txt" + }, + { + "@id": "#enableFastMode" + } + ] + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes", + "affiliation": { + "@id": "https://ror.org/027m9bs27" + }, + "memberOf": [ + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + ] + }, + { + "@id": "https://ror.org/027m9bs27", + "@type": "Organization", + "name": "The University of Manchester" + }, + { + "@id": "https://ror.org/01ee9ar58", + "@type": "Organization", + "name": "University of Nottingham" + }, + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70", + "@type": "Project", + "name": "Investigation of cancer (TRE72 project 81)", + "identifier": [ + { + "@id": "_:localid:tre72:project81" + } + ], + "funding": { + "@id": "https://gtr.ukri.org/projects?ref=10038961" + }, + "member": [ + { + "@id": "https://ror.org/027m9bs27" + }, + { + "@id": "https://ror.org/01ee9ar58" + } + ] + }, + { + "@id": "_:localid:tre72:project81", + "@type": "PropertyValue", + "name": "tre72", + "value": "project81" + }, + { + "@id": "https://gtr.ukri.org/projects?ref=10038961", + "@type": "Grant", + "name": "EOSC4Cancer" + }, + { + "@id": "input1.txt", + "@type": "File", + "name": "input1", + "exampleOfWork": { + "@id": "#sequence" + } + }, + { + "@id": "#enableFastMode", + "@type": "PropertyValue", + "name": "--fast-mode", + "value": "True", + "exampleOfWork": { + "@id": "#fast" + } + }, + { + "@id": "#sequence", + "@type": "FormalParameter", + "name": "input-sequence" + }, + { + "@id": "#fast", + "@type": "FormalParameter", + "name": "fast-mode" + } + ] +} diff --git a/tests/data/crates/invalid/five_safes_crate/context_single_wrong_version/ro-crate-metadata.json b/tests/data/crates/invalid/five_safes_crate/context_single_wrong_version/ro-crate-metadata.json new file mode 100644 index 00000000..beba6839 --- /dev/null +++ b/tests/data/crates/invalid/five_safes_crate/context_single_wrong_version/ro-crate-metadata.json @@ -0,0 +1,169 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "about": { + "@id": "./" + }, + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.2" + } + }, + { + "@id": "./", + "@type": "Dataset", + "name": "5-Safe RO-Crate Request", + "description": "example 5-Safe RO-Crate request metadata for testing", + "license": "Apache-2.0", + "datePublished": "2025-09-20T14:38:00+00:00", + "conformsTo": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "hasPart": [ + { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + { + "@id": "input1.txt" + } + ], + "mainEntity": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "mentions": { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538" + }, + "sourceOrganization": { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + }, + { + "@id": "https://w3id.org/5s-crate/0.4", + "@type": "Profile", + "name": "Five Safes RO-Crate profile" + }, + { + "@id": "https://workflowhub.eu/workflows/289?version=1", + "@type": "Dataset", + "name": "CWL Protein MD Setup tutorial with mutations", + "conformsTo": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + }, + "distribution": { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1" + } + }, + { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1", + "@type": "DataDownload", + "conformsTo": { + "@id": "https://w3id.org/ro/crate" + }, + "encodingFormat": "application/zip" + }, + { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538", + "@type": "CreateAction", + "actionStatus": "http://schema.org/PotentialActionStatus", + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "instrument": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "name": "Execute query 12389 on workflow ", + "object": [ + { + "@id": "input1.txt" + }, + { + "@id": "#enableFastMode" + } + ] + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes", + "affiliation": { + "@id": "https://ror.org/027m9bs27" + }, + "memberOf": [ + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + ] + }, + { + "@id": "https://ror.org/027m9bs27", + "@type": "Organization", + "name": "The University of Manchester" + }, + { + "@id": "https://ror.org/01ee9ar58", + "@type": "Organization", + "name": "University of Nottingham" + }, + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70", + "@type": "Project", + "name": "Investigation of cancer (TRE72 project 81)", + "identifier": [ + { + "@id": "_:localid:tre72:project81" + } + ], + "funding": { + "@id": "https://gtr.ukri.org/projects?ref=10038961" + }, + "member": [ + { + "@id": "https://ror.org/027m9bs27" + }, + { + "@id": "https://ror.org/01ee9ar58" + } + ] + }, + { + "@id": "_:localid:tre72:project81", + "@type": "PropertyValue", + "name": "tre72", + "value": "project81" + }, + { + "@id": "https://gtr.ukri.org/projects?ref=10038961", + "@type": "Grant", + "name": "EOSC4Cancer" + }, + { + "@id": "input1.txt", + "@type": "File", + "name": "input1", + "exampleOfWork": { + "@id": "#sequence" + } + }, + { + "@id": "#enableFastMode", + "@type": "PropertyValue", + "name": "--fast-mode", + "value": "True", + "exampleOfWork": { + "@id": "#fast" + } + }, + { + "@id": "#sequence", + "@type": "FormalParameter", + "name": "input-sequence" + }, + { + "@id": "#fast", + "@type": "FormalParameter", + "name": "fast-mode" + } + ] +} diff --git a/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json index c5c50961..73a56b11 100644 --- a/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json +++ b/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json @@ -1,5 +1,5 @@ { - "@context": "https://w3id.org/ro/crate/1.1/context", + "@context": "https://w3id.org/ro/crate/1.2/context", "@graph": [ { "@type": "CreativeWork", @@ -8,7 +8,7 @@ "@id": "./" }, "conformsTo": { - "@id": "https://w3id.org/ro/crate/1.1" + "@id": "https://w3id.org/ro/crate/1.2" } }, { @@ -404,4 +404,4 @@ "name": "sha-512 algorithm" } ] -} \ No newline at end of file +} diff --git a/tests/ro_crates.py b/tests/ro_crates.py index 53f77259..81367e23 100644 --- a/tests/ro_crates.py +++ b/tests/ro_crates.py @@ -98,6 +98,10 @@ def five_safes_crate_request(self) -> Path: @property def five_safes_crate_result(self) -> Path: return VALID_CRATES_DATA_PATH / "five-safes-crate-result" + + @property + def five_safes_crate_multiple_context(self) -> Path: + return VALID_CRATES_DATA_PATH / "five-safes-crate-multiple-context" class InvalidFileDescriptor: @@ -980,20 +984,15 @@ def propertyvalue_no_unitcode(self) -> Path: class Invalid5sROC: - base_path = INVALID_CRATES_DATA_PATH / "6_five_safes_crate/" - - @property - def funding_project_no_name(self) -> Path: - return self.base_path / "funding_project_no_name" + base_path = INVALID_CRATES_DATA_PATH / "five_safes_crate/" @property - def root_data_entity_no_source_organization(self) -> Path: - return self.base_path / "root_data_entity_no_source_organization" + def context_multiple_wrong_version(self) -> Path: + return self.base_path / "context_multiple_wrong_version" @property - def root_data_entity_source_organization_not_entity(self) -> Path: - return self.base_path / "root_data_entity_source_organization_not_entity" - + def context_single_wrong_version(self) -> Path: + return self.base_path / "context_single_wrong_version" class InvalidMultiProfileROC: From b16679f61e855204358bb6f941569f79d18e3a23 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Fri, 5 Dec 2025 17:34:50 +0000 Subject: [PATCH 03/18] version check tests --- .../ro-crate-metadata.json | 169 ++++++++++++++++++ .../ro-crate-metadata.json | 6 +- .../test_5src_15_metadata_file.py | 69 +++++++ .../five-safes-crate/test_valid_5src.py | 37 +++- 4 files changed, 274 insertions(+), 7 deletions(-) create mode 100644 tests/data/crates/valid/five-safes-crate-multiple-context/ro-crate-metadata.json create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py diff --git a/tests/data/crates/valid/five-safes-crate-multiple-context/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-crate-multiple-context/ro-crate-metadata.json new file mode 100644 index 00000000..bf1e9bd2 --- /dev/null +++ b/tests/data/crates/valid/five-safes-crate-multiple-context/ro-crate-metadata.json @@ -0,0 +1,169 @@ +{ + "@context": ["https://w3id.org/ro/crate/1.2/context", "http://schema.org", {"test": "http://schema.org/test"}], + "@graph": [ + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "about": { + "@id": "./" + }, + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.2" + } + }, + { + "@id": "./", + "@type": "Dataset", + "name": "5-Safe RO-Crate Request", + "description": "example 5-Safe RO-Crate request metadata for testing", + "license": "Apache-2.0", + "datePublished": "2025-09-20T14:38:00+00:00", + "conformsTo": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "hasPart": [ + { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + { + "@id": "input1.txt" + } + ], + "mainEntity": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "mentions": { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538" + }, + "sourceOrganization": { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + }, + { + "@id": "https://w3id.org/5s-crate/0.4", + "@type": "Profile", + "name": "Five Safes RO-Crate profile" + }, + { + "@id": "https://workflowhub.eu/workflows/289?version=1", + "@type": "Dataset", + "name": "CWL Protein MD Setup tutorial with mutations", + "conformsTo": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + }, + "distribution": { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1" + } + }, + { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1", + "@type": "DataDownload", + "conformsTo": { + "@id": "https://w3id.org/ro/crate" + }, + "encodingFormat": "application/zip" + }, + { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538", + "@type": "CreateAction", + "actionStatus": "http://schema.org/PotentialActionStatus", + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "instrument": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "name": "Execute query 12389 on workflow ", + "object": [ + { + "@id": "input1.txt" + }, + { + "@id": "#enableFastMode" + } + ] + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes", + "affiliation": { + "@id": "https://ror.org/027m9bs27" + }, + "memberOf": [ + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + ] + }, + { + "@id": "https://ror.org/027m9bs27", + "@type": "Organization", + "name": "The University of Manchester" + }, + { + "@id": "https://ror.org/01ee9ar58", + "@type": "Organization", + "name": "University of Nottingham" + }, + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70", + "@type": "Project", + "name": "Investigation of cancer (TRE72 project 81)", + "identifier": [ + { + "@id": "_:localid:tre72:project81" + } + ], + "funding": { + "@id": "https://gtr.ukri.org/projects?ref=10038961" + }, + "member": [ + { + "@id": "https://ror.org/027m9bs27" + }, + { + "@id": "https://ror.org/01ee9ar58" + } + ] + }, + { + "@id": "_:localid:tre72:project81", + "@type": "PropertyValue", + "name": "tre72", + "value": "project81" + }, + { + "@id": "https://gtr.ukri.org/projects?ref=10038961", + "@type": "Grant", + "name": "EOSC4Cancer" + }, + { + "@id": "input1.txt", + "@type": "File", + "name": "input1", + "exampleOfWork": { + "@id": "#sequence" + } + }, + { + "@id": "#enableFastMode", + "@type": "PropertyValue", + "name": "--fast-mode", + "value": "True", + "exampleOfWork": { + "@id": "#fast" + } + }, + { + "@id": "#sequence", + "@type": "FormalParameter", + "name": "input-sequence" + }, + { + "@id": "#fast", + "@type": "FormalParameter", + "name": "fast-mode" + } + ] +} diff --git a/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json index 73285401..a53902d4 100644 --- a/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json +++ b/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json @@ -1,5 +1,5 @@ { - "@context": "https://w3id.org/ro/crate/1.1/context", + "@context": "https://w3id.org/ro/crate/1.2/context", "@graph": [ { "@type": "CreativeWork", @@ -8,7 +8,7 @@ "@id": "./" }, "conformsTo": { - "@id": "https://w3id.org/ro/crate/1.1" + "@id": "https://w3id.org/ro/crate/1.2" } }, { @@ -166,4 +166,4 @@ "name": "fast-mode" } ] -} \ No newline at end of file +} diff --git a/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py b/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py new file mode 100644 index 00000000..612835a7 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py @@ -0,0 +1,69 @@ +# Copyright (c) 2024-2025 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC, Invalid5sROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_5src_conforms_to_old_version(): + sparql = SPARQL_PREFIXES + """ + DELETE { + dct:conformsTo ?version . + } + INSERT { + dct:conformsTo . + } + WHERE { + dct:conformsTo ?version . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RO-Crate conforms to 1.2 or later minor version"], + expected_triggered_issues=["The RO-Crate metadata file descriptor MUST have a `conformsTo` property with RO-Crate specification version 1.2 or later minor version"], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + +def test_5src_context_single_wrong_version(): + do_entity_test( + rocrate_path=Invalid5sROC().context_single_wrong_version, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RO-Crate context version"], + expected_triggered_issues=["The RO-Crate metadata file MUST include the RO-Crate context version 1.2 (or later minor version) in `@context`"], + profile_identifier="five-safes-crate", + ) + +def test_5src_context_multiple_wrong_version(): + do_entity_test( + rocrate_path=Invalid5sROC().context_multiple_wrong_version, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RO-Crate context version"], + expected_triggered_issues=["The RO-Crate metadata file MUST include the RO-Crate context version 1.2 (or later minor version) in `@context`"], + profile_identifier="five-safes-crate", + ) diff --git a/tests/integration/profiles/five-safes-crate/test_valid_5src.py b/tests/integration/profiles/five-safes-crate/test_valid_5src.py index cfc5aa53..2e24411e 100644 --- a/tests/integration/profiles/five-safes-crate/test_valid_5src.py +++ b/tests/integration/profiles/five-safes-crate/test_valid_5src.py @@ -13,7 +13,9 @@ # limitations under the License. import logging +import pytest +from rocrate_validator import services from rocrate_validator.models import Severity from tests.conftest import SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER from tests.ro_crates import ValidROC @@ -23,23 +25,50 @@ logger.setLevel(logging.DEBUG) -def test_valid_five_safes_crate_request_required(): +@pytest.fixture +def skip_spec_version_identifier(): + """Returns identifiers for RO-Crate version checks in the base RO-Crate profile. + + Used to skip version checks while there is not a base profile available for RO-Crate 1.2 (only 1.1) + """ + rocrate_profile = services.get_profile("ro-crate") + if not rocrate_profile: + raise RuntimeError("Unable to load the RO-Crate profile") + check_conformsTo_version = \ + rocrate_profile.get_requirement_check("Metadata File Descriptor entity: `conformsTo` property") + assert check_conformsTo_version, \ + "Metadata File Descriptor entity: `conformsTo` property" + SKIP_CONFORMSTO_VERSION_CHECK_IDENTIFIER = check_conformsTo_version.identifier + return SKIP_CONFORMSTO_VERSION_CHECK_IDENTIFIER + + +def test_valid_five_safes_crate_request_required(skip_spec_version_identifier): """Test a valid Five Safes Crate representing a request.""" do_entity_test( ValidROC().five_safes_crate_request, Severity.REQUIRED, True, profile_identifier="five-safes-crate", - skip_checks=[SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER], + skip_checks=[ SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, skip_spec_version_identifier], ) -def test_valid_five_safes_crate_result_required(): +def test_valid_five_safes_crate_result_required(skip_spec_version_identifier): """Test a valid Five Safes Crate representing a result.""" do_entity_test( ValidROC().five_safes_crate_result, Severity.REQUIRED, True, profile_identifier="five-safes-crate", - skip_checks=[SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER], + skip_checks=[SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, skip_spec_version_identifier], + ) + +def test_valid_five_safes_crate_multiple_context(skip_spec_version_identifier): + """Test a valid Five Safes Crate representing a result.""" + do_entity_test( + ValidROC().five_safes_crate_multiple_context, + Severity.REQUIRED, + True, + profile_identifier="five-safes-crate", + skip_checks=[SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, skip_spec_version_identifier], ) From c9697bc7e094e4907177597d2c01f7bef4a99ec1 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Fri, 5 Dec 2025 17:35:11 +0000 Subject: [PATCH 04/18] add dct to sparql_prefixes --- tests/shared.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/shared.py b/tests/shared.py index 978f60e4..fed031e2 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -34,7 +34,9 @@ T = TypeVar("T") -SPARQL_PREFIXES = """PREFIX schema: +SPARQL_PREFIXES = """ +PREFIX schema: +PREFIX dct: """ From 6a737387fa90bdfe1f6b2fdd4690dc88144e553e Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Fri, 5 Dec 2025 18:12:01 +0000 Subject: [PATCH 05/18] correct error message --- tests/integration/profiles/five-safes-crate/test_valid_5src.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/profiles/five-safes-crate/test_valid_5src.py b/tests/integration/profiles/five-safes-crate/test_valid_5src.py index 2e24411e..ddb71e33 100644 --- a/tests/integration/profiles/five-safes-crate/test_valid_5src.py +++ b/tests/integration/profiles/five-safes-crate/test_valid_5src.py @@ -37,7 +37,7 @@ def skip_spec_version_identifier(): check_conformsTo_version = \ rocrate_profile.get_requirement_check("Metadata File Descriptor entity: `conformsTo` property") assert check_conformsTo_version, \ - "Metadata File Descriptor entity: `conformsTo` property" + 'Unable to find the requirement "Metadata File Descriptor entity: `conformsTo` property"' SKIP_CONFORMSTO_VERSION_CHECK_IDENTIFIER = check_conformsTo_version.identifier return SKIP_CONFORMSTO_VERSION_CHECK_IDENTIFIER From 4ab9ea04958b7dcf692940f67757faadb5e7189b Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Fri, 5 Dec 2025 18:16:07 +0000 Subject: [PATCH 06/18] fix failing test --- .../ro-crate-metadata.json | 2 +- .../five-safes-crate/test_5src_15_metadata_file.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/data/crates/valid/five-safes-crate-multiple-context/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-crate-multiple-context/ro-crate-metadata.json index bf1e9bd2..89813ee6 100644 --- a/tests/data/crates/valid/five-safes-crate-multiple-context/ro-crate-metadata.json +++ b/tests/data/crates/valid/five-safes-crate-multiple-context/ro-crate-metadata.json @@ -1,5 +1,5 @@ { - "@context": ["https://w3id.org/ro/crate/1.2/context", "http://schema.org", {"test": "http://schema.org/test"}], + "@context": ["https://w3id.org/ro/crate/1.2/context", "https://w3id.org/ro/terms/workflow-run/context", {"test": "http://schema.org/test"}], "@graph": [ { "@type": "CreativeWork", diff --git a/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py b/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py index 612835a7..329307c1 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py @@ -28,13 +28,14 @@ def test_5src_conforms_to_old_version(): sparql = SPARQL_PREFIXES + """ DELETE { - dct:conformsTo ?version . + ?this dct:conformsTo ?version . } INSERT { - dct:conformsTo . + ?this dct:conformsTo . } WHERE { - dct:conformsTo ?version . + ?this dct:conformsTo ?version ; + schema:about <./> . } """ From 3d59e3087280ad6d2fe5f96100412e5a64b51b96 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Fri, 5 Dec 2025 18:19:29 +0000 Subject: [PATCH 07/18] formatting --- .../five-safes-crate/must/15_metadata_file.py | 24 ++++++++++------- .../test_5src_15_metadata_file.py | 26 +++++++++++++++---- .../five-safes-crate/test_valid_5src.py | 26 ++++++++++++++----- tests/ro_crates.py | 3 ++- 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py index fb5b7d92..c141788c 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py +++ b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py @@ -12,14 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -from typing import Any import re import rocrate_validator.log as logging from rocrate_validator.models import Severity, ValidationContext -from rocrate_validator.requirements.python import (PyFunctionCheck, check, - requirement) -from rocrate_validator.utils import HttpRequester +from rocrate_validator.requirements.python import PyFunctionCheck, check, requirement # set up logging logger = logging.getLogger(__name__) @@ -32,23 +29,32 @@ class FileDescriptorExistence(PyFunctionCheck): @check(name="RO-Crate context version", severity=Severity.REQUIRED) def test_existence(self, context: ValidationContext) -> bool: """ - The RO-Crate metadata file MUST include the RO-Crate context version 1.2 (or later minor version) in `@context` + The RO-Crate metadata file MUST include the RO-Crate context version 1.2 + (or later minor version) in `@context` """ try: json_dict = context.ro_crate.metadata.as_dict() context_value = json_dict["@context"] - pattern = re.compile(r"https://w3id\.org/ro/crate/1\.[2-9](-DRAFT)?/context") + pattern = re.compile( + r"https://w3id\.org/ro/crate/1\.[2-9](-DRAFT)?/context" + ) passed = True if isinstance(context_value, list): - if not any(pattern.match(item) for item in context_value if isinstance(item, str)): + if not any( + pattern.match(item) + for item in context_value + if isinstance(item, str) + ): passed = False else: if not pattern.match(context_value): passed = False if not passed: context.result.add_issue( - f"The RO-Crate metadata file MUST include the RO-Crate context " - "version 1.2 (or later minor version) in `@context`", self) + "The RO-Crate metadata file MUST include the RO-Crate context " + "version 1.2 (or later minor version) in `@context`", + self, + ) return passed except Exception as e: diff --git a/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py b/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py index 329307c1..52cdce45 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py @@ -26,7 +26,9 @@ def test_5src_conforms_to_old_version(): - sparql = SPARQL_PREFIXES + """ + sparql = ( + SPARQL_PREFIXES + + """ DELETE { ?this dct:conformsTo ?version . } @@ -38,33 +40,47 @@ def test_5src_conforms_to_old_version(): schema:about <./> . } """ + ) do_entity_test( rocrate_path=ValidROC().five_safes_crate_request, requirement_severity=Severity.REQUIRED, expected_validation_result=False, - expected_triggered_requirements=["RO-Crate conforms to 1.2 or later minor version"], - expected_triggered_issues=["The RO-Crate metadata file descriptor MUST have a `conformsTo` property with RO-Crate specification version 1.2 or later minor version"], + expected_triggered_requirements=[ + "RO-Crate conforms to 1.2 or later minor version" + ], + expected_triggered_issues=[ + "The RO-Crate metadata file descriptor MUST have a `conformsTo` property with " + "RO-Crate specification version 1.2 or later minor version" + ], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) + def test_5src_context_single_wrong_version(): do_entity_test( rocrate_path=Invalid5sROC().context_single_wrong_version, requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["RO-Crate context version"], - expected_triggered_issues=["The RO-Crate metadata file MUST include the RO-Crate context version 1.2 (or later minor version) in `@context`"], + expected_triggered_issues=[ + "The RO-Crate metadata file MUST include the RO-Crate context version 1.2 " + "(or later minor version) in `@context`" + ], profile_identifier="five-safes-crate", ) + def test_5src_context_multiple_wrong_version(): do_entity_test( rocrate_path=Invalid5sROC().context_multiple_wrong_version, requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["RO-Crate context version"], - expected_triggered_issues=["The RO-Crate metadata file MUST include the RO-Crate context version 1.2 (or later minor version) in `@context`"], + expected_triggered_issues=[ + "The RO-Crate metadata file MUST include the RO-Crate context version 1.2 " + "(or later minor version) in `@context`" + ], profile_identifier="five-safes-crate", ) diff --git a/tests/integration/profiles/five-safes-crate/test_valid_5src.py b/tests/integration/profiles/five-safes-crate/test_valid_5src.py index ddb71e33..07711180 100644 --- a/tests/integration/profiles/five-safes-crate/test_valid_5src.py +++ b/tests/integration/profiles/five-safes-crate/test_valid_5src.py @@ -34,10 +34,12 @@ def skip_spec_version_identifier(): rocrate_profile = services.get_profile("ro-crate") if not rocrate_profile: raise RuntimeError("Unable to load the RO-Crate profile") - check_conformsTo_version = \ - rocrate_profile.get_requirement_check("Metadata File Descriptor entity: `conformsTo` property") - assert check_conformsTo_version, \ - 'Unable to find the requirement "Metadata File Descriptor entity: `conformsTo` property"' + check_conformsTo_version = rocrate_profile.get_requirement_check( + "Metadata File Descriptor entity: `conformsTo` property" + ) + assert ( + check_conformsTo_version + ), 'Unable to find the requirement "Metadata File Descriptor entity: `conformsTo` property"' SKIP_CONFORMSTO_VERSION_CHECK_IDENTIFIER = check_conformsTo_version.identifier return SKIP_CONFORMSTO_VERSION_CHECK_IDENTIFIER @@ -49,7 +51,10 @@ def test_valid_five_safes_crate_request_required(skip_spec_version_identifier): Severity.REQUIRED, True, profile_identifier="five-safes-crate", - skip_checks=[ SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, skip_spec_version_identifier], + skip_checks=[ + SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, + skip_spec_version_identifier, + ], ) @@ -60,9 +65,13 @@ def test_valid_five_safes_crate_result_required(skip_spec_version_identifier): Severity.REQUIRED, True, profile_identifier="five-safes-crate", - skip_checks=[SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, skip_spec_version_identifier], + skip_checks=[ + SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, + skip_spec_version_identifier, + ], ) + def test_valid_five_safes_crate_multiple_context(skip_spec_version_identifier): """Test a valid Five Safes Crate representing a result.""" do_entity_test( @@ -70,5 +79,8 @@ def test_valid_five_safes_crate_multiple_context(skip_spec_version_identifier): Severity.REQUIRED, True, profile_identifier="five-safes-crate", - skip_checks=[SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, skip_spec_version_identifier], + skip_checks=[ + SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, + skip_spec_version_identifier, + ], ) diff --git a/tests/ro_crates.py b/tests/ro_crates.py index 81367e23..b78d9880 100644 --- a/tests/ro_crates.py +++ b/tests/ro_crates.py @@ -98,7 +98,7 @@ def five_safes_crate_request(self) -> Path: @property def five_safes_crate_result(self) -> Path: return VALID_CRATES_DATA_PATH / "five-safes-crate-result" - + @property def five_safes_crate_multiple_context(self) -> Path: return VALID_CRATES_DATA_PATH / "five-safes-crate-multiple-context" @@ -994,6 +994,7 @@ def context_multiple_wrong_version(self) -> Path: def context_single_wrong_version(self) -> Path: return self.base_path / "context_single_wrong_version" + class InvalidMultiProfileROC: @property From f3cd29cd189e512661453c1f5432ce42e7944a2d Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Mon, 8 Dec 2025 10:59:09 +0000 Subject: [PATCH 08/18] work around unskippable check --- .../five-safes-crate/must/15_metadata_file.py | 3 ++- .../profiles/five-safes-crate/test_valid_5src.py | 6 ++++++ tests/shared.py | 11 ++++++++--- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py index c141788c..40f619b7 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py +++ b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py @@ -24,7 +24,8 @@ @requirement(name="RO-Crate context version") class FileDescriptorExistence(PyFunctionCheck): - """The RO-Crate metadata file MUST include the RO-Crate context version 1.2 (or later minor version) in `@context`""" + """The RO-Crate metadata file MUST include the RO-Crate context version 1.2 + (or later minor version) in `@context`""" @check(name="RO-Crate context version", severity=Severity.REQUIRED) def test_existence(self, context: ValidationContext) -> bool: diff --git a/tests/integration/profiles/five-safes-crate/test_valid_5src.py b/tests/integration/profiles/five-safes-crate/test_valid_5src.py index 07711180..6ee43823 100644 --- a/tests/integration/profiles/five-safes-crate/test_valid_5src.py +++ b/tests/integration/profiles/five-safes-crate/test_valid_5src.py @@ -55,6 +55,8 @@ def test_valid_five_safes_crate_request_required(skip_spec_version_identifier): SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, skip_spec_version_identifier, ], + # argument below can be removed when https://github.com/crs4/rocrate-validator/issues/126 is fixed + disable_inherited_profiles_reporting=True, ) @@ -69,6 +71,8 @@ def test_valid_five_safes_crate_result_required(skip_spec_version_identifier): SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, skip_spec_version_identifier, ], + # argument below can be removed when https://github.com/crs4/rocrate-validator/issues/126 is fixed + disable_inherited_profiles_reporting=True, ) @@ -83,4 +87,6 @@ def test_valid_five_safes_crate_multiple_context(skip_spec_version_identifier): SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, skip_spec_version_identifier, ], + # argument below can be removed when https://github.com/crs4/rocrate-validator/issues/126 is fixed + disable_inherited_profiles_reporting=True, ) diff --git a/tests/shared.py b/tests/shared.py index fed031e2..a689aa6f 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -101,10 +101,14 @@ def do_entity_test( profile_identifier: str = DEFAULT_PROFILE_IDENTIFIER, rocrate_entity_patch: Optional[dict] = None, rocrate_entity_mod_sparql: Optional[str] = None, - skip_checks: Optional[list[str]] = () + skip_checks: Optional[list[str]] = (), + **kwargs ): """ - Shared function to test a RO-Crate entity + Shared function to test a RO-Crate entity. + + Additional keyword arguments (kwargs) are passed through to ValidationSettings, + allowing individual tests to tweak settings as needed. """ assert not (rocrate_entity_patch and rocrate_entity_mod_sparql), \ "Cannot use rocrate_entity_patch and rocrate_entity_mod_sparql together" @@ -172,7 +176,8 @@ def do_entity_test( "requirement_severity": requirement_severity, "abort_on_first": abort_on_first, "profile_identifier": profile_identifier, - "skip_checks": skip_checks + "skip_checks": skip_checks, + **kwargs })) logger.debug("Expected validation result: %s", expected_validation_result) From ee18c9fb8ef226d8cefcc1b245ff2342178abb53 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Mon, 8 Dec 2025 11:31:47 +0000 Subject: [PATCH 09/18] linting --- .../profiles/five-safes-crate/must/15_metadata_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py index 40f619b7..dd5ca572 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py +++ b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py @@ -24,7 +24,7 @@ @requirement(name="RO-Crate context version") class FileDescriptorExistence(PyFunctionCheck): - """The RO-Crate metadata file MUST include the RO-Crate context version 1.2 + """The RO-Crate metadata file MUST include the RO-Crate context version 1.2 (or later minor version) in `@context`""" @check(name="RO-Crate context version", severity=Severity.REQUIRED) From 6ded827ef88795fd8abb7b45b389a2eea818ec37 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Mon, 8 Dec 2025 11:32:56 +0000 Subject: [PATCH 10/18] linting 2 --- tests/shared.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/shared.py b/tests/shared.py index a689aa6f..39cb0060 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -107,7 +107,7 @@ def do_entity_test( """ Shared function to test a RO-Crate entity. - Additional keyword arguments (kwargs) are passed through to ValidationSettings, + Additional keyword arguments (kwargs) are passed through to ValidationSettings, allowing individual tests to tweak settings as needed. """ assert not (rocrate_entity_patch and rocrate_entity_mod_sparql), \ @@ -184,7 +184,7 @@ def do_entity_test( assert result.context is not None, "Validation context should not be None" f"Expected requirement severity to be {requirement_severity}, but got {result.context.requirement_severity}" assert result.passed() == expected_validation_result, \ - f"RO-Crate should be {'valid' if expected_validation_result else 'invalid'}" + f"RO-Crate should be {'valid' if expected_validation_result else 'invalid'}. {result.failed_requirements} {result.failed_checks} {[issue.message for issue in result.get_issues(requirement_severity)]}" # check requirement failed_requirements = [_.name for _ in result.failed_requirements] @@ -196,7 +196,7 @@ def do_entity_test( for expected_triggered_requirement in expected_triggered_requirements: if expected_triggered_requirement not in failed_requirements: assert False, f"The expected requirement " \ - f"\"{expected_triggered_requirement}\" was not found in the failed requirements" + f"\"{expected_triggered_requirement}\" was not found in the failed requirements {failed_requirements}" # check requirement issues detected_issues = [issue.message for issue in result.get_issues(requirement_severity) @@ -205,7 +205,7 @@ def do_entity_test( logger.debug("Expected issues: %s", expected_triggered_issues) for expected_issue in expected_triggered_issues: if not any(expected_issue in issue for issue in detected_issues): # support partial match - assert False, f"The expected issue \"{expected_issue}\" was not found in the detected issues" + assert False, f"The expected issue \"{expected_issue}\" was not found in the detected issues {detected_issues}" except Exception as e: if logger.isEnabledFor(logging.DEBUG): logger.exception(e) From 0dfbc83b2a928f95373560dc385dc127506d5755 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Mon, 8 Dec 2025 11:41:34 +0000 Subject: [PATCH 11/18] Revert "linting 2" This reverts commit 6ded827ef88795fd8abb7b45b389a2eea818ec37. --- tests/shared.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/shared.py b/tests/shared.py index 39cb0060..a689aa6f 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -107,7 +107,7 @@ def do_entity_test( """ Shared function to test a RO-Crate entity. - Additional keyword arguments (kwargs) are passed through to ValidationSettings, + Additional keyword arguments (kwargs) are passed through to ValidationSettings, allowing individual tests to tweak settings as needed. """ assert not (rocrate_entity_patch and rocrate_entity_mod_sparql), \ @@ -184,7 +184,7 @@ def do_entity_test( assert result.context is not None, "Validation context should not be None" f"Expected requirement severity to be {requirement_severity}, but got {result.context.requirement_severity}" assert result.passed() == expected_validation_result, \ - f"RO-Crate should be {'valid' if expected_validation_result else 'invalid'}. {result.failed_requirements} {result.failed_checks} {[issue.message for issue in result.get_issues(requirement_severity)]}" + f"RO-Crate should be {'valid' if expected_validation_result else 'invalid'}" # check requirement failed_requirements = [_.name for _ in result.failed_requirements] @@ -196,7 +196,7 @@ def do_entity_test( for expected_triggered_requirement in expected_triggered_requirements: if expected_triggered_requirement not in failed_requirements: assert False, f"The expected requirement " \ - f"\"{expected_triggered_requirement}\" was not found in the failed requirements {failed_requirements}" + f"\"{expected_triggered_requirement}\" was not found in the failed requirements" # check requirement issues detected_issues = [issue.message for issue in result.get_issues(requirement_severity) @@ -205,7 +205,7 @@ def do_entity_test( logger.debug("Expected issues: %s", expected_triggered_issues) for expected_issue in expected_triggered_issues: if not any(expected_issue in issue for issue in detected_issues): # support partial match - assert False, f"The expected issue \"{expected_issue}\" was not found in the detected issues {detected_issues}" + assert False, f"The expected issue \"{expected_issue}\" was not found in the detected issues" except Exception as e: if logger.isEnabledFor(logging.DEBUG): logger.exception(e) From 26563c9dade8b2430048f3c50a34335db64e1e4c Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Mon, 8 Dec 2025 11:42:15 +0000 Subject: [PATCH 12/18] linting 2b --- tests/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/shared.py b/tests/shared.py index a689aa6f..662ba4bb 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -107,7 +107,7 @@ def do_entity_test( """ Shared function to test a RO-Crate entity. - Additional keyword arguments (kwargs) are passed through to ValidationSettings, + Additional keyword arguments (kwargs) are passed through to ValidationSettings, allowing individual tests to tweak settings as needed. """ assert not (rocrate_entity_patch and rocrate_entity_mod_sparql), \ From 8f87ff2c2b9da7390e2b35df819c40285f80a38a Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Mon, 8 Dec 2025 14:24:04 +0000 Subject: [PATCH 13/18] move kwargs outside dict --- tests/shared.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/tests/shared.py b/tests/shared.py index 662ba4bb..bffc8172 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -170,15 +170,18 @@ def do_entity_test( abort_on_first = abort_on_first # validate RO-Crate - result: models.ValidationResult = \ - services.validate(models.ValidationSettings(**{ - "rocrate_uri": rocrate_path, - "requirement_severity": requirement_severity, - "abort_on_first": abort_on_first, - "profile_identifier": profile_identifier, - "skip_checks": skip_checks, - **kwargs - })) + result: models.ValidationResult = services.validate( + models.ValidationSettings( + **{ + "rocrate_uri": rocrate_path, + "requirement_severity": requirement_severity, + "abort_on_first": abort_on_first, + "profile_identifier": profile_identifier, + "skip_checks": skip_checks, + }, + **kwargs, + ) + ) logger.debug("Expected validation result: %s", expected_validation_result) assert result.context is not None, "Validation context should not be None" From 729a2be72ac7fab7525f119c0311987ec4056cf9 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Tue, 9 Dec 2025 10:37:20 +0000 Subject: [PATCH 14/18] deactivate conformsTo check directly in SHACL --- .../must/15_metadata_file.ttl | 2 ++ .../must/1_file-descriptor_metadata.ttl | 19 +++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.ttl b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.ttl index c8a3b918..14d939f0 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.ttl +++ b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.ttl @@ -37,3 +37,5 @@ five-safes-crate:MetadataFileDescriptorProperties a sh:NodeShape ; sh:severity sh:Violation; sh:message "The RO-Crate metadata file descriptor MUST have a `conformsTo` property with RO-Crate specification version 1.2 or later minor version" ; ] . + +ro-crate:conformsToROCrateSpec sh:deactivated true . diff --git a/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl b/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl index 075f5f21..fee8c135 100644 --- a/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl +++ b/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl @@ -89,13 +89,12 @@ ro-crate:ROCrateMetadataFileDescriptorRecommendedProperties a sh:NodeShape ; sh:class schema_org:Dataset ; sh:message "The RO-Crate metadata file descriptor MUST have an `about` property referencing the Root Data Entity" ; ] ; - sh:property [ - a sh:PropertyShape ; - sh:name "Metadata File Descriptor entity: `conformsTo` property" ; - sh:description """Check if the RO-Crate Metadata File Descriptor has a `conformsTo` property which points to the RO-Crate specification version""" ; - sh:minCount 1 ; - sh:nodeKind sh:IRI ; - sh:path dct:conformsTo ; - sh:hasValue ; - sh:message "The RO-Crate metadata file descriptor MUST have a `conformsTo` property with the RO-Crate specification version" ; - ] . + sh:property ro-crate:conformsToROCrateSpec . + +ro-crate:conformsToROCrateSpec sh:name "Metadata File Descriptor entity: `conformsTo` property" ; + sh:description """Check if the RO-Crate Metadata File Descriptor has a `conformsTo` property which points to the RO-Crate specification version""" ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path dct:conformsTo ; + sh:hasValue ; + sh:message "The RO-Crate metadata file descriptor MUST have a `conformsTo` property with the RO-Crate specification version" . From 2e8f9ac1d5ee8adefaabe9bf7168241e78e7f7f3 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Tue, 9 Dec 2025 10:40:31 +0000 Subject: [PATCH 15/18] remove unneeded fixture & workaround from tests --- .../five-safes-crate/test_valid_5src.py | 34 ++----------------- 1 file changed, 3 insertions(+), 31 deletions(-) diff --git a/tests/integration/profiles/five-safes-crate/test_valid_5src.py b/tests/integration/profiles/five-safes-crate/test_valid_5src.py index 6ee43823..a4e34cc6 100644 --- a/tests/integration/profiles/five-safes-crate/test_valid_5src.py +++ b/tests/integration/profiles/five-safes-crate/test_valid_5src.py @@ -25,26 +25,7 @@ logger.setLevel(logging.DEBUG) -@pytest.fixture -def skip_spec_version_identifier(): - """Returns identifiers for RO-Crate version checks in the base RO-Crate profile. - - Used to skip version checks while there is not a base profile available for RO-Crate 1.2 (only 1.1) - """ - rocrate_profile = services.get_profile("ro-crate") - if not rocrate_profile: - raise RuntimeError("Unable to load the RO-Crate profile") - check_conformsTo_version = rocrate_profile.get_requirement_check( - "Metadata File Descriptor entity: `conformsTo` property" - ) - assert ( - check_conformsTo_version - ), 'Unable to find the requirement "Metadata File Descriptor entity: `conformsTo` property"' - SKIP_CONFORMSTO_VERSION_CHECK_IDENTIFIER = check_conformsTo_version.identifier - return SKIP_CONFORMSTO_VERSION_CHECK_IDENTIFIER - - -def test_valid_five_safes_crate_request_required(skip_spec_version_identifier): +def test_valid_five_safes_crate_request_required(): """Test a valid Five Safes Crate representing a request.""" do_entity_test( ValidROC().five_safes_crate_request, @@ -53,14 +34,11 @@ def test_valid_five_safes_crate_request_required(skip_spec_version_identifier): profile_identifier="five-safes-crate", skip_checks=[ SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, - skip_spec_version_identifier, ], - # argument below can be removed when https://github.com/crs4/rocrate-validator/issues/126 is fixed - disable_inherited_profiles_reporting=True, ) -def test_valid_five_safes_crate_result_required(skip_spec_version_identifier): +def test_valid_five_safes_crate_result_required(): """Test a valid Five Safes Crate representing a result.""" do_entity_test( ValidROC().five_safes_crate_result, @@ -69,14 +47,11 @@ def test_valid_five_safes_crate_result_required(skip_spec_version_identifier): profile_identifier="five-safes-crate", skip_checks=[ SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, - skip_spec_version_identifier, ], - # argument below can be removed when https://github.com/crs4/rocrate-validator/issues/126 is fixed - disable_inherited_profiles_reporting=True, ) -def test_valid_five_safes_crate_multiple_context(skip_spec_version_identifier): +def test_valid_five_safes_crate_multiple_context(): """Test a valid Five Safes Crate representing a result.""" do_entity_test( ValidROC().five_safes_crate_multiple_context, @@ -85,8 +60,5 @@ def test_valid_five_safes_crate_multiple_context(skip_spec_version_identifier): profile_identifier="five-safes-crate", skip_checks=[ SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, - skip_spec_version_identifier, ], - # argument below can be removed when https://github.com/crs4/rocrate-validator/issues/126 is fixed - disable_inherited_profiles_reporting=True, ) From a39c810e33d2c77236e35122ddb57b4c07896716 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Tue, 9 Dec 2025 10:41:53 +0000 Subject: [PATCH 16/18] linting --- tests/integration/profiles/five-safes-crate/test_valid_5src.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/integration/profiles/five-safes-crate/test_valid_5src.py b/tests/integration/profiles/five-safes-crate/test_valid_5src.py index a4e34cc6..7de8c869 100644 --- a/tests/integration/profiles/five-safes-crate/test_valid_5src.py +++ b/tests/integration/profiles/five-safes-crate/test_valid_5src.py @@ -13,9 +13,7 @@ # limitations under the License. import logging -import pytest -from rocrate_validator import services from rocrate_validator.models import Severity from tests.conftest import SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER from tests.ro_crates import ValidROC From 261587877bf7302e1bc7bfe9573741d03c46cbd8 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Tue, 9 Dec 2025 10:47:31 +0000 Subject: [PATCH 17/18] revert unneeded changes --- tests/shared.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/tests/shared.py b/tests/shared.py index 3d143635..7a9dca32 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -103,14 +103,10 @@ def do_entity_test( profile_identifier: str = DEFAULT_PROFILE_IDENTIFIER, rocrate_entity_patch: Optional[dict] = None, rocrate_entity_mod_sparql: Optional[str] = None, - skip_checks: Optional[list[str]] = (), - **kwargs + skip_checks: Optional[list[str]] = () ): """ - Shared function to test a RO-Crate entity. - - Additional keyword arguments (kwargs) are passed through to ValidationSettings, - allowing individual tests to tweak settings as needed. + Shared function to test a RO-Crate entity """ assert not (rocrate_entity_patch and rocrate_entity_mod_sparql), \ "Cannot use rocrate_entity_patch and rocrate_entity_mod_sparql together" @@ -172,18 +168,14 @@ def do_entity_test( abort_on_first = abort_on_first # validate RO-Crate - result: models.ValidationResult = services.validate( - models.ValidationSettings( - **{ - "rocrate_uri": rocrate_path, - "requirement_severity": requirement_severity, - "abort_on_first": abort_on_first, - "profile_identifier": profile_identifier, - "skip_checks": skip_checks, - }, - **kwargs, - ) - ) + result: models.ValidationResult = \ + services.validate(models.ValidationSettings(**{ + "rocrate_uri": rocrate_path, + "requirement_severity": requirement_severity, + "abort_on_first": abort_on_first, + "profile_identifier": profile_identifier, + "skip_checks": skip_checks + })) logger.debug("Expected validation result: %s", expected_validation_result) assert result.context is not None, "Validation context should not be None" From 8c61b6a7f364a1daa3b14860001268604b1eb0d6 Mon Sep 17 00:00:00 2001 From: Eli Chadwick Date: Wed, 10 Dec 2025 14:05:37 +0000 Subject: [PATCH 18/18] name class more accurately --- .../profiles/five-safes-crate/must/15_metadata_file.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py index dd5ca572..7fd46ccf 100644 --- a/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py +++ b/rocrate_validator/profiles/five-safes-crate/must/15_metadata_file.py @@ -23,7 +23,7 @@ @requirement(name="RO-Crate context version") -class FileDescriptorExistence(PyFunctionCheck): +class FileDescriptorContextVersion(PyFunctionCheck): """The RO-Crate metadata file MUST include the RO-Crate context version 1.2 (or later minor version) in `@context`"""