diff --git a/rocrate_validator/profiles/five-safes-crate/may/11_workflow_execution_phase.ttl b/rocrate_validator/profiles/five-safes-crate/may/11_workflow_execution_phase.ttl new file mode 100644 index 000000000..4a043d096 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/may/11_workflow_execution_phase.ttl @@ -0,0 +1,58 @@ +# 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: . + + +five-safes-crate:WorkflowexecutionObjectHasStartTimeIfBegun + a sh:NodeShape ; + sh:name "WorkflowExecution" ; + sh:description "The workflow execution object MAY have a startTime if execution was initiated." ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT ?this + WHERE { + ?this rdf:type schema:CreateAction ; + 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 execution was initiated." ; + sh:message "The workflow execution object MAY have a startTime if execution was initiated." ; + ] . diff --git a/rocrate_validator/profiles/five-safes-crate/must/11_workflow_execution_phase.ttl b/rocrate_validator/profiles/five-safes-crate/must/11_workflow_execution_phase.ttl new file mode 100644 index 000000000..a132fbe10 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/must/11_workflow_execution_phase.ttl @@ -0,0 +1,96 @@ +# 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: . + + +five-safes-crate:WorkflowMustHaveDescriptiveName + a sh:NodeShape ; + sh:name "WorkflowExecution" ; + sh:targetClass schema:CreateAction ; + + sh:property [ + sh:a sh:PropertyShape ; + sh:name "name" ; + sh:minCount 1 ; + sh:description "Workflow (CreateAction) 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 "Workflow (CreateAction) MUST have a name string of at least 10 characters." ; + ] . + + +five-safes-crate:WorkflowexecutionObjectHasCompliantStartTimeFormat + a sh:NodeShape ; + sh:name "WorkflowExecution" ; + sh:description "The startTime of the workflow execution object MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ; + sh:targetClass schema:CreateAction ; + + sh:property [ + a sh:PropertyShape ; + sh:name "StartTime" ; + 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 "The startTime of the workflow execution object MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ; + ] . + + +five-safes-crate:WorkflowexecutionObjectHasCompliantEndTimeFormat + a sh:NodeShape ; + sh:name "WorkflowExecution" ; + sh:description "The endTime of the workflow execution object MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ; + sh:targetClass schema:CreateAction ; + + sh:property [ + a sh:PropertyShape ; + sh:name "EndTime" ; + 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 "The endTime of the workflow execution object MUST follow the RFC 3339 standard (YYYY-MM-DD'T'hh:mm:ss[.fraction](Z | ±hh:mm))." ; + ] . + + +five-safes-crate:WorkflowMustHaveActionStatusWithAllowedValues + a sh:NodeShape ; + sh:name "WorkflowExecution" ; + sh:targetClass schema:CreateAction ; + sh:property [ + a sh:PropertyShape ; + sh:minCount 1 ; + sh:name "actionStatus" ; + sh:description "WorkflowExecution 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 "WorkflowExecution MUST have an actionStatus with an allowed value (see https://schema.org/ActionStatusType)." ; + ] . \ No newline at end of file diff --git a/rocrate_validator/profiles/five-safes-crate/should/11_workflow_execution_phase.ttl b/rocrate_validator/profiles/five-safes-crate/should/11_workflow_execution_phase.ttl new file mode 100644 index 000000000..0c1523102 --- /dev/null +++ b/rocrate_validator/profiles/five-safes-crate/should/11_workflow_execution_phase.ttl @@ -0,0 +1,85 @@ +# 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: . + + + +five-safes-crate:RootDataEntityShouldMentionWorkflow + a sh:NodeShape ; + sh:name "RootDataEntity" ; + sh:description "RootDataEntity SHOULD mention workflow execution object (typed CreateAction)." ; + sh:targetClass ro-crate:RootDataEntity ; + sh:sparql [ + a sh:SPARQLConstraint ; + sh:name "mentions" ; + sh:select """ + PREFIX schema: + PREFIX rdf: + SELECT $this + WHERE { + + FILTER NOT EXISTS { + $this schema:mentions ?workflowExecution . + ?workflowExecution rdf:type schema:CreateAction . + } + } + """ ; + sh:severity sh:Warning ; + sh:message "RootDataEntity SHOULD mention workflow execution object (typed CreateAction)." ; + ] . + + + +five-safes-crate:WorkflowexecutionObjectHasEndTimeIfEnded + a sh:NodeShape ; + sh:name "WorkflowExecution" ; + sh:description "The workflow execution object SHOULD have an endTime property if it has ended." ; + + sh:target [ + a sh:SPARQLTarget ; + sh:select """ + PREFIX schema: + PREFIX rdf: + + SELECT ?this + WHERE { + ?this rdf:type schema:CreateAction ; + 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." ; + ] . \ No newline at end of file diff --git a/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json b/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json index f1f48cd99..e4aa7f030 100644 --- a/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json +++ b/tests/data/crates/valid/five-safes-crate-result/ro-crate-metadata.json @@ -106,7 +106,9 @@ { "@id": "#query-37252371-c937-43bd-a0a7-3680b48c0538", "@type": "CreateAction", - "actionStatus": "http://schema.org/CompleteActionStatus", + "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" }, @@ -160,7 +162,7 @@ "@type": "PropertyValue", "name": "tre72", "value": "project81" - }, + }, { "@id": "input1.txt", "@type": "File", @@ -303,6 +305,7 @@ "@id": "https://w3id.org/shp#DisclosureCheck" }, "name": "Disclosure check of workflow results: approved", + "startTime": "2023-04-24T00:00:00+01:00", "endTime": "2023-04-25T16:00:00+01:00", "object": { "@id": "./" 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..7bbefd94d --- /dev/null +++ b/tests/integration/profiles/five-safes-crate/test_5src_11_workflow_execution.py @@ -0,0 +1,288 @@ +# Copyright (c) 2024-2025 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging + +from rocrate_validator.models import Severity +from tests.ro_crates import ValidROC +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=["Workflow (CreateAction) 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=["Workflow (CreateAction) 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=["Workflow (CreateAction) 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=["WorkflowExecution"], + expected_triggered_issues=[( + "The startTime of the workflow execution object 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=["WorkflowExecution"], + expected_triggered_issues=[( + "The endTime of the workflow execution object 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=[( + "WorkflowExecution 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=[( + "WorkflowExecution 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_not_mentioned_by_root_data_entity(): + sparql = (SPARQL_PREFIXES + """ + DELETE { + <./> schema:mentions ?o . + } + WHERE { + ?o rdf:type schema:CreateAction . + } + """) + + 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 workflow execution object (typed CreateAction)." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) + + +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 execution was initiated." + ], + profile_identifier="five-safes-crate", + rocrate_entity_mod_sparql=sparql, + ) diff --git a/tests/shared.py b/tests/shared.py index 978f60e4a..b82d4ade7 100644 --- a/tests/shared.py +++ b/tests/shared.py @@ -34,7 +34,10 @@ T = TypeVar("T") -SPARQL_PREFIXES = """PREFIX schema: +SPARQL_PREFIXES = """ +PREFIX schema: +PREFIX shp: +PREFIX rdf: """