From ab465be75a0ff9c9daf1ca3e856bd3272a82eb4d Mon Sep 17 00:00:00 2001 From: EttoreM Date: Thu, 2 Jul 2026 15:20:36 +0100 Subject: [PATCH 1/3] Add Five Safes Crate profile to upstream RO-Crate 1.2 base --- .../0_workflow_run_inference.ttl | 47 ++ .../profiles/five-safes-crate/10_outputs.ttl | 97 +++ .../11_workflow_execution_phase.ttl | 145 +++++ .../five-safes-crate/12_check_phase.ttl | 299 +++++++++ .../five-safes-crate/13_validation_phase.ttl | 308 ++++++++++ .../14_workflow_retrieval_phase.ttl | 344 +++++++++++ .../five-safes-crate/15_metadata_file.py | 64 ++ .../five-safes-crate/15_metadata_file.ttl | 53 ++ .../five-safes-crate/16_publishing_phase.ttl | 51 ++ .../five-safes-crate/1_requesting_agent.ttl | 114 ++++ .../1_responsible_project.ttl | 139 +++++ .../five-safes-crate/1_root_data_entity.ttl | 56 ++ .../five-safes-crate/2_requesting_agent.ttl | 97 +++ .../five-safes-crate/3_timestamp_format.ttl | 64 ++ .../profiles/five-safes-crate/4_sign_off.ttl | 295 +++++++++ .../5_profile_conformance.ttl | 89 +++ .../five-safes-crate/6_workflow_reference.ttl | 109 ++++ .../7_requested_workflow_run.ttl | 108 ++++ .../five-safes-crate/8_disclosure_phase.ttl | 233 +++++++ .../profiles/five-safes-crate/9_inputs.ttl | 67 ++ .../profiles/five-safes-crate/ontology.ttl | 37 ++ .../profiles/five-safes-crate/profile.ttl | 83 +++ .../ro-crate-metadata.json | 169 +++++ .../ro-crate-metadata.json | 169 +++++ .../ro-crate-metadata.json | 169 +++++ .../ro-crate-metadata.json | 169 +++++ .../ro-crate-metadata.json | 413 +++++++++++++ .../ro-crate-metadata.json | 461 ++++++++++++++ .../five-safes-crate/test_5src_10_outputs.py | 95 +++ .../test_5src_11_workflow_execution.py | 308 ++++++++++ .../test_5src_12_check_phase.py | 385 ++++++++++++ .../test_5src_13_validation_phase.py | 354 +++++++++++ .../test_5src_14_workflow_retrieval_phase.py | 425 +++++++++++++ .../test_5src_15_metadata_file.py | 87 +++ .../test_5src_16_publishing_phase.py | 56 ++ .../test_5src_1_requesting_agent.py | 139 +++++ .../test_5src_1_responsible_project.py | 219 +++++++ .../test_5src_1_root_data_entity_metadata.py | 90 +++ .../test_5src_2_requesting_agent.py | 165 +++++ .../test_5src_4_signoff_phase.py | 576 ++++++++++++++++++ .../test_5src_5_profile_conformance.py | 193 ++++++ .../test_5src_6_workflow_reference.py | 258 ++++++++ .../test_5src_7_requesting_workflow_run.py | 200 ++++++ .../test_5src_8_disclosure_phase.py | 373 ++++++++++++ .../five-safes-crate/test_5src_9_inputs.py | 64 ++ .../five-safes-crate/test_valid_5src.py | 119 ++++ tests/ro_crates.py | 24 + tests/shared.py | 8 +- 48 files changed, 8586 insertions(+), 1 deletion(-) create mode 100644 rocrate_validator/profiles/five-safes-crate/0_workflow_run_inference.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/10_outputs.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/11_workflow_execution_phase.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/12_check_phase.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/13_validation_phase.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/14_workflow_retrieval_phase.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/15_metadata_file.py create mode 100644 rocrate_validator/profiles/five-safes-crate/15_metadata_file.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/16_publishing_phase.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/1_requesting_agent.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/1_responsible_project.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/1_root_data_entity.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/2_requesting_agent.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/3_timestamp_format.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/4_sign_off.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/5_profile_conformance.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/6_workflow_reference.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/7_requested_workflow_run.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/8_disclosure_phase.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/9_inputs.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/ontology.ttl create mode 100644 rocrate_validator/profiles/five-safes-crate/profile.ttl 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 create mode 100644 tests/data/crates/valid/five-safes-crate-multiple-context/ro-crate-metadata.json create mode 100644 tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json create mode 100644 tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json create mode 100644 tests/data/crates/valid/five-safes-profile-crate/ro-crate-metadata.json create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_10_outputs.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_12_check_phase.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_13_validation_phase.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_14_workflow_retrieval_phase.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_16_publishing_phase.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_2_requesting_agent.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_4_signoff_phase.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_5_profile_conformance.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_6_workflow_reference.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_7_requesting_workflow_run.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_8_disclosure_phase.py create mode 100644 tests/integration/profiles/five-safes-crate/test_5src_9_inputs.py create mode 100644 tests/integration/profiles/five-safes-crate/test_valid_5src.py diff --git a/rocrate_validator/profiles/five-safes-crate/0_workflow_run_inference.ttl b/rocrate_validator/profiles/five-safes-crate/0_workflow_run_inference.ttl new file mode 100644 index 000000000..d496bd324 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/0_workflow_run_inference.ttl @@ -0,0 +1,47 @@ +# 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 rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . + + +ro-crate:FindWorkflowRunAction a sh:NodeShape, validator:HiddenShape; + sh:order 1 ; # load this check after ro-crate:FindRootDataEntity; pySHACL sorts rules by sh:order before execution + sh:name "Identify the CreateAction Entity that corresponds to the Workflow run" ; + sh:description """The Workflow Run is the CreateAction entity that refers to Workflow run. + This is identified by checking that the CreateAction entity has an `instrument` property + that references the same entity as the `mainEntity` property of the Root Data Entity.""" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?this a schema:CreateAction ; + schema:instrument ?instrument . + ?root a ro-crate:RootDataEntity ; + schema:mainEntity ?instrument . + } + """ + ] ; + + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:WorkflowRunAction ; + ] . diff --git a/rocrate_validator/profiles/five-safes-crate/10_outputs.ttl b/rocrate_validator/profiles/five-safes-crate/10_outputs.ttl new file mode 100644 index 000000000..3e3dbb5e3 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/10_outputs.ttl @@ -0,0 +1,97 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# +# (none) + + +#=== SHOULD shapes ===# + +five-safes-crate:CreateActionHasResultIfActionCompleted + a sh:NodeShape ; + sh:name "WorkflowRunAction" ; + sh:description "The `CreateAction` corresponding to the workflow run, with CompletedActionStatus, SHOULD have the `schema:result` property." ; + + sh:target [ + a sh:SPARQLTarget ; + sh:name "WorkflowRunAction" ; + sh:description "The `CreateAction` corresponding to the workflow run, with CompletedActionStatus, SHOULD have the `result` property." ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this WHERE { + ?this a ro-crate:WorkflowRunAction ; + schema:actionStatus "http://schema.org/CompletedActionStatus" . + } + """ + ] ; + + + sh:property [ + a sh:PropertyShape ; + sh:name "Result" ; + sh:path schema:result ; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message "The `CreateAction` corresponding to the workflow run, with CompletedActionStatus, SHOULD have the `result` property." ; + ] . + + +five-safes-crate:WorkflowRunActionResultOutputsHaveAllowedTypes + a sh:NodeShape ; + sh:name "Output" ; + sh:description "Result SHOULD have a `@type` among an allowed set of values." ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?workflowRunAction a ro-crate:WorkflowRunAction . + ?workflowRunAction schema:result ?this . + } + """ ; + ] ; + sh:message "Result SHOULD have a `@type` among an allowed set of values." ; + sh:severity sh:Warning ; + sh:or ( + [ + sh:class schema:MediaObject; + ] + [ + sh:class schema:Dataset; + ] + [ + sh:class schema:Collection; + ] + [ + sh:class schema:DigitalDocument; + ] + [ + sh:class schema:PropertyValue; + ] + ) . + + +#=== MAY shapes ===# +# (none) diff --git a/rocrate_validator/profiles/five-safes-crate/11_workflow_execution_phase.ttl b/rocrate_validator/profiles/five-safes-crate/11_workflow_execution_phase.ttl new file mode 100644 index 000000000..c5b813e26 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/11_workflow_execution_phase.ttl @@ -0,0 +1,145 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:WorkflowMustHaveDescriptiveName + a sh:NodeShape ; + sh:name "WorkflowExecution" ; + sh:targetClass ro-crate:WorkflowRunAction ; + + sh:property [ + a sh:PropertyShape ; + sh:name "name" ; + sh:minCount 1 ; + sh:description "The `CreateAction` corresponding to the workflow run MUST have a name string of at least 10 characters." ; + sh:path schema:name ; + sh:datatype xsd:string ; + sh:minLength 10 ; + sh:severity sh:Violation ; + sh:message "The `CreateAction` corresponding to the workflow run MUST have a name string of at least 10 characters." ; + ] . + + +five-safes-crate:WorkflowMustHaveActionStatusWithAllowedValues + a sh:NodeShape ; + sh:name "WorkflowExecution" ; + sh:targetClass ro-crate:WorkflowRunAction ; + sh:property [ + a sh:PropertyShape ; + sh:minCount 1 ; + sh:name "actionStatus" ; + sh:description "`CreateAction` MUST have an actionStatus with an allowed value (see https://schema.org/ActionStatusType)." ; + sh:path schema:actionStatus ; + sh:in ( + "http://schema.org/PotentialActionStatus" + "http://schema.org/ActiveActionStatus" + "http://schema.org/CompletedActionStatus" + "http://schema.org/FailedActionStatus" + ) ; + sh:severity sh:Violation ; + sh:message "`CreateAction` MUST have an actionStatus with an allowed value (see https://schema.org/ActionStatusType)." ; + ] . + + +#=== SHOULD shapes ===# + +five-safes-crate:WorkflowexecutionObjectHasEndTimeIfEnded + a sh:NodeShape ; + sh:name "WorkflowExecution" ; + sh:description "The workflow run object SHOULD have an endTime property if it has ended." ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX rocrate: + + SELECT ?this + WHERE { + ?this a rocrate:WorkflowRunAction ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "EndTime" ; + sh:path schema:endTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Warning ; + sh:description "The workflow execution object SHOULD have an endTime property if it has ended." ; + sh:message "The workflow execution object SHOULD have an endTime property if it has ended." ; + ] . + + +#=== MAY shapes ===# + +five-safes-crate:WorkflowexecutionObjectHasStartTimeIfBegun + a sh:NodeShape ; + sh:name "WorkflowExecution" ; + sh:description ( + "The workflow execution object MAY have a startTime if actionStatus is " + "either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." + ) ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX rocrate: + + SELECT ?this + WHERE { + ?this a rocrate:WorkflowRunAction ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus", + "http://schema.org/ActiveActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "StartTime" ; + sh:path schema:startTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Info ; + sh:description ( + "The workflow execution object MAY have a startTime if actionStatus is " + "either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." + ) ; + sh:message "The workflow execution object MAY have a startTime if actionStatus is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." ; + ] . diff --git a/rocrate_validator/profiles/five-safes-crate/12_check_phase.ttl b/rocrate_validator/profiles/five-safes-crate/12_check_phase.ttl new file mode 100644 index 000000000..fc4d100f5 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/12_check_phase.ttl @@ -0,0 +1,299 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:CheckValueObjectHasDescriptiveNameAndIsAssessAction + a sh:NodeShape ; + sh:name "CheckValue" ; + sh:description "" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:CheckValue . + } + """ ; + ] ; + + sh:property [ + sh:path rdf:type ; + sh:minCount 1 ; + sh:hasValue schema:AssessAction; + sh:severity sh:Violation ; + sh:message "CheckValue MUST be a `AssessAction`." ; + ] ; + + sh:property [ + sh:a sh:PropertyShape ; + sh:name "name" ; + sh:description "CheckValue MUST have a human readable name string." ; + sh:path schema:name ; + sh:datatype xsd:string ; + sh:severity sh:Violation ; + sh:message "CheckValue MUST have a human readable name string." ; + ] . + +five-safes-crate:CheckValueActionStatusMustHaveAllowedValues + a sh:NodeShape ; + sh:name "CheckValue" ; + sh:description "" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:CheckValue ; + schema:actionStatus ?status . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "ActionStatus" ; + sh:path schema:actionStatus ; + sh:in ( + "http://schema.org/PotentialActionStatus" + "http://schema.org/ActiveActionStatus" + "http://schema.org/CompletedActionStatus" + "http://schema.org/FailedActionStatus" + ) ; + sh:severity sh:Violation ; + sh:message "`CheckValue` --> `actionStatus` MUST have one of the allowed values." ; + ] . + + +#=== SHOULD shapes ===# + +five-safes-crate:RootDataEntityShouldMentionCheckValueObject + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:description "" ; + sh:severity sh:Warning ; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "mentions" ; + sh:description "RootDataEntity SHOULD mention a check value object." ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT $this + WHERE { + FILTER NOT EXISTS{ + $this schema:mentions ?action . + ?action schema:additionalType shp:CheckValue . + } + } + """ ; + sh:message "RootDataEntity SHOULD mention a check value object." ; + ] . + + +five-safes-crate:CheckValueObjectShouldPointToRootDataEntity + a sh:NodeShape ; + sh:name "CheckValue" ; + sh:description "" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:CheckValue . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "object" ; + sh:path schema:object ; + sh:minCount 1 ; + sh:class ro-crate:RootDataEntity ; + sh:severity sh:Warning ; + sh:message "`CheckValue` --> `object` SHOULD point to the root of the RO-Crate" ; + ] . + + +five-safes-crate:CheckValueInstrumentShouldPointToEntityTypedDefinedTerm + a sh:NodeShape ; + sh:name "CheckValue" ; + sh:description "" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:CheckValue . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "instrument" ; + sh:path schema:instrument ; + sh:minCount 1 ; + sh:class schema:DefinedTerm ; + sh:severity sh:Warning ; + sh:message "`CheckValue` --> `instrument` SHOULD point to an entity typed `DefinedTerm`" ; + ] . + + +five-safes-crate:CheckValueAgentShouldIdentifyTheAgentWhoPerformnedTheCheck + a sh:NodeShape ; + sh:name "CheckValue" ; + sh:description "" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:CheckValue . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "agent" ; + sh:path schema:agent ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:severity sh:Warning ; + sh:message "`CheckValue` --> `agent` SHOULD reference the agent who initiated the check" ; + ] . + + +five-safes-crate:CheckValueShouldHaveEndTime + a sh:NodeShape ; + sh:name "CheckValue" ; + sh:description "" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:CheckValue ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "EndTime" ; + sh:path schema:endTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Warning ; + sh:message "`CheckValue` SHOULD have the `endTime` property." ; + ] . + + +five-safes-crate:CheckValueShouldHaveActionStatus + a sh:NodeShape ; + sh:name "CheckValue" ; + sh:description "" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:CheckValue . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "ActionStatus" ; + sh:path schema:actionStatus ; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message "CheckValue SHOULD have actionStatus property." ; + ] . + + +#=== MAY shapes ===# + +five-safes-crate:CheckValueMayHaveStartTime + a sh:NodeShape ; + sh:name "CheckValue" ; + sh:description "" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:CheckValue ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus", + "http://schema.org/ActiveActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "StartTime" ; + sh:path schema:startTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Info ; + sh:message "`CheckValue` MAY have the `startTime` property." ; + ] . diff --git a/rocrate_validator/profiles/five-safes-crate/13_validation_phase.ttl b/rocrate_validator/profiles/five-safes-crate/13_validation_phase.ttl new file mode 100644 index 000000000..e9d25d46b --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/13_validation_phase.ttl @@ -0,0 +1,308 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:ValidationCheckObjectHasDescriptiveNameAndIsAssessAction + a sh:NodeShape ; + sh:name "ValidationCheck" ; + sh:description "" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:ValidationCheck . + } + """ ; + ] ; + + sh:property [ + sh:path rdf:type ; + sh:minCount 1 ; + sh:hasValue schema:AssessAction; + sh:severity sh:Violation ; + sh:message "ValidationCheck MUST be a `AssessAction`." ; + ] ; + + sh:property [ + sh:a sh:PropertyShape ; + sh:name "name" ; + sh:description "ValidationCheck MUST have a human readable name string." ; + sh:path schema:name ; + sh:datatype xsd:string ; + sh:severity sh:Violation ; + sh:message "ValidationCheck MUST have a human readable name string." ; + ] . + + +five-safes-crate:ValidationCheckActionStatusMustHaveAllowedValue + a sh:NodeShape ; + sh:name "ValidationCheck" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:ValidationCheck ; + schema:actionStatus ?status . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "actionStatus" ; + sh:description "The `actionStatus` of ValidationCheck MUST have an allowed value (see https://schema.org/ActionStatusType)." ; + sh:path schema:actionStatus ; + sh:in ( + "http://schema.org/PotentialActionStatus" + "http://schema.org/ActiveActionStatus" + "http://schema.org/CompletedActionStatus" + "http://schema.org/FailedActionStatus" + ) ; + sh:severity sh:Violation ; + sh:message "The `actionStatus` of ValidationCheck MUST have an allowed value (see https://schema.org/ActionStatusType)." ; + ] . + + +five-safes-crate:ValidationCheckActionStatusMustHaveAllowedValue + a sh:NodeShape ; + sh:name "ValidationCheck" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:ValidationCheck . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:minCount 1 ; + sh:name "actionStatus" ; + sh:description "actionStatus MUST be either PotentialActionStatus, ActiveActionStatus, CompletedActionStatus, or FailedActionStatus." ; + sh:path schema:actionStatus ; + sh:in ( + "http://schema.org/PotentialActionStatus" + "http://schema.org/ActiveActionStatus" + "http://schema.org/CompletedActionStatus" + "http://schema.org/FailedActionStatus" + ) ; + sh:severity sh:Violation ; + sh:message "actionStatus MUST be either PotentialActionStatus, ActiveActionStatus, CompletedActionStatus, or FailedActionStatus." ; + ] . + + +#=== SHOULD shapes ===# + +five-safes-crate:RootDataEntityShouldMentionValidationCheckObject + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:description "" ; + sh:severity sh:Warning ; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "mentions" ; + sh:description "RootDataEntity SHOULD mention a ValidationCheck object." ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT $this + WHERE { + FILTER NOT EXISTS{ + $this schema:mentions ?action . + ?action schema:additionalType shp:ValidationCheck . + } + } + """ ; + sh:message "RootDataEntity SHOULD mention a ValidationCheck object." ; + ] . + + +five-safes-crate:ValidationCheckObjectShouldPointToRootDataEntity + a sh:NodeShape ; + sh:name "ValidationCheck" ; + sh:description "" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:ValidationCheck . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "object" ; + sh:path schema:object ; + sh:minCount 1 ; + sh:class ro-crate:RootDataEntity ; + sh:severity sh:Warning ; + sh:message "`ValidationCheck` --> `object` SHOULD point to the root of the RO-Crate" ; + ] . + +five-safes-crate:ValidationCheckInstrumentShouldPointToEntityWithSpecificId + a sh:NodeShape ; + sh:name "ValidationCheck" ; + sh:description "" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:ValidationCheck . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "instrument" ; + sh:description "" ; + sh:path schema:instrument ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:hasValue ; + sh:severity sh:Warning ; + sh:message "`ValidationCheck` --> `instrument` SHOULD point to an entity with @id https://w3id.org/5s-crate/0.4" ; + ] . + + +five-safes-crate:ValidationCheckShouldHaveActionStatus + a sh:NodeShape ; + sh:name "ValidationCheck" ; + sh:description "" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:ValidationCheck . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "ActionStatus" ; + sh:path schema:actionStatus ; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message "ValidationCheck SHOULD have actionStatus property." ; + ] . + + +five-safes-crate:DownloadActionShouldHaveEndTimeIfBegun + a sh:NodeShape ; + sh:name "ValidationCheck" ; + sh:description "" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:ValidationCheck ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "EndTime" ; + sh:path schema:endTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Warning ; + sh:description "ValidationCheck SHOULD have the `endTime` property if `actionStatus` is either CompletedActionStatus or FailedActionStatus." ; + sh:message "ValidationCheck SHOULD have the `endTime` property if `actionStatus` is either CompletedActionStatus or FailedActionStatus." ; + ] . + + + #=== MAY shapes ===# + + five-safes-crate:DownloadActionMayHaveStartTimeIfBegun + a sh:NodeShape ; + sh:name "ValidationCheck" ; + sh:description "" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:ValidationCheck ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus", + "http://schema.org/ActiveActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "StartTime" ; + sh:path schema:startTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Info ; + sh:description "ValidationCheck MAY have the `startTime` property if `actionStatus` is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." ; + sh:message "ValidationCheck MAY have the `startTime` property if `actionStatus` is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." ; + ] . diff --git a/rocrate_validator/profiles/five-safes-crate/14_workflow_retrieval_phase.ttl b/rocrate_validator/profiles/five-safes-crate/14_workflow_retrieval_phase.ttl new file mode 100644 index 000000000..df7ad33e8 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/14_workflow_retrieval_phase.ttl @@ -0,0 +1,344 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:DownloadActionObjectMUSTHavesDescriptiveName + a sh:NodeShape ; + sh:name "DownloadAction" ; + sh:targetClass schema:DownloadAction ; + sh:description "" ; + + sh:property [ + a sh:PropertyShape ; + sh:name "name" ; + sh:description "DownloadAction MUST have a human readable name string." ; + sh:path schema:name ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:datatype xsd:string ; + sh:severity sh:Violation ; + sh:message "DownloadAction MUST have a human readable name string." ; + ] . + + + +five-safes-crate:WorkflowSameAsAndRootDataEntityMainEntityMUSTBeTheSame + a sh:NodeShape ; + sh:name "Downloaded Workflow" ; + sh:description "" ; + sh:severity sh:Violation ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT ?this + WHERE { + ?this rdf:type schema:Dataset . + ?s rdf:type schema:DownloadAction ; + schema:result ?this . + } + """ ; + ]; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT $this + WHERE { + FILTER NOT EXISTS { + $this schema:sameAs ?o . + ?s schema:mainEntity ?o . + # ?o rdf:type schema:Dataset . + } + } + """ ; + + sh:description "The property `sameAs` of the entity representing the downloaded workflow MUST point to the same entity as `RootDataEntity` --> `mainEntity`." ; + sh:message "The property `sameAs` of the entity representing the downloaded workflow MUST point to the same entity as `RootDataEntity` --> `mainEntity`." ; + ] . + + +five-safes-crate:DownloadedWorkflowDistributionAndDownloadActionObjectMUSTBeTheSame + a sh:NodeShape ; + sh:name "Downloaded Workflow" ; + sh:description "" ; + sh:severity sh:Violation ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT ?this + WHERE { + ?this rdf:type schema:Dataset . + ?s rdf:type schema:DownloadAction ; + schema:result ?this . + } + """ ; + ]; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "" ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT $this + WHERE { + ?action rdf:type schema:DownloadAction . + FILTER NOT EXISTS { + $this schema:distribution ?url . + ?action schema:object ?url . + } + } + """ ; + sh:message "DownloadedWorkflow --> `distribution` MUST reference the same entity as `DownloadAction` --> `object`." ; + ] . + + +five-safes-crate:DownloadActionActionStatusMUSTHaveAllowedValues + a sh:NodeShape ; + sh:name "DownloadAction" ; + sh:description "" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT ?this + WHERE { + ?this rdf:type schema:DownloadAction ; + schema:actionStatus ?status . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "ActionStatus" ; + sh:path schema:actionStatus ; + sh:in ( + "http://schema.org/PotentialActionStatus" + "http://schema.org/ActiveActionStatus" + "http://schema.org/CompletedActionStatus" + "http://schema.org/FailedActionStatus" + ) ; + sh:severity sh:Violation ; + sh:message "The value of actionStatus MUST be one of the allowed values: PotentialActionStatus; ActiveActionStatus; CompletedActionStatus; FailedActionStatus." ; + ] . + + +#=== SHOULD shapes ===# + +five-safes-crate:DownloadActionEntitySHOULDExist + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:description "" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:severity sh:Warning ; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "" ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT $this + WHERE { + FILTER NOT EXISTS { + ?s rdf:type schema:DownloadAction . + } + } + """ ; + sh:message "An entity typed DownloadAction SHOULD exist." ; + ] . + + + +five-safes-crate:RootDataEntitySHOULDMentionDownloadActionIfPresent + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:description "" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:severity sh:Warning ; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "" ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT $this ?da + WHERE { + ?da rdf:type schema:DownloadAction . + FILTER NOT EXISTS { + $this schema:mentions ?da . + } + } + """ ; + sh:message "RootDataEntity SHOULD mention DownloadAction if this exists." ; + ] . + + +five-safes-crate:DownloadActionShouldHaveEndTimeIfEnded + a sh:NodeShape ; + sh:name "DownloadAction" ; + sh:description "" ; + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT ?this + WHERE { + ?this rdf:type schema:DownloadAction ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "EndTime" ; + sh:path schema:endTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Warning ; + sh:message "`DownloadAction` SHOULD have the `endTime` property if `actionStatus` is either CompletedActionStatus or FailedActionStatus." ; + ] . + + +five-safes-crate:DownloadActionShouldHaveActionStatus + a sh:NodeShape ; + sh:name "DownloadAction" ; + sh:targetClass schema:DownloadAction ; + sh:description "" ; + + sh:property [ + a sh:PropertyShape ; + sh:name "ActionStatus" ; + sh:path schema:actionStatus ; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message "`DownloadAction` SHOULD have `actionStatus` property." ; + ] . + + +#=== MAY shapes ===# + +five-safes-crate:DownloadedWorkflowSHOULDExistAndBeReferencedByDownloadActionResult + a sh:NodeShape ; + sh:name "DownloadAction" ; + sh:description "Validates that DownloadAction result references an existing entity" ; + sh:targetClass schema:DownloadAction ; + + sh:property [ + a sh:PropertyShape ; + sh:name "Result" ; + sh:description "The result property must reference an existing entity in the RO-Crate" ; + sh:path schema:result ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:sparql [ + a sh:SPARQLConstraint ; + sh:select """ + PREFIX rdf: + PREFIX schema: + + SELECT $this $value + WHERE { + $this schema:result $value . + + # Entity must have BOTH type AND name (proper definition) + FILTER NOT EXISTS { + $value rdf:type schema:Dataset . + } + } + """ ; + ] ; + sh:severity sh:Info ; + sh:message "The entity representing the downloaded workflow is not defined, OR is not referenced by `DownloadAction` --> `result`, OR is not of type `Dataset`." ; + ] . + + +five-safes-crate:DownloadActionMayHaveStartTimeIfBegun + a sh:NodeShape ; + sh:name "DownloadAction" ; + sh:description ( + "`DownloadAction` MAY have the `startTime` property if `actionStatus` " + "is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." + ); + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT ?this + WHERE { + ?this rdf:type schema:DownloadAction ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus", + "http://schema.org/ActiveActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "StartTime" ; + sh:path schema:startTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:description ( + "`DownloadAction` MAY have the `startTime` property if `actionStatus` " + "is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." + ); + sh:severity sh:Info ; + sh:message "`DownloadAction` MAY have the `startTime` property if `actionStatus` is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." ; + ] . diff --git a/rocrate_validator/profiles/five-safes-crate/15_metadata_file.py b/rocrate_validator/profiles/five-safes-crate/15_metadata_file.py new file mode 100644 index 000000000..b589d332f --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/15_metadata_file.py @@ -0,0 +1,64 @@ +# 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 re + +import rocrate_validator.utils.log as logging +from rocrate_validator.models import Severity, ValidationContext +from rocrate_validator.requirements.python import PyFunctionCheck, check, requirement + +# set up logging +logger = logging.getLogger(__name__) + + +@requirement(name="RO-Crate context version") +class FileDescriptorContextVersion(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( + "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/15_metadata_file.ttl b/rocrate_validator/profiles/five-safes-crate/15_metadata_file.ttl new file mode 100644 index 000000000..72f5e020e --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/15_metadata_file.ttl @@ -0,0 +1,53 @@ +# 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: . + + + +#=== MUST shapes ===# + +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" ; + ] . + +ro-crate:conformsToROCrateSpec sh:deactivated true . + + +#=== SHOULD shapes ===# +# (none) + + +#=== MAY shapes ===# +# (none) \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/16_publishing_phase.ttl b/rocrate_validator/profiles/five-safes-crate/16_publishing_phase.ttl new file mode 100644 index 000000000..c56e67b72 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/16_publishing_phase.ttl @@ -0,0 +1,51 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:AllAssessActionsMentioned + a sh:NodeShape ; + sh:name "All AssessActions are mentioned from Root Data Entity" ; + sh:description "All AssessAction entities in the crate MUST be referenced from the Root Dataset via `mentions`." ; + sh:targetClass schema:AssessAction; + + sh:property [ + a sh:PropertyShape ; + sh:name "AssessAction mentions from RDE" ; + sh:description "All AssessAction entities in the crate MUST be referenced from the Root Dataset via `mentions`." ; + sh:path [ sh:inversePath schema:mentions ] ; + sh:node ro-crate:RootDataEntity ; + sh:minCount 1 ; + sh:severity sh:Violation ; + sh:message "All AssessAction entities in the crate MUST be referenced from the Root Dataset via `mentions`." ; + ] . + + +#=== SHOULD shapes ===# +# (none) + + +#=== MAY shapes ===# +# (none) \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/1_requesting_agent.ttl b/rocrate_validator/profiles/five-safes-crate/1_requesting_agent.ttl new file mode 100644 index 000000000..812691666 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/1_requesting_agent.ttl @@ -0,0 +1,114 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:AgentIsMemberOf + a sh:NodeShape ; + sh:name "Requesting Agent" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT DISTINCT ?this WHERE { + ?action a schema:CreateAction ; + schema:agent ?this . + } + """ + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "memberOf" ; + sh:path schema:memberOf; + sh:class schema:Project ; + sh:severity sh:Violation ; + sh:message """The 'memberOf' property of an agent MUST be of type Project.""" ; + ] . + + +five-safes-crate:AgentProjectIntersection + a sh:NodeShape ; + sh:name "Agent Project Intersection" ; + sh:description """At least one Project referenced by Agent -> memberOf MUST be included in the set of Projects referenced by RootDataEntity -> sourceOrganization.""" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT DISTINCT ?this WHERE { + ?action a schema:CreateAction ; + schema:agent ?this . + } + """ + ] ; + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "Agent Project Intersection" ; + sh:description """At least one Project referenced by Agent -> memberOf MUST be included in the set of Projects referenced by RootDataEntity -> sourceOrganization.""" ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT $this WHERE { + FILTER EXISTS { + $this schema:memberOf ?anyProject . + } + FILTER NOT EXISTS { + $this schema:memberOf ?commonProject . + ?metadata schema:about ?root . + ?root schema:sourceOrganization ?commonProject . + } + } + """ ; + ] ; + sh:severity sh:Violation ; + sh:message """At least one Project referenced by Agent -> memberOf MUST be included in the set of Projects referenced by RootDataEntity -> sourceOrganization.""" . + + +#=== SHOULD shapes ===# + +five-safes-crate:AgentIsMemberOf + a sh:NodeShape ; + sh:name "Requesting Agent" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT DISTINCT ?this WHERE { + ?action a rocrate:WorkflowRunAction ; + schema:agent ?this . + } + """ + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "memberOf" ; + sh:path schema:memberOf; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message """The Requesting Agent SHOULD have a `memberOf` property.""" ; + ] . + + + #=== MAY shapes ===# + # (none) \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/1_responsible_project.ttl b/rocrate_validator/profiles/five-safes-crate/1_responsible_project.ttl new file mode 100644 index 000000000..7be332d23 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/1_responsible_project.ttl @@ -0,0 +1,139 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:ResponsibleProject + a sh:NodeShape ; + sh:name "Responsible Project" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT DISTINCT ?this WHERE { + ?action a schema:CreateAction ; + schema:agent ?agent . + ?agent schema:memberOf ?this . + } + """ + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "funding" ; + sh:path schema:funding; + sh:class schema:Grant ; + sh:severity sh:Violation ; + sh:message """The property 'funding' of the Responsible Project MUST be of type Grant.""" ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "member" ; + sh:path schema:member; + sh:or ( + [ sh:class schema:Organization ] + [ sh:class schema:Person ] + ) ; + sh:severity sh:Violation ; + sh:message """The property 'member' of the Responsible Project MUST be of type schema:Organization or Person.""" ; + ] . + + +#=== SHOULD shapes ===# + +five-safes-crate:ResponsibleProjectMemberAndSourceOrganizationIntersection + a sh:NodeShape ; + sh:name "Organizations (members of Responsible Project)" ; + sh:description """At least one of the organisations that are members of the responsible project SHOULD be included in the Requesting Agent's affiliations, if such properties exist.""" ; + sh:severity sh:Warning ; + + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT DISTINCT ?this WHERE { + ?action a ro-crate:WorkflowRunAction ; + schema:agent ?this . + ?this a schema:Person ; + schema:memberOf ?project ; + schema:affiliation ?someAffiliation . + ?project schema:member ?org2 . + } + """ + ] ; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "Intersection with agent affiliations" ; + sh:description """At least one of the organisations that are members of the responsible project SHOULD be included in the Requesting Agent's affiliations, if such properties exist.""" ; + + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT $this WHERE { + FILTER NOT EXISTS { + $this schema:affiliation ?org . + $this schema:memberOf ?project . + ?project schema:member ?org . + } + } + """ ; + sh:message """At least one of the organisations that are members of the responsible project SHOULD be included in the Requesting Agent's affiliations, if such properties exist.""" ; + ] . + + +#=== MAY shapes ===# + +five-safes-crate:ResponsibleProject + a sh:NodeShape ; + sh:name "Responsible Project" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT DISTINCT ?this WHERE { + ?action a ro-crate:WorkflowRunAction ; + schema:agent ?agent . + ?agent schema:memberOf ?this . + } + """ + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "funding" ; + sh:path schema:funding; + sh:minCount 1 ; + sh:severity sh:Info ; + sh:message """The Responsible Project does not have the property `funding`.""" ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "member" ; + sh:path schema:member; + sh:minCount 1 ; + sh:severity sh:Info ; + sh:message """The Responsible Project does not have the property `member`.""" ; + ] . \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/1_root_data_entity.ttl b/rocrate_validator/profiles/five-safes-crate/1_root_data_entity.ttl new file mode 100644 index 000000000..b5481fd12 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/1_root_data_entity.ttl @@ -0,0 +1,56 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:RootDataEntityRequiredProperties + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:targetClass ro-crate:RootDataEntity ; + + sh:property [ + a sh:PropertyShape ; + sh:name "sourceOrganization" ; + sh:path schema:sourceOrganization; + sh:minCount 1 ; + sh:severity sh:Violation ; + sh:message """The Root Data Entity MUST have a `sourceOrganization` property.""" ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "sourceOrganization" ; + sh:path schema:sourceOrganization ; + sh:class schema:Project ; + sh:severity sh:Violation ; + sh:message """The `sourceOrganization` property of the RootDataEntity MUST point to a Project entity.""" ; + ] . + + +#=== SHOULD shapes ===# +# (none) + + +#=== MAY shapes ===# +# (none) diff --git a/rocrate_validator/profiles/five-safes-crate/2_requesting_agent.ttl b/rocrate_validator/profiles/five-safes-crate/2_requesting_agent.ttl new file mode 100644 index 000000000..49e5ba3f1 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/2_requesting_agent.ttl @@ -0,0 +1,97 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:CreateActionHasAgent + a sh:NodeShape ; + sh:name "CreateAction" ; + sh:targetClass schema:CreateAction ; + sh:description "Checks that a CreateAction has an agent and that each agent is a Person." ; + + # CreateAction entity MUST have an agent (IRI) + sh:property [ + a sh:PropertyShape ; + sh:name "Has Agent" ; + sh:path schema:agent ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:severity sh:Violation ; + sh:message "CreateAction MUST have at least one agent that is a contextual entity." ; + ] ; + + # The agent of a CreateAction entity MUST be a Person + sh:property [ + a sh:PropertyShape ; + sh:name "Agent is a Person" ; + sh:path schema:agent ; + sh:nodeKind sh:IRI ; + sh:class schema:Person ; + sh:severity sh:Violation ; + sh:message "Each CreateAction agent MUST be typed as Person." ; + ] ; + + # If any agent affiliation exists, it MUST be an Organization (IRI) + sh:property [ + a sh:PropertyShape ; + sh:name "Affiliation is an Organization" ; + sh:path ( schema:agent schema:affiliation ) ; + sh:class schema:Organization ; + sh:nodeKind sh:IRI ; + sh:severity sh:Violation ; + sh:message "The affiliation of a CreateAction's agent MUST be a contextual entity with type Organization." ; + ] . + + +#=== SHOULD shapes ===# + +# Person who is the agent of a WorkflowRunAction SHOULD have an affiliation +five-safes-crate:PersonAgentHasAffiliation + a sh:NodeShape ; + sh:name "Agent of WorkflowRunAction" ; + sh:description "The agent of a WorkflowRunAction entity" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT DISTINCT ?this WHERE { + ?action a ro-crate:WorkflowRunAction ; + schema:agent ?this . + } + """ + ] ; + + # The agent of the `CreateAction` corresponding to the workflowrunAction SHOULD have an affiliation + sh:property [ + a sh:PropertyShape ; + sh:name "Presence of affiliations" ; + sh:path schema:affiliation ; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message "The agent of the `CreateAction` corresponding to the workflow run SHOULD have an affiliation" ; + ] . + + +# === MAY shapes ===# +# (none) \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/3_timestamp_format.ttl b/rocrate_validator/profiles/five-safes-crate/3_timestamp_format.ttl new file mode 100644 index 000000000..64055a033 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/3_timestamp_format.ttl @@ -0,0 +1,64 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +# to ensure the entity id will be included in any error message, +# target all entities which have startTime and/or endTime properties using sh:targetSubjectsOf, +# then we use sh:property to validate the values of those properties. +# the properties are listed individually so that the property id appears in any error message too +five-safes-crate:TimeStampFormat + a sh:NodeShape ; + sh:name "Timestamp Format" ; + sh:description "Timestamps MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ; + sh:targetSubjectsOf schema:startTime, schema:endTime; + sh:property [ + a sh:PropertyShape ; + sh:name "End TimeStamp" ; + sh:description "End timestamps MUST follow the RFC 3339 standard." ; + sh:path schema:endTime ; + sh:minCount 0 ; + sh:pattern "^[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-9]{2}([.|,][0-9]+)?(Z|z|[+-][0-9]{2}:[0-9]{2})$" ; + sh:severity sh:Violation ; + sh:message "All `startTime` and `endTime` values MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Start TimeStamp" ; + sh:description "Start timestamps MUST follow the RFC 3339 standard." ; + sh:path schema:startTime ; + sh:minCount 0 ; + sh:pattern "^[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-9]{2}([.|,][0-9]+)?(Z|z|[+-][0-9]{2}:[0-9]{2})$" ; + sh:severity sh:Violation ; + sh:message "All `startTime` and `endTime` values MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ; + ] . + + +#=== SHOULD shapes ===# +# (none) + + +#=== MAY shapes ===# +# (none) \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/4_sign_off.ttl b/rocrate_validator/profiles/five-safes-crate/4_sign_off.ttl new file mode 100644 index 000000000..0c91901e4 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/4_sign_off.ttl @@ -0,0 +1,295 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:SignOffObjectActionAndName + a sh:NodeShape ; + sh:name "SignOff" ; + sh:description "Sign Off phase" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:SignOff . + } + """ ; + ] ; + + sh:property [ + sh:path schema:name ; + sh:datatype xsd:string ; + sh:minCount 1 ; + sh:severity sh:Violation ; + sh:message "Sign Off phase MUST have a human-readable name string." ; + ] ; + + sh:property [ + sh:path rdf:type ; + sh:minCount 1 ; + sh:hasValue schema:AssessAction; + sh:severity sh:Violation ; + sh:message "Sign Off phase MUST be a `AssessAction`." ; + ] . + +five-safes-crate:SignOffObjectHasActionStatus + a sh:NodeShape ; + sh:name "SignOffStatus" ; + sh:description "Sign Off Phase Action Status" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:SignOff ; + schema:actionStatus ?status . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "actionStatus" ; + sh:description "The value of actionStatus MUST be one of the allowed values." ; + sh:path schema:actionStatus ; + sh:in ( + "http://schema.org/PotentialActionStatus" + "http://schema.org/ActiveActionStatus" + "http://schema.org/CompletedActionStatus" + "http://schema.org/FailedActionStatus" + ) ; + sh:severity sh:Violation ; + sh:message "The value of actionStatus MUST be one of the allowed values: PotentialActionStatus; ActiveActionStatus; CompletedActionStatus; FailedActionStatus." ; + ] . + + +#=== SHOULD shapes ===# + +# There SHOULD be a Sign-Off Phase +five-safes-crate:SignOffPhase + a sh:NodeShape ; + sh:name "SignOffPhase" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:description "Check the Sign-Off Phase" ; + sh:severity sh:Warning ; + + sh:sparql [ + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT $this + WHERE { + FILTER NOT EXISTS { + ?action schema:additionalType shp:SignOff . + } + } + """ ; + sh:message "There SHOULD be a Sign-Off Phase in the Final RO-Crate" ; + ] ; + + sh:sparql [ + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT $this + WHERE { + ?action schema:additionalType shp:SignOff . + FILTER NOT EXISTS { + $this schema:mentions ?action . + } + } + """ ; + sh:message "The Root Data Entity SHOULD mention a Sign-Off Phase Object" ; + ] . + + +five-safes-crate:SignOffPhaseProperties + a sh:NodeShape ; + sh:name "SignOffPhaseProperties" ; + sh:description "Check Sign-Off Phase Properties" ; + sh:severity sh:Warning ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:SignOff . + } + """ + ] ; + + sh:property [ + sh:description "Check if the Sign Off phase has an actionStatus" ; + sh:path schema:actionStatus ; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message "The Sign-Off Phase SHOULD have an actionStatus" ; + ] ; + + sh:property [ + sh:description "Check if the Sign Off phase has an agent" ; + sh:path schema:agent ; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message "The Sign-Off Phase SHOULD have an agent" ; + ] ; + + sh:property [ + sh:description "Check if the Sign Off phase has an instrument (TRE Policy)" ; + sh:path schema:instrument ; + sh:class schema:CreativeWork ; + sh:nodeKind sh:IRI; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message "The Sign-Off Phase SHOULD have an TRE policy (instrument) with type CreativeWork" ; + ] ; + + sh:property [ + sh:description "Check if the Sign Off phase has an instrument (TRE Policy)" ; + sh:path ( schema:instrument schema:name ) ; + sh:datatype xsd:string ; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message "The Sign-Off Phase SHOULD have an TRE policy (instrument) with a human-readable name" ; + ] ; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:description "Check if the Sign Off phase lists the workflow as an object" ; + sh:select """ + PREFIX schema: + PREFIX rocrate: + SELECT $this + WHERE { + ?root a schema:Dataset ; + schema:mainEntity ?mainEntity ; + rdf:type rocrate:RootDataEntity . + FILTER NOT EXISTS { + $this schema:object ?mainEntity . + } + } + """ ; + sh:message "The Sign-Off Phase SHOULD list the workflow (mainEntity) as an object" ; + ]; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:description "Check if the Sign Off phase lists the Responsible Project as an object" ; + sh:select """ + PREFIX schema: + PREFIX rocrate: + SELECT $this + WHERE { + ?root a schema:Dataset ; + rdf:type rocrate:RootDataEntity ; + schema:sourceOrganization ?sourceOrg . + FILTER NOT EXISTS { + $this schema:object ?sourceOrg . + } + } + """ ; + sh:message "The Sign-Off Phase SHOULD list the Responsible Project (sourceOrganization) as an object" ; + ]. + + +five-safes-crate:SignOffPhaseEndTime + a sh:NodeShape ; + sh:name "SignOffPhaseEndTime" ; + sh:description "Sign Off end time check" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:SignOff ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "EndTime" ; + sh:path schema:endTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Warning ; + sh:description "Sign Off object SHOULD have endTime property if action completed or failed." ; + sh:message "Sign Off object SHOULD have endTime property if action completed or failed." ; + ] . + + +#=== MAY shapes ===# + +five-safes-crate:SignOffPhaseStartTime + a sh:NodeShape ; + sh:name "SignOffPhaseStartTime" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT ?this + WHERE { + ?this schema:additionalType shp:SignOff ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/ActiveActionStatus", + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "StartTime" ; + sh:path schema:startTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:pattern "^[0-9]{4}-[0-9]{2}-[0-9]{2}[Tt][0-9]{2}:[0-9]{2}:[0-9]{2}([.|,][0-9]+)?(Z|z|[+-][0-9]{2}:[0-9]{2})$" ; + sh:severity sh:Info ; + sh:description "Sign Off object MAY have a startTime property if action is active, completed or failed." ; + sh:message "Sign Off object MAY have a startTime property if action is active, completed or failed." ; + ] . diff --git a/rocrate_validator/profiles/five-safes-crate/5_profile_conformance.ttl b/rocrate_validator/profiles/five-safes-crate/5_profile_conformance.ttl new file mode 100644 index 000000000..069d390ed --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/5_profile_conformance.ttl @@ -0,0 +1,89 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix dct: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . +@prefix shp: . + + +#=== MUST shapes ===# +# (none) + + +#=== SHOULD shapes ===# + +# Root Dataset SHOULD declare conformsTo Five Safes profile +five-safes-crate:RootDatasetConformsToFiveSafes + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:targetClass ro-crate:RootDataEntity ; + + sh:property [ + a sh:PropertyShape ; + sh:name "conformsTo Five Safes profile" ; + sh:path dct:conformsTo ; + sh:hasValue ; + sh:severity sh:Warning ; + sh:message "Root Dataset SHOULD include `conformsTo` https://w3id.org/5s-crate/0.4" ; + ] . + +five-safes-crate:RootDatasetDatePublishedWhenPublished + a sh:NodeShape ; + sh:name "datePublished present on published crates" ; + sh:description "If the root dataset is published (has schema:publisher), it SHOULD have schema:datePublished." ; + sh:targetClass ro-crate:RootDataEntity ; + sh:severity sh:Warning ; + sh:message "A crate SHOULD have a publishedDate if and only if it has a publisher." ; + sh:xone ( + # datePublished SHOULD be present if and only if the publisher is specified: + [ sh:not [ sh:property [ + sh:path schema:publisher ; + sh:minCount 1 ; + ]]] + [ sh:property [ + sh:path schema:datePublished ; + sh:minCount 1 ; + ]] + ) . + +five-safes-crate:RootDatasetLicenseWhenPublished + a sh:NodeShape ; + sh:name "License present on published crates" ; + sh:description "If the root dataset is published (has schema:publisher), it SHOULD declare a license." ; + sh:targetClass ro-crate:RootDataEntity ; + sh:severity sh:Warning ; + sh:message "Profile Conformance: Published crates SHOULD include a license." ; + sh:or ( + # license not required if no publisher: + [ sh:not [ sh:property [ + sh:path schema:publisher ; + sh:minCount 1 ; + ]]] + # license required if publisher present: + [ sh:property [ + sh:path schema:license ; + sh:minCount 1 ; + ]] + ) . + + +#=== MAY shapes ===# +# (none) \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/6_workflow_reference.ttl b/rocrate_validator/profiles/five-safes-crate/6_workflow_reference.ttl new file mode 100644 index 000000000..222f2bc9b --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/6_workflow_reference.ttl @@ -0,0 +1,109 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:ReferenceToWorkflowCrate + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:targetClass ro-crate:RootDataEntity ; + + # RootDataEntity MUST have an mainEntity property + sh:property [ + a sh:PropertyShape ; + sh:name "mainEntity" ; + sh:path schema:mainEntity ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:nodeKind sh:IRI ; + sh:severity sh:Violation ; + sh:message "The RootDataEntity MUST have exactly one mainEntity property that is an IRI." ; + ] ; + + # The mainEntity of a RootDataEntity MUST be a Dataset + sh:property [ + a sh:PropertyShape ; + sh:name "mainEntity" ; + sh:path schema:mainEntity ; + sh:class schema:Dataset ; + sh:severity sh:Violation ; + sh:message "The mainEntity pointed to by the RootDataEntity MUST be of type Dataset" ; + ] . + +five-safes-crate:mainEntityHasProperConformsTo + a sh:NodeShape ; + sh:name "mainEntity" ; + sh:description "The mainEntity of the RootDataEntity MUST have a conformsTo property with an IRI starting with https://w3id.org/workflowhub/workflow-ro-crate" ; + sh:targetObjectsOf schema:mainEntity ; + sh:property [ + a sh:PropertyShape ; + sh:name "conformsTo" ; + sh:path purl:conformsTo ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Violation ; + sh:message "mainEntity MUST have one and only one `conformsTo` property." ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "conformsTo" ; + sh:path purl:conformsTo ; + sh:nodeKind sh:IRI ; + sh:pattern "^https://w3id\\.org/workflowhub/workflow-ro-crate" ; + sh:severity sh:Violation ; + sh:message "conformsTo IRI must start with https://w3id.org/workflowhub/workflow-ro-crate" ; + ] . + + +#=== SHOULD shapes ===# + +five-safes-crate:DatasetMustHaveDistributionIfURI + a sh:NodeShape ; + sh:name "mainEntity" ; + sh:targetObjectsOf schema:mainEntity ; + sh:description "If mainEntity has an HTTP(S) @id, it SHOULD have a distribution that is an HTTP(S) URL." ; + sh:severity sh:Warning ; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "distribution" ; + sh:description "If mainEntity has an HTTP(S) @id, it SHOULD have a distribution that is an HTTP(S) URL." ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT $this + WHERE { + FILTER (STRSTARTS(STR($this), "http://") || STRSTARTS(STR($this), "https://")) . + FILTER NOT EXISTS { + $this schema:distribution ?dist . + FILTER (STRSTARTS(STR(?dist), "http://") || STRSTARTS(STR(?dist), "https://")) . + } + } + """ ; + sh:message "If mainEntity has an HTTP(S) @id SHOULD have at least one distribution with an HTTP(S) URL." ; + ] . + + +#=== MAY shapes ===# +# (none) \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/7_requested_workflow_run.ttl b/rocrate_validator/profiles/five-safes-crate/7_requested_workflow_run.ttl new file mode 100644 index 000000000..9b66f1769 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/7_requested_workflow_run.ttl @@ -0,0 +1,108 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:RootDataEntityMentionsWorkflowRunAction + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:description "" ; + + sh:property [ + a sh:PropertyShape ; + sh:name "mentions" ; + sh:path schema:mentions; + sh:qualifiedValueShape [ + sh:class ro-crate:WorkflowRunAction ; + ] ; + sh:qualifiedMinCount 1 ; + sh:severity sh:Violation ; + sh:message "`RootDataEntity` MUST reference at least one `CreateAction` (corresponding to the workflow run) through `mentions`" ; + ] . + + +five-safes-crate:WorkflowRunActionExistence + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:description "" ; + sh:severity sh:Violation ; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "WorkflowRunAction" ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT $this + WHERE { + FILTER NOT EXISTS { + ?workflowRunAction a ro-crate:WorkflowRunAction . + } + } + """ ; + sh:message "The CreateAction entity corresponding to the workflow MUST reference, as an instrument, the entity that is referenced as mainEntity by the RO-Crate" ; + ] . + + +five-safes-crate:WorkflowRunObject + a sh:NodeShape ; + sh:name "WorkflowRunAction" ; + sh:targetClass ro-crate:WorkflowRunAction ; + sh:description "" ; + sh:severity sh:Violation ; # Apply to all property shapes / constraints below + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:name "object" ; + sh:select """ + SELECT $this ?object + WHERE { + $this schema:object ?object . + FILTER NOT EXISTS { ?object a ?type . } + } + """ ; + sh:message "In the `CreateAction` entity corresponding to the workflow run, each `object` MUST reference an existing entity." ; + ] . + + +#=== SHOULD shapes ===# + +# WorkflowRunAction SHOULD have object property with minimum cardinality 1 +five-safes-crate:WorkflowRunActionShouldHaveObjectProperty + a sh:NodeShape ; + sh:targetClass ro-crate:WorkflowRunAction ; + sh:name "WorkflowRunAction" ; + sh:property [ + sh:path schema:object ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:severity sh:Warning ; + sh:message "`CreateAction` (corresponding to the workflow run) SHOULD have the property `object` with IRI values." ; + ] . + + +#=== MAY shapes ===# +# (none) diff --git a/rocrate_validator/profiles/five-safes-crate/8_disclosure_phase.ttl b/rocrate_validator/profiles/five-safes-crate/8_disclosure_phase.ttl new file mode 100644 index 000000000..61b785a43 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/8_disclosure_phase.ttl @@ -0,0 +1,233 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# + +five-safes-crate:DisclosureObjectHasDescriptiveNameAndIsAssessAction + a sh:NodeShape ; + sh:name "DisclosureCheck" ; + sh:description "DisclosureCheck" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:DisclosureCheck . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "AssessAction" ; + sh:description "`DisclosureCheck` MUST be a `AssessAction`." ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:hasValue schema:AssessAction; + sh:severity sh:Violation ; + sh:message "`DisclosureCheck` MUST be a `AssessAction`." ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "name" ; + sh:description "`DisclosureCheck` MUST have a name string of at least 10 characters." ; + sh:minCount 1 ; + sh:path schema:name ; + sh:datatype xsd:string ; + sh:minLength 10 ; + sh:severity sh:Violation ; + sh:message "`DisclosureCheck` MUST have a name string of at least 10 characters." ; + ] . + + +five-safes-crate:DisclosureObjectHasActionStatusWithAcceptedValue + a sh:NodeShape ; + sh:name "DisclosureCheck" ; + sh:description "`DisclosureCheck` MUST have an actionStatus with an allowed value (see https://schema.org/ActionStatusType)." ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:DisclosureCheck ; + schema:actionStatus ?status . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "actionStatus" ; + sh:description "`DisclosureCheck` MUST have an actionStatus with an allowed value (see https://schema.org/ActionStatusType)." ; + sh:path schema:actionStatus ; + sh:in ( + "http://schema.org/PotentialActionStatus" + "http://schema.org/ActiveActionStatus" + "http://schema.org/CompletedActionStatus" + "http://schema.org/FailedActionStatus" + ) ; + sh:severity sh:Violation ; + sh:message "`DisclosureCheck` MUST have an actionStatus with an allowed value (see https://schema.org/ActionStatusType)." ; + ] . + + +#=== SHOULD shapes ===# + +five-safes-crate:RootDataEntityShouldMentionDisclosureObject + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:description "RootDataEntity SHOULD mention a disclosure object." ; + sh:severity sh:Warning ; + + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "mentions" ; + sh:description "`RootDataEntity` SHOULD mention a disclosure object." ; + sh:select """ + PREFIX schema: + PREFIX shp: + SELECT $this + WHERE { + FILTER NOT EXISTS{ + $this schema:mentions ?action . + ?action a schema:AssessAction ; + schema:additionalType shp:DisclosureCheck . + } + } + """ ; + sh:message "`RootDataEntity` SHOULD mention a disclosure object." ; + ] . + + +five-safes-crate:DisclosureObjectHasActionStatus + a sh:NodeShape ; + sh:name "DisclosureCheck" ; + sh:description "The `DisclosureCheck` SHOULD have `actionStatus` property." ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:DisclosureCheck . + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "ActionStatus" ; + sh:description "The `DisclosureCheck` SHOULD have `actionStatus` property." ; + sh:path schema:actionStatus ; + sh:minCount 1 ; + sh:severity sh:Warning ; + sh:message "The `DisclosureCheck` SHOULD have `actionStatus` property." ; + ] . + + +five-safes-crate:DisclosureObjectHasEndTimeIfcompletedOrFailed + a sh:NodeShape ; + sh:name "DisclosureCheck" ; + sh:description "DisclosureCheck" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:DisclosureCheck ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "EndTime" ; + sh:path schema:endTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Warning ; + sh:description "`DisclosureCheck` SHOULD have the `endTime` property if `actionStatus` is either CompletedActionStatus or FailedActionStatus." ; + sh:message "`DisclosureCheck` SHOULD have the `endTime` property if `actionStatus` is either CompletedActionStatus or FailedActionStatus." ; + ] . + + +#=== MAY shapes ===# + +five-safes-crate:DisclosureObjectHasStartTimeIfBegun + a sh:NodeShape ; + sh:name "DisclosureCheck" ; + sh:description "DisclosureCheck" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX shp: + + SELECT ?this + WHERE { + ?this schema:additionalType shp:DisclosureCheck ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus", + "http://schema.org/ActiveActionStatus" + )) + } + """ ; + ] ; + + sh:property [ + a sh:PropertyShape ; + sh:name "StartTime" ; + sh:path schema:startTime ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:severity sh:Info ; + sh:description "`DisclosureCheck` MAY have the `startTime` property if `actionStatus` is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." ; + sh:message "`DisclosureCheck` MAY have the `startTime` property if `actionStatus` is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." ; + ] . \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/9_inputs.ttl b/rocrate_validator/profiles/five-safes-crate/9_inputs.ttl new file mode 100644 index 000000000..e0e83c940 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/9_inputs.ttl @@ -0,0 +1,67 @@ +# 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: <./> . +@prefix ro-crate: . +@prefix five-safes-crate: . +@prefix rdf: . +@prefix schema: . +@prefix bioschemas: . +@prefix purl: . +@prefix sh: . +@prefix validator: . +@prefix xsd: . + + +#=== MUST shapes ===# +# (none) + + +#=== SHOULD shapes ===# + +five-safes-crate:InputEntityReferencesFormalParameterViaExampleOfWork + a sh:NodeShape ; + sh:name "Input" ; + sh:description "" ; + sh:severity sh:Warning ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this WHERE { + ?action a ro-crate:WorkflowRunAction ; + schema:object ?this . + } + """ + ] ; + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "exampleOfWork" ; + sh:description "Input SHOULD reference a FormalParameter using exampleOfWork" ; + + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT $this WHERE { + FILTER NOT EXISTS { + $this schema:exampleOfWork ?par . + ?par a bioschemas:FormalParameter . + } + } + """ ; + sh:message "Input SHOULD reference a FormalParameter using exampleOfWork" ; + ] . + + +#=== MAY shapes ===# +# (none) diff --git a/rocrate_validator/profiles/five-safes-crate/ontology.ttl b/rocrate_validator/profiles/five-safes-crate/ontology.ttl new file mode 100644 index 000000000..d37dfd45e --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/ontology.ttl @@ -0,0 +1,37 @@ +# Copyright (c) 2024-2026 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. + +@prefix ro: <./> . +@prefix owl: . +@prefix rdf: . +@prefix xml: . +@prefix xsd: . +@prefix rdfs: . +@prefix schema: . +@prefix rocrate: . +@prefix bioschemas: . +@prefix ro-crate: . +@prefix isa-ro-crate: . + + rdf:type owl:Ontology ; + owl:versionIRI . + +# # ################################################################# +# # # Classes +# # ################################################################# + +# Declare a WorkflowRunAction class +ro-crate:WorkflowRunAction rdf:type owl:Class ; + rdfs:subClassOf schema:CreateAction ; + rdfs:label "WorkflowRunAction"@en . diff --git a/rocrate_validator/profiles/five-safes-crate/profile.ttl b/rocrate_validator/profiles/five-safes-crate/profile.ttl new file mode 100644 index 000000000..28abe62c0 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/profile.ttl @@ -0,0 +1,83 @@ +# Copyright (c) 2024-2025 CRS4, 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 dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Five Safes RO-Crate 0.4" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Five Safes RO-Crate Metadata Specification 0.4"""@en ; + + # URI of the publisher of the Metadata Specification + dct:publisher ; + + # TODO: resolve failures when these profiles are applied + # This profile is an extension of Workflow Run Crate for use in Trusted Research Environments (TRE) + # prof:isProfileOf ; + + # This profile is a transitive profile of the RO-Crate Metadata Specification + prof:isTransitiveProfileOf ; + # TODO: resolve failures when these profiles are applied + # , + # ; + + # this profile has a JSON-LD context resource + prof:hasResource [ + a prof:ResourceDescriptor ; + + # it's in JSON-LD format + dct:format ; + + # it conforms to JSON-LD, here referred to by its namespace URI as a Profile + dct:conformsTo ; + + # this profile resource plays the role of "Vocabulary" + # described in this ontology's accompanying Roles vocabulary + prof:hasRole role:Vocabulary ; + + # this profile resource's actual file + prof:hasArtifact ; + ] ; + + # this profile has a human-readable documentation resource + prof:hasResource [ + a prof:ResourceDescriptor ; + + # it's in HTML format + dct:format ; + + # it conforms to HTML, here referred to by its namespace URI as a Profile + dct:conformsTo ; + + # this profile resource plays the role of "Specification" + # described in this ontology's accompanying Roles vocabulary + prof:hasRole role:Specification ; + + # this profile resource's actual file + prof:hasArtifact ; + + # this profile is inherited from Workflow Run profile + prof:isInheritedFrom ; + ] ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "five-safes-crate" ; +. 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 000000000..79733fa7e --- /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 000000000..beba68399 --- /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-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 000000000..89813ee62 --- /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", "https://w3id.org/ro/terms/workflow-run/context", {"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 new file mode 100644 index 000000000..a53902d45 --- /dev/null +++ b/tests/data/crates/valid/five-safes-crate-request/ro-crate-metadata.json @@ -0,0 +1,169 @@ +{ + "@context": "https://w3id.org/ro/crate/1.2/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 new file mode 100644 index 000000000..2fa6838d2 --- /dev/null +++ b/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json @@ -0,0 +1,413 @@ +{ + "@context": "https://w3id.org/ro/crate/1.2/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 Result", + "description": "example 5-Safe RO-Crate result metadata for testing", + "datePublished": "2025-09-20T14:45:00+00:00", + "conformsTo": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "hasPart": [ + { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + { + "@id": "input1.txt" + }, + { + "@id": "outputs/qa.csv" + }, + { + "@id": "outputs/diagrams/" + }, + { + "@id": "workflow/289/" + } + ], + "mainEntity": { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + "mentions": [ + { + "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538" + }, + { + "@id": "#check-f33fe90c-0c22-4c72-b299-de509028410e" + }, + { + "@id": "#validate-1146f640-819e-4c86-b029-b763a0040896" + }, + { + "@id": "#download-8b51bf57-6b29-44da-b24b-638c8df91639" + }, + { + "@id": "#signoff-3b741265-cfef-49ea-8138-a2fa149bf2f0" + }, + { + "@id": "#disclosure-b16c1f0a-ae7f-4582-9b28-7d9df3313e27" + }, + { + "@id": "#bagit-ce785c0b-c988-4043-8cbd-1489dcebc14f" + } + ], + "sourceOrganization": { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + }, + "publisher": { + "@id": "https://tre72.example.com/" + }, + "license": { + "@id": "http://spdx.org/licenses/CC-BY-4.0" + } + }, + { + "@id": "https://w3id.org/5s-crate/0.4", + "@type": "Profile", + "name": "Five Safes RO-Crate profile" + }, + { + "@id": "https://spdx.org/licenses/CC-BY-4.0", + "@type": "CreativeWork", + "name": "Creative Commons Attribution 4.0 International", + "identifier": "CC-BY-4.0" + }, + { + "@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", + "startTime": "2023-04-18T12:12:00+01:00", + "endTime": "2023-04-19T16:59:59+01:00", + "actionStatus": "http://schema.org/CompletedActionStatus", + "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" + } + ], + "result": [ + { + "@id": "outputs/qa.csv" + }, + { + "@id": "outputs/diagrams/" + }, + { + "@id": "urn:uuid:07b81e0f-7ac4-5428-9940-878b241e2397" + } + ] + }, + { + "@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": "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" + }, + { + "@id": "outputs/qa.csv", + "@type": "File", + "encodingFormat": "text/csv", + "name": "Tabular listing of quality assessment" + }, + { + "@id": "outputs/diagrams/", + "@type": "Dataset", + "name": "Diagrams of regions of interest" + }, + { + "@id": "workflow/289/", + "sameAs": { + "@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": "#check-f33fe90c-0c22-4c72-b299-de509028410e", + "@type": "AssessAction", + "additionalType": { + "@id": "https://w3id.org/shp#CheckValue" + }, + "name": "BagIt checksum of Crate: OK", + "startTime": "2023-04-18T12:11:00+01:00", + "endTime": "2023-04-18T12:11:45+01:00", + "object": { + "@id": "./" + }, + "instrument": { + "@id": "https://www.iana.org/assignments/named-information#sha-512" + }, + "agent": { + "@id": "#validator-a4a66c63-2fe0-4c57-830d-268a40718313" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "#validate-1146f640-819e-4c86-b029-b763a0040896", + "@type": "AssessAction", + "additionalType": { + "@id": "https://w3id.org/shp#ValidationCheck" + }, + "name": "Validation against Five Safes RO-Crate profile: approved", + "startTime": "2023-04-18T12:11:46+01:00", + "endTime": "2023-04-18T12:11:49+01:00", + "object": { + "@id": "./" + }, + "instrument": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "agent": { + "@id": "#validator-a4a66c63-2fe0-4c57-830d-268a40718313" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "#download-8b51bf57-6b29-44da-b24b-638c8df91639", + "@type": "DownloadAction", + "name": "Downloaded workflow RO-Crate via proxy", + "startTime": "2023-04-18T12:11:50+01:00", + "endTime": "2023-04-18T12:11:52+01:00", + "object": { + "@id": "https://workflowhub.eu/workflows/289/ro_crate?version=1" + }, + "result": { + "@id": "workflow/289/" + }, + "agent": { + "@id": "http://proxy.example.com/" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "#signoff-3b741265-cfef-49ea-8138-a2fa149bf2f0", + "@type": "AssessAction", + "additionalType": { + "@id": "https://w3id.org/shp#SignOff" + }, + "name": "Sign-off of execution according to Agreement policy: approved", + "endTime": "2023-04-19T17:15:12+01:00", + "startTime": "2023-04-19T10:15:12+01:00", + "object": [ + { + "@id": "./" + }, + { + "@id": "https://workflowhub.eu/workflows/289?version=1" + }, + { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + ], + "instrument": { + "@id": "https://tre72.example.com/agreement-policy/81" + }, + "agent": { + "@id": "https://orcid.org/0000-0002-1825-0097" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "#disclosure-b16c1f0a-ae7f-4582-9b28-7d9df3313e27", + "@type": "AssessAction", + "additionalType": { + "@id": "https://w3id.org/shp#DisclosureCheck" + }, + "name": "Disclosure check of workflow results: approved", + "startTime": "2023-04-25T15:00:00+01:00", + "endTime": "2023-04-25T16:00:00+01:00", + "object": { + "@id": "./" + }, + "agent": { + "@id": "https://orcid.org/0000-0002-1825-0097" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "#bagit-ce785c0b-c988-4043-8cbd-1489dcebc14f", + "@type": "UpdateAction", + "startTime": "2023-04-29T12:12:25+01:00", + "additionalType": { + "@id": "https://w3id.org/shp#GenerateCheckValue" + }, + "name": "BagIt manifests of Crate updated", + "object": { + "@id": "./" + }, + "instrument": { + "@id": "https://www.iana.org/assignments/named-information#sha-512" + }, + "agent": { + "@id": "#validator-a4a66c63-2fe0-4c57-830d-268a40718313" + }, + "actionStatus": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "urn:uuid:07b81e0f-7ac4-5428-9940-878b241e2397", + "@type": "DigitalDocument", + "encodingFormat": "text/csv", + "name": "Patient measurement 07b81e0f-7ac4-5428-9940-878b241e2397", + "hasDigitalDocumentPermission": { + "@id": "#permissions-07b81e0f" + } + }, + { + "@id": "#permissions-07b81e0f", + "@type": "DigitalDocumentPermission", + "permissionType": "http://schema.org/ReadPermission", + "grantee": { + "@id": "#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70" + } + }, + { + "@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": "https://gtr.ukri.org/projects?ref=10038961", + "@type": "Grant", + "name": "EOSC4Cancer" + }, + { + "@id": "https://tre72.example.com/", + "@type": "Organization", + "name": "TRE 72 trusted research environment at The University of Manchester", + "parentOrganization": { + "@id": "https://ror.org/027m9bs27" + } + }, + { + "@id": "https://tre72.example.com/#crate-validator", + "@type": "SoftwareApplication", + "name": "RO-Crate validator at TRE72", + "url": "https://tre72.example.com/#crate-validator", + "version": "1.0", + "provider": { + "@id": "https://tre72.example.com/" + } + }, + { + "@id": "https://tre72.example.com/agreement-policy/81", + "@type": "CreativeWork", + "name": "Agreement policy for TRE72 for project 81" + }, + { + "@id": "https://www.iana.org/assignments/named-information#sha-512", + "@type": "DefinedTerm", + "name": "sha-512 algorithm" + } + ] +} diff --git a/tests/data/crates/valid/five-safes-profile-crate/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-profile-crate/ro-crate-metadata.json new file mode 100644 index 000000000..5ca7ea110 --- /dev/null +++ b/tests/data/crates/valid/five-safes-profile-crate/ro-crate-metadata.json @@ -0,0 +1,461 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@type": "CreativeWork", + "@id": "ro-crate-metadata.json", + "about": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "licence": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + } + }, + { + "@id": "https://w3id.org/5s-crate/0.4", + "@type": [ + "Dataset", + "Profile" + ], + "name": "Five Safes RO-Crate profile", + "cite-as": "https://w3id.org/5s-crate/0.4", + "version": "0.4", + "datePublished": "2023-05-15T10:32:00Z", + "licence": { + "@id": "http://spdx.org/licenses/MIT" + }, + "copyrightHolder": { + "@id": "https://ror.org/027m9bs27" + }, + "copyrightYear": 2023, + "hasPart": [ + { + "@id": "https://trefx.uk/5s-crate/0.4/index.html" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request.bagit.zip" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result.bagit.zip" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/" + }, + { + "@id": "http://schema.org/PotentialActionStatus" + }, + { + "@id": "http://schema.org/CompletedActionStatus" + }, + { + "@id": "http://schema.org/ActiveActionStatus" + }, + { + "@id": "http://schema.org/FailedActionStatus" + }, + { + "@id": "http://schema.org/CreateAction" + }, + { + "@id": "http://schema.org/AssessAction" + }, + { + "@id": "http://schema.org/DownloadAction" + }, + { + "@id": "http://schema.org/UpdateAction" + }, + { + "@id": "http://schema.org/grantee" + }, + { + "@id": "http://schema.org/DigitalDocument" + }, + { + "@id": "http://schema.org/DigitalDocumentPermission" + }, + { + "@id": "http://schema.org/hasDigitalDocumentPermission" + }, + { + "@id": "http://schema.org/ReadPermission" + }, + { + "@id": "https://bioschemas.org/FormalParameter" + }, + { + "@id": "https://w3id.org/shp#CheckValue" + }, + { + "@id": "https://w3id.org/shp#ValidationCheck" + }, + { + "@id": "https://w3id.org/shp#SignOff" + }, + { + "@id": "https://w3id.org/shp#DisclosureCheck" + }, + { + "@id": "https://w3id.org/shp#GenerateCheckValue" + } + ], + "hasResource": [ + { + "@id": "#hasSpecification" + }, + { + "@id": "#hasExampleRequest" + }, + { + "@id": "#hasExampleResult" + }, + { + "@id": "#usesSHP" + } + ], + "publisher": { + "@id": "https://trefx.uk/" + }, + "funding": { + "@id": "https://gtr.ukri.org/projects?ref=MC_PC_23007" + }, + "author": [ + { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + { + "@id": "https://orcid.org/0009-0003-2419-1964" + } + ] + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/index.html", + "@type": "CreativeWork", + "name": "Five Safes RO-Crate profile (specification)" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/", + "@type": "Dataset", + "name": "Example Crate in Request state", + "conformsTo": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "subjectOf": [ + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/ro-crate-metadata.json" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/ro-crate-preview.html" + } + ], + "distribution": { + "@id": "https://trefx.uk/5s-crate/0.4/example-request.bagit.zip" + } + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/", + "@type": "Dataset", + "name": "Example Crate in Request state", + "conformsTo": { + "@id": "https://w3id.org/5s-crate/0.4" + }, + "subjectOf": [ + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/ro-crate-metadata.json" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/ro-crate-preview.html" + } + ], + "distribution": { + "@id": "https://trefx.uk/5s-crate/0.4/example-result.bagit.zip" + } + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result.bagit.zip", + "@type": "DataDownload", + "name": "example-result.bagit.zip", + "description": "Example Result following the 5s-crate profile, archived as BagIt ZIP", + "encodingFormat": "application/zip", + "conformsTo": { + "@id": "https://w3id.org/ro/crate" + } + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request.bagit.zip", + "@type": "DataDownload", + "name": "example-request.bagit.zip", + "description": "Example Request following the 5s-crate profile, archived as BagIt ZIP", + "encodingFormat": "application/zip", + "conformsTo": { + "@id": "https://w3id.org/ro/crate" + } + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/ro-crate-metadata.json", + "@type": "CreativeWork", + "encodingFormat": "application/ld+json" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/ro-crate-preview.html", + "@type": "CreativeWork", + "encodingFormat": "text/html" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/ro-crate-metadata.json", + "@type": "CreativeWork", + "encodingFormat": "application/ld+json" + }, + { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/ro-crate-preview.html", + "@type": "CreativeWork", + "encodingFormat": "text/html" + }, + { + "@id": "https://bioschemas.org/FormalParameter", + "@type": "DefinedTerm" + }, + { + "@id": "https://w3id.org/shp", + "@type": "DefinedTermSet", + "name": "The Safe Haven Provenance (SHP) Ontology", + "version": "0.1" + }, + { + "@id": "https://w3id.org/shp#CheckValue", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#ValidationCheck", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#SignOff", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#DisclosureCheck", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#GenerateCheckValue", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#CheckValue", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#ValidationCheck", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#SignOff", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#DisclosureCheck", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://w3id.org/shp#GenerateCheckValue", + "@type": "DefinedTerm", + "inDefinedTermSet": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "https://www.iana.org/assignments/named-information#sha-512", + "@type": "DefinedTerm", + "name": "sha-512 algorithm" + }, + { + "@id": "#usesSHP", + "name": "uses vocabulary SHP", + "@type": "ResourceDescriptor", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/vocabulary" + }, + "hasArtifact": { + "@id": "https://w3id.org/shp" + } + }, + { + "@id": "#hasSpecification", + "@type": "ResourceDescriptor", + "name": "has specification", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/specification" + }, + "hasArtifact": { + "@id": "https://trefx.uk/5s-crate/0.4/index.html" + } + }, + { + "@id": "#hasExampleRequest", + "@type": "ResourceDescriptor", + "name": "has example: Request", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/example" + }, + "hasArtifact": { + "@id": "https://trefx.uk/5s-crate/0.4/example-request/data/" + } + }, + { + "@id": "#hasExampleResult", + "@type": "ResourceDescriptor", + "name": "has example: Result", + "hasRole": { + "@id": "http://www.w3.org/ns/dx/prof/role/example" + }, + "hasArtifact": { + "@id": "https://trefx.uk/5s-crate/0.4/example-result/data/" + } + }, + + { + "@id": "http://www.w3.org/ns/dx/prof/role/example", + "@type": [ + "DefinedTerm", + "ResourceRole" + ], + "name": "Example", + "description": "Sample instance data conforming to the profile" + }, + { + "@id": "http://www.w3.org/ns/dx/prof/role/specification", + "@type": [ + "DefinedTerm", + "ResourceRole" + ], + "name": "Specification", + "description": "Defining the profile in human-readable form" + }, + { + "@id": "http://www.w3.org/ns/dx/prof/role/vocabulary", + "@type": [ + "DefinedTerm", + "ResourceRole" + ], + "name": "Vocabulary", + "description": "Defines terms used in the profile specification" + }, + { + "@id": "http://spdx.org/licenses/CC0-1.0", + "@type": "CreativeWork", + "name": "Creative Commons Zero v1.0 Universal", + "identifier": "CC0-1.0", + "url": "https://creativecommons.org/publicdomain/zero/1.0/" + }, + { + "@id": "http://spdx.org/licenses/MIT", + "@type": "CreativeWork", + "name": "MIT Licence", + "identifier": "MIT" + }, + { + "@id": "https://gtr.ukri.org/projects?ref=MC_PC_23007", + "@type": "Grant", + "identifier": { + "@id": "_:ukri:MC_PC_23007" + }, + "name": "DARE-FX: Delivering a federated network of TREs to enable safe analytics", + "description": "", + "funder": { + "@id": "https://ror.org/001aqnf71" + }, + "sponsor": { + "@id": "https://dareuk.org.uk/" + } + }, + { + "@id": "https://dareuk.org.uk/", + "@type": "FundingScheme", + "name": "DARE UK", + "funder": { + "@id": "https://ror.org/001aqnf71" + } + }, + { + "@id": "_:ukri:MC_PC_23007", + "@type": "PropertyValue", + "name": "UKRI", + "value": "MC_PC_23007" + }, + { + "@id": "https://trefx.uk/", + "@type": "ResearchProject", + "name": "TRE-FX", + "description": "Delivering a federated network of TREs to enable safe analytics", + "funding": { + "@id": "https://gtr.ukri.org/projects?ref=MC_PC_23007" + } + }, + { + "@id": "https://ror.org/001aqnf71", + "@type": "FundingAgency", + "name": "UK Research and Innovation", + "alternateName": "UKRI", + "url": "https://www.ukri.org/" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes", + "affiliation": { + "@id": "https://ror.org/027m9bs27" + }, + "memberOf": { + "@id": "https://trefx.uk/" + } + }, + { + "@id": "https://ror.org/027m9bs27", + "@type": "CollegeOrUniversity", + "name": "The University of Manchester", + "url": "https://www.manchester.ac.uk/" + }, + { + "@id": "https://orcid.org/0009-0003-2419-1964", + "@type": "Person", + "name": "Stuart Wheater", + "memberOf": { + "@id": "https://trefx.uk/" + } + } + ] +} \ No newline at end of file diff --git a/tests/integration/profiles/five-safes-crate/test_5src_10_outputs.py b/tests/integration/profiles/five-safes-crate/test_5src_10_outputs.py new file mode 100644 index 000000000..1cbdbe6d0 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_10_outputs.py @@ -0,0 +1,95 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- SHOULD fails tests + + +def test_completed_createaction_does_not_have_result(): + """ + Test a Five Safes Crate where `CreateAction` has `CompletedActionStatus` but + does not have the property `result`. + (We remove `result` from `CreateAction` if this has `CompletedActionStatus`) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?action schema:result ?o . + } + WHERE { + ?action a schema:CreateAction ; + schema:actionStatus "http://schema.org/CompletedActionStatus" ; + schema:result ?o + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["WorkflowRunAction"], + expected_triggered_issues=[ + "The `CreateAction` corresponding to the workflow run,", + "with CompletedActionStatus, SHOULD have the `result` property.", + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_result_output_does_not_have_allowed_type(): + """ + Test a Five Safes Crate where the result output does not have a type that + is among those allowed. + (We remove the output entity and replace it with one that is of a wrong type)""" + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?result a ?oldType . + } + INSERT { + ?result a schema:Person . + } + WHERE { + ?action a schema:CreateAction ; + schema:result ?result . + ?result a ?oldType . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["Output"], + expected_triggered_issues=[ + "Result SHOULD have a `@type` among an allowed set of values." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py b/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py new file mode 100644 index 000000000..a271978a8 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py @@ -0,0 +1,308 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_5src_workflow_object_with_no_name(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:name ?name . + } + WHERE { + ?this rdf:type schema:CreateAction ; + schema:name ?name . + <./> schema:mentions ?this . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["WorkflowExecution"], + expected_triggered_issues=[ + "The `CreateAction` corresponding to the workflow run MUST have a name string of at least 10 characters." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_workflow_object_with_name_not_string(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:name ?name . + } + INSERT { + ?this schema:name 123 . + } + WHERE { + ?this rdf:type schema:CreateAction ; + schema:name ?name . + <./> schema:mentions ?this . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["WorkflowExecution"], + expected_triggered_issues=[ + "The `CreateAction` corresponding to the workflow run MUST have a name string of at least 10 characters." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_workflow_object_with_not_long_enough_name(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:name ?name . + } + INSERT { + ?this schema:name "Short" . + } + WHERE { + ?this rdf:type schema:CreateAction ; + schema:name ?name . + <./> schema:mentions ?this . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["WorkflowExecution"], + expected_triggered_issues=[ + "The `CreateAction` corresponding to the workflow run MUST have a name string of at least 10 characters." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_workflow_object_has_no_properly_formatted_start_time(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:startTime ?time . + } + INSERT { + ?s schema:startTime "1st Dec '25 @ 10:00:00" . + } + WHERE { + ?s rdf:type schema:CreateAction ; + schema:startTime ?time . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_workflow_object_has_no_properly_formatted_end_time(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:endTime ?time . + } + INSERT { + ?s schema:endTime "1st Dec '25 @ 10:00:00" . + } + WHERE { + ?s rdf:type schema:CreateAction ; + schema:endTime ?time . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_workflow_object_with_no_action_status(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:actionStatus ?o . + } + WHERE { + ?this schema:actionStatus ?o ; + rdf:type schema:CreateAction . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["WorkflowExecution"], + expected_triggered_issues=[ + ( + "`CreateAction` MUST have an actionStatus " + "with an allowed value (see https://schema.org/ActionStatusType)." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_workflow_object_with_no_properly_valued_action_status(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:actionStatus ?o . + } + INSERT { + ?this schema:actionStatus "Not a proper actionStatus value" . + } + WHERE { + ?this schema:actionStatus ?o ; + rdf:type schema:CreateAction . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["WorkflowExecution"], + expected_triggered_issues=[ + ( + "`CreateAction` MUST have an actionStatus " + "with an allowed value (see https://schema.org/ActionStatusType)." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- SHOULD fails tests + + +def test_5src_workflow_object_has_no_end_time_if_ended(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:endTime ?time . + } + WHERE { + ?s rdf:type schema:CreateAction ; + schema:endTime ?time . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["WorkflowExecution"], + expected_triggered_issues=[ + "The workflow execution object SHOULD have an endTime property if it has ended." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ------- MAY fail tests + + +def test_5src_workflow_object_has_no_start_time_if_begun(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:startTime ?time . + } + WHERE { + ?s rdf:type schema:CreateAction; + schema:startTime ?time . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.OPTIONAL, + expected_validation_result=False, + expected_triggered_requirements=["WorkflowExecution"], + expected_triggered_issues=[ + ( + "The workflow execution object MAY have a startTime if actionStatus is " + "either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_12_check_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_12_check_phase.py new file mode 100644 index 000000000..53171a4c0 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_12_check_phase.py @@ -0,0 +1,385 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +# TO BE CHECKED AGAIN +def test_5src_check_value_not_of_type_assess_action(): + sparql = """ + PREFIX schema: + PREFIX shp: + PREFIX rdf: + + DELETE { + ?this rdf:type ?type . + } + INSERT { + ?this rdf:type . + } + WHERE { + ?this a schema:AssessAction ; + schema:additionalType shp:CheckValue ; + rdf:type ?type . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["CheckValue"], + expected_triggered_issues=["CheckValue MUST be a `AssessAction`."], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_check_value_name_not_a_string(): + sparql = """ + PREFIX schema: + PREFIX shp: + PREFIX rdf: + + DELETE { + ?this schema:name ?name . + } + INSERT { + ?this schema:name "123"^^xsd:integer . + } + WHERE { + ?this schema:additionalType shp:CheckValue . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["CheckValue"], + expected_triggered_issues=[ + "CheckValue MUST have a human readable name string." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_check_value_start_time_not_iso_standard(): + sparql = """ + PREFIX schema: + PREFIX shp: + PREFIX rdf: + + DELETE { + ?c schema:startTime ?t . + } + INSERT { + ?c schema:startTime "1st of Jan 2021" . + } + WHERE { + ?c schema:additionalType shp:CheckValue ; + schema:startTime ?t . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_check_value_end_time_not_iso_standard(): + sparql = """ + PREFIX schema: + PREFIX shp: + PREFIX rdf: + + DELETE { + ?c schema:endTime ?t . + } + INSERT { + ?c schema:endTime "1st of Jan 2021" . + } + WHERE { + ?c schema:additionalType shp:CheckValue ; + schema:endTime ?t . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_check_value_has_action_status_with_not_allowed_value(): + sparql = """ + PREFIX schema: + PREFIX shp: + PREFIX rdf: + + DELETE { + ?s schema:actionStatus ?o . + } + INSERT { + ?s schema:actionStatus "Not a good action status" . + } + WHERE { + ?s schema:additionalType ; + schema:actionStatus ?o . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["CheckValue"], + expected_triggered_issues=[ + "`CheckValue` --> `actionStatus` MUST have one of the allowed values." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- SHOULD fails tests + + +def test_5src_root_data_entity_does_not_mention_check_value_entity(): + sparql = """ + PREFIX schema: + PREFIX shp: + + DELETE { + <./> schema:mentions ?o . + } + WHERE { + ?o schema:additionalType shp:CheckValue ; + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "RootDataEntity SHOULD mention a check value object." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_check_value_object_does_not_point_to_root_data_entity(): + sparql = """ + PREFIX schema: + PREFIX shp: + + DELETE { + ?s schema:object <./> . + } + INSERT { + ?s schema:object "not the RootDataEntity" . + } + WHERE { + ?s schema:additionalType shp:CheckValue ; + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["CheckValue"], + expected_triggered_issues=[ + "`CheckValue` --> `object` SHOULD point to the root of the RO-Crate" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_check_value_instrument_does_not_point_to_entity_with_type_defined_term(): + sparql = """ + PREFIX schema: + PREFIX shp: + PREFIX rdf: + + DELETE { + ?s rdf:type schema:DefinedTerm . + } + INSERT { + ?s rdf:type schema:Persona . + } + WHERE { + ?cv schema:additionalType shp:CheckValue ; + schema:instrument ?s . + ?s rdf:type schema:DefinedTerm . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["CheckValue"], + expected_triggered_issues=[ + "`CheckValue` --> `instrument` SHOULD point to an entity typed `DefinedTerm`" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_check_value_does_not_have_end_time(): + sparql = """ + PREFIX schema: + PREFIX shp: + PREFIX rdf: + + DELETE { + ?c schema:endTime ?t . + } + WHERE { + ?c schema:additionalType shp:CheckValue ; + schema:endTime ?t . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["CheckValue"], + expected_triggered_issues=["`CheckValue` SHOULD have the `endTime` property."], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_check_value_does_not_have_action_status_property(): + sparql = """ + PREFIX schema: + PREFIX shp: + PREFIX rdf: + + DELETE { + ?s schema:actionStatus ?o . + } + WHERE { + ?s schema:additionalType shp:CheckValue ; + schema:actionStatus ?o . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["CheckValue"], + expected_triggered_issues=["CheckValue SHOULD have actionStatus property."], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_check_value_does_not_point_to_an_agent(): + sparql = """ + PREFIX schema: + PREFIX shp: + PREFIX rdf: + + DELETE { + ?s schema:agent ?o . + } + WHERE { + ?s schema:additionalType shp:CheckValue ; + schema:agent ?o . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["CheckValue"], + expected_triggered_issues=[ + "`CheckValue` --> `agent` SHOULD reference the agent who initiated the check" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- MAY fails tests + + +def test_5src_check_value_does_not_have_start_time(): + sparql = """ + PREFIX schema: + PREFIX shp: + PREFIX rdf: + + DELETE { + ?c schema:startTime ?t . + } + WHERE { + ?c schema:additionalType shp:CheckValue ; + schema:startTime ?t . + } + """ + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.OPTIONAL, + expected_validation_result=False, + expected_triggered_requirements=["CheckValue"], + expected_triggered_issues=["`CheckValue` MAY have the `startTime` property."], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_13_validation_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_13_validation_phase.py new file mode 100644 index 000000000..be6f44906 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_13_validation_phase.py @@ -0,0 +1,354 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_5src_validation_check_not_of_type_assess_action(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this rdf:type schema:AssessAction . + } + INSERT { + ?this rdf:type . + } + WHERE { + ?this rdf:type schema:AssessAction ; + schema:additionalType shp:ValidationCheck . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["ValidationCheck"], + expected_triggered_issues=["ValidationCheck MUST be a `AssessAction`."], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_validation_check_name_not_a_string(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:name ?name . + } + INSERT { + ?this schema:name 123 . + } + WHERE { + ?this schema:additionalType shp:ValidationCheck . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["ValidationCheck"], + expected_triggered_issues=[ + "ValidationCheck MUST have a human readable name string." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_validation_check_has_action_status_with_not_allowed_value(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:actionStatus ?o . + } + WHERE { + ?s schema:additionalType shp:ValidationCheck ; + schema:actionStatus ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["ValidationCheck"], + expected_triggered_issues=[ + ( + "actionStatus MUST be either PotentialActionStatus, ActiveActionStatus, " + "CompletedActionStatus, or FailedActionStatus." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_validation_check_start_time_not_iso_standard(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?c schema:startTime ?t . + } + INSERT { + ?c schema:startTime "1st of Jan 2021" . + } + WHERE { + ?c schema:additionalType shp:ValidationCheck ; + schema:startTime ?t . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_validation_check_end_time_not_iso_standard(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?c schema:endTime ?t . + } + INSERT { + ?c schema:endTime "1st of Jan 2021" . + } + WHERE { + ?c schema:additionalType shp:ValidationCheck ; + schema:endTime ?t . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- SHOULD fails tests + + +def test_5src_root_data_entity_does_not_mention_validation_check_entity(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:mentions ?o . + } + WHERE { + ?o schema:additionalType shp:ValidationCheck ; + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "RootDataEntity SHOULD mention a ValidationCheck object." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_validation_check_object_does_not_point_to_root_data_entity(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:object <./> . + } + INSERT { + ?s schema:object "not the RootDataEntity" . + } + WHERE { + ?s schema:additionalType shp:ValidationCheck ; + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["ValidationCheck"], + expected_triggered_issues=[ + "`ValidationCheck` --> `object` SHOULD point to the root of the RO-Crate" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_validation_check_instrument_does_not_point_to_5scrate_0p4(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:instrument . + } + WHERE { + ?s schema:additionalType shp:ValidationCheck ; + schema:instrument . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["ValidationCheck"], + expected_triggered_issues=[ + "`ValidationCheck` --> `instrument` SHOULD point to an entity with @id https://w3id.org/5s-crate/0.4" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_Validation_check_does_not_have_action_status_property(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:actionStatus ?o . + } + WHERE { + ?s schema:additionalType shp:ValidationCheck ; + schema:actionStatus ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["ValidationCheck"], + expected_triggered_issues=[ + "ValidationCheck SHOULD have actionStatus property." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_download_action_does_not_have_end_time(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:endTime ?time . + } + WHERE { + ?s schema:additionalType shp:ValidationCheck ; + schema:endTime ?time . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["ValidationCheck"], + expected_triggered_issues=[ + ( + "ValidationCheck SHOULD have the `endTime` property if `actionStatus` " + "is either CompletedActionStatus or FailedActionStatus." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- MAY fails tests + + +def test_5src_download_action_does_not_have_start_time(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:startTime ?time . + } + WHERE { + ?s schema:additionalType shp:ValidationCheck ; + schema:startTime ?time . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.OPTIONAL, + expected_validation_result=False, + expected_triggered_requirements=["ValidationCheck"], + expected_triggered_issues=[ + ( + "ValidationCheck MAY have the `startTime` property if `actionStatus` " + "is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_14_workflow_retrieval_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_14_workflow_retrieval_phase.py new file mode 100644 index 000000000..52228408e --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_14_workflow_retrieval_phase.py @@ -0,0 +1,425 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_5src_download_action_does_not_have_name(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:name ?name . + } + WHERE { + ?this schema:name ?name ; + rdf:type schema:DownloadAction . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["DownloadAction"], + expected_triggered_issues=[ + "DownloadAction MUST have a human readable name string." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_download_action_name_not_a_string(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:name ?name . + } + INSERT { + ?this schema:name 123 . + } + WHERE { + ?this rdf:type schema:DownloadAction . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["DownloadAction"], + expected_triggered_issues=[ + "DownloadAction MUST have a human readable name string." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_download_action_start_time_not_iso_standard(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?c schema:startTime ?t . + } + INSERT { + ?c schema:startTime "1st of Jan 2021" . + } + WHERE { + ?c rdf:type schema:DownloadAction ; + schema:startTime ?t . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_check_value_end_time_not_iso_standard(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?c schema:endTime ?t . + } + INSERT { + ?c schema:endTime "1st of Jan 2021" . + } + WHERE { + ?c rdf:type schema:DownloadAction ; + schema:endTime ?t . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_downloaded_workflow_same_as_is_not_the_same_as_root_data_entity_main_entity(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?wf schema:sameAs ?me . + } + INSERT { + ?wf schema:sameAs "This is not the same as the main entity" . + } + WHERE { + ?wf schema:sameAs ?me . + <./> schema:mainEntity ?me . + ?da schema:result ?wf ; + rdf:type schema:DownloadAction . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Downloaded Workflow"], + expected_triggered_issues=[ + ( + "The property `sameAs` of the entity representing the downloaded workflow " + "MUST point to the same entity as `RootDataEntity` --> `mainEntity`." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_downloaded_workflow_distribution_is_not_the_same_as_download_action_object(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:object ?o . + } + INSERT { + ?s schema:result "This is not the downloaded workflow entity" . + } + WHERE { + ?s schema:object ?o ; + rdf:type schema:DownloadAction . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Downloaded Workflow"], + expected_triggered_issues=[ + ( + "DownloadedWorkflow --> `distribution` MUST reference " + "the same entity as `DownloadAction` --> `object`." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_download_action_has_action_status_with_not_allowed_value(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:actionStatus ?o . + } + INSERT { + ?s schema:actionStatus "Not a good action status" . + } + WHERE { + ?s rdf:type schema:DownloadAction ; + schema:actionStatus ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["DownloadAction"], + expected_triggered_issues=[ + ( + "The value of actionStatus MUST be one of the allowed values: " + "PotentialActionStatus; ActiveActionStatus; CompletedActionStatus; FailedActionStatus." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- SHOULD fails tests + + +def test_5src_download_action_is_not_present(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?da ?p ?o . + } + WHERE { + ?da rdf:type schema:DownloadAction ; + ?p ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=["An entity typed DownloadAction SHOULD exist."], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_root_data_entity_does_not_mention_download_action_entity(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:mentions ?o . + } + WHERE { + ?o rdf:type schema:DownloadAction ; + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "RootDataEntity SHOULD mention DownloadAction if this exists." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_download_action_does_not_have_end_time(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?c schema:endTime ?t . + } + WHERE { + ?c rdf:type schema:DownloadAction ; + schema:endTime ?t . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["DownloadAction"], + expected_triggered_issues=[ + ( + "`DownloadAction` SHOULD have the `endTime` property " + "if `actionStatus` is either CompletedActionStatus or FailedActionStatus." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_download_action_does_not_have_action_status_property(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:actionStatus ?o . + } + WHERE { + ?s rdf:type schema:DownloadAction ; + schema:actionStatus ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["DownloadAction"], + expected_triggered_issues=[ + "`DownloadAction` SHOULD have `actionStatus` property." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- MAY fails tests + + +def test_5src_downloaded_workflow_is_not_represented_by_its_own_entity(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?wf ?p ?o . + } + WHERE { + ?wf ?p ?o . + ?da schema:result ?wf ; + rdf:type schema:DownloadAction . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.OPTIONAL, + expected_validation_result=False, + expected_triggered_requirements=["DownloadAction"], + expected_triggered_issues=[ + ( + "The entity representing the downloaded workflow is not defined, " + "OR is not referenced by `DownloadAction` --> `result`, " + "OR is not of type `Dataset`." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_download_action_does_not_have_start_time(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:startTime ?time . + } + WHERE { + ?s rdf:type schema:DownloadAction ; + schema:startTime ?time . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.OPTIONAL, + expected_validation_result=False, + expected_triggered_requirements=["DownloadAction"], + expected_triggered_issues=[ + ( + "`DownloadAction` MAY have the `startTime` property if `actionStatus` " + "is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) 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 000000000..e94a76fa9 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_15_metadata_file.py @@ -0,0 +1,87 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +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 { + ?this dct:conformsTo ?version . + } + INSERT { + ?this dct:conformsTo . + } + WHERE { + ?this dct:conformsTo ?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" + ], + 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_5src_16_publishing_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_16_publishing_phase.py new file mode 100644 index 000000000..8149d1b24 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_16_publishing_phase.py @@ -0,0 +1,56 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_5src_assess_action_not_referenced_from_rde(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:mentions ?this . + } + WHERE { + ?this a schema:AssessAction . + <./> schema:mentions ?this . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=[ + "All AssessActions are mentioned from Root Data Entity" + ], + expected_triggered_issues=[ + "All AssessAction entities in the crate MUST be referenced from " + "the Root Dataset via `mentions`." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py b/tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py new file mode 100644 index 000000000..1ab9b4b37 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py @@ -0,0 +1,139 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_5src_agent_memberOf_not_project(): + """ + Test a Five Safes Crate where an agent's `memberOf` does NOT reference a schema:Project. + (We replace the referenced Project with a plain literal.) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?agent schema:memberOf ?org . + } + INSERT { + ?agent schema:memberOf "Not a project (literal replacement)" + } + WHERE { + ?action a schema:CreateAction ; + schema:agent ?agent . + ?agent schema:memberOf ?org . + ?org a schema:Project . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Requesting Agent"], + expected_triggered_issues=[ + "The 'memberOf' property of an agent MUST be of type Project." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_agent_memberOf_project_not_in_root(): + """ + Test a Five Safes Crate where NONE of the Projects referenced by Agent->memberOf are included + in the set of Projects referenced by RootDataEntity->sourceOrganization. + (We replace an agent's memberOf with a new Project that the root does not reference.) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?agent schema:memberOf ?org . + } + INSERT { + # assign the agent to a new Project that is not referenced by the Root Data Entity + ?agent schema:memberOf <#missing-project> . + <#missing-project> a schema:Project . + } + WHERE { + # locate a CreateAction -> agent -> memberOf that currently points to a Project + ?action a schema:CreateAction ; + schema:agent ?agent . + + ?agent schema:memberOf ?org . + ?org a schema:Project . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Agent Project Intersection"], + expected_triggered_issues=[ + ( + "At least one Project referenced by Agent -> memberOf MUST be included " + "in the set of Projects referenced by RootDataEntity -> sourceOrganization." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- SHOULD warns tests +def test_5src_agent_memberOf_missing_warning(): + """ + Test a Five Safes Crate where the Requesting Agent does NOT have the 'memberOf' property. + This should trigger the SHACL warning: the Requesting Agent SHOULD have a `memberOf` property. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?agent schema:memberOf ?org . + } + WHERE { + ?action a schema:CreateAction ; + schema:agent ?agent . + ?agent schema:memberOf ?org . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, # or True if warnings are not treated as failures + expected_triggered_requirements=["Requesting Agent"], + expected_triggered_issues=[ + "The Requesting Agent SHOULD have a `memberOf` property." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py b/tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py new file mode 100644 index 000000000..1f5c9664b --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py @@ -0,0 +1,219 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ---- MUST fails tests + + +def test_5src_responsible_project_funding_not_grant(): + """ + Test a Five Safes Crate where a Responsible Project's `funding` property + is NOT of type schema:Grant. + (We replace the funding reference with a literal.) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?project schema:funding ?grant . + } + INSERT { + ?project schema:funding "Not a grant (literal replacement)" . + } + WHERE { + ?action a schema:CreateAction ; + schema:agent ?agent . + ?agent schema:memberOf ?project . + ?project schema:funding ?grant . + ?grant a schema:Grant . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Responsible Project"], + expected_triggered_issues=[ + "The property 'funding' of the Responsible Project MUST be of type Grant." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_responsible_project_member_not_organization(): + """ + Test a Five Safes Crate where a Responsible Project's `member` property + is NOT of type schema:Organization. + (We replace the member reference with a literal.) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?project schema:member ?org . + } + INSERT { + ?project schema:member "Not organization or person (literal replacement)" . + } + WHERE { + ?action a schema:CreateAction ; + schema:agent ?agent . + ?agent schema:memberOf ?project . + ?project schema:member ?org . + ?org a schema:Organization . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Responsible Project"], + expected_triggered_issues=[ + "The property 'member' of the Responsible Project MUST be of type schema:Organization or Person." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ---- SHOULD warns tests + + +def test_5src_responsible_project_member_and_agent_affiliation_no_intersection(): + """ + Test a Five Safes Crate where none of the organisations that are members of the + Responsible Project appear in the Requesting Agent's affiliations (violates the + 'Intersection with agent affiliations' SHACL warning). + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?agent schema:affiliation ?oldAff . + } + INSERT { + ?agent schema:affiliation <#missing-affiliation> . + <#missing-affiliation> a schema:Organization . + } + WHERE { + ?action a schema:CreateAction ; + schema:agent ?agent . + ?agent a schema:Person ; + schema:memberOf ?project ; + schema:affiliation ?oldAff . + ?project schema:member ?oldAff . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=[ + "Organizations (members of Responsible Project)" + ], + expected_triggered_issues=[ + ( + "At least one of the organisations that are members of the responsible project SHOULD " + "be included in the Requesting Agent's affiliations, if such properties exist." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ---- MAY warns tests + + +def test_5src_responsible_project_missing_funding_property(): + """ + Test a Five Safes Crate where a Responsible Project does NOT have the `funding` property. + This should trigger the SHACL info: 'The Responsible Project does not have the property `funding`.' + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?project schema:funding ?f . + } + WHERE { + ?action a schema:CreateAction ; + schema:agent ?agent . + ?agent schema:memberOf ?project . + ?project schema:funding ?f . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.OPTIONAL, + expected_validation_result=False, + expected_triggered_requirements=["Responsible Project"], + expected_triggered_issues=[ + "The Responsible Project does not have the property `funding`." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_responsible_project_missing_member_property(): + """ + Test a Five Safes Crate where a Responsible Project does NOT have the `member` property. + This should trigger the SHACL info: 'The Responsible Project does not have the property `member`.' + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?project schema:member ?m . + } + WHERE { + ?action a schema:CreateAction ; + schema:agent ?agent . + ?agent schema:memberOf ?project . + ?project schema:member ?m . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.OPTIONAL, + expected_validation_result=False, + expected_triggered_requirements=["Responsible Project"], + expected_triggered_issues=[ + "The Responsible Project does not have the property `member`." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py b/tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py new file mode 100644 index 000000000..23c51832d --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py @@ -0,0 +1,90 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_5src_root_data_entity_without_source_organization(): + """ + Test a Five Safes Crate where the Root Data Entity does NOT have the 'sourceOrganization' property. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:sourceOrganization ?o . + } + WHERE { + <./> schema:sourceOrganization ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + """The Root Data Entity MUST have a `sourceOrganization` property.""" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_root_data_entity_source_organization_not_organization(): + """ + Test a Five Safes Crate where the Root Data Entity's `sourceOrganization` property + does NOT reference a `schema:Project` entity. + (We replace any existing sourceOrganization with a literal to violate the class constraint.) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:sourceOrganization ?o . + } + INSERT { + # insert a literal instead of an IRI + <./> schema:sourceOrganization "Investigation of cancer (TRE72 project 81)" . + } + WHERE { + <./> schema:sourceOrganization ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + """The `sourceOrganization` property of the RootDataEntity MUST point to a Project entity.""" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_2_requesting_agent.py b/tests/integration/profiles/five-safes-crate/test_5src_2_requesting_agent.py new file mode 100644 index 000000000..41a4279d1 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_2_requesting_agent.py @@ -0,0 +1,165 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_createaction_does_not_have_agent(): + """ + Test a Five Safes Crate where CreateAction does not have the property agent. + (We remove the property agent from the CreateAction entity) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?action schema:agent ?agent . + } + WHERE { + ?action schema:agent ?agent ; + a schema:CreateAction . + ?agent a schema:Person . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["CreateAction"], + expected_triggered_issues=[ + "CreateAction MUST have at least one agent that is a contextual entity." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_createaction_agent_is_not_person(): + """ + Test a Five Safes Crate where CreateAction has an agent that is not of type schema:Person. + (We replace the CreateAction's agent with an entity that has no type). + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?action schema:agent ?agent . + } + INSERT { + ?action schema:agent <#not-a-person> . + } + WHERE { + ?action schema:agent ?agent ; + a schema:CreateAction . + ?agent a schema:Person . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["CreateAction"], + expected_triggered_issues=["Each CreateAction agent MUST be typed as Person."], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_agent_affiliation_not_organization(): + """ + Test a Five Safes Crate where the agent of CreateAction has an affiliation + that is not of type schema:Organization. + (We rereplace the agent's affiliation with an entity that has no type) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?agent schema:affiliation ?aff . + } + INSERT { + ?agent schema:affiliation <#not-an-organization> . + } + WHERE { + ?agent schema:affiliation ?aff ; + a schema:Person . + ?aff a schema:Organization . + ?action a schema:CreateAction ; + schema:agent ?agent . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["CreateAction"], + expected_triggered_issues=[ + "The affiliation of a CreateAction's agent MUST be a contextual entity with type Organization." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- SHOULD warning tests + + +def test_5src_agent_does_not_have_affiliation(): + """ + Test a Five Safes Crate where the agent of CreteAction does not have an affiliatin. + (We remove the triplet ?agent schema:affiliation ?org from the RO-Crate graph) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?agent schema:affiliation ?org . + } + WHERE { + ?agent schema:affiliation ?org ; + a schema:Person . + ?action a schema:CreateAction ; + schema:agent ?agent . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["Agent of WorkflowRunAction"], + expected_triggered_issues=[ + "The agent of the `CreateAction` corresponding to the workflow run SHOULD have an affiliation" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_4_signoff_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_4_signoff_phase.py new file mode 100644 index 000000000..a98c850bd --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_4_signoff_phase.py @@ -0,0 +1,576 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ---- SHOULD fails tests + + +def test_5src_no_signoff_phase(): + """ + Test a Five Safes Crate where no Sign-Off phase is listed. + """ + + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <#signoff-3b741265-cfef-49ea-8138-a2fa149bf2f0> ?p ?o . + } + WHERE { + <#signoff-3b741265-cfef-49ea-8138-a2fa149bf2f0> ?p ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhase"], + expected_triggered_issues=[ + "There SHOULD be a Sign-Off Phase in the Final RO-Crate" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_no_name(): + """ + Test a Five Safes Crate where the Sign-Off phase has no name. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:name ?name . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:name ?name . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["SignOff"], + expected_triggered_issues=[ + "Sign Off phase MUST have a human-readable name string." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_wrong_type(): + """ + Test a Five Safes Crate where the Sign-Off phase has no name. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff rdf:type ?type . + } + INSERT { + ?signoff rdf:type . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + rdf:type ?type . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["SignOff"], + expected_triggered_issues=["Sign Off phase MUST be a `AssessAction`."], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_wrong_action_status(): + """ + Test a Five Safes Crate where the Sign-Off phase has the wrong action status. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:actionStatus ?status . + } + INSERT { + ?signoff schema:actionStatus . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:actionStatus ?status . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffStatus"], + expected_triggered_issues=[ + "The value of actionStatus MUST be one of the allowed values:" + + " PotentialActionStatus; ActiveActionStatus; CompletedActionStatus; FailedActionStatus." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_not_mentioned(): + """ + Test a Five Safes Crate where the Sign-Off phase is not mentioned by the MainRootEntity. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:mentions <#signoff-3b741265-cfef-49ea-8138-a2fa149bf2f0> . + } + WHERE { + <./> schema:mentions <#signoff-3b741265-cfef-49ea-8138-a2fa149bf2f0> . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhase"], + expected_triggered_issues=[ + "The Root Data Entity SHOULD mention a Sign-Off Phase Object" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_no_endtime(): + """ + Test a Five Safes Crate where the Sign-Off phase has no endTime. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:endTime ?endTime . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:endTime ?endTime . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhaseEndTime"], + expected_triggered_issues=[ + "Sign Off object SHOULD have endTime property if action completed or failed." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_malformed_endtime(): + """ + Test a Five Safes Crate where the Sign-Off phase has an endTime + in the wrong format. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:endTime ?endTime . + } + INSERT { + ?signoff schema:endTime <2025-10-20> . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:endTime ?endTime . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_no_starttime(): + """ + Test a Five Safes Crate where the Sign-Off phase has no startTime. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:startTime ?startTime . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:startTime ?startTime . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.OPTIONAL, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhaseStartTime"], + expected_triggered_issues=[ + "Sign Off object MAY have a startTime property if action is active, completed or failed." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_malformed_starttime(): + """ + Test a Five Safes Crate where the Sign-Off phase has a startTime + in the wrong format. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:startTime ?startTime . + } + INSERT { + ?signoff schema:startTime <2025-10-20> . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:startTime ?startTime . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.OPTIONAL, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_no_actionstatus(): + """ + Test a Five Safes Crate where the Sign-Off phase has no actionStatus. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:actionStatus ?actionStatus . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:actionStatus ?actionStatus . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhaseProperties"], + expected_triggered_issues=["The Sign-Off Phase SHOULD have an actionStatus"], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_no_agent(): + """ + Test a Five Safes Crate where the Sign-Off phase has no agent. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:agent ?agent . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:agent ?agent . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhaseProperties"], + expected_triggered_issues=["The Sign-Off Phase SHOULD have an agent"], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_no_instrument(): + """ + Test a Five Safes Crate where the Sign-Off phase has no TRE policy (instrument). + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:instrument ?instrument . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:instrument ?instrument . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhaseProperties"], + expected_triggered_issues=[ + "The Sign-Off Phase SHOULD have an TRE policy (instrument) with type CreativeWork" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_instrument_not_iri(): + """ + Test a Five Safes Crate where the Sign-Off phase TRE policy (instrument) is not an IRI. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:instrument ?instrument . + } + INSERT { + ?signoff schema:instrument "Not a cross-reference" . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:instrument ?instrument . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhaseProperties"], + expected_triggered_issues=[ + "The Sign-Off Phase SHOULD have an TRE policy (instrument) with type CreativeWork" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_instrument_no_type(): + """ + Test a Five Safes Crate where the Sign-Off phase instrument has no type. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?instrument rdf:type ?type . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:instrument ?instrument . + ?instrument rdf:type ?type . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhaseProperties"], + expected_triggered_issues=[ + "The Sign-Off Phase SHOULD have an TRE policy (instrument) with type CreativeWork" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_instrument_no_name(): + """ + Test a Five Safes Crate where the Sign-Off phase instrument has no type. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?instrument schema:name ?name . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:instrument ?instrument . + ?instrument schema:name ?name . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhaseProperties"], + expected_triggered_issues=[ + "The Sign-Off Phase SHOULD have an TRE policy (instrument) with a human-readable name" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_object_notworkflow(): + """ + Test a Five Safes Crate where there is no workflow in the Sign-Off objects. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:object . + } + INSERT { + ?signoff schema:object . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:object . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhaseProperties"], + expected_triggered_issues=[ + "The Sign-Off Phase SHOULD list the workflow (mainEntity) as an object" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_signoff_phase_object_not_responsible_project(): + """ + Test a Five Safes Crate where there is no Responsible Project in the Sign-Off objects. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?signoff schema:object <#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70> . + } + INSERT { + ?signoff schema:object . + } + WHERE { + ?signoff a schema:AssessAction ; + schema:additionalType ; + schema:object <#project-be6ffb55-4f5a-4c14-b60e-47e0951090c70> . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["SignOffPhaseProperties"], + expected_triggered_issues=[ + "The Sign-Off Phase SHOULD list the Responsible Project (sourceOrganization) as an object" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_5_profile_conformance.py b/tests/integration/profiles/five-safes-crate/test_5src_5_profile_conformance.py new file mode 100644 index 000000000..313e794e4 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_5_profile_conformance.py @@ -0,0 +1,193 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +# ----- SHOULD fails tests + + +def test_5src_root_data_entity_missing_conformsto_property(): + """ + Test a Five Safes Crate where the RootDataEntity does not have the conformsTo property. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?rootdataentity dct:conformsTo ?profile . + } + WHERE { + ?metadatafile a schema:CreativeWork ; + schema:about ?rootdataentity . + ?rootdataentity dct:conformsTo ?profile . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "Root Dataset SHOULD include `conformsTo` https://w3id.org/5s-crate/0.4" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_root_data_entity_conforms_to_wrong_profile(): + """ + Test a Five Safes Crate where the RootDataEntity does not conform to the expected profile. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?rootdataentity dct:conformsTo ?profile . + } + INSERT { + ?rootdataentity dct:conformsTo "This is not the IRI to the 5sc profile" + } + WHERE { + ?metadatafile a schema:CreativeWork ; + schema:about ?rootdataentity . + ?rootdataentity dct:conformsTo ?profile . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "Root Dataset SHOULD include `conformsTo` https://w3id.org/5s-crate/0.4" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_root_data_entity_has_publisher_but_not_date_published(): + """ + Test a Five Safes Crate where the RootDataEntity has published but not date published. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?rootdataentity schema:datePublished ?datePublished . + } + WHERE { + ?metadatafile a schema:CreativeWork ; + schema:about ?rootdataentity . + ?rootdataentity schema:publisher ?publisher ; + schema:datePublished ?datePublished . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["datePublished present on published crates"], + expected_triggered_issues=[ + "A crate SHOULD have a publishedDate if and only if it has a publisher." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_root_data_entity_has_date_published_but_not_publisher(): + """ + Test a Five Safes Crate where the RootDataEntity has published but not date published. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?rootdataentity schema:publisher ?publisher . + } + WHERE { + ?metadatafile a schema:CreativeWork ; + schema:about ?rootdataentity . + ?rootdataentity schema:publisher ?publisher ; + schema:datePublished ?datePublished . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["datePublished present on published crates"], + expected_triggered_issues=[ + "A crate SHOULD have a publishedDate if and only if it has a publisher." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_root_data_entity_has_publisher_but_not_license(): + """ + Test a Five Safes Crate where the RootDataEntity has publisher but not license. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?rootdataentity schema:license ?license . + } + WHERE { + ?metadatafile a schema:CreativeWork ; + schema:about ?rootdataentity . + ?rootdataentity schema:publisher ?publisher ; + schema:license ?license . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["License present on published crates"], + expected_triggered_issues=[ + "Profile Conformance: Published crates SHOULD include a license." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- MAY fails tests diff --git a/tests/integration/profiles/five-safes-crate/test_5src_6_workflow_reference.py b/tests/integration/profiles/five-safes-crate/test_5src_6_workflow_reference.py new file mode 100644 index 000000000..1271adffd --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_6_workflow_reference.py @@ -0,0 +1,258 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_5src_root_data_entity_two_main_entities(): + """ + Add a 2nd RootDataEntity's mainEntity so maxCount=1 is violated. + """ + sparql = ( + SPARQL_PREFIXES + + """ + INSERT { + # add an IRI that is NOT typed as schema:Dataset (e.g. a schema:SoftwareSourceCode) + <./> schema:mainEntity . + a schema:Dataset . + } + WHERE { + <./> schema:mainEntity ?m . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "The RootDataEntity MUST have exactly one mainEntity property that is an IRI." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_root_data_entity_no_main_entity(): + """ + Remove the RootDataEntity's mainEntity so minCount=1 is violated. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:mainEntity ?m . + } + WHERE { + <./> schema:mainEntity ?m . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "The RootDataEntity MUST have exactly one mainEntity property that is an IRI." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_root_data_entity_main_entity_not_dataset_iri(): + """ + Test a Five Safes Crate where the RootDataEntity's mainEntity is an IRI but not typed as schema:Dataset. + (We point mainEntity to a new crate-local entity typed as something else.) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:mainEntity ?m . + } + INSERT { + # add an IRI that is NOT typed as schema:Dataset (e.g. a schema:SoftwareSourceCode) + <./> schema:mainEntity <./not-a-dataset> . + <./not-a-dataset> a schema:SoftwareSourceCode . + } + WHERE { + <./> schema:mainEntity ?m . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "The mainEntity pointed to by the RootDataEntity MUST be of type Dataset" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_main_entity_conformsTo_absent(): + """ + Test a Five Safes Crate where the mainEntity does not have the purl:conformsTo property + (we remove from the graph the triplet 'mainEntity conformsTo ?o'). + """ + sparql = ( + SPARQL_PREFIXES + + """ + PREFIX purl: + DELETE { + ?dataset purl:conformsTo ?o . + } + WHERE { + <./> schema:mainEntity ?dataset . + ?dataset purl:conformsTo ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["mainEntity"], + expected_triggered_issues=[ + "mainEntity MUST have one and only one `conformsTo` property." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_main_entity_has_two_conformsto(): + """ + Test a Five Safes Crate where the mainEntity -> purl:conformsTo has two + objects. + (we add second object to mainEntity -> purl:conformsTo to violate maxCount 1). + """ + sparql = ( + SPARQL_PREFIXES + + """ + PREFIX purl: + INSERT { + ?main purl:conformsTo . + } + WHERE { + <./> schema:mainEntity ?main . + ?main purl:conformsTo ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["mainEntity"], + expected_triggered_issues=[ + "mainEntity MUST have one and only one `conformsTo` property." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_main_entity_conformsTo_invalid(): + """ + Test a Five Safes Crate where the mainEntity's conformsTo IRI does NOT start with + "https://w3id.org/workflowhub/workflow-ro-crate" (violates the SHACL SPARQL constraint). + """ + sparql = ( + SPARQL_PREFIXES + + """ + PREFIX purl: + DELETE { + ?dataset purl:conformsTo ?iri . + } + INSERT { + ?dataset purl:conformsTo . + } + WHERE { + <./> schema:mainEntity ?dataset . + ?dataset purl:conformsTo ?iri . + FILTER(STRSTARTS(STR(?iri), "https://w3id.org/workflowhub/workflow-ro-crate")) + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["mainEntity"], + expected_triggered_issues=[ + "conformsTo IRI must start with https://w3id.org/workflowhub/workflow-ro-crate" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- SHOULD fails tests + + +def test_5src_main_entity_missing_distribution_warning(): + """ + Test a Five Safes Crate where a mainEntity has an HTTP(S) IRI but no distribution with an HTTP(S) URL. + This should trigger the SHACL warning about missing or non-HTTP(S) distributions. + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?dataset schema:distribution ?dist . + } + WHERE { + <./> schema:mainEntity ?dataset . + ?dataset schema:distribution ?dist . + FILTER (STRSTARTS(STR(?dataset), "http://") || STRSTARTS(STR(?dataset), "https://")) . + FILTER (STRSTARTS(STR(?dist), "http://") || STRSTARTS(STR(?dist), "https://")) . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["mainEntity"], + expected_triggered_issues=[ + "If mainEntity has an HTTP(S) @id SHOULD have at least one distribution with an HTTP(S) URL." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_7_requesting_workflow_run.py b/tests/integration/profiles/five-safes-crate/test_5src_7_requesting_workflow_run.py new file mode 100644 index 000000000..b58147651 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_7_requesting_workflow_run.py @@ -0,0 +1,200 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_rocrate_does_not_have_createaction(): + """ + Test a Five Safes Crate where no `CreateAction` entity exists. + (We remove the entire CreateAction entity from the RO-Crate) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?action ?p ?o . + ?s ?p2 ?action . + } + WHERE { + ?action a schema:CreateAction . + ?action ?p ?o . + OPTIONAL { ?s ?p2 ?action . } + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "`RootDataEntity` MUST reference at least one `CreateAction`", + " (corresponding to the workflow run) through `mentions`", + ( + "The CreateAction entity corresponding to the workflow MUST " + "reference, as an instrument, the entity that is referenced " + "as mainEntity by the RO-Crate" + ), + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_rootdataentity_does_not_have_mentions_property(): + """ + Test a Five Safes Crate where RootDataEntity does not have the property mentions. + (We remove the property mentions from the RootDataEntity entity) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:mentions ?o . + } + WHERE { + <./> schema:mentions ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "`RootDataEntity` MUST reference at least one `CreateAction`", + " (corresponding to the workflow run) through `mentions`", + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_rootdataentity_does_not_mention_create_action(): + """ + Test a Five Safes Crate where `RootDataEntity` does not `mention` a `CreateAction` entity. + (We replace the object of RooDataEntity --> mentions with a string literal). + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:mentions ?o . + } + INSERT { + <./> schema:mentions "This is not a CreateAction entity" . + } + WHERE { + <./> schema:mentions ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "`RootDataEntity` MUST reference at least one `CreateAction`", + " (corresponding to the workflow run) through `mentions`", + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_createaction_object_does_not_reference_existing_entities(): + """ + Test a Five Safes Crate where `CreateAction` --> `object` does not + reference an existing entity in the RO-Crate. + (We replace the objects of `CreateAction` --> `object` with a literal.`) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?action schema:object ?o . + } + INSERT { + ?action schema:object "This is not an entity in the RO-Crate" . + } + WHERE { + ?action schema:object ?o ; + a schema:CreateAction . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["WorkflowRunAction"], + expected_triggered_issues=[ + "In the `CreateAction` entity corresponding to the workflow run," + + " each `object` MUST reference an existing entity." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- SHOULD fails tests + + +def test_createaction_does_not_have_object_property(): + """ + Test a Five Safes Crate where `CreateAction` does not have the property `object`. + (We remove the property `object` from `CreateAction`) + """ + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?action schema:object ?o . + } + WHERE { + ?action schema:object ?o ; + a schema:CreateAction . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["WorkflowRunAction"], + expected_triggered_issues=[ + "`CreateAction` (corresponding to the workflow run) SHOULD have the property `object` with IRI values." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_8_disclosure_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_8_disclosure_phase.py new file mode 100644 index 000000000..b50710cc8 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_8_disclosure_phase.py @@ -0,0 +1,373 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- MUST fails tests + + +def test_5src_disclosure_object_with_no_name(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:name ?name . + } + WHERE { + ?this schema:additionalType shp:DisclosureCheck ; + schema:name ?name . + <./> schema:mentions ?this . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["DisclosureCheck"], + expected_triggered_issues=[ + "`DisclosureCheck` MUST have a name string of at least 10 characters." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_disclosure_object_with_name_not_string(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:name ?name . + } + INSERT { + ?this schema:name 123 . + } + WHERE { + ?this schema:additionalType shp:DisclosureCheck ; + schema:name ?name . + <./> schema:mentions ?this . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["DisclosureCheck"], + expected_triggered_issues=[ + "`DisclosureCheck` MUST have a name string of at least 10 characters." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_disclosure_object_with_not_long_enough_name(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this schema:name ?name . + } + INSERT { + ?this schema:name "Short" . + } + WHERE { + ?this schema:additionalType shp:DisclosureCheck ; + schema:name ?name . + <./> schema:mentions ?this . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["DisclosureCheck"], + expected_triggered_issues=[ + "`DisclosureCheck` MUST have a name string of at least 10 characters." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_disclosure_object_not_an_assess_action(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?this rdf:type schema:AssessAction . + } + INSERT { + ?this rdf:type "Not an AssessAction type" . + } + WHERE { + ?this rdf:type schema:AssessAction ; + schema:additionalType shp:DisclosureCheck . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["DisclosureCheck"], + expected_triggered_issues=["`DisclosureCheck` MUST be a `AssessAction`."], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_disclosure_object_with_no_proper_action_status(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:actionStatus ?o . + } + INSERT { + ?s schema:actionStatus "This is not a proper actionStatus" . + } + WHERE { + ?s schema:actionStatus ?o ; + schema:additionalType shp:DisclosureCheck . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["DisclosureCheck"], + expected_triggered_issues=[ + ( + "`DisclosureCheck` MUST have an actionStatus with an allowed value " + "(see https://schema.org/ActionStatusType)." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_disclosure_object_has_no_properly_formatted_start_time(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:startTime ?time . + } + INSERT { + ?s schema:startTime "1st Dec '25 @ 10:00:00" . + } + WHERE { + ?s schema:additionalType shp:DisclosureCheck ; + schema:startTime ?time . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_disclosure_object_has_no_properly_formatted_end_time(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:endTime ?time . + } + INSERT { + ?s schema:endTime "1st Dec '25 @ 10:00:00" . + } + WHERE { + ?s schema:additionalType shp:DisclosureCheck ; + schema:endTime ?time . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.REQUIRED, + expected_validation_result=False, + expected_triggered_requirements=["Timestamp Format"], + expected_triggered_issues=[ + ( + "All `startTime` and `endTime` values MUST follow the RFC 3339 standard " + "(YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- SHOULD fails tests + + +def test_5src_disclosure_object_not_mentioned_by_root_data_entity(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + <./> schema:mentions ?o . + } + WHERE { + ?o schema:additionalType shp:DisclosureCheck . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["RootDataEntity"], + expected_triggered_issues=[ + "`RootDataEntity` SHOULD mention a disclosure object." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_disclosure_object_with_no_action_status(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:actionStatus ?o . + } + WHERE { + ?s schema:additionalType shp:DisclosureCheck ; + schema:actionStatus ?o . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["DisclosureCheck"], + expected_triggered_issues=[ + "The `DisclosureCheck` SHOULD have `actionStatus` property." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +def test_5src_disclosure_object_has_no_end_time_if_ended(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:endTime ?o . + } + WHERE { + ?s schema:additionalType shp:DisclosureCheck ; + schema:endTime ?o ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus" + )) + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["DisclosureCheck"], + expected_triggered_issues=[ + ( + "`DisclosureCheck` SHOULD have the `endTime` property if `actionStatus` " + "is either CompletedActionStatus or FailedActionStatus." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +# ----- MAY fails tests + + +def test_5src_disclosure_object_has_no_start_time_if_begun(): + sparql = ( + SPARQL_PREFIXES + + """ + DELETE { + ?s schema:startTime ?o . + } + WHERE { + ?s schema:additionalType shp:DisclosureCheck ; + schema:startTime ?o ; + schema:actionStatus ?status . + FILTER(?status IN ( + "http://schema.org/CompletedActionStatus", + "http://schema.org/FailedActionStatus", + "http://schema.org/ActiveActionStatus" + )) + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_result, + requirement_severity=Severity.OPTIONAL, + expected_validation_result=False, + expected_triggered_requirements=["DisclosureCheck"], + expected_triggered_issues=[ + ( + "`DisclosureCheck` MAY have the `startTime` property if `actionStatus` " + "is either ActiveActionStatus, CompletedActionStatus or FailedActionStatus." + ) + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_9_inputs.py b/tests/integration/profiles/five-safes-crate/test_5src_9_inputs.py new file mode 100644 index 000000000..e6517098d --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_9_inputs.py @@ -0,0 +1,64 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test, SPARQL_PREFIXES + +# set up logging +logger = logging.getLogger(__name__) + + +# ----- SHOULD fails tests + + +def test_input_does_not_reference_formalparameter(): + """ + Test a Five Safes Crate where an input entity does not reference a + `bioschemas:FormalParameter using `schema:exampleOfWork`. + (We replace the ?object of input --> exampleOfWork with a literal) + """ + sparql = ( + SPARQL_PREFIXES + + """ + PREFIX bioschemas: + DELETE { + ?input schema:exampleOfWork ?formalParameter . + } + INSERT { + ?input schema:exampleOfWork "not-a-formal-parameter" . + } + WHERE { + ?input schema:exampleOfWork ?formalParameter . + ?formalParameter a bioschemas:FormalParameter . + ?action a schema:CreateAction ; + schema:object ?input . + } + """ + ) + + do_entity_test( + rocrate_path=ValidROC().five_safes_crate_request, + requirement_severity=Severity.RECOMMENDED, + expected_validation_result=False, + expected_triggered_requirements=["Input"], + expected_triggered_issues=[ + "Input SHOULD reference a FormalParameter using exampleOfWork" + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/integration/profiles/five-safes-crate/test_valid_5src.py b/tests/integration/profiles/five-safes-crate/test_valid_5src.py new file mode 100644 index 000000000..04bb321b1 --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_valid_5src.py @@ -0,0 +1,119 @@ +# Copyright (c) 2024-2025 CRS4 +# Copyright (c) 2025-2026 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. + +import logging +import pytest + +from rocrate_validator import services +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +from tests.shared import do_entity_test + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +# Dynamically fetch the SKIP_WEB_RESOURCE_AVAILABILITY_IDENTIFIER +# required as disable_inherited_profiles_reporting does not disable Python checks from +# inherited profiles (https://github.com/crs4/rocrate-validator/issues/135) +rocrate_profile = services.get_profile("ro-crate-1.2") +if not rocrate_profile: + raise RuntimeError("Unable to load the RO-Crate 1.2 profile") +check_local_data_entity_existence = rocrate_profile.get_requirement_check( + "Data Entity: REQUIRED resource availability" +) +assert check_local_data_entity_existence, "Unable to find the requirement 'Data Entity: REQUIRED resource availability'" +SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER = check_local_data_entity_existence.identifier +check_local_data_entity_existence = rocrate_profile.get_requirement_check( + "Web-based Data Entity: REQUIRED resource availability" +) +assert ( + check_local_data_entity_existence +), "Unable to find the requirement 'Web-based Data Entity: REQUIRED resource availability'" +SKIP_WEB_RESOURCE_AVAILABILITY_IDENTIFIER = check_local_data_entity_existence.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, + Severity.REQUIRED, + True, + profile_identifier="five-safes-crate", + skip_checks=[ + SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, + ], + ) + + +@pytest.mark.xfail( + reason=""" + Checks that ensure certain Five Safes actions are present currently fail for this crate, + as this crate represents an early stage of a process before those actions have happened. + """ +) +def test_valid_five_safes_crate_request_recommended(): + """Test a valid Five Safes Crate representing a request.""" + do_entity_test( + ValidROC().five_safes_crate_request, + Severity.RECOMMENDED, + True, + profile_identifier="five-safes-crate", + skip_checks=[ + SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, + SKIP_WEB_RESOURCE_AVAILABILITY_IDENTIFIER, + ], + disable_inherited_profiles_issue_reporting=True, + ) + + +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, + Severity.REQUIRED, + True, + profile_identifier="five-safes-crate", + skip_checks=[ + SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, + ], + ) + + +def test_valid_five_safes_crate_result_recommended(): + """Test a valid Five Safes Crate representing a result.""" + do_entity_test( + ValidROC().five_safes_crate_result, + Severity.RECOMMENDED, + True, + profile_identifier="five-safes-crate", + skip_checks=[ + SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, + SKIP_WEB_RESOURCE_AVAILABILITY_IDENTIFIER, + ], + disable_inherited_profiles_issue_reporting=True, + ) + + +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, + Severity.REQUIRED, + True, + profile_identifier="five-safes-crate", + skip_checks=[ + SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER, + ], + ) diff --git a/tests/ro_crates.py b/tests/ro_crates.py index facb1457d..4a450c9d0 100644 --- a/tests/ro_crates.py +++ b/tests/ro_crates.py @@ -121,6 +121,18 @@ def isa_ro_crate(self) -> Path: def multi_profile_crate(self) -> Path: return VALID_CRATES_DATA_PATH / "multi-profile-crate" + @property + def five_safes_crate_request(self) -> Path: + return VALID_CRATES_DATA_PATH / "five-safes-crate-request" + + @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: base_path = INVALID_CRATES_DATA_PATH / "0_file_descriptor_format" @@ -991,6 +1003,18 @@ def propertyvalue_no_unitcode(self) -> Path: return self.base_path / "propertyvalue_no_unitcode" +class Invalid5sROC: + base_path = INVALID_CRATES_DATA_PATH / "five_safes_crate" + + @property + def context_multiple_wrong_version(self) -> Path: + return self.base_path / "context_multiple_wrong_version" + + @property + def context_single_wrong_version(self) -> Path: + return self.base_path / "context_single_wrong_version" + + class InvalidMultiProfileROC: @property def invalid_multi_profile_crate(self) -> Path: diff --git a/tests/shared.py b/tests/shared.py index 9fbe3bce2..30a98ec65 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -35,7 +35,13 @@ T = TypeVar("T") -SPARQL_PREFIXES = "PREFIX schema: " +SPARQL_PREFIXES = """ +PREFIX schema: +PREFIX shp: +PREFIX rdf: +PREFIX rocrate: +PREFIX dct: +""" def first(c: Collection[T]) -> T: From 9b12bc79fe5692d8cb1892b5682cb44d27f75c48 Mon Sep 17 00:00:00 2001 From: EttoreM Date: Thu, 2 Jul 2026 15:40:49 +0100 Subject: [PATCH 2/3] Amended formatting --- .../five-safes-crate/test_5src_10_outputs.py | 6 ++-- .../test_5src_11_workflow_execution.py | 6 ++-- .../test_5src_13_validation_phase.py | 18 +++------- .../test_5src_14_workflow_retrieval_phase.py | 23 ++++-------- .../test_5src_15_metadata_file.py | 8 ++--- .../test_5src_16_publishing_phase.py | 9 ++--- .../test_5src_1_requesting_agent.py | 10 ++---- .../test_5src_1_responsible_project.py | 18 +++------- .../test_5src_1_root_data_entity_metadata.py | 6 ++-- .../test_5src_2_requesting_agent.py | 6 ++-- .../test_5src_4_signoff_phase.py | 36 ++++++------------- .../test_5src_5_profile_conformance.py | 22 ++++-------- .../test_5src_6_workflow_reference.py | 26 ++++---------- .../test_5src_7_requesting_workflow_run.py | 4 +-- .../test_5src_8_disclosure_phase.py | 22 ++++-------- .../five-safes-crate/test_5src_9_inputs.py | 6 ++-- .../five-safes-crate/test_valid_5src.py | 11 +++--- 17 files changed, 71 insertions(+), 166 deletions(-) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_10_outputs.py b/tests/integration/profiles/five-safes-crate/test_5src_10_outputs.py index 1cbdbe6d0..15dcc2449 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_10_outputs.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_10_outputs.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -87,9 +87,7 @@ def test_result_output_does_not_have_allowed_type(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["Output"], - expected_triggered_issues=[ - "Result SHOULD have a `@type` among an allowed set of values." - ], + expected_triggered_issues=["Result SHOULD have a `@type` among an allowed set of values."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py b/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py index a271978a8..9f54354d9 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -267,9 +267,7 @@ def test_5src_workflow_object_has_no_end_time_if_ended(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["WorkflowExecution"], - expected_triggered_issues=[ - "The workflow execution object SHOULD have an endTime property if it has ended." - ], + expected_triggered_issues=["The workflow execution object SHOULD have an endTime property if it has ended."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_13_validation_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_13_validation_phase.py index be6f44906..c19e54663 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_13_validation_phase.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_13_validation_phase.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -75,9 +75,7 @@ def test_5src_validation_check_name_not_a_string(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["ValidationCheck"], - expected_triggered_issues=[ - "ValidationCheck MUST have a human readable name string." - ], + expected_triggered_issues=["ValidationCheck MUST have a human readable name string."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -200,9 +198,7 @@ def test_5src_root_data_entity_does_not_mention_validation_check_entity(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["RootDataEntity"], - expected_triggered_issues=[ - "RootDataEntity SHOULD mention a ValidationCheck object." - ], + expected_triggered_issues=["RootDataEntity SHOULD mention a ValidationCheck object."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -229,9 +225,7 @@ def test_5src_validation_check_object_does_not_point_to_root_data_entity(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["ValidationCheck"], - expected_triggered_issues=[ - "`ValidationCheck` --> `object` SHOULD point to the root of the RO-Crate" - ], + expected_triggered_issues=["`ValidationCheck` --> `object` SHOULD point to the root of the RO-Crate"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -283,9 +277,7 @@ def test_5src_Validation_check_does_not_have_action_status_property(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["ValidationCheck"], - expected_triggered_issues=[ - "ValidationCheck SHOULD have actionStatus property." - ], + expected_triggered_issues=["ValidationCheck SHOULD have actionStatus property."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_14_workflow_retrieval_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_14_workflow_retrieval_phase.py index 52228408e..c7d92d0cd 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_14_workflow_retrieval_phase.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_14_workflow_retrieval_phase.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -45,9 +45,7 @@ def test_5src_download_action_does_not_have_name(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["DownloadAction"], - expected_triggered_issues=[ - "DownloadAction MUST have a human readable name string." - ], + expected_triggered_issues=["DownloadAction MUST have a human readable name string."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -74,9 +72,7 @@ def test_5src_download_action_name_not_a_string(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["DownloadAction"], - expected_triggered_issues=[ - "DownloadAction MUST have a human readable name string." - ], + expected_triggered_issues=["DownloadAction MUST have a human readable name string."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -206,10 +202,7 @@ def test_5src_downloaded_workflow_distribution_is_not_the_same_as_download_actio expected_validation_result=False, expected_triggered_requirements=["Downloaded Workflow"], expected_triggered_issues=[ - ( - "DownloadedWorkflow --> `distribution` MUST reference " - "the same entity as `DownloadAction` --> `object`." - ) + ("DownloadedWorkflow --> `distribution` MUST reference the same entity as `DownloadAction` --> `object`.") ], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, @@ -295,9 +288,7 @@ def test_5src_root_data_entity_does_not_mention_download_action_entity(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["RootDataEntity"], - expected_triggered_issues=[ - "RootDataEntity SHOULD mention DownloadAction if this exists." - ], + expected_triggered_issues=["RootDataEntity SHOULD mention DownloadAction if this exists."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -352,9 +343,7 @@ def test_5src_download_action_does_not_have_action_status_property(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["DownloadAction"], - expected_triggered_issues=[ - "`DownloadAction` SHOULD have `actionStatus` property." - ], + expected_triggered_issues=["`DownloadAction` SHOULD have `actionStatus` property."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) 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 e94a76fa9..a166ed4aa 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 @@ -16,8 +16,8 @@ import logging from rocrate_validator.models import Severity -from tests.ro_crates import ValidROC, Invalid5sROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.ro_crates import Invalid5sROC, ValidROC +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -47,9 +47,7 @@ def test_5src_conforms_to_old_version(): 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_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" diff --git a/tests/integration/profiles/five-safes-crate/test_5src_16_publishing_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_16_publishing_phase.py index 8149d1b24..485aeb026 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_16_publishing_phase.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_16_publishing_phase.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -44,12 +44,9 @@ def test_5src_assess_action_not_referenced_from_rde(): rocrate_path=ValidROC().five_safes_crate_result, requirement_severity=Severity.REQUIRED, expected_validation_result=False, - expected_triggered_requirements=[ - "All AssessActions are mentioned from Root Data Entity" - ], + expected_triggered_requirements=["All AssessActions are mentioned from Root Data Entity"], expected_triggered_issues=[ - "All AssessAction entities in the crate MUST be referenced from " - "the Root Dataset via `mentions`." + "All AssessAction entities in the crate MUST be referenced from the Root Dataset via `mentions`." ], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, diff --git a/tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py b/tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py index 1ab9b4b37..84c1372a7 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_1_requesting_agent.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -54,9 +54,7 @@ def test_5src_agent_memberOf_not_project(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["Requesting Agent"], - expected_triggered_issues=[ - "The 'memberOf' property of an agent MUST be of type Project." - ], + expected_triggered_issues=["The 'memberOf' property of an agent MUST be of type Project."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -131,9 +129,7 @@ def test_5src_agent_memberOf_missing_warning(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, # or True if warnings are not treated as failures expected_triggered_requirements=["Requesting Agent"], - expected_triggered_issues=[ - "The Requesting Agent SHOULD have a `memberOf` property." - ], + expected_triggered_issues=["The Requesting Agent SHOULD have a `memberOf` property."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py b/tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py index 1f5c9664b..96bda9e82 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_1_responsible_project.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -56,9 +56,7 @@ def test_5src_responsible_project_funding_not_grant(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["Responsible Project"], - expected_triggered_issues=[ - "The property 'funding' of the Responsible Project MUST be of type Grant." - ], + expected_triggered_issues=["The property 'funding' of the Responsible Project MUST be of type Grant."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -136,9 +134,7 @@ def test_5src_responsible_project_member_and_agent_affiliation_no_intersection() rocrate_path=ValidROC().five_safes_crate_request, requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, - expected_triggered_requirements=[ - "Organizations (members of Responsible Project)" - ], + expected_triggered_requirements=["Organizations (members of Responsible Project)"], expected_triggered_issues=[ ( "At least one of the organisations that are members of the responsible project SHOULD " @@ -178,9 +174,7 @@ def test_5src_responsible_project_missing_funding_property(): requirement_severity=Severity.OPTIONAL, expected_validation_result=False, expected_triggered_requirements=["Responsible Project"], - expected_triggered_issues=[ - "The Responsible Project does not have the property `funding`." - ], + expected_triggered_issues=["The Responsible Project does not have the property `funding`."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -211,9 +205,7 @@ def test_5src_responsible_project_missing_member_property(): requirement_severity=Severity.OPTIONAL, expected_validation_result=False, expected_triggered_requirements=["Responsible Project"], - expected_triggered_issues=[ - "The Responsible Project does not have the property `member`." - ], + expected_triggered_issues=["The Responsible Project does not have the property `member`."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py b/tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py index 23c51832d..19e3f21fe 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_1_root_data_entity_metadata.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -47,9 +47,7 @@ def test_5src_root_data_entity_without_source_organization(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["RootDataEntity"], - expected_triggered_issues=[ - """The Root Data Entity MUST have a `sourceOrganization` property.""" - ], + expected_triggered_issues=["""The Root Data Entity MUST have a `sourceOrganization` property."""], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_2_requesting_agent.py b/tests/integration/profiles/five-safes-crate/test_5src_2_requesting_agent.py index 41a4279d1..7f496cdac 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_2_requesting_agent.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_2_requesting_agent.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -50,9 +50,7 @@ def test_createaction_does_not_have_agent(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["CreateAction"], - expected_triggered_issues=[ - "CreateAction MUST have at least one agent that is a contextual entity." - ], + expected_triggered_issues=["CreateAction MUST have at least one agent that is a contextual entity."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_4_signoff_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_4_signoff_phase.py index a98c850bd..79d08229f 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_4_signoff_phase.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_4_signoff_phase.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -48,9 +48,7 @@ def test_5src_no_signoff_phase(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["SignOffPhase"], - expected_triggered_issues=[ - "There SHOULD be a Sign-Off Phase in the Final RO-Crate" - ], + expected_triggered_issues=["There SHOULD be a Sign-Off Phase in the Final RO-Crate"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -79,9 +77,7 @@ def test_5src_signoff_phase_no_name(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["SignOff"], - expected_triggered_issues=[ - "Sign Off phase MUST have a human-readable name string." - ], + expected_triggered_issues=["Sign Off phase MUST have a human-readable name string."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -147,7 +143,7 @@ def test_5src_signoff_phase_wrong_action_status(): expected_triggered_requirements=["SignOffStatus"], expected_triggered_issues=[ "The value of actionStatus MUST be one of the allowed values:" - + " PotentialActionStatus; ActiveActionStatus; CompletedActionStatus; FailedActionStatus." + " PotentialActionStatus; ActiveActionStatus; CompletedActionStatus; FailedActionStatus." ], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, @@ -175,9 +171,7 @@ def test_5src_signoff_phase_not_mentioned(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["SignOffPhase"], - expected_triggered_issues=[ - "The Root Data Entity SHOULD mention a Sign-Off Phase Object" - ], + expected_triggered_issues=["The Root Data Entity SHOULD mention a Sign-Off Phase Object"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -206,9 +200,7 @@ def test_5src_signoff_phase_no_endtime(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["SignOffPhaseEndTime"], - expected_triggered_issues=[ - "Sign Off object SHOULD have endTime property if action completed or failed." - ], + expected_triggered_issues=["Sign Off object SHOULD have endTime property if action completed or failed."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -402,9 +394,7 @@ def test_5src_signoff_phase_no_instrument(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["SignOffPhaseProperties"], - expected_triggered_issues=[ - "The Sign-Off Phase SHOULD have an TRE policy (instrument) with type CreativeWork" - ], + expected_triggered_issues=["The Sign-Off Phase SHOULD have an TRE policy (instrument) with type CreativeWork"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -436,9 +426,7 @@ def test_5src_signoff_phase_instrument_not_iri(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["SignOffPhaseProperties"], - expected_triggered_issues=[ - "The Sign-Off Phase SHOULD have an TRE policy (instrument) with type CreativeWork" - ], + expected_triggered_issues=["The Sign-Off Phase SHOULD have an TRE policy (instrument) with type CreativeWork"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -468,9 +456,7 @@ def test_5src_signoff_phase_instrument_no_type(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["SignOffPhaseProperties"], - expected_triggered_issues=[ - "The Sign-Off Phase SHOULD have an TRE policy (instrument) with type CreativeWork" - ], + expected_triggered_issues=["The Sign-Off Phase SHOULD have an TRE policy (instrument) with type CreativeWork"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -534,9 +520,7 @@ def test_5src_signoff_phase_object_notworkflow(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["SignOffPhaseProperties"], - expected_triggered_issues=[ - "The Sign-Off Phase SHOULD list the workflow (mainEntity) as an object" - ], + expected_triggered_issues=["The Sign-Off Phase SHOULD list the workflow (mainEntity) as an object"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_5_profile_conformance.py b/tests/integration/profiles/five-safes-crate/test_5src_5_profile_conformance.py index 313e794e4..8012a37b4 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_5_profile_conformance.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_5_profile_conformance.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -52,9 +52,7 @@ def test_5src_root_data_entity_missing_conformsto_property(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["RootDataEntity"], - expected_triggered_issues=[ - "Root Dataset SHOULD include `conformsTo` https://w3id.org/5s-crate/0.4" - ], + expected_triggered_issues=["Root Dataset SHOULD include `conformsTo` https://w3id.org/5s-crate/0.4"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -86,9 +84,7 @@ def test_5src_root_data_entity_conforms_to_wrong_profile(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["RootDataEntity"], - expected_triggered_issues=[ - "Root Dataset SHOULD include `conformsTo` https://w3id.org/5s-crate/0.4" - ], + expected_triggered_issues=["Root Dataset SHOULD include `conformsTo` https://w3id.org/5s-crate/0.4"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -118,9 +114,7 @@ def test_5src_root_data_entity_has_publisher_but_not_date_published(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["datePublished present on published crates"], - expected_triggered_issues=[ - "A crate SHOULD have a publishedDate if and only if it has a publisher." - ], + expected_triggered_issues=["A crate SHOULD have a publishedDate if and only if it has a publisher."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -150,9 +144,7 @@ def test_5src_root_data_entity_has_date_published_but_not_publisher(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["datePublished present on published crates"], - expected_triggered_issues=[ - "A crate SHOULD have a publishedDate if and only if it has a publisher." - ], + expected_triggered_issues=["A crate SHOULD have a publishedDate if and only if it has a publisher."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -182,9 +174,7 @@ def test_5src_root_data_entity_has_publisher_but_not_license(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["License present on published crates"], - expected_triggered_issues=[ - "Profile Conformance: Published crates SHOULD include a license." - ], + expected_triggered_issues=["Profile Conformance: Published crates SHOULD include a license."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_6_workflow_reference.py b/tests/integration/profiles/five-safes-crate/test_5src_6_workflow_reference.py index 1271adffd..2aacf26e3 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_6_workflow_reference.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_6_workflow_reference.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -49,9 +49,7 @@ def test_5src_root_data_entity_two_main_entities(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["RootDataEntity"], - expected_triggered_issues=[ - "The RootDataEntity MUST have exactly one mainEntity property that is an IRI." - ], + expected_triggered_issues=["The RootDataEntity MUST have exactly one mainEntity property that is an IRI."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -78,9 +76,7 @@ def test_5src_root_data_entity_no_main_entity(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["RootDataEntity"], - expected_triggered_issues=[ - "The RootDataEntity MUST have exactly one mainEntity property that is an IRI." - ], + expected_triggered_issues=["The RootDataEntity MUST have exactly one mainEntity property that is an IRI."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -113,9 +109,7 @@ def test_5src_root_data_entity_main_entity_not_dataset_iri(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["RootDataEntity"], - expected_triggered_issues=[ - "The mainEntity pointed to by the RootDataEntity MUST be of type Dataset" - ], + expected_triggered_issues=["The mainEntity pointed to by the RootDataEntity MUST be of type Dataset"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -145,9 +139,7 @@ def test_5src_main_entity_conformsTo_absent(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["mainEntity"], - expected_triggered_issues=[ - "mainEntity MUST have one and only one `conformsTo` property." - ], + expected_triggered_issues=["mainEntity MUST have one and only one `conformsTo` property."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -178,9 +170,7 @@ def test_5src_main_entity_has_two_conformsto(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["mainEntity"], - expected_triggered_issues=[ - "mainEntity MUST have one and only one `conformsTo` property." - ], + expected_triggered_issues=["mainEntity MUST have one and only one `conformsTo` property."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -214,9 +204,7 @@ def test_5src_main_entity_conformsTo_invalid(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["mainEntity"], - expected_triggered_issues=[ - "conformsTo IRI must start with https://w3id.org/workflowhub/workflow-ro-crate" - ], + expected_triggered_issues=["conformsTo IRI must start with https://w3id.org/workflowhub/workflow-ro-crate"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_7_requesting_workflow_run.py b/tests/integration/profiles/five-safes-crate/test_5src_7_requesting_workflow_run.py index b58147651..c2a82ba3e 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_7_requesting_workflow_run.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_7_requesting_workflow_run.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -159,7 +159,7 @@ def test_createaction_object_does_not_reference_existing_entities(): expected_triggered_requirements=["WorkflowRunAction"], expected_triggered_issues=[ "In the `CreateAction` entity corresponding to the workflow run," - + " each `object` MUST reference an existing entity." + " each `object` MUST reference an existing entity." ], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, diff --git a/tests/integration/profiles/five-safes-crate/test_5src_8_disclosure_phase.py b/tests/integration/profiles/five-safes-crate/test_5src_8_disclosure_phase.py index b50710cc8..8113a5111 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_8_disclosure_phase.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_8_disclosure_phase.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -46,9 +46,7 @@ def test_5src_disclosure_object_with_no_name(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["DisclosureCheck"], - expected_triggered_issues=[ - "`DisclosureCheck` MUST have a name string of at least 10 characters." - ], + expected_triggered_issues=["`DisclosureCheck` MUST have a name string of at least 10 characters."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -77,9 +75,7 @@ def test_5src_disclosure_object_with_name_not_string(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["DisclosureCheck"], - expected_triggered_issues=[ - "`DisclosureCheck` MUST have a name string of at least 10 characters." - ], + expected_triggered_issues=["`DisclosureCheck` MUST have a name string of at least 10 characters."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -108,9 +104,7 @@ def test_5src_disclosure_object_with_not_long_enough_name(): requirement_severity=Severity.REQUIRED, expected_validation_result=False, expected_triggered_requirements=["DisclosureCheck"], - expected_triggered_issues=[ - "`DisclosureCheck` MUST have a name string of at least 10 characters." - ], + expected_triggered_issues=["`DisclosureCheck` MUST have a name string of at least 10 characters."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -264,9 +258,7 @@ def test_5src_disclosure_object_not_mentioned_by_root_data_entity(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["RootDataEntity"], - expected_triggered_issues=[ - "`RootDataEntity` SHOULD mention a disclosure object." - ], + expected_triggered_issues=["`RootDataEntity` SHOULD mention a disclosure object."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) @@ -291,9 +283,7 @@ def test_5src_disclosure_object_with_no_action_status(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["DisclosureCheck"], - expected_triggered_issues=[ - "The `DisclosureCheck` SHOULD have `actionStatus` property." - ], + expected_triggered_issues=["The `DisclosureCheck` SHOULD have `actionStatus` property."], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) diff --git a/tests/integration/profiles/five-safes-crate/test_5src_9_inputs.py b/tests/integration/profiles/five-safes-crate/test_5src_9_inputs.py index e6517098d..eb8efa7ef 100644 --- a/tests/integration/profiles/five-safes-crate/test_5src_9_inputs.py +++ b/tests/integration/profiles/five-safes-crate/test_5src_9_inputs.py @@ -17,7 +17,7 @@ from rocrate_validator.models import Severity from tests.ro_crates import ValidROC -from tests.shared import do_entity_test, SPARQL_PREFIXES +from tests.shared import SPARQL_PREFIXES, do_entity_test # set up logging logger = logging.getLogger(__name__) @@ -56,9 +56,7 @@ def test_input_does_not_reference_formalparameter(): requirement_severity=Severity.RECOMMENDED, expected_validation_result=False, expected_triggered_requirements=["Input"], - expected_triggered_issues=[ - "Input SHOULD reference a FormalParameter using exampleOfWork" - ], + expected_triggered_issues=["Input SHOULD reference a FormalParameter using exampleOfWork"], profile_identifier="five-safes-crate", rocrate_entity_mod_sparql=sparql, ) 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 04bb321b1..fdc4c143a 100644 --- a/tests/integration/profiles/five-safes-crate/test_valid_5src.py +++ b/tests/integration/profiles/five-safes-crate/test_valid_5src.py @@ -14,6 +14,7 @@ # limitations under the License. import logging + import pytest from rocrate_validator import services @@ -30,17 +31,15 @@ rocrate_profile = services.get_profile("ro-crate-1.2") if not rocrate_profile: raise RuntimeError("Unable to load the RO-Crate 1.2 profile") -check_local_data_entity_existence = rocrate_profile.get_requirement_check( - "Data Entity: REQUIRED resource availability" -) +check_local_data_entity_existence = rocrate_profile.get_requirement_check("Data Entity: REQUIRED resource availability") assert check_local_data_entity_existence, "Unable to find the requirement 'Data Entity: REQUIRED resource availability'" SKIP_LOCAL_DATA_ENTITY_EXISTENCE_CHECK_IDENTIFIER = check_local_data_entity_existence.identifier check_local_data_entity_existence = rocrate_profile.get_requirement_check( "Web-based Data Entity: REQUIRED resource availability" ) -assert ( - check_local_data_entity_existence -), "Unable to find the requirement 'Web-based Data Entity: REQUIRED resource availability'" +assert check_local_data_entity_existence, ( + "Unable to find the requirement 'Web-based Data Entity: REQUIRED resource availability'" +) SKIP_WEB_RESOURCE_AVAILABILITY_IDENTIFIER = check_local_data_entity_existence.identifier From 69db1e812140a9dd832b4ec0629abfb393abecae Mon Sep 17 00:00:00 2001 From: EttoreM Date: Thu, 2 Jul 2026 15:49:08 +0100 Subject: [PATCH 3/3] More formatting fixes --- .../five-safes-crate/15_metadata_file.py | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/rocrate_validator/profiles/five-safes-crate/15_metadata_file.py b/rocrate_validator/profiles/five-safes-crate/15_metadata_file.py index b589d332f..f284bfe8a 100644 --- a/rocrate_validator/profiles/five-safes-crate/15_metadata_file.py +++ b/rocrate_validator/profiles/five-safes-crate/15_metadata_file.py @@ -36,20 +36,13 @@ def test_existence(self, context: ValidationContext) -> bool: 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) - ): - passed = False - else: - if not pattern.match(context_value): + if not any(pattern.match(item) for item in context_value if isinstance(item, str)): passed = False + elif not pattern.match(context_value): + passed = False if not passed: context.result.add_issue( "The RO-Crate metadata file MUST include the RO-Crate context " @@ -58,7 +51,7 @@ def test_existence(self, context: ValidationContext) -> bool: ) return passed - except Exception as e: + except Exception: if logger.isEnabledFor(logging.DEBUG): - logger.exception(e) + logger.exception("Unexpected error during RO-Crate context version check") return True