diff --git a/.env b/.env new file mode 100644 index 00000000..e86de2aa --- /dev/null +++ b/.env @@ -0,0 +1 @@ +PYTHONPATH=rocrate_validator \ No newline at end of file diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..4512ded8 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,192 @@ +# This workflow is triggered on push to tags and runs the following steps: +# 1. Check and Build Distribution +# 2. Publish to TestPyPI +# 3. Publish to PyPI if the previous step is successful +# 4. Sign Distribution with Sigstore +# 5. Create GitHub Release with the signed distribution +name: πŸ“¦ CI Pipeline 2 -- Release + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + tags: + - "*.*.*" + paths: + - "**" + - "!docs/**" + - "!examples/**" + +env: + TERM: xterm + VENV_PATH: .venv + +jobs: + # Wait for the testing pipeline to finish + wait-for-testing: + name: πŸ•’ Wait for Testing Pipeline + runs-on: ubuntu-latest + if: ${{ github.repository == 'crs4/rocrate-validator' }} + steps: + - name: Wait for testing pipeline to succeed + uses: fountainhead/action-wait-for-check@v1.2.0 + id: wait-for-testing + with: + token: ${{ secrets.GITHUB_TOKEN }} + checkName: βŒ› Run tests + ref: ${{ github.sha }} + + - name: Do something with a passing build + if: steps.wait-for-testing.outputs.conclusion == 'success' + run: echo "Testing pipeline passed" && exit 0 + + - name: Do something with a failing build + if: steps.wait-for-testing.outputs.conclusion == 'failure' + run: echo "Testing pipeline failed" && exit 1 + # Check and Build Distribution + build: + name: πŸ— Check and Build Distribution + runs-on: ubuntu-latest + needs: wait-for-testing + if: ${{ github.repository == 'crs4/rocrate-validator' }} + steps: + # Access the tag from the first workflow's outputs + - name: ⬇️ Checkout code + uses: actions/checkout@v4 + - name: 🐍 Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.x" + - name: 🚧 Set up Python Environment + run: | + pip install --upgrade pip + pip install poetry + - name: πŸ“¦ Install Package Dependencies + run: poetry install --no-interaction --no-ansi + - name: βœ… Check version + run: | + if [ "${{ github.event_name }}" == "push" ] && [ "${{ github.ref_type }}" == "tag" ]; then + declared_version=$(poetry version -s) + echo "Checking tag '${{ github.ref }}' against package version $declared_version" + if [ "${{ github.ref }}" != "refs/tags/$declared_version" ]; then + echo "Tag '${{ github.ref }}' does not match the declared package version '$declared_version'" + exit 1 + else + echo "Tag '${{ github.ref }}' matches the declared package version '$declared_version'" + fi + fi + - name: πŸ—οΈ Build a binary wheel and a source tarball + run: poetry build + - name: πŸ“¦ Store the distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-distributions + path: | + dist/*.whl + dist/*.tar.gz + + # Publish to TestPyPI + publish-to-testpypi: + name: πŸ“¦ Publish to TestPyPI + runs-on: ubuntu-latest + needs: build + environment: + name: testpypi + url: https://test.pypi.org/p/test-py-pipelines + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: ⬇️ Download all the distribution packages + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: πŸ“¦ Publish distribution to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + # Publish to PyPI + publish-to-pypi: + name: πŸ“¦ Publish to PyPI + runs-on: ubuntu-latest + needs: [build, publish-to-testpypi] + environment: + name: pypi + url: https://pypi.org/p/test-py-pipelines + permissions: + id-token: write # IMPORTANT: mandatory for trusted publishing + steps: + - name: ⬇️ Download all the dists + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: πŸ“¦ Publish distribution to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + + # Sign and Upload to GitHub Release + sign-packages: + name: πŸ–ŠοΈ Sign the Python distribution with Sigstore + needs: publish-to-pypi + runs-on: ubuntu-latest + + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + + steps: + - name: ⬇️ Download all the distribution packages + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: πŸ–ŠοΈ Sign the dists with Sigstore + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: >- + ./dist/*.tar.gz + ./dist/*.whl + - name: πŸ“¦ Store the signed distribution packages + uses: actions/upload-artifact@v4 + with: + name: python-package-signatures + path: dist/*.sigstore + + # Create GitHub Release + github_release: + name: πŸŽ‰ Release on GitHub + needs: sign-packages + runs-on: ubuntu-latest + permissions: + contents: write # IMPORTANT: mandatory for making GitHub Releases + id-token: write # IMPORTANT: mandatory for sigstore + steps: + - name: ⬇️ Download all the distribution packages + uses: actions/download-artifact@v4 + with: + name: python-package-distributions + path: dist/ + - name: ⬇️ Download all the distribution signatures + uses: actions/download-artifact@v4 + with: + name: python-package-signatures + path: dist/ + - name: πŸŽ‰ Create GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + run: >- + gh release create + '${{ github.ref_name }}' + --repo '${{ github.repository }}' + --generate-notes + - name: πŸ“¦ Upload artifacts to GitHub Release + env: + GITHUB_TOKEN: ${{ github.token }} + # Upload to GitHub Release using the `gh` CLI. + # `dist/` contains the built packages, and the + # sigstore-produced signatures and certificates. + run: >- + gh release upload + '${{ github.ref_name }}' dist/** + --repo '${{ github.repository }}' diff --git a/.github/workflows/testing.yaml b/.github/workflows/testing.yaml new file mode 100644 index 00000000..8505f61a --- /dev/null +++ b/.github/workflows/testing.yaml @@ -0,0 +1,67 @@ +name: πŸ§ͺ CI Pipeline 1 -- Testing + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + branches: + - "**" + tags: + - "*.*.*" + paths: + - "**" + - "!docs/**" + - "!examples/**" + pull_request: + paths: + - "**" + - "!docs/**" + - "!examples/**" + +env: + TERM: xterm + VENV_PATH: .venv + PYTHON_VERSION: "3.11" + +jobs: + # Verifies pep8, pyflakes and circular complexity + flake8: + name: 🚨 Lint Python Code + runs-on: ubuntu-latest + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - name: ⬇️ Checkout code + uses: actions/checkout@v4 + - name: 🐍 Set up Python v${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: πŸ”½ Install flake8 + run: pip install flake8 + - name: βŒ› Run checks + run: flake8 -v rocrate_validator tests + + # Runs the tests + test: + name: βŒ› Run tests + runs-on: ubuntu-latest + needs: [flake8] + steps: + - name: ⬇️ Checkout + uses: actions/checkout@v4 + - name: 🐍 Set up Python v${{ env.PYTHON_VERSION }} + uses: actions/setup-python@v5 + with: + python-version: ${{ env.PYTHON_VERSION }} + - name: πŸ”„ Upgrade pip + run: pip install --upgrade pip + - name: 🐍 Initialise a virtual env + run: python -m venv ${VENV_PATH} + - name: 🐍 Enable virtual env + run: source ${VENV_PATH}/bin/activate + - name: πŸ”½ Install Poetry + run: pip install poetry + - name: πŸ”½ Install dependencies + run: poetry install --no-interaction --no-ansi + - name: βŒ› Run tests + run: poetry run pytest diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..f049cc1b --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +**/__pycache__ +**/*.pyc +**/.pytest_cache + +# ignore virtualenv +.venv + +# ignore coverage files +**/.coverage +**/.coverage.* +**/.report diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 00000000..e5a1e7c6 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,81 @@ +# Copyright (c) 2024 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. + +stages: + - install + - test + - build + +variables: + IMAGE: python:3.12-slim # Define the desired Docker image for the runner + VENV_PATH: .venv # Define the virtual environment path + POETRY_VERSION: 1.8.3 # Define the desired Poetry version + CACHE_KEY: $CI_COMMIT_REF_NAME # Define the cache key + RUNNER_TAG: rvdev # Define the desired runner tag + +# Install dependencies +install_dependencies: + stage: install + before_script: + - pip install --upgrade pip + - python -m venv ${VENV_PATH} + - source ${VENV_PATH}/bin/activate + - pip install poetry==${POETRY_VERSION} + script: + - poetry config virtualenvs.in-project true + - poetry install --no-interaction --no-ansi + cache: + key: ${CACHE_KEY} + paths: + - ${VENV_PATH} + tags: + - ${RUNNER_TAG} + +# Run tests +test: + stage: test + before_script: + - source ${VENV_PATH}/bin/activate + script: + - poetry run pytest + dependencies: + - install_dependencies + coverage: '/TOTAL\s+\d+\s+\d+\s+(\d+)%/' + cache: + key: ${CACHE_KEY} + paths: + - ${VENV_PATH} + tags: + - ${RUNNER_TAG} + + +# Build the application +build: + stage: build + before_script: + - source ${VENV_PATH}/bin/activate + script: + - poetry build + dependencies: + - test + artifacts: + paths: + - dist/ + expire_in: 30 minutes + cache: + key: ${CACHE_KEY} + paths: + - ${VENV_PATH} + tags: + - ${RUNNER_TAG} diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..261eeb9e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/README b/README deleted file mode 100644 index e69de29b..00000000 diff --git a/README.md b/README.md new file mode 100644 index 00000000..c7276512 --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +# rocrate-validator + +[![main workflow](https://github.com/crs4/rocrate-validator/actions/workflows/testing.yaml/badge.svg)](https://github.com/crs4/rocrate-validator/actions/workflows/testing.yaml) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) + + + + + + + +A Python package to validate [RO-Crate](https://researchobject.github.io/ro-crate/) packages. + +* Supports CLI-based validation as well as programmatic validation (so it can + easily be used by Python code). +* Implements an extensible validation framework to which new RO-Crate profiles + can be added. Validation is based on SHACL shapes and Python code. +* Currently, validations for the following profiles are implemented: RO-Crate + (base profile), [Workflow + RO-Crate](https://www.researchobject.org/ro-crate/specification/1.1/workflows.html), + [Process Run + Crate](https://www.researchobject.org/workflow-run-crate/profiles/0.1/process_run_crate.html). + More are being implemented. + +**Note**: this software is still work in progress. Feel free to try it out, +report positive and negative feedback. Do send a note (e.g., by opening an +Issue) before starting to develop patches you would like to contribute. The +implementation of validation code for additional RO-Crate profiles would be +particularly welcome. + +## Setup + +Follow these steps to set up the project: + +1. **Clone the repository** + +```bash +git clone https://github.com/crs4/rocrate-validator.git +cd rocrate-validator +``` + +2. **Set up a Python virtual environment (optional)** + +Set up a Python virtual environment using `venv`: + +```bash +python3 -m venv .venv +source .venv/bin/activate +``` + +Or using `virtualenv`: + +```bash +virtualenv .venv +source .venv/bin/activate +``` + +This step, while optional, is recommended for isolating your project dependencies. If skipped, Poetry will automatically create a virtual environment for you. + +3. **Install the project using Poetry** + +Ensure you have Poetry installed. If not, follow the instructions [here](https://python-poetry.org/docs/#installation). Then, install the project: + +```bash +poetry install +``` + +## Usage + +After installation, you can use the main command `rocrate-validator` to validate ROCrates. + +### Using Poetry + +Run the validator using the following command: + +```bash +poetry run rocrate-validator validate +``` + +Replace `` with the path to the RO-Crate you want to validate. + +Type `poetry run rocrate-validator --help` for more information. + +### Using the installed package on your virtual environment + +Activate the virtual environment: + +```bash +source .venv/bin/activate +``` + +Then, run the validator using the following command: + +```bash +rocrate-validator validate +``` + +Replace `` with the path to the RO-Crate you want to validate. + +Type `rocrate-validator --help` for more information. + +## Running the tests + +To run the tests, use the following command: + +```bash +poetry run pytest +``` + + + +## License + +This project is licensed under the terms of the Apache License 2.0. See the +[LICENSE](LICENSE) file for details. + +## Acknowledgements + +This work has been partially funded by the following sources: + +* the [BY-COVID](https://by-covid.org/) project (HORIZON Europe grant agreement number 101046203); +* the [LIFEMap](https://www.thelifemap.it/) project, funded by the Italian Ministry of Health (Piano Operative Salute, Trajectory 3). + +Co-funded by the EU diff --git a/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_BLACK Outline.png b/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_BLACK Outline.png new file mode 100644 index 00000000..6764ff47 Binary files /dev/null and b/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_BLACK Outline.png differ diff --git a/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_BLACK.png b/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_BLACK.png new file mode 100644 index 00000000..6a2cbbfc Binary files /dev/null and b/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_BLACK.png differ diff --git a/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_Monochrome.png b/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_Monochrome.png new file mode 100644 index 00000000..2ec6efaa Binary files /dev/null and b/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_Monochrome.png differ diff --git a/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_NEG.png b/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_NEG.png new file mode 100644 index 00000000..272a0bcb Binary files /dev/null and b/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_NEG.png differ diff --git a/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_POS.png b/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_POS.png new file mode 100644 index 00000000..2a494fb2 Binary files /dev/null and b/docs/img/eu-logo/EN_Co-fundedbytheEU_RGB_POS.png differ diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 00000000..5ac8c403 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1708 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "appnope" +version = "0.1.4" +description = "Disable App Nap on macOS >= 10.9" +optional = false +python-versions = ">=3.6" +files = [ + {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, + {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, +] + +[[package]] +name = "astroid" +version = "3.2.4" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "astroid-3.2.4-py3-none-any.whl", hash = "sha256:413658a61eeca6202a59231abb473f932038fbcbf1666587f66d482083413a25"}, + {file = "astroid-3.2.4.tar.gz", hash = "sha256:0e14202810b30da1b735827f78f5157be2bbd4a7a59b7707ca0bfc2fb4c0063a"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" +optional = false +python-versions = "*" +files = [ + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, +] + +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + +[[package]] +name = "attrs" +version = "24.2.0" +description = "Classes Without Boilerplate" +optional = false +python-versions = ">=3.7" +files = [ + {file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"}, + {file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +optional = false +python-versions = "*" +files = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] + +[[package]] +name = "cattrs" +version = "24.1.0" +description = "Composable complex class support for attrs and dataclasses." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cattrs-24.1.0-py3-none-any.whl", hash = "sha256:043bb8af72596432a7df63abcff0055ac0f198a4d2e95af8db5a936a7074a761"}, + {file = "cattrs-24.1.0.tar.gz", hash = "sha256:8274f18b253bf7674a43da851e3096370d67088165d23138b04a1c04c8eaf48e"}, +] + +[package.dependencies] +attrs = ">=23.1.0" +exceptiongroup = {version = ">=1.1.1", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.1.0,<4.6.3 || >4.6.3", markers = "python_version < \"3.11\""} + +[package.extras] +bson = ["pymongo (>=4.4.0)"] +cbor2 = ["cbor2 (>=5.4.6)"] +msgpack = ["msgpack (>=1.0.5)"] +msgspec = ["msgspec (>=0.18.5)"] +orjson = ["orjson (>=3.9.2)"] +pyyaml = ["pyyaml (>=6.0)"] +tomlkit = ["tomlkit (>=0.11.8)"] +ujson = ["ujson (>=5.7.0)"] + +[[package]] +name = "certifi" +version = "2024.8.30" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, +] + +[[package]] +name = "cffi" +version = "1.17.1" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "colorlog" +version = "6.8.2" +description = "Add colours to the output of Python's logging module." +optional = false +python-versions = ">=3.6" +files = [ + {file = "colorlog-6.8.2-py3-none-any.whl", hash = "sha256:4dcbb62368e2800cb3c5abd348da7e53f6c362dda502ec27c560b2e58a66bd33"}, + {file = "colorlog-6.8.2.tar.gz", hash = "sha256:3e3e079a41feb5a1b64f978b5ea4f46040a94f11f0e8bbb8261e3dbbeca64d44"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} + +[package.extras] +development = ["black", "flake8", "mypy", "pytest", "types-colorama"] + +[[package]] +name = "comm" +version = "0.2.2" +description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." +optional = false +python-versions = ">=3.8" +files = [ + {file = "comm-0.2.2-py3-none-any.whl", hash = "sha256:e6fb86cb70ff661ee8c9c14e7d36d6de3b4066f1441be4063df9c5009f0a64d3"}, + {file = "comm-0.2.2.tar.gz", hash = "sha256:3fd7a84065306e07bea1773df6eb8282de51ba82f77c72f9c85716ab11fe980e"}, +] + +[package.dependencies] +traitlets = ">=4" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "coverage" +version = "7.6.1" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b06079abebbc0e89e6163b8e8f0e16270124c154dc6e4a47b413dd538859af16"}, + {file = "coverage-7.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cf4b19715bccd7ee27b6b120e7e9dd56037b9c0681dcc1adc9ba9db3d417fa36"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61c0abb4c85b095a784ef23fdd4aede7a2628478e7baba7c5e3deba61070a02"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd21f6ae3f08b41004dfb433fa895d858f3f5979e7762d052b12aef444e29afc"}, + {file = "coverage-7.6.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f59d57baca39b32db42b83b2a7ba6f47ad9c394ec2076b084c3f029b7afca23"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:a1ac0ae2b8bd743b88ed0502544847c3053d7171a3cff9228af618a068ed9c34"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e6a08c0be454c3b3beb105c0596ebdc2371fab6bb90c0c0297f4e58fd7e1012c"}, + {file = "coverage-7.6.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:f5796e664fe802da4f57a168c85359a8fbf3eab5e55cd4e4569fbacecc903959"}, + {file = "coverage-7.6.1-cp310-cp310-win32.whl", hash = "sha256:7bb65125fcbef8d989fa1dd0e8a060999497629ca5b0efbca209588a73356232"}, + {file = "coverage-7.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:3115a95daa9bdba70aea750db7b96b37259a81a709223c8448fa97727d546fe0"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7dea0889685db8550f839fa202744652e87c60015029ce3f60e006f8c4462c93"}, + {file = "coverage-7.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ed37bd3c3b063412f7620464a9ac1314d33100329f39799255fb8d3027da50d3"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d85f5e9a5f8b73e2350097c3756ef7e785f55bd71205defa0bfdaf96c31616ff"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9bc572be474cafb617672c43fe989d6e48d3c83af02ce8de73fff1c6bb3c198d"}, + {file = "coverage-7.6.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0c0420b573964c760df9e9e86d1a9a622d0d27f417e1a949a8a66dd7bcee7bc6"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:1f4aa8219db826ce6be7099d559f8ec311549bfc4046f7f9fe9b5cea5c581c56"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:fc5a77d0c516700ebad189b587de289a20a78324bc54baee03dd486f0855d234"}, + {file = "coverage-7.6.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b48f312cca9621272ae49008c7f613337c53fadca647d6384cc129d2996d1133"}, + {file = "coverage-7.6.1-cp311-cp311-win32.whl", hash = "sha256:1125ca0e5fd475cbbba3bb67ae20bd2c23a98fac4e32412883f9bcbaa81c314c"}, + {file = "coverage-7.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:8ae539519c4c040c5ffd0632784e21b2f03fc1340752af711f33e5be83a9d6c6"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:95cae0efeb032af8458fc27d191f85d1717b1d4e49f7cb226cf526ff28179778"}, + {file = "coverage-7.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5621a9175cf9d0b0c84c2ef2b12e9f5f5071357c4d2ea6ca1cf01814f45d2391"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:260933720fdcd75340e7dbe9060655aff3af1f0c5d20f46b57f262ab6c86a5e8"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07e2ca0ad381b91350c0ed49d52699b625aab2b44b65e1b4e02fa9df0e92ad2d"}, + {file = "coverage-7.6.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c44fee9975f04b33331cb8eb272827111efc8930cfd582e0320613263ca849ca"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:877abb17e6339d96bf08e7a622d05095e72b71f8afd8a9fefc82cf30ed944163"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:3e0cadcf6733c09154b461f1ca72d5416635e5e4ec4e536192180d34ec160f8a"}, + {file = "coverage-7.6.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c3c02d12f837d9683e5ab2f3d9844dc57655b92c74e286c262e0fc54213c216d"}, + {file = "coverage-7.6.1-cp312-cp312-win32.whl", hash = "sha256:e05882b70b87a18d937ca6768ff33cc3f72847cbc4de4491c8e73880766718e5"}, + {file = "coverage-7.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:b5d7b556859dd85f3a541db6a4e0167b86e7273e1cdc973e5b175166bb634fdb"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a4acd025ecc06185ba2b801f2de85546e0b8ac787cf9d3b06e7e2a69f925b106"}, + {file = "coverage-7.6.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a6d3adcf24b624a7b778533480e32434a39ad8fa30c315208f6d3e5542aeb6e9"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0c212c49b6c10e6951362f7c6df3329f04c2b1c28499563d4035d964ab8e08c"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e81d7a3e58882450ec4186ca59a3f20a5d4440f25b1cff6f0902ad890e6748a"}, + {file = "coverage-7.6.1-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78b260de9790fd81e69401c2dc8b17da47c8038176a79092a89cb2b7d945d060"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a78d169acd38300060b28d600344a803628c3fd585c912cacc9ea8790fe96862"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2c09f4ce52cb99dd7505cd0fc8e0e37c77b87f46bc9c1eb03fe3bc9991085388"}, + {file = "coverage-7.6.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6878ef48d4227aace338d88c48738a4258213cd7b74fd9a3d4d7582bb1d8a155"}, + {file = "coverage-7.6.1-cp313-cp313-win32.whl", hash = "sha256:44df346d5215a8c0e360307d46ffaabe0f5d3502c8a1cefd700b34baf31d411a"}, + {file = "coverage-7.6.1-cp313-cp313-win_amd64.whl", hash = "sha256:8284cf8c0dd272a247bc154eb6c95548722dce90d098c17a883ed36e67cdb129"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:d3296782ca4eab572a1a4eca686d8bfb00226300dcefdf43faa25b5242ab8a3e"}, + {file = "coverage-7.6.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:502753043567491d3ff6d08629270127e0c31d4184c4c8d98f92c26f65019962"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a89ecca80709d4076b95f89f308544ec8f7b4727e8a547913a35f16717856cb"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a318d68e92e80af8b00fa99609796fdbcdfef3629c77c6283566c6f02c6d6704"}, + {file = "coverage-7.6.1-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13b0a73a0896988f053e4fbb7de6d93388e6dd292b0d87ee51d106f2c11b465b"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:4421712dbfc5562150f7554f13dde997a2e932a6b5f352edcce948a815efee6f"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:166811d20dfea725e2e4baa71fffd6c968a958577848d2131f39b60043400223"}, + {file = "coverage-7.6.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:225667980479a17db1048cb2bf8bfb39b8e5be8f164b8f6628b64f78a72cf9d3"}, + {file = "coverage-7.6.1-cp313-cp313t-win32.whl", hash = "sha256:170d444ab405852903b7d04ea9ae9b98f98ab6d7e63e1115e82620807519797f"}, + {file = "coverage-7.6.1-cp313-cp313t-win_amd64.whl", hash = "sha256:b9f222de8cded79c49bf184bdbc06630d4c58eec9459b939b4a690c82ed05657"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6db04803b6c7291985a761004e9060b2bca08da6d04f26a7f2294b8623a0c1a0"}, + {file = "coverage-7.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f1adfc8ac319e1a348af294106bc6a8458a0f1633cc62a1446aebc30c5fa186a"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a95324a9de9650a729239daea117df21f4b9868ce32e63f8b650ebe6cef5595b"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b43c03669dc4618ec25270b06ecd3ee4fa94c7f9b3c14bae6571ca00ef98b0d3"}, + {file = "coverage-7.6.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8929543a7192c13d177b770008bc4e8119f2e1f881d563fc6b6305d2d0ebe9de"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:a09ece4a69cf399510c8ab25e0950d9cf2b42f7b3cb0374f95d2e2ff594478a6"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:9054a0754de38d9dbd01a46621636689124d666bad1936d76c0341f7d71bf569"}, + {file = "coverage-7.6.1-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:0dbde0f4aa9a16fa4d754356a8f2e36296ff4d83994b2c9d8398aa32f222f989"}, + {file = "coverage-7.6.1-cp38-cp38-win32.whl", hash = "sha256:da511e6ad4f7323ee5702e6633085fb76c2f893aaf8ce4c51a0ba4fc07580ea7"}, + {file = "coverage-7.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f1156e3e8f2872197af3840d8ad307a9dd18e615dc64d9ee41696f287c57ad8"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:abd5fd0db5f4dc9289408aaf34908072f805ff7792632250dcb36dc591d24255"}, + {file = "coverage-7.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:547f45fa1a93154bd82050a7f3cddbc1a7a4dd2a9bf5cb7d06f4ae29fe94eaf8"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:645786266c8f18a931b65bfcefdbf6952dd0dea98feee39bd188607a9d307ed2"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e0b2df163b8ed01d515807af24f63de04bebcecbd6c3bfeff88385789fdf75a"}, + {file = "coverage-7.6.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:609b06f178fe8e9f89ef676532760ec0b4deea15e9969bf754b37f7c40326dbc"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:702855feff378050ae4f741045e19a32d57d19f3e0676d589df0575008ea5004"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:2bdb062ea438f22d99cba0d7829c2ef0af1d768d1e4a4f528087224c90b132cb"}, + {file = "coverage-7.6.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:9c56863d44bd1c4fe2abb8a4d6f5371d197f1ac0ebdee542f07f35895fc07f36"}, + {file = "coverage-7.6.1-cp39-cp39-win32.whl", hash = "sha256:6e2cd258d7d927d09493c8df1ce9174ad01b381d4729a9d8d4e38670ca24774c"}, + {file = "coverage-7.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:06a737c882bd26d0d6ee7269b20b12f14a8704807a01056c80bb881a4b2ce6ca"}, + {file = "coverage-7.6.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:e9a6e0eb86070e8ccaedfbd9d38fec54864f3125ab95419970575b42af7541df"}, + {file = "coverage-7.6.1.tar.gz", hash = "sha256:953510dfb7b12ab69d20135a0662397f077c59b1e6379a768e97c59d852ee51d"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "debugpy" +version = "1.8.5" +description = "An implementation of the Debug Adapter Protocol for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "debugpy-1.8.5-cp310-cp310-macosx_12_0_x86_64.whl", hash = "sha256:7e4d594367d6407a120b76bdaa03886e9eb652c05ba7f87e37418426ad2079f7"}, + {file = "debugpy-1.8.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4413b7a3ede757dc33a273a17d685ea2b0c09dbd312cc03f5534a0fd4d40750a"}, + {file = "debugpy-1.8.5-cp310-cp310-win32.whl", hash = "sha256:dd3811bd63632bb25eda6bd73bea8e0521794cda02be41fa3160eb26fc29e7ed"}, + {file = "debugpy-1.8.5-cp310-cp310-win_amd64.whl", hash = "sha256:b78c1250441ce893cb5035dd6f5fc12db968cc07f91cc06996b2087f7cefdd8e"}, + {file = "debugpy-1.8.5-cp311-cp311-macosx_12_0_universal2.whl", hash = "sha256:606bccba19f7188b6ea9579c8a4f5a5364ecd0bf5a0659c8a5d0e10dcee3032a"}, + {file = "debugpy-1.8.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9fb642938a7a609a6c865c32ecd0d795d56c1aaa7a7a5722d77855d5e77f2b"}, + {file = "debugpy-1.8.5-cp311-cp311-win32.whl", hash = "sha256:4fbb3b39ae1aa3e5ad578f37a48a7a303dad9a3d018d369bc9ec629c1cfa7408"}, + {file = "debugpy-1.8.5-cp311-cp311-win_amd64.whl", hash = "sha256:345d6a0206e81eb68b1493ce2fbffd57c3088e2ce4b46592077a943d2b968ca3"}, + {file = "debugpy-1.8.5-cp312-cp312-macosx_12_0_universal2.whl", hash = "sha256:5b5c770977c8ec6c40c60d6f58cacc7f7fe5a45960363d6974ddb9b62dbee156"}, + {file = "debugpy-1.8.5-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0a65b00b7cdd2ee0c2cf4c7335fef31e15f1b7056c7fdbce9e90193e1a8c8cb"}, + {file = "debugpy-1.8.5-cp312-cp312-win32.whl", hash = "sha256:c9f7c15ea1da18d2fcc2709e9f3d6de98b69a5b0fff1807fb80bc55f906691f7"}, + {file = "debugpy-1.8.5-cp312-cp312-win_amd64.whl", hash = "sha256:28ced650c974aaf179231668a293ecd5c63c0a671ae6d56b8795ecc5d2f48d3c"}, + {file = "debugpy-1.8.5-cp38-cp38-macosx_12_0_x86_64.whl", hash = "sha256:3df6692351172a42af7558daa5019651f898fc67450bf091335aa8a18fbf6f3a"}, + {file = "debugpy-1.8.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cd04a73eb2769eb0bfe43f5bfde1215c5923d6924b9b90f94d15f207a402226"}, + {file = "debugpy-1.8.5-cp38-cp38-win32.whl", hash = "sha256:8f913ee8e9fcf9d38a751f56e6de12a297ae7832749d35de26d960f14280750a"}, + {file = "debugpy-1.8.5-cp38-cp38-win_amd64.whl", hash = "sha256:a697beca97dad3780b89a7fb525d5e79f33821a8bc0c06faf1f1289e549743cf"}, + {file = "debugpy-1.8.5-cp39-cp39-macosx_12_0_x86_64.whl", hash = "sha256:0a1029a2869d01cb777216af8c53cda0476875ef02a2b6ff8b2f2c9a4b04176c"}, + {file = "debugpy-1.8.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e84c276489e141ed0b93b0af648eef891546143d6a48f610945416453a8ad406"}, + {file = "debugpy-1.8.5-cp39-cp39-win32.whl", hash = "sha256:ad84b7cde7fd96cf6eea34ff6c4a1b7887e0fe2ea46e099e53234856f9d99a34"}, + {file = "debugpy-1.8.5-cp39-cp39-win_amd64.whl", hash = "sha256:7b0fe36ed9d26cb6836b0a51453653f8f2e347ba7348f2bbfe76bfeb670bfb1c"}, + {file = "debugpy-1.8.5-py2.py3-none-any.whl", hash = "sha256:55919dce65b471eff25901acf82d328bbd5b833526b6c1364bd5133754777a44"}, + {file = "debugpy-1.8.5.zip", hash = "sha256:b2112cfeb34b4507399d298fe7023a16656fc553ed5246536060ca7bd0e668d0"}, +] + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +optional = false +python-versions = ">=3.5" +files = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] + +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "exceptiongroup" +version = "1.2.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.2-py3-none-any.whl", hash = "sha256:3111b9d131c238bec2f8f516e123e14ba243563fb135d3fe885990585aa7795b"}, + {file = "exceptiongroup-1.2.2.tar.gz", hash = "sha256:47c2edf7c6738fafb49fd34290706d1a1a2f4d1c6df275526b62cbb4aa5393cc"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "execnet" +version = "2.1.1" +description = "execnet: rapid multi-Python deployment" +optional = false +python-versions = ">=3.8" +files = [ + {file = "execnet-2.1.1-py3-none-any.whl", hash = "sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc"}, + {file = "execnet-2.1.1.tar.gz", hash = "sha256:5189b52c6121c24feae288166ab41b32549c7e2348652736540b9e6e7d4e72e3"}, +] + +[package.extras] +testing = ["hatch", "pre-commit", "pytest", "tox"] + +[[package]] +name = "executing" +version = "2.1.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.8" +files = [ + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + +[[package]] +name = "flake8" +version = "6.1.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-6.1.0-py2.py3-none-any.whl", hash = "sha256:ffdfce58ea94c6580c77888a86506937f9a1a227dfcd15f245d694ae20a6b6e5"}, + {file = "flake8-6.1.0.tar.gz", hash = "sha256:d5b3857f07c030bdb5bf41c7f53799571d75c4491748a3adcd47de929e34cd23"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.1.0,<3.2.0" + +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["chardet (>=2.2)", "genshi", "lxml"] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +name = "idna" +version = "3.8" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.6" +files = [ + {file = "idna-3.8-py3-none-any.whl", hash = "sha256:050b4e5baadcd44d760cedbd2b8e639f2ff89bbc7a5730fcc662954303377aac"}, + {file = "idna-3.8.tar.gz", hash = "sha256:d838c2c0ed6fced7693d5e8ab8e734d5f8fda53a039c0164afb0b82e771e3603"}, +] + +[[package]] +name = "importlib-metadata" +version = "8.4.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, + {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "inquirerpy" +version = "0.3.4" +description = "Python port of Inquirer.js (A collection of common interactive command-line user interfaces)" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "InquirerPy-0.3.4-py3-none-any.whl", hash = "sha256:c65fdfbac1fa00e3ee4fb10679f4d3ed7a012abf4833910e63c295827fe2a7d4"}, + {file = "InquirerPy-0.3.4.tar.gz", hash = "sha256:89d2ada0111f337483cb41ae31073108b2ec1e618a49d7110b0d7ade89fc197e"}, +] + +[package.dependencies] +pfzy = ">=0.3.1,<0.4.0" +prompt-toolkit = ">=3.0.1,<4.0.0" + +[package.extras] +docs = ["Sphinx (>=4.1.2,<5.0.0)", "furo (>=2021.8.17-beta.43,<2022.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.4.0,<0.5.0)"] + +[[package]] +name = "ipykernel" +version = "6.29.5" +description = "IPython Kernel for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipykernel-6.29.5-py3-none-any.whl", hash = "sha256:afdb66ba5aa354b09b91379bac28ae4afebbb30e8b39510c9690afb7a10421b5"}, + {file = "ipykernel-6.29.5.tar.gz", hash = "sha256:f093a22c4a40f8828f8e330a9c297cb93dcab13bd9678ded6de8e5cf81c56215"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +comm = ">=0.1.1" +debugpy = ">=1.6.5" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=24" +tornado = ">=6.1" +traitlets = ">=5.4.0" + +[package.extras] +cov = ["coverage[toml]", "curio", "matplotlib", "pytest-cov", "trio"] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "trio"] +pyqt5 = ["pyqt5"] +pyside6 = ["pyside6"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-asyncio (>=0.23.5)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.12.3" +description = "IPython: Productive Interactive Computing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "ipython-8.12.3-py3-none-any.whl", hash = "sha256:b0340d46a933d27c657b211a329d0be23793c36595acf9e6ef4164bc01a1804c"}, + {file = "ipython-8.12.3.tar.gz", hash = "sha256:3910c4b54543c2ad73d06579aa771041b7d5707b033bd488669b4cf544e3b363"}, +] + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">=3.0.30,<3.0.37 || >3.0.37,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" +typing-extensions = {version = "*", markers = "python_version < \"3.10\""} + +[package.extras] +all = ["black", "curio", "docrepr", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.21)", "pandas", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "trio", "typing-extensions"] +black = ["black"] +doc = ["docrepr", "ipykernel", "matplotlib", "pytest (<7)", "pytest (<7.1)", "pytest-asyncio", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "stack-data", "testpath", "typing-extensions"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.21)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = "*" +files = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jedi" +version = "0.19.1" +description = "An autocompletion tool for Python that can be used for text editors." +optional = false +python-versions = ">=3.6" +files = [ + {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, + {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, +] + +[package.dependencies] +parso = ">=0.8.3,<0.9.0" + +[package.extras] +docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jupyter-client" +version = "8.6.2" +description = "Jupyter protocol implementation and client libraries" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_client-8.6.2-py3-none-any.whl", hash = "sha256:50cbc5c66fd1b8f65ecb66bc490ab73217993632809b6e505687de18e9dea39f"}, + {file = "jupyter_client-8.6.2.tar.gz", hash = "sha256:2bda14d55ee5ba58552a8c53ae43d215ad9868853489213f37da060ced54d8df"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = ">=5.3" + +[package.extras] +docs = ["ipykernel", "myst-parser", "pydata-sphinx-theme", "sphinx (>=4)", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] +test = ["coverage", "ipykernel (>=6.14)", "mypy", "paramiko", "pre-commit", "pytest (<8.2.0)", "pytest-cov", "pytest-jupyter[client] (>=0.4.1)", "pytest-timeout"] + +[[package]] +name = "jupyter-core" +version = "5.7.2" +description = "Jupyter core package. A base package on which Jupyter projects rely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "jupyter_core-5.7.2-py3-none-any.whl", hash = "sha256:4f7315d2f6b4bcf2e3e7cb6e46772eba760ae459cd1f59d29eb57b0a01bd7409"}, + {file = "jupyter_core-5.7.2.tar.gz", hash = "sha256:aa5f8d32bbf6b431ac830496da7392035d6f61b4f54872f15c4bd2a9c3f536d9"}, +] + +[package.dependencies] +platformdirs = ">=2.5" +pywin32 = {version = ">=300", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = ">=5.3" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx-autodoc-typehints", "sphinxcontrib-github-alt", "sphinxcontrib-spelling", "traitlets"] +test = ["ipykernel", "pre-commit", "pytest (<8)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "markdown-it-py" +version = "3.0.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +optional = false +python-versions = ">=3.8" +files = [ + {file = "markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb"}, + {file = "markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["jupyter_sphinx", "mdit-py-plugins", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "matplotlib-inline" +version = "0.1.7" +description = "Inline Matplotlib backend for Jupyter" +optional = false +python-versions = ">=3.8" +files = [ + {file = "matplotlib_inline-0.1.7-py3-none-any.whl", hash = "sha256:df192d39a4ff8f21b1895d72e6a13f5fcc5099f00fa84384e0ea28c2cc0653ca"}, + {file = "matplotlib_inline-0.1.7.tar.gz", hash = "sha256:8423b23ec666be3d16e16b60bdd8ac4e86e840ebd1dd11a30b9f117f2fa0ab90"}, +] + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + +[[package]] +name = "nest-asyncio" +version = "1.6.0" +description = "Patch asyncio to allow nested event loops" +optional = false +python-versions = ">=3.5" +files = [ + {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"}, + {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"}, +] + +[[package]] +name = "owlrl" +version = "6.0.2" +description = "OWL-RL and RDFS based RDF Closure inferencing for Python" +optional = false +python-versions = "*" +files = [ + {file = "owlrl-6.0.2-py3-none-any.whl", hash = "sha256:57eca06b221edbbc682376c8d42e2ddffc99f61e82c0da02e26735592f08bacc"}, + {file = "owlrl-6.0.2.tar.gz", hash = "sha256:904e3310ff4df15101475776693d2427d1f8244ee9a6a9f9e13c3c57fae90b74"}, +] + +[package.dependencies] +rdflib = ">=6.0.2" + +[[package]] +name = "packaging" +version = "24.1" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, +] + +[[package]] +name = "parso" +version = "0.8.4" +description = "A Python Parser" +optional = false +python-versions = ">=3.6" +files = [ + {file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"}, + {file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"}, +] + +[package.extras] +qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] +testing = ["docopt", "pytest"] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pfzy" +version = "0.3.4" +description = "Python port of the fzy fuzzy string matching algorithm" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "pfzy-0.3.4-py3-none-any.whl", hash = "sha256:5f50d5b2b3207fa72e7ec0ef08372ef652685470974a107d0d4999fc5a903a96"}, + {file = "pfzy-0.3.4.tar.gz", hash = "sha256:717ea765dd10b63618e7298b2d98efd819e0b30cd5905c9707223dceeb94b3f1"}, +] + +[package.extras] +docs = ["Sphinx (>=4.1.2,<5.0.0)", "furo (>=2021.8.17-beta.43,<2022.0.0)", "myst-parser (>=0.15.1,<0.16.0)", "sphinx-autobuild (>=2021.3.14,<2022.0.0)", "sphinx-copybutton (>=0.4.0,<0.5.0)"] + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +optional = false +python-versions = "*" +files = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] + +[[package]] +name = "platformdirs" +version = "4.3.2" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.3.2-py3-none-any.whl", hash = "sha256:eb1c8582560b34ed4ba105009a4badf7f6f85768b30126f351328507b2beb617"}, + {file = "platformdirs-4.3.2.tar.gz", hash = "sha256:9e5e27a08aa095dd127b9f2e764d74254f482fef22b0970773bfba79d091ab8c"}, +] + +[package.extras] +docs = ["furo (>=2024.8.6)", "proselint (>=0.14)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=8.3.2)", "pytest-cov (>=5)", "pytest-mock (>=3.14)"] +type = ["mypy (>=1.11.2)"] + +[[package]] +name = "pluggy" +version = "1.5.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"}, + {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "prettytable" +version = "3.11.0" +description = "A simple Python library for easily displaying tabular data in a visually appealing ASCII table format" +optional = false +python-versions = ">=3.8" +files = [ + {file = "prettytable-3.11.0-py3-none-any.whl", hash = "sha256:aa17083feb6c71da11a68b2c213b04675c4af4ce9c541762632ca3f2cb3546dd"}, + {file = "prettytable-3.11.0.tar.gz", hash = "sha256:7e23ca1e68bbfd06ba8de98bf553bf3493264c96d5e8a615c0471025deeba722"}, +] + +[package.dependencies] +wcwidth = "*" + +[package.extras] +tests = ["pytest", "pytest-cov", "pytest-lazy-fixtures"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.47" +description = "Library for building powerful interactive command lines in Python" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, + {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, +] + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "psutil" +version = "6.0.0" +description = "Cross-platform lib for process and system monitoring in Python." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" +files = [ + {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, + {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, + {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, + {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, +] + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pyflakes" +version = "3.1.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.1.0-py2.py3-none-any.whl", hash = "sha256:4132f6d49cb4dae6819e5379898f2b8cce3c5f23994194c24b77d5da2e36f774"}, + {file = "pyflakes-3.1.0.tar.gz", hash = "sha256:a0aae034c444db0071aa077972ba4768d40c830d9539fd45bf4cd3f8f6992efc"}, +] + +[[package]] +name = "pygments" +version = "2.18.0" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pygments-2.18.0-py3-none-any.whl", hash = "sha256:b8e6aca0523f3ab76fee51799c488e38782ac06eafcf95e7ba832985c8e7b13a"}, + {file = "pygments-2.18.0.tar.gz", hash = "sha256:786ff802f32e91311bff3889f6e9a86e81505fe99f2735bb6d60ae0c5004f199"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pylint" +version = "3.2.7" +description = "python code static checker" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "pylint-3.2.7-py3-none-any.whl", hash = "sha256:02f4aedeac91be69fb3b4bea997ce580a4ac68ce58b89eaefeaf06749df73f4b"}, + {file = "pylint-3.2.7.tar.gz", hash = "sha256:1b7a721b575eaeaa7d39db076b6e7743c993ea44f57979127c517c6c572c803e"}, +] + +[package.dependencies] +astroid = ">=3.2.4,<=3.3.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, +] +isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pyparsing" +version = "3.1.4" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +optional = false +python-versions = ">=3.6.8" +files = [ + {file = "pyparsing-3.1.4-py3-none-any.whl", hash = "sha256:a6a7ee4235a3f944aa1fa2249307708f893fe5717dc603503c6c7969c070fb7c"}, + {file = "pyparsing-3.1.4.tar.gz", hash = "sha256:f86ec8d1a83f11977c9a6ea7598e8c27fc5cddfa5b07ea2241edbbde1d7bc032"}, +] + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyproject-flake8" +version = "6.1.0" +description = "pyproject-flake8 (`pflake8`), a monkey patching wrapper to connect flake8 with pyproject.toml configuration" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "pyproject_flake8-6.1.0-py3-none-any.whl", hash = "sha256:86ea5559263c098e1aa4f866776aa2cf45362fd91a576b9fd8fbbbb55db12c4e"}, + {file = "pyproject_flake8-6.1.0.tar.gz", hash = "sha256:6da8e5a264395e0148bc11844c6fb50546f1fac83ac9210f7328664135f9e70f"}, +] + +[package.dependencies] +flake8 = "6.1.0" +tomli = {version = "*", markers = "python_version < \"3.11\""} + +[[package]] +name = "pyshacl" +version = "0.26.0" +description = "Python SHACL Validator" +optional = false +python-versions = "<4.0.0,>=3.8.1" +files = [ + {file = "pyshacl-0.26.0-py3-none-any.whl", hash = "sha256:a4bef4296d56305a30e0a97509e541ebe4f2cc2d5da73536d0541233e28f2d22"}, + {file = "pyshacl-0.26.0.tar.gz", hash = "sha256:48d44f317cd9aad8e3fdb5df8aa5706fa92dc6b2746419698035e84a320fb89d"}, +] + +[package.dependencies] +html5lib = ">=1.1,<2" +importlib-metadata = {version = ">6", markers = "python_version < \"3.12\""} +owlrl = ">=6.0.2,<7" +packaging = ">=21.3" +prettytable = [ + {version = ">=3.5.0", markers = "python_version >= \"3.8\" and python_version < \"3.12\""}, + {version = ">=3.7.0", markers = "python_version >= \"3.12\""}, +] +rdflib = {version = ">=6.3.2,<8.0", markers = "python_full_version >= \"3.8.1\""} + +[package.extras] +dev-coverage = ["coverage (>6.1,!=6.1.1,<7)", "platformdirs", "pytest-cov (>=2.8.1,<3.0.0)"] +dev-lint = ["black (==24.3.0)", "platformdirs", "ruff (>=0.1.5,<0.2.0)"] +dev-type-checking = ["mypy (>=0.812,<0.900)", "mypy (>=0.900,<0.1000)", "platformdirs", "types-setuptools"] +http = ["sanic (>=22.12,<23)", "sanic-cors (==2.2.0)", "sanic-ext (>=23.3,<23.6)"] +js = ["pyduktape2 (>=0.4.6,<0.5.0)"] + +[[package]] +name = "pytest" +version = "8.3.2" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.3.2-py3-none-any.whl", hash = "sha256:4ba08f9ae7dcf84ded419494d229b48d0903ea6407b030eaec46df5e6a73bba5"}, + {file = "pytest-8.3.2.tar.gz", hash = "sha256:c132345d12ce551242c87269de812483f5bcc87cdbb4722e48487ba194f9fdce"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.5,<2" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-xdist" +version = "3.6.1" +description = "pytest xdist plugin for distributed testing, most importantly across multiple CPUs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest_xdist-3.6.1-py3-none-any.whl", hash = "sha256:9ed4adfb68a016610848639bb7e02c9352d5d9f03d04809919e2dafc3be4cca7"}, + {file = "pytest_xdist-3.6.1.tar.gz", hash = "sha256:ead156a4db231eec769737f57668ef58a2084a34b2e55c4a8fa20d861107300d"}, +] + +[package.dependencies] +execnet = ">=2.1" +pytest = ">=7.0.0" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pywin32" +version = "306" +description = "Python for Window Extensions" +optional = false +python-versions = "*" +files = [ + {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, + {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, + {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, + {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, + {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, + {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, + {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, + {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, + {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, + {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, + {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, + {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, + {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, + {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, +] + +[[package]] +name = "pyzmq" +version = "26.2.0" +description = "Python bindings for 0MQ" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:ddf33d97d2f52d89f6e6e7ae66ee35a4d9ca6f36eda89c24591b0c40205a3629"}, + {file = "pyzmq-26.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dacd995031a01d16eec825bf30802fceb2c3791ef24bcce48fa98ce40918c27b"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89289a5ee32ef6c439086184529ae060c741334b8970a6855ec0b6ad3ff28764"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5506f06d7dc6ecf1efacb4a013b1f05071bb24b76350832c96449f4a2d95091c"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ea039387c10202ce304af74def5021e9adc6297067f3441d348d2b633e8166a"}, + {file = "pyzmq-26.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a2224fa4a4c2ee872886ed00a571f5e967c85e078e8e8c2530a2fb01b3309b88"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:28ad5233e9c3b52d76196c696e362508959741e1a005fb8fa03b51aea156088f"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:1c17211bc037c7d88e85ed8b7d8f7e52db6dc8eca5590d162717c654550f7282"}, + {file = "pyzmq-26.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b8f86dd868d41bea9a5f873ee13bf5551c94cf6bc51baebc6f85075971fe6eea"}, + {file = "pyzmq-26.2.0-cp310-cp310-win32.whl", hash = "sha256:46a446c212e58456b23af260f3d9fb785054f3e3653dbf7279d8f2b5546b21c2"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:49d34ab71db5a9c292a7644ce74190b1dd5a3475612eefb1f8be1d6961441971"}, + {file = "pyzmq-26.2.0-cp310-cp310-win_arm64.whl", hash = "sha256:bfa832bfa540e5b5c27dcf5de5d82ebc431b82c453a43d141afb1e5d2de025fa"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:8f7e66c7113c684c2b3f1c83cdd3376103ee0ce4c49ff80a648643e57fb22218"}, + {file = "pyzmq-26.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3a495b30fc91db2db25120df5847d9833af237546fd59170701acd816ccc01c4"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77eb0968da535cba0470a5165468b2cac7772cfb569977cff92e240f57e31bef"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ace4f71f1900a548f48407fc9be59c6ba9d9aaf658c2eea6cf2779e72f9f317"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:92a78853d7280bffb93df0a4a6a2498cba10ee793cc8076ef797ef2f74d107cf"}, + {file = "pyzmq-26.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:689c5d781014956a4a6de61d74ba97b23547e431e9e7d64f27d4922ba96e9d6e"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0aca98bc423eb7d153214b2df397c6421ba6373d3397b26c057af3c904452e37"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f3496d76b89d9429a656293744ceca4d2ac2a10ae59b84c1da9b5165f429ad3"}, + {file = "pyzmq-26.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5c2b3bfd4b9689919db068ac6c9911f3fcb231c39f7dd30e3138be94896d18e6"}, + {file = "pyzmq-26.2.0-cp311-cp311-win32.whl", hash = "sha256:eac5174677da084abf378739dbf4ad245661635f1600edd1221f150b165343f4"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:5a509df7d0a83a4b178d0f937ef14286659225ef4e8812e05580776c70e155d5"}, + {file = "pyzmq-26.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:c0e6091b157d48cbe37bd67233318dbb53e1e6327d6fc3bb284afd585d141003"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_15_universal2.whl", hash = "sha256:ded0fc7d90fe93ae0b18059930086c51e640cdd3baebdc783a695c77f123dcd9"}, + {file = "pyzmq-26.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:17bf5a931c7f6618023cdacc7081f3f266aecb68ca692adac015c383a134ca52"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55cf66647e49d4621a7e20c8d13511ef1fe1efbbccf670811864452487007e08"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4661c88db4a9e0f958c8abc2b97472e23061f0bc737f6f6179d7a27024e1faa5"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea7f69de383cb47522c9c208aec6dd17697db7875a4674c4af3f8cfdac0bdeae"}, + {file = "pyzmq-26.2.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:7f98f6dfa8b8ccaf39163ce872bddacca38f6a67289116c8937a02e30bbe9711"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:e3e0210287329272539eea617830a6a28161fbbd8a3271bf4150ae3e58c5d0e6"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:6b274e0762c33c7471f1a7471d1a2085b1a35eba5cdc48d2ae319f28b6fc4de3"}, + {file = "pyzmq-26.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:29c6a4635eef69d68a00321e12a7d2559fe2dfccfa8efae3ffb8e91cd0b36a8b"}, + {file = "pyzmq-26.2.0-cp312-cp312-win32.whl", hash = "sha256:989d842dc06dc59feea09e58c74ca3e1678c812a4a8a2a419046d711031f69c7"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:2a50625acdc7801bc6f74698c5c583a491c61d73c6b7ea4dee3901bb99adb27a"}, + {file = "pyzmq-26.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:4d29ab8592b6ad12ebbf92ac2ed2bedcfd1cec192d8e559e2e099f648570e19b"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9dd8cd1aeb00775f527ec60022004d030ddc51d783d056e3e23e74e623e33726"}, + {file = "pyzmq-26.2.0-cp313-cp313-macosx_10_15_universal2.whl", hash = "sha256:28c812d9757fe8acecc910c9ac9dafd2ce968c00f9e619db09e9f8f54c3a68a3"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d80b1dd99c1942f74ed608ddb38b181b87476c6a966a88a950c7dee118fdf50"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c997098cc65e3208eca09303630e84d42718620e83b733d0fd69543a9cab9cb"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ad1bc8d1b7a18497dda9600b12dc193c577beb391beae5cd2349184db40f187"}, + {file = "pyzmq-26.2.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:bea2acdd8ea4275e1278350ced63da0b166421928276c7c8e3f9729d7402a57b"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:23f4aad749d13698f3f7b64aad34f5fc02d6f20f05999eebc96b89b01262fb18"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_i686.whl", hash = "sha256:a4f96f0d88accc3dbe4a9025f785ba830f968e21e3e2c6321ccdfc9aef755115"}, + {file = "pyzmq-26.2.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ced65e5a985398827cc9276b93ef6dfabe0273c23de8c7931339d7e141c2818e"}, + {file = "pyzmq-26.2.0-cp313-cp313-win32.whl", hash = "sha256:31507f7b47cc1ead1f6e86927f8ebb196a0bab043f6345ce070f412a59bf87b5"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:70fc7fcf0410d16ebdda9b26cbd8bf8d803d220a7f3522e060a69a9c87bf7bad"}, + {file = "pyzmq-26.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:c3789bd5768ab5618ebf09cef6ec2b35fed88709b104351748a63045f0ff9797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:034da5fc55d9f8da09015d368f519478a52675e558c989bfcb5cf6d4e16a7d2a"}, + {file = "pyzmq-26.2.0-cp313-cp313t-macosx_10_15_universal2.whl", hash = "sha256:c92d73464b886931308ccc45b2744e5968cbaade0b1d6aeb40d8ab537765f5bc"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:794a4562dcb374f7dbbfb3f51d28fb40123b5a2abadee7b4091f93054909add5"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aee22939bb6075e7afededabad1a56a905da0b3c4e3e0c45e75810ebe3a52672"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ae90ff9dad33a1cfe947d2c40cb9cb5e600d759ac4f0fd22616ce6540f72797"}, + {file = "pyzmq-26.2.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:43a47408ac52647dfabbc66a25b05b6a61700b5165807e3fbd40063fcaf46386"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_aarch64.whl", hash = "sha256:25bf2374a2a8433633c65ccb9553350d5e17e60c8eb4de4d92cc6bd60f01d306"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_i686.whl", hash = "sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6"}, + {file = "pyzmq-26.2.0-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:470d4a4f6d48fb34e92d768b4e8a5cc3780db0d69107abf1cd7ff734b9766eb0"}, + {file = "pyzmq-26.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b55a4229ce5da9497dd0452b914556ae58e96a4381bb6f59f1305dfd7e53fc8"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9cb3a6460cdea8fe8194a76de8895707e61ded10ad0be97188cc8463ffa7e3a8"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8ab5cad923cc95c87bffee098a27856c859bd5d0af31bd346035aa816b081fe1"}, + {file = "pyzmq-26.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9ed69074a610fad1c2fda66180e7b2edd4d31c53f2d1872bc2d1211563904cd9"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cccba051221b916a4f5e538997c45d7d136a5646442b1231b916d0164067ea27"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:0eaa83fc4c1e271c24eaf8fb083cbccef8fde77ec8cd45f3c35a9a123e6da097"}, + {file = "pyzmq-26.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:9edda2df81daa129b25a39b86cb57dfdfe16f7ec15b42b19bfac503360d27a93"}, + {file = "pyzmq-26.2.0-cp37-cp37m-win32.whl", hash = "sha256:ea0eb6af8a17fa272f7b98d7bebfab7836a0d62738e16ba380f440fceca2d951"}, + {file = "pyzmq-26.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4ff9dc6bc1664bb9eec25cd17506ef6672d506115095411e237d571e92a58231"}, + {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:2eb7735ee73ca1b0d71e0e67c3739c689067f055c764f73aac4cc8ecf958ee3f"}, + {file = "pyzmq-26.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1a534f43bc738181aa7cbbaf48e3eca62c76453a40a746ab95d4b27b1111a7d2"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:aedd5dd8692635813368e558a05266b995d3d020b23e49581ddd5bbe197a8ab6"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8be4700cd8bb02cc454f630dcdf7cfa99de96788b80c51b60fe2fe1dac480289"}, + {file = "pyzmq-26.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fcc03fa4997c447dce58264e93b5aa2d57714fbe0f06c07b7785ae131512732"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:402b190912935d3db15b03e8f7485812db350d271b284ded2b80d2e5704be780"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8685fa9c25ff00f550c1fec650430c4b71e4e48e8d852f7ddcf2e48308038640"}, + {file = "pyzmq-26.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:76589c020680778f06b7e0b193f4b6dd66d470234a16e1df90329f5e14a171cd"}, + {file = "pyzmq-26.2.0-cp38-cp38-win32.whl", hash = "sha256:8423c1877d72c041f2c263b1ec6e34360448decfb323fa8b94e85883043ef988"}, + {file = "pyzmq-26.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:76589f2cd6b77b5bdea4fca5992dc1c23389d68b18ccc26a53680ba2dc80ff2f"}, + {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:b1d464cb8d72bfc1a3adc53305a63a8e0cac6bc8c5a07e8ca190ab8d3faa43c2"}, + {file = "pyzmq-26.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4da04c48873a6abdd71811c5e163bd656ee1b957971db7f35140a2d573f6949c"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d049df610ac811dcffdc147153b414147428567fbbc8be43bb8885f04db39d98"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05590cdbc6b902101d0e65d6a4780af14dc22914cc6ab995d99b85af45362cc9"}, + {file = "pyzmq-26.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c811cfcd6a9bf680236c40c6f617187515269ab2912f3d7e8c0174898e2519db"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:6835dd60355593de10350394242b5757fbbd88b25287314316f266e24c61d073"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc6bee759a6bddea5db78d7dcd609397449cb2d2d6587f48f3ca613b19410cfc"}, + {file = "pyzmq-26.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c530e1eecd036ecc83c3407f77bb86feb79916d4a33d11394b8234f3bd35b940"}, + {file = "pyzmq-26.2.0-cp39-cp39-win32.whl", hash = "sha256:367b4f689786fca726ef7a6c5ba606958b145b9340a5e4808132cc65759abd44"}, + {file = "pyzmq-26.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:e6fa2e3e683f34aea77de8112f6483803c96a44fd726d7358b9888ae5bb394ec"}, + {file = "pyzmq-26.2.0-cp39-cp39-win_arm64.whl", hash = "sha256:7445be39143a8aa4faec43b076e06944b8f9d0701b669df4af200531b21e40bb"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:706e794564bec25819d21a41c31d4df2d48e1cc4b061e8d345d7fb4dd3e94072"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b435f2753621cd36e7c1762156815e21c985c72b19135dac43a7f4f31d28dd1"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:160c7e0a5eb178011e72892f99f918c04a131f36056d10d9c1afb223fc952c2d"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2c4a71d5d6e7b28a47a394c0471b7e77a0661e2d651e7ae91e0cab0a587859ca"}, + {file = "pyzmq-26.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:90412f2db8c02a3864cbfc67db0e3dcdbda336acf1c469526d3e869394fe001c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2ea4ad4e6a12e454de05f2949d4beddb52460f3de7c8b9d5c46fbb7d7222e02c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fc4f7a173a5609631bb0c42c23d12c49df3966f89f496a51d3eb0ec81f4519d6"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:878206a45202247781472a2d99df12a176fef806ca175799e1c6ad263510d57c"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17c412bad2eb9468e876f556eb4ee910e62d721d2c7a53c7fa31e643d35352e6"}, + {file = "pyzmq-26.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0d987a3ae5a71c6226b203cfd298720e0086c7fe7c74f35fa8edddfbd6597eed"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:39887ac397ff35b7b775db7201095fc6310a35fdbae85bac4523f7eb3b840e20"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fdb5b3e311d4d4b0eb8b3e8b4d1b0a512713ad7e6a68791d0923d1aec433d919"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:226af7dcb51fdb0109f0016449b357e182ea0ceb6b47dfb5999d569e5db161d5"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bed0e799e6120b9c32756203fb9dfe8ca2fb8467fed830c34c877e25638c3fc"}, + {file = "pyzmq-26.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:29c7947c594e105cb9e6c466bace8532dc1ca02d498684128b339799f5248277"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:cdeabcff45d1c219636ee2e54d852262e5c2e085d6cb476d938aee8d921356b3"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35cffef589bcdc587d06f9149f8d5e9e8859920a071df5a2671de2213bef592a"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18c8dc3b7468d8b4bdf60ce9d7141897da103c7a4690157b32b60acb45e333e6"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7133d0a1677aec369d67dd78520d3fa96dd7f3dcec99d66c1762870e5ea1a50a"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:6a96179a24b14fa6428cbfc08641c779a53f8fcec43644030328f44034c7f1f4"}, + {file = "pyzmq-26.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4f78c88905461a9203eac9faac157a2a0dbba84a0fd09fd29315db27be40af9f"}, + {file = "pyzmq-26.2.0.tar.gz", hash = "sha256:070672c258581c8e4f640b5159297580a9974b026043bd4ab0470be9ed324f1f"}, +] + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "rdflib" +version = "7.0.0" +description = "RDFLib is a Python library for working with RDF, a simple yet powerful language for representing information." +optional = false +python-versions = ">=3.8.1,<4.0.0" +files = [ + {file = "rdflib-7.0.0-py3-none-any.whl", hash = "sha256:0438920912a642c866a513de6fe8a0001bd86ef975057d6962c79ce4771687cd"}, + {file = "rdflib-7.0.0.tar.gz", hash = "sha256:9995eb8569428059b8c1affd26b25eac510d64f5043d9ce8c84e0d0036e995ae"}, +] + +[package.dependencies] +isodate = ">=0.6.0,<0.7.0" +pyparsing = ">=2.1.0,<4" + +[package.extras] +berkeleydb = ["berkeleydb (>=18.1.0,<19.0.0)"] +html = ["html5lib (>=1.0,<2.0)"] +lxml = ["lxml (>=4.3.0,<5.0.0)"] +networkx = ["networkx (>=2.0.0,<3.0.0)"] + +[[package]] +name = "requests" +version = "2.32.3" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6"}, + {file = "requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-cache" +version = "1.2.1" +description = "A persistent cache for python requests" +optional = false +python-versions = ">=3.8" +files = [ + {file = "requests_cache-1.2.1-py3-none-any.whl", hash = "sha256:1285151cddf5331067baa82598afe2d47c7495a1334bfe7a7d329b43e9fd3603"}, + {file = "requests_cache-1.2.1.tar.gz", hash = "sha256:68abc986fdc5b8d0911318fbb5f7c80eebcd4d01bfacc6685ecf8876052511d1"}, +] + +[package.dependencies] +attrs = ">=21.2" +cattrs = ">=22.2" +platformdirs = ">=2.5" +requests = ">=2.22" +url-normalize = ">=1.4" +urllib3 = ">=1.25.5" + +[package.extras] +all = ["boto3 (>=1.15)", "botocore (>=1.18)", "itsdangerous (>=2.0)", "pymongo (>=3)", "pyyaml (>=6.0.1)", "redis (>=3)", "ujson (>=5.4)"] +bson = ["bson (>=0.5)"] +docs = ["furo (>=2023.3,<2024.0)", "linkify-it-py (>=2.0,<3.0)", "myst-parser (>=1.0,<2.0)", "sphinx (>=5.0.2,<6.0.0)", "sphinx-autodoc-typehints (>=1.19)", "sphinx-automodapi (>=0.14)", "sphinx-copybutton (>=0.5)", "sphinx-design (>=0.2)", "sphinx-notfound-page (>=0.8)", "sphinxcontrib-apidoc (>=0.3)", "sphinxext-opengraph (>=0.9)"] +dynamodb = ["boto3 (>=1.15)", "botocore (>=1.18)"] +json = ["ujson (>=5.4)"] +mongodb = ["pymongo (>=3)"] +redis = ["redis (>=3)"] +security = ["itsdangerous (>=2.0)"] +yaml = ["pyyaml (>=6.0.1)"] + +[[package]] +name = "rich" +version = "13.8.0" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.8.0-py3-none-any.whl", hash = "sha256:2e85306a063b9492dffc86278197a60cbece75bcb766022f3436f567cae11bdc"}, + {file = "rich-13.8.0.tar.gz", hash = "sha256:a5ac1f1cd448ade0d59cc3356f7db7a7ccda2c8cbae9c7a90c28ff463d3e91f4"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + +[[package]] +name = "rich-click" +version = "1.8.3" +description = "Format click help output nicely with rich" +optional = false +python-versions = ">=3.7" +files = [ + {file = "rich_click-1.8.3-py3-none-any.whl", hash = "sha256:636d9c040d31c5eee242201b5bf4f2d358bfae4db14bb22ec1cafa717cfd02cd"}, + {file = "rich_click-1.8.3.tar.gz", hash = "sha256:6d75bdfa7aa9ed2c467789a0688bc6da23fbe3a143e19aa6ad3f8bac113d2ab3"}, +] + +[package.dependencies] +click = ">=7" +rich = ">=10.7" +typing-extensions = "*" + +[package.extras] +dev = ["mypy", "packaging", "pre-commit", "pytest", "pytest-cov", "rich-codex", "ruff", "types-setuptools"] +docs = ["markdown-include", "mkdocs", "mkdocs-glightbox", "mkdocs-material-extensions", "mkdocs-material[imaging] (>=9.5.18,<9.6.0)", "mkdocs-rss-plugin", "mkdocstrings[python]", "rich-codex"] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.13.2" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tomlkit-0.13.2-py3-none-any.whl", hash = "sha256:7a974427f6e119197f670fbbbeae7bef749a6c14e793db934baefc1b5f03efde"}, + {file = "tomlkit-0.13.2.tar.gz", hash = "sha256:fff5fe59a87295b278abd31bec92c15d9bc4a06885ab12bcea52c71119392e79"}, +] + +[[package]] +name = "tornado" +version = "6.4.1" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +optional = false +python-versions = ">=3.8" +files = [ + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:163b0aafc8e23d8cdc3c9dfb24c5368af84a81e3364745ccb4427669bf84aec8"}, + {file = "tornado-6.4.1-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:6d5ce3437e18a2b66fbadb183c1d3364fb03f2be71299e7d10dbeeb69f4b2a14"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e20b9113cd7293f164dc46fffb13535266e713cdb87bd2d15ddb336e96cfc4"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ae50a504a740365267b2a8d1a90c9fbc86b780a39170feca9bcc1787ff80842"}, + {file = "tornado-6.4.1-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:613bf4ddf5c7a95509218b149b555621497a6cc0d46ac341b30bd9ec19eac7f3"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25486eb223babe3eed4b8aecbac33b37e3dd6d776bc730ca14e1bf93888b979f"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:454db8a7ecfcf2ff6042dde58404164d969b6f5d58b926da15e6b23817950fc4"}, + {file = "tornado-6.4.1-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:a02a08cc7a9314b006f653ce40483b9b3c12cda222d6a46d4ac63bb6c9057698"}, + {file = "tornado-6.4.1-cp38-abi3-win32.whl", hash = "sha256:d9a566c40b89757c9aa8e6f032bcdb8ca8795d7c1a9762910c722b1635c9de4d"}, + {file = "tornado-6.4.1-cp38-abi3-win_amd64.whl", hash = "sha256:b24b8982ed444378d7f21d563f4180a2de31ced9d8d84443907a0a64da2072e7"}, + {file = "tornado-6.4.1.tar.gz", hash = "sha256:92d3ab53183d8c50f8204a51e6f91d18a15d5ef261e84d452800d4ff6fc504e9"}, +] + +[[package]] +name = "traitlets" +version = "5.14.3" +description = "Traitlets Python configuration system" +optional = false +python-versions = ">=3.8" +files = [ + {file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"}, + {file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"}, +] + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] + +[[package]] +name = "typing-extensions" +version = "4.12.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, +] + +[[package]] +name = "url-normalize" +version = "1.4.3" +description = "URL normalization for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "url-normalize-1.4.3.tar.gz", hash = "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2"}, + {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"}, +] + +[package.dependencies] +six = "*" + +[[package]] +name = "urllib3" +version = "2.2.2" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, + {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "wcwidth" +version = "0.2.13" +description = "Measures the displayed width of unicode strings in a terminal" +optional = false +python-versions = "*" +files = [ + {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, + {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, +] + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +optional = false +python-versions = "*" +files = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] + +[[package]] +name = "zipp" +version = "3.20.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"}, + {file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.8.1" +content-hash = "3973b73c6ba7eed27a4ebe8ce3f43654a2752abd6681a96802c06321ff977057" diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..91b6ea65 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,63 @@ +[tool.poetry] +name = "roc-validator" +version = "0.2.0" +description = "A Python package to validate RO-Crates" +authors = [ + "Marco Enrico Piras ", + "Luca Pireddu ", + "Simone Leo ", +] +readme = "README.md" +packages = [{ include = "rocrate_validator", from = "." }] +include = [ + { path = "pyproject.toml", format = [ + "sdist", + "wheel", + ] }, + { path = "README.md", format = [ + "sdist", + "wheel", + ] }, + { path = "LICENSE", format = [ + "sdist", + "wheel", + ] } +] + +[tool.poetry.dependencies] +python = "^3.8.1" +rdflib = "^7.0.0" +pyshacl = "^0.26.0" +click = "^8.1.7" +rich = "^13.8.0" +toml = "^0.10.2" +rich-click = "^1.8.3" +colorlog = "^6.8" +requests = "^2.32.3" +requests-cache = "^1.2.1" +inquirerpy = "^0.3.4" + +[tool.poetry.group.dev.dependencies] +pyproject-flake8 = "^6.1.0" +pylint = "^3.1.0" +ipykernel = "^6.29.3" + +[tool.poetry.group.test.dependencies] +pytest-cov = "^5.0.0" +pytest-xdist = "^3.6.1" + +[tool.flake8] +max-line-length = 120 + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" + +[tool.poetry.scripts] +rocrate-validator = "rocrate_validator.cli:cli" + +[tool.rocrate_validator] +skip_dirs = [".git", ".github", ".vscode"] + +[tool.pytest.ini_options] +testpaths = ["tests"] diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..bc3faa76 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,20 @@ +# Copyright (c) 2024 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. + +[pytest] +# markers = sources +# log_cli=true +# log_level=INFO +addopts = -n auto +; filterwarnings = diff --git a/rocrate_validator/__init__.py b/rocrate_validator/__init__.py new file mode 100644 index 00000000..52268bac --- /dev/null +++ b/rocrate_validator/__init__.py @@ -0,0 +1,20 @@ +# Copyright (c) 2024 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. + +def get_version(): + from rocrate_validator.utils import get_version + return get_version() + + +__version__ = get_version() diff --git a/rocrate_validator/cli/__init__.py b/rocrate_validator/cli/__init__.py new file mode 100644 index 00000000..bc4038b7 --- /dev/null +++ b/rocrate_validator/cli/__init__.py @@ -0,0 +1,18 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .commands import profiles, validate +from .main import cli + +__all__ = ["cli", "profiles", "validate"] diff --git a/rocrate_validator/cli/commands/errors.py b/rocrate_validator/cli/commands/errors.py new file mode 100644 index 00000000..412b98c2 --- /dev/null +++ b/rocrate_validator/cli/commands/errors.py @@ -0,0 +1,58 @@ +# Copyright (c) 2024 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 sys +import textwrap + +from rich.console import Console + +import rocrate_validator.log as logging +from rocrate_validator.errors import InvalidProfilePath, ProfileNotFound, ProfilesDirectoryNotFound + +# Create a logger for this module +logger = logging.getLogger(__name__) + + +def handle_error(e: Exception, console: Console) -> None: + error_message = "" + if isinstance(e, ProfilesDirectoryNotFound): + error_message = f""" + The profile folder could not be located at the specified path: [red]{e.profiles_path}[/red]. + Please ensure that the path is correct and try again. + """ + elif isinstance(e, ProfileNotFound): + error_message = f"""The profile with the identifier "[red bold]{e.profile_name}[/red bold]" could not be found. + Please ensure that the profile exists and try again. + + To see the available profiles, run: + [cyan bold]rocrate-validator profiles list[/cyan bold] + """ + elif isinstance(e, InvalidProfilePath): + error_message = f"""The profile path "[red bold]{e.profile_path}[/red bold]" is not valid. + Please ensure that the profile exists and try again. + + To see the available profiles, run: + [cyan bold]rocrate-validator profiles list[/cyan bold] + """ + else: + error_message = f"\n\n[bold][[red]FAILED[/red]] Unexpected error: {e} !!![/bold]\n" + if logger.isEnabledFor(logging.DEBUG): + console.print_exception() + console.print(textwrap.indent("This error may be due to a bug.\n" + "Please report it to the issue tracker " + "along with the following stack trace:\n", ' ' * 9)) + console.print_exception() + + console.print(f"\n\n[bold][[red]ERROR[/red]] {error_message}[/bold]\n", style="white") + sys.exit(2) diff --git a/rocrate_validator/cli/commands/profiles.py b/rocrate_validator/cli/commands/profiles.py new file mode 100644 index 00000000..f04f1802 --- /dev/null +++ b/rocrate_validator/cli/commands/profiles.py @@ -0,0 +1,316 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path + +from rich.markdown import Markdown +from rich.padding import Padding +from rich.panel import Panel +from rich.table import Table + +import rocrate_validator.log as logging +from rocrate_validator import services +from rocrate_validator.cli.commands.errors import handle_error +from rocrate_validator.cli.main import cli, click +from rocrate_validator.cli.utils import get_app_header_rule +from rocrate_validator.colors import get_severity_color +from rocrate_validator.constants import DEFAULT_PROFILE_IDENTIFIER +from rocrate_validator.models import LevelCollection, RequirementLevel, Severity +from rocrate_validator.utils import get_profiles_path + +# set the default profiles path +DEFAULT_PROFILES_PATH = get_profiles_path() + +# set up logging +logger = logging.getLogger(__name__) + + +@cli.group("profiles") +@click.option( + "--profiles-path", + type=click.Path(exists=True), + default=DEFAULT_PROFILES_PATH, + show_default=True, + help="Path containing the profiles files" +) +@click.pass_context +def profiles(ctx, profiles_path: Path = DEFAULT_PROFILES_PATH): + """ + [magenta]rocrate-validator:[/magenta] Manage profiles + """ + logger.debug("Profiles path: %s", profiles_path) + ctx.obj['profiles_path'] = profiles_path + + +@profiles.command("list") +@click.option( + '--no-paging', + is_flag=True, + help="Disable paging", + default=False, + show_default=True +) +@click.pass_context +def list_profiles(ctx, no_paging: bool = False): # , profiles_path: Path = DEFAULT_PROFILES_PATH): + """ + List available profiles + """ + profiles_path = ctx.obj['profiles_path'] + console = ctx.obj['console'] + pager = ctx.obj['pager'] + interactive = ctx.obj['interactive'] + # Get the no_paging flag + enable_pager = not no_paging + # override the enable_pager flag if the interactive flag is False + if not interactive: + enable_pager = False + + try: + # Get the profiles + profiles = services.get_profiles(profiles_path=profiles_path) + + table = Table(show_header=True, + title=" Available profiles", + title_style="italic bold cyan", + title_justify="left", + header_style="bold cyan", + border_style="bright_black", + show_footer=False, + caption_style="italic bold", + caption="[cyan](*)[/cyan] Number of requirements checks by severity") + + # Define columns + table.add_column("Identifier", style="magenta bold", justify="center") + table.add_column("URI", style="yellow bold", justify="center") + table.add_column("Version", style="green bold", justify="center") + table.add_column("Name", style="white bold", justify="center") + table.add_column("Description", style="white italic") + table.add_column("Based on", style="white", justify="center") + table.add_column("Requirements Checks (*)", style="white", justify="center") + + # Define levels + levels = (LevelCollection.REQUIRED, LevelCollection.RECOMMENDED, LevelCollection.OPTIONAL) + + # Add data to the table + for profile in profiles: + # Count requirements by severity + checks_info = {} + for level in levels: + checks_info[level.severity.name] = { + "count": 0, + "color": get_severity_color(level.severity) + } + + requirements = [_ for _ in profile.get_requirements(severity=Severity.OPTIONAL) if not _.hidden] + for requirement in requirements: + for level in levels: + count = len(requirement.get_checks_by_level(level)) + checks_info[level.severity.name]["count"] += count + + checks_summary = "\n".join( + [f"[{v['color']}]{k}[/{v['color']}]: {v['count']}" for k, v in checks_info.items()]) + + # Add the row to the table + table.add_row(profile.identifier, profile.uri, profile.version, + profile.name, Markdown(profile.description.strip()), + "\n".join([p.identifier for p in profile.inherited_profiles]), + checks_summary) + table.add_row() + + # Print the table + with console.pager(pager=pager, styles=not console.no_color) if enable_pager else console: + console.print(get_app_header_rule()) + console.print(Padding(table, (0, 1))) + + except Exception as e: + handle_error(e, console) + + +@profiles.command("describe") +@click.option( + '-v', + '--verbose', + is_flag=True, + help="Show detailed list of requirements", + default=False, + show_default=True +) +@click.argument("profile-identifier", type=click.STRING, default=DEFAULT_PROFILE_IDENTIFIER, required=True) +@click.option( + '--no-paging', + is_flag=True, + help="Disable paging", + default=False, + show_default=True +) +@click.pass_context +def describe_profile(ctx, + profile_identifier: str = DEFAULT_PROFILE_IDENTIFIER, + profiles_path: Path = DEFAULT_PROFILES_PATH, + verbose: bool = False, no_paging: bool = False): + """ + Show a profile + """ + # Get the console + console = ctx.obj['console'] + pager = ctx.obj['pager'] + interactive = ctx.obj['interactive'] + # Get the no_paging flag + enable_pager = not no_paging + # override the enable_pager flag if the interactive flag is False + if not interactive: + enable_pager = False + + try: + # Get the profile + profile = services.get_profile(profiles_path=profiles_path, profile_identifier=profile_identifier) + + # Set the subheader title + subheader_title = f"[bold][cyan]Profile:[/cyan] [magenta italic]{profile.identifier}[/magenta italic][/bold]" + + # Set the subheader content + subheader_content = f"[bold cyan]Version:[/bold cyan] [italic green]{profile.version}[/italic green]\n" + subheader_content += f"[bold cyan]URI:[/bold cyan] [italic yellow]{profile.uri}[/italic yellow]\n\n" + subheader_content += f"[bold cyan]Description:[/bold cyan] [italic]{profile.description.strip()}[/italic]" + + # Build the profile table + if not verbose: + table = __compacted_describe_profile__(profile) + else: + table = __verbose_describe_profile__(profile) + + with console.pager(pager=pager, styles=not console.no_color) if enable_pager else console: + console.print(get_app_header_rule()) + console.print(Padding(Panel(subheader_content, title=subheader_title, padding=(1, 1), + title_align="left", border_style="cyan"), (0, 1, 0, 1))) + console.print(Padding(table, (1, 1))) + + except Exception as e: + handle_error(e, console) + + +def __requirement_level_style__(requirement: RequirementLevel): + """ + Format the requirement level + """ + color = get_severity_color(requirement.severity) + return f"{color} bold" + + +def __compacted_describe_profile__(profile): + """ + Show a profile in a compact way + """ + table_rows = [] + levels_list = set() + requirements = [_ for _ in profile.requirements if not _.hidden] + for requirement in requirements: + # add the requirement to the list + levels = (LevelCollection.REQUIRED, LevelCollection.RECOMMENDED, LevelCollection.OPTIONAL) + levels_count = [] + for level in levels: + count = len(requirement.get_checks_by_level(level)) + levels_count.append(count) + if count > 0: + color = get_severity_color(level.severity) + level_info = f"[{color}]{level.severity.name}[/{color}]" + levels_list.add(level_info) + table_rows.append((str(requirement.order_number), requirement.name, + Markdown(requirement.description.strip()), + f"{levels_count[0]}", + f"{levels_count[1]}", + f"{levels_count[2]}")) + + table = Table(show_header=True, + # renderer=renderer, + title=f"[cyan]{len(requirements)}[/cyan] Profile Requirements", + title_style="italic bold", + header_style="bold cyan", + border_style="bright_black", + show_footer=False, + show_lines=True, + caption_style="italic bold", + caption=f"[cyan](*)[/cyan] number of checks by severity level: {', '.join(levels_list)}") + + # Define columns + table.add_column("#", style="cyan bold", justify="right") + table.add_column("Name", style="magenta bold", justify="left") + table.add_column("Description", style="white italic") + table.add_column("# REQUIRED", style=__requirement_level_style__(LevelCollection.REQUIRED), justify="center") + table.add_column("# RECOMMENDED", style=__requirement_level_style__(LevelCollection.RECOMMENDED), justify="center") + table.add_column("# OPTIONAL", style=__requirement_level_style__(LevelCollection.OPTIONAL), justify="center") + # Add data to the table + for row in table_rows: + table.add_row(*row) + return table + + +def __verbose_describe_profile__(profile): + """ + Show a profile in a verbose way + """ + table_rows = [] + levels_list = set() + count_checks = 0 + for requirement in profile.requirements: + # skip hidden requirements + if requirement.hidden: + continue + # add the requirement to the list + for check in requirement.get_checks(): + color = get_severity_color(check.severity) + level_info = f"[{color}]{check.severity.name}[/{color}]" + levels_list.add(level_info) + override = None + # Uncomment the following lines to show the overridden checks + # if check.overridden_by: + # severity_color = get_severity_color(check.overridden_by.severity) + # override = "[overridden by: " \ + # f"[bold][magenta]{check.overridden_by.requirement.profile.identifier}[/magenta] "\ + # f"[{severity_color}]{check.overridden_by.relative_identifier}[/{severity_color}][/bold]]" + if check.override: + severity_color = get_severity_color(check.override.severity) + override = f"[override: [bold][magenta]{check.override.requirement.profile.identifier}[/magenta] "\ + f"[{severity_color}]{check.override.relative_identifier}[/{severity_color}][/bold]]" + from rich.align import Align + description_table = Table(show_header=False, show_footer=False, show_lines=False, show_edge=False) + if override: + description_table.add_row(Align(Padding(override, (0, 0, 1, 0)), align="right")) + description_table.add_row(Markdown(check.description.strip())) + + table_rows.append((str(check.relative_identifier), check.name, + description_table, level_info)) + count_checks += 1 + + table = Table(show_header=True, + # renderer=renderer, + title=f"[cyan]{count_checks}[/cyan] Profile Requirements Checks", + title_style="italic bold", + header_style="bold cyan", + border_style="bright_black", + show_footer=False, + show_lines=True, + caption_style="italic bold", + caption=f"[cyan](*)[/cyan] number of checks by severity level: {', '.join(levels_list)}") + + # Define columns + table.add_column("Identifier", style="cyan bold", justify="right") + table.add_column("Name", style="magenta bold", justify="left") + table.add_column("Description", style="white italic") + table.add_column("Severity (*)", style="bold", justify="center") + + # Add data to the table + for row in table_rows: + table.add_row(*row) + return table diff --git a/rocrate_validator/cli/commands/validate.py b/rocrate_validator/cli/commands/validate.py new file mode 100644 index 00000000..bade6af2 --- /dev/null +++ b/rocrate_validator/cli/commands/validate.py @@ -0,0 +1,815 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import os +import sys +import termios +import tty +from pathlib import Path +from typing import Optional + +from InquirerPy import prompt +from InquirerPy.base.control import Choice +from rich.align import Align +from rich.layout import Layout +from rich.live import Live +from rich.markdown import Markdown +from rich.padding import Padding +from rich.pager import Pager +from rich.panel import Panel +from rich.progress import BarColumn, Progress, TextColumn, TimeElapsedColumn +from rich.rule import Rule + +import rocrate_validator.log as logging +from rocrate_validator import services +from rocrate_validator.cli.commands.errors import handle_error +from rocrate_validator.cli.main import cli, click +from rocrate_validator.cli.utils import Console, get_app_header_rule +from rocrate_validator.colors import get_severity_color +from rocrate_validator.events import Event, EventType, Subscriber +from rocrate_validator.models import (LevelCollection, Profile, Severity, + ValidationResult) +from rocrate_validator.utils import URI, get_profiles_path + +# from rich.markdown import Markdown +# from rich.table import Table + +# set the default profiles path +DEFAULT_PROFILES_PATH = get_profiles_path() + +# set up logging +logger = logging.getLogger(__name__) + + +def validate_uri(ctx, param, value): + """ + Validate if the value is a path or a URI + """ + if value: + try: + # parse the value to extract the scheme + uri = URI(value) + if not uri.is_remote_resource() and not uri.is_local_directory() and not uri.is_local_file(): + raise click.BadParameter(f"Invalid RO-Crate URI \"{value}\": " + "it MUST be a local directory or a ZIP file (local or remote).", param=param) + if not uri.is_available(): + raise click.BadParameter("RO-crate URI not available", param=param) + except ValueError as e: + logger.debug(e) + raise click.BadParameter("Invalid RO-crate path or URI", param=param) + + return value + + +def get_single_char(console: Optional[Console] = None, end: str = "\n", + message: Optional[str] = None, + choices: Optional[list[str]] = None) -> str: + """ + Get a single character from the console + """ + fd = sys.stdin.fileno() + old_settings = termios.tcgetattr(fd) + + char = None + while char is None or (choices and char not in choices): + if console and message: + console.print(f"\n{message}", end="") + try: + tty.setraw(sys.stdin.fileno()) + char = sys.stdin.read(1) + finally: + termios.tcsetattr(fd, termios.TCSADRAIN, old_settings) + if console: + console.print(char, end=end if choices and char in choices else "") + if choices and char not in choices: + if console: + console.print(" [bold red]INVALID CHOICE[/bold red]", end=end) + return char + + +@cli.command("validate") +@click.argument("rocrate-uri", callback=validate_uri, default=".") +@click.option( + '-nff', + '--no-fail-fast', + is_flag=True, + help="Disable fail fast validation mode", + default=False, + show_default=True +) +@click.option( + "--profiles-path", + type=click.Path(exists=True), + default=DEFAULT_PROFILES_PATH, + show_default=True, + help="Path containing the profiles files", +) +@click.option( + "-p", + "--profile-identifier", + multiple=True, + type=click.STRING, + default=None, + show_default=True, + help="Identifier of the profile to use for validation", +) +@click.option( + "-np", + "--no-auto-profile", + is_flag=True, + help="Disable automatic detection of the profile to use for validation", + default=False, + show_default=True +) +@click.option( + '-nh', + '--disable-profile-inheritance', + is_flag=True, + help="Disable inheritance of profiles", + default=False, + show_default=True +) +@click.option( + "-l", + "--requirement-severity", + type=click.Choice([s.name for s in Severity], case_sensitive=False), + default=Severity.REQUIRED.name, + show_default=True, + help="Severity of the requirements to validate", +) +@click.option( + '-lo', + '--requirement-severity-only', + is_flag=True, + help="Validate only the requirements of the specified severity (no requirements with lower severity)", + default=False, + show_default=True +) +@click.option( + '-v', + '--verbose', + is_flag=True, + help="Output the validation details without prompting", + default=False, + show_default=True +) +@click.option( + '--no-paging', + is_flag=True, + help="Disable pagination of the validation details", + default=False, + show_default=True +) +@click.option( + '-f', + '--output-format', + type=click.Choice(["json", "text"], case_sensitive=False), + default="text", + show_default=True, + help="Output format of the validation report" +) +@click.option( + '-o', + '--output-file', + type=click.Path(), + default=None, + show_default=True, + help="Path to the output file for the validation report", +) +@click.option( + '-w', + '--output-line-width', + type=click.INT, + default=120, + show_default=True, + help="Width of the output line", +) +@click.pass_context +def validate(ctx, + profiles_path: Path = DEFAULT_PROFILES_PATH, + profile_identifier: Optional[str] = None, + no_auto_profile: bool = False, + disable_profile_inheritance: bool = False, + requirement_severity: str = Severity.REQUIRED.name, + requirement_severity_only: bool = False, + rocrate_uri: Path = ".", + no_fail_fast: bool = False, + ontologies_path: Optional[Path] = None, + no_paging: bool = False, + verbose: bool = False, + output_format: str = "text", + output_file: Optional[Path] = None, + output_line_width: Optional[int] = None): + """ + [magenta]rocrate-validator:[/magenta] Validate a RO-Crate against a profile + """ + console: Console = ctx.obj['console'] + pager = ctx.obj['pager'] + interactive = ctx.obj['interactive'] + # Get the no_paging flag + enable_pager = not no_paging + # override the enable_pager flag if the interactive flag is False + if not interactive: + enable_pager = False + # Log the input parameters for debugging + logger.debug("profiles_path: %s", os.path.abspath(profiles_path)) + logger.debug("profile_identifier: %s", profile_identifier) + logger.debug("requirement_severity: %s", requirement_severity) + logger.debug("requirement_severity_only: %s", requirement_severity_only) + + logger.debug("disable_inheritance: %s", disable_profile_inheritance) + logger.debug("rocrate_uri: %s", rocrate_uri) + logger.debug("no_fail_fast: %s", no_fail_fast) + logger.debug("fail fast: %s", not no_fail_fast) + + if ontologies_path: + logger.debug("ontologies_path: %s", os.path.abspath(ontologies_path)) + if rocrate_uri: + logger.debug("rocrate_path: %s", os.path.abspath(rocrate_uri)) + + try: + # Validation settings + validation_settings = { + "profiles_path": profiles_path, + "profile_identifier": profile_identifier, + "requirement_severity": requirement_severity, + "requirement_severity_only": requirement_severity_only, + "inherit_profiles": not disable_profile_inheritance, + "verbose": verbose, + "data_path": rocrate_uri, + "ontology_path": Path(ontologies_path).absolute() if ontologies_path else None, + "abort_on_first": not no_fail_fast + } + + # Print the application header + if output_format == "text" and output_file is None: + console.print(get_app_header_rule()) + + # Get the available profiles + available_profiles = services.get_profiles(profiles_path) + + # Detect the profile to use for validation + autodetection = False + selected_profile = profile_identifier + if selected_profile is None or len(selected_profile) == 0: + + # Auto-detect the profile to use for validation (if not disabled) + candidate_profiles = None + if not no_auto_profile: + candidate_profiles = services.detect_profiles(settings=validation_settings) + logger.debug("Candidate profiles: %s", candidate_profiles) + else: + logger.info("Auto-detection of the profiles to use for validation is disabled") + + # Prompt the user to select the profile to use for validation if the interactive mode is enabled + # and no profile is auto-detected or multiple profiles are detected + if interactive and ( + not candidate_profiles or + len(candidate_profiles) == 0 or + len(candidate_profiles) == len(available_profiles) + ): + # Define the list of choices + console.print( + Padding( + Rule( + "[bold yellow]WARNING: [/bold yellow]" + "[bold]Unable to automatically detect the profile to use for validation[/bold]\n", + align="center", + style="bold yellow" + ), + (2, 2, 0, 2) + ) + ) + selected_options = multiple_choice(console, available_profiles) + profile_identifier = [available_profiles[int( + selected_option)].identifier for selected_option in selected_options] + logger.debug("Profile selected: %s", selected_options) + console.print(Padding(Rule(style="bold yellow"), (1, 2))) + + elif candidate_profiles and len(candidate_profiles) < len(available_profiles): + logger.debug("Profile identifier autodetected: %s", candidate_profiles[0].identifier) + autodetection = True + profile_identifier = [_.identifier for _ in candidate_profiles] + + # Fall back to the selected profile + if not profile_identifier or len(profile_identifier) == 0: + console.print(f"\n{' '*2}[bold yellow]WARNING: [/bold yellow]", end="") + if no_auto_profile: + console.print("[bold]Auto-detection of the profiles to use for validation is disabled[/bold]") + else: + console.print("[bold]Unable to automatically detect the profile to use for validation[/bold]") + console.print(f"{' '*11}[bold]The base `ro-crate` profile will be used for validation[/bold]") + profile_identifier = ["ro-crate"] + + # Validate the RO-Crate against the selected profiles + is_valid = True + for profile in profile_identifier: + # Set the selected profile + validation_settings["profile_identifier"] = profile + validation_settings["profile_autodetected"] = autodetection + logger.debug("Profile selected for validation: %s", validation_settings["profile_identifier"]) + logger.debug("Profile autodetected: %s", autodetection) + + # Compute the profile statistics + profile_stats = __compute_profile_stats__(validation_settings) + + report_layout = ValidationReportLayout(console, validation_settings, profile_stats, None) + + # Validate RO-Crate against the profile and get the validation result + result: ValidationResult = None + if output_format == "text": + console.disabled = output_file is not None + result: ValidationResult = report_layout.live( + lambda: services.validate( + validation_settings, + subscribers=[report_layout.progress_monitor] + ) + ) + console.disabled = False + else: + result: ValidationResult = services.validate( + validation_settings + ) + + # store the cumulative validation result + is_valid = is_valid and result.passed(LevelCollection.get(requirement_severity).severity) + + # Print the validation result + if output_format == "text" and not output_file: + if not result.passed(): + verbose_choice = "n" + if interactive and not verbose and enable_pager: + verbose_choice = get_single_char(console, choices=['y', 'n'], + message=( + "[bold] > Do you want to see the validation details? " + "([magenta]y/n[/magenta]): [/bold]" + )) + if verbose_choice == "y" or verbose: + report_layout.show_validation_details(pager, enable_pager=enable_pager) + + if output_format == "json" and not output_file: + console.print(result.to_json()) + + if output_file: + # Print the validation report to a file + if output_format == "json": + with open(output_file, "w") as f: + f.write(result.to_json()) + elif output_format == "text": + with open(output_file, "w") as f: + c = Console(file=f, color_system=None, width=output_line_width, height=31) + c.print(report_layout.layout) + report_layout.console = c + if not result.passed() and verbose: + report_layout.show_validation_details(None, enable_pager=False) + + # Interrupt the validation if the fail fast mode is enabled + if no_fail_fast and not is_valid: + break + + # using ctx.exit seems to raise an Exception that gets caught below, + # so we use sys.exit instead. + sys.exit(0 if is_valid else 1) + except Exception as e: + handle_error(e, console) + + +def multiple_choice(console: Console, + choices: list[Profile]): + """ + Display a multiple choice menu + """ + # Build the prompt text + prompt_text = "Please select the profiles to validate the RO-Crate against ( to select):" + + # Get the selected option + question = [ + { + "type": "checkbox", + "name": "profiles", + "message": prompt_text, + "choices": [Choice(i, f"{choices[i].identifier}: {choices[i].name}") for i in range(0, len(choices))] + } + ] + console.print("\n") + selected = prompt(question, style={"questionmark": "#ff9d00 bold", + "question": "bold", + "checkbox": "magenta", + "answer": "magenta"}, + style_override=False) + logger.debug("Selected profiles: %s", selected) + return selected["profiles"] + + +class ProgressMonitor(Subscriber): + + PROFILE_VALIDATION = "Profiles" + REQUIREMENT_VALIDATION = "Requirements" + REQUIREMENT_CHECK_VALIDATION = "Requirements Checks" + + def __init__(self, layout: ValidationReportLayout, stats: dict): + self.__progress = Progress( + TextColumn("[progress.description]{task.description}"), + BarColumn(), + TextColumn("{task.completed}/{task.total}"), + TimeElapsedColumn(), + expand=True) + self._stats = stats + self.profile_validation = self.progress.add_task( + self.PROFILE_VALIDATION, total=len(stats.get("profiles"))) + self.requirement_validation = self.progress.add_task( + self.REQUIREMENT_VALIDATION, total=stats.get("total_requirements")) + self.requirement_check_validation = self.progress.add_task( + self.REQUIREMENT_CHECK_VALIDATION, total=stats.get("total_checks")) + self.__layout = layout + super().__init__("ProgressMonitor") + + def start(self): + self.progress.start() + + def stop(self): + self.progress.stop() + + @property + def layout(self) -> ValidationReportLayout: + return self.__layout + + @property + def progress(self) -> Progress: + return self.__progress + + def update(self, event: Event): + # logger.debug("Event: %s", event.event_type) + if event.event_type == EventType.PROFILE_VALIDATION_START: + logger.debug("Profile validation start: %s", event.profile.identifier) + elif event.event_type == EventType.REQUIREMENT_VALIDATION_START: + logger.debug("Requirement validation start") + elif event.event_type == EventType.REQUIREMENT_CHECK_VALIDATION_START: + logger.debug("Requirement check validation start") + elif event.event_type == EventType.REQUIREMENT_CHECK_VALIDATION_END: + if not event.requirement_check.requirement.hidden: + self.progress.update(task_id=self.requirement_check_validation, advance=1) + if event.validation_result is not None: + if event.validation_result: + self._stats["passed_checks"].append(event.requirement_check) + else: + self._stats["failed_checks"].append(event.requirement_check) + self.layout.update(self._stats) + elif event.event_type == EventType.REQUIREMENT_VALIDATION_END: + if not event.requirement.hidden: + self.progress.update(task_id=self.requirement_validation, advance=1) + if event.validation_result: + self._stats["passed_requirements"].append(event.requirement) + else: + self._stats["failed_requirements"].append(event.requirement) + self.layout.update(self._stats) + elif event.event_type == EventType.PROFILE_VALIDATION_END: + self.progress.update(task_id=self.profile_validation, advance=1) + elif event.event_type == EventType.VALIDATION_END: + self.layout.set_overall_result(event.validation_result) + + +class ValidationReportLayout(Layout): + + def __init__(self, console: Console, validation_settings: dict, profile_stats: dict, result: ValidationResult): + super().__init__() + self.console = console + self.validation_settings = validation_settings + self.profile_stats = profile_stats + self.result = result + self.__layout = None + self._validation_checks_progress = None + self.__progress_monitor = None + self.requirement_checks_container_layout = None + self.passed_checks = None + self.failed_checks = None + self.report_details_container = None + + @property + def layout(self): + if not self.__layout: + self.__init_layout__() + return self.__layout + + @property + def validation_checks_progress(self): + return self._validation_checks_progress + + @property + def progress_monitor(self) -> ProgressMonitor: + if not self.__progress_monitor: + self.__progress_monitor = ProgressMonitor(self, self.profile_stats) + return self.__progress_monitor + + def live(self, update_callable: callable) -> any: + assert update_callable, "Update callable must be provided" + # Start live rendering + result = None + with Live(self.layout, console=self.console, refresh_per_second=10, transient=False): + result = update_callable() + return result + + def __init_layout__(self): + + # Get the validation settings + settings = self.validation_settings + + # Set the console height + self.console.height = 31 + + # Create the layout of the base info of the validation report + severity_color = get_severity_color(Severity.get(settings["requirement_severity"])) + base_info_layout = Layout( + Align( + f"\n[bold cyan]RO-Crate:[/bold cyan] [bold]{URI(settings['data_path']).uri}[/bold]" + "\n[bold cyan]Target Profile:[/bold cyan][bold magenta] " + f"{settings['profile_identifier']}[/bold magenta] " + f"{ '[italic](autodetected)[/italic]' if settings['profile_autodetected'] else ''}" + f"\n[bold cyan]Validation Severity:[/bold cyan] " + f"[bold {severity_color}]{settings['requirement_severity']}[/bold {severity_color}]", + style="white", align="left"), + name="Base Info", size=5) + # + self.passed_checks = Layout(name="PASSED") + self.failed_checks = Layout(name="FAILED") + # Create the layout of the requirement checks section + validated_checks_container = Layout(name="Requirement Checks Validated") + validated_checks_container.split_row( + self.passed_checks, + self.failed_checks + ) + + # Create the layout of the requirement checks section + self.requirement_checks_by_severity_container_layout = Layout(name="Requirement Checks Validation", size=5) + self.requirement_checks_by_severity_container_layout.split_row( + Layout(name="required"), + Layout(name="recommended"), + Layout(name="optional") + ) + + # Create the layout of the requirement checks section + requirement_checks_container_layout = Layout(name="Requirement Checks") + requirement_checks_container_layout.split_column( + self.requirement_checks_by_severity_container_layout, + validated_checks_container + ) + + # Create the layout of the validation checks progress + self._validation_checks_progress = Layout( + Panel(Align(self.progress_monitor.progress, align="center"), + border_style="white", padding=(0, 1), title="Overall Progress"), + name="Validation Progress", size=5) + + # Create the layout of the report container + report_container_layout = Layout(name="Report Container Layout") + report_container_layout.split_column( + base_info_layout, + Layout(Panel(requirement_checks_container_layout, + title="[bold]Requirements Checks Validation[/bold]", border_style="white", padding=(1, 1))), + self._validation_checks_progress + ) + + # Update the layout with the profile stats + self.update(self.profile_stats) + + # Create the main layout + self.checks_stats_layout = Layout( + Panel(report_container_layout, title="[bold]- Validation Report -[/bold]", + border_style="cyan", title_align="center", padding=(1, 2))) + + # Create the overall result layout + self.overall_result = Layout( + Padding(Rule("\n[italic][cyan]Validating ROCrate...[/cyan][/italic]"), (1, 1)), size=3) + + group_layout = Layout() + group_layout.add_split(self.checks_stats_layout) + group_layout.add_split(self.overall_result) + + self.__layout = Padding(group_layout, (1, 1)) + + def update(self, profile_stats: dict = None): + assert profile_stats, "Profile stats must be provided" + self.profile_stats = profile_stats + self.requirement_checks_by_severity_container_layout["required"].update( + Panel( + Align( + str(profile_stats['check_count_by_severity'][Severity.REQUIRED]) if profile_stats else "0", + align="center" + ), + padding=(1, 1), + title="Severity: REQUIRED", + title_align="center", + border_style="RED" + ) + ) + self.requirement_checks_by_severity_container_layout["recommended"].update( + Panel( + Align( + str(profile_stats['check_count_by_severity'][Severity.RECOMMENDED]) if profile_stats else "0", + align="center" + ), + padding=(1, 1), + title="Severity: RECOMMENDED", + title_align="center", + border_style="orange1" + ) + ) + self.requirement_checks_by_severity_container_layout["optional"].update( + Panel( + Align( + str(profile_stats['check_count_by_severity'][Severity.OPTIONAL]) if profile_stats else "0", + align="center" + ), + padding=(1, 1), + title="Severity: OPTIONAL", + title_align="center", + border_style="yellow" + ) + ) + + self.passed_checks.update( + Panel( + Align( + str(len(self.profile_stats["passed_checks"])), + align="center" + ), + padding=(1, 1), + title="PASSED Checks", + title_align="center", + border_style="green" + ) + ) + + self.failed_checks.update( + Panel( + Align( + str(len(self.profile_stats["failed_checks"])), + align="center" + ), + padding=(1, 1), + title="FAILED Checks", + title_align="center", + border_style="red" + ) + ) + + def set_overall_result(self, result: ValidationResult): + assert result, "Validation result must be provided" + self.result = result + if result.passed(): + self.overall_result.update( + Padding(Rule("[bold][[green]OK[/green]] RO-Crate is a [green]valid[/green] " + f"[magenta]{result.context.target_profile.identifier}[/magenta] !!![/bold]\n\n", + style="bold green"), (1, 1))) + else: + self.overall_result.update( + Padding(Rule("[bold][[red]FAILED[/red]] RO-Crate is [red]not[/red] a [red]valid[/red] " + f"[magenta]{result.context.target_profile.identifier}[/magenta] !!![/bold]\n", + style="bold red"), (1, 1))) + + def show_validation_details(self, pager: Pager, enable_pager: bool = True): + """ + Print the validation result + """ + if not self.result: + raise ValueError("Validation result is not available") + + # init references to the console, result and severity + console = self.console + result = self.result + + logger.debug("Validation failed: %s", result.failed_requirements) + + # Print validation details + with console.pager(pager=pager, styles=not console.no_color) if enable_pager else console: + # Print the list of failed requirements + console.print( + Padding("\n[bold]The following requirements have not meet: [/bold]", (0, 2)), style="white") + for requirement in sorted(result.failed_requirements, key=lambda x: x.identifier): + console.print( + Align(f"\n[profile: [magenta bold]{requirement.profile.name }[/magenta bold]]", align="right") + ) + console.print( + Padding( + f"[bold][cyan][u][ {requirement.identifier} ]: " + f"{Markdown(requirement.name).markup}[/u][/cyan][/bold]", (0, 5)), style="white") + console.print(Padding(Markdown(requirement.description), (1, 6))) + console.print(Padding("[white bold u] Failed checks [/white bold u]\n", + (0, 8)), style="white bold") + + for check in sorted(result.get_failed_checks_by_requirement(requirement), + key=lambda x: (-x.severity.value, x)): + issue_color = get_severity_color(check.level.severity) + console.print( + Padding( + f"[bold][{issue_color}][{check.relative_identifier.center(16)}][/{issue_color}] " + f"[magenta]{check.name}[/magenta][/bold]:", (0, 7)), + style="white bold") + console.print(Padding(Markdown(check.description), (0, 27))) + console.print(Padding("[u] Detected issues [/u]", (0, 8)), style="white bold") + for issue in sorted(result.get_issues_by_check(check), + key=lambda x: (-x.severity.value, x)): + path = "" + if issue.resultPath and issue.value: + path = f" of [yellow]{issue.resultPath}[/yellow]" + if issue.value: + if issue.resultPath: + path += "=" + path += f"\"[green]{issue.value}[/green]\" " # keep the ending space + if issue.focusNode: + path = f"{path} on [cyan]<{issue.focusNode}>[/cyan]" + console.print( + Padding(f"- [[red]Violation[/red]{path}]: " + f"{Markdown(issue.message).markup}", (0, 9)), style="white") + console.print("\n", style="white") + + +def __compute_profile_stats__(validation_settings: dict): + """ + Compute the statistics of the profile + """ + # extract the validation settings + severity_validation = Severity.get(validation_settings.get("requirement_severity")) + profiles = services.get_profiles(validation_settings.get("profiles_path"), severity=severity_validation) + profile = services.get_profile(validation_settings.get("profiles_path"), + validation_settings.get("profile_identifier"), + severity=severity_validation) + # initialize the profiles list + profiles = [profile] + + # add inherited profiles if enabled + if validation_settings.get("inherit_profiles"): + profiles.extend(profile.inherited_profiles) + logger.debug("Inherited profiles: %r", profile.inherited_profiles) + + # Initialize the counters + total_requirements = 0 + total_checks = 0 + check_count_by_severity = {} + + # Initialize the counters + for severity in (Severity.REQUIRED, Severity.RECOMMENDED, Severity.OPTIONAL): + check_count_by_severity[severity] = 0 + + # Process the requirements and checks + processed_requirements = [] + for profile in profiles: + for requirement in profile.requirements: + processed_requirements.append(requirement) + if requirement.hidden: + continue + + requirement_checks_count = 0 + for severity in (Severity.REQUIRED, Severity.RECOMMENDED, Severity.OPTIONAL): + logger.debug( + f"Checking requirement: {requirement} severity: {severity} {severity < severity_validation}") + # skip requirements with lower severity + if severity < severity_validation: + continue + # count the checks + num_checks = len( + [_ for _ in requirement.get_checks_by_level(LevelCollection.get(severity.name)) + if not _.overridden]) + check_count_by_severity[severity] += num_checks + requirement_checks_count += num_checks + + # count the requirements and checks + if requirement_checks_count == 0: + logger.debug(f"No checks for requirement: {requirement}") + else: + logger.debug(f"Requirement: {requirement} checks count: {requirement_checks_count}") + assert not requirement.hidden, "Hidden requirements should not be counted" + total_requirements += 1 + total_checks += requirement_checks_count + + # log processed requirements + logger.debug("Processed requirements %r: %r", len(processed_requirements), processed_requirements) + + # Prepare the result + result = { + "profile": profile, + "profiles": profiles, + "severity": severity_validation, + "check_count_by_severity": check_count_by_severity, + "total_requirements": total_requirements, + "total_checks": total_checks, + "failed_requirements": [], + "failed_checks": [], + "passed_requirements": [], + "passed_checks": [] + } + logger.debug(result) + return result diff --git a/rocrate_validator/cli/main.py b/rocrate_validator/cli/main.py new file mode 100644 index 00000000..c3876e19 --- /dev/null +++ b/rocrate_validator/cli/main.py @@ -0,0 +1,102 @@ +# Copyright (c) 2024 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 sys + +import rich_click as click + +import rocrate_validator.log as logging +from rocrate_validator.cli.utils import Console, SystemPager +from rocrate_validator.utils import get_version + +# set up logging +logger = logging.getLogger(__name__) + +__all__ = ["cli", "click"] + + +@click.group(invoke_without_command=True) +@click.rich_config(help_config=click.RichHelpConfiguration(use_rich_markup=True)) +@click.option( + '--debug', + is_flag=True, + help="Enable debug logging", + default=False +) +@click.option( + '-v', + '--version', + is_flag=True, + help="Show the version of the rocrate-validator package", + default=False +) +@click.option( + '-y', + '--no-interactive', + is_flag=True, + help="Disable interactive mode", + default=False +) +@click.option( + '--disable-color', + is_flag=True, + help="Disable colored console output", + default=False +) +@click.pass_context +def cli(ctx: click.Context, debug: bool, version: bool, disable_color: bool, no_interactive: bool): + ctx.ensure_object(dict) + + # determine if the console is interactive + interactive = sys.stdout.isatty() and not no_interactive + + console = Console(no_color=disable_color or not interactive) + # pass the console to subcommands through the click context, after configuration + ctx.obj['console'] = console + ctx.obj['pager'] = SystemPager() + ctx.obj['interactive'] = interactive + + try: + # If the version flag is set, print the version and exit + if version: + console.print( + f"[bold]rocrate-validator [cyan]{get_version()}[/cyan][/bold]") + sys.exit(0) + # Set the log level + logging.basicConfig(level=logging.DEBUG if debug else logging.WARNING) + # If no subcommand is provided, invoke the default command + if ctx.invoked_subcommand is None: + # If no subcommand is provided, invoke the default command + from .commands.validate import validate + ctx.invoke(validate) + else: + logger.debug("Command invoked: %s", ctx.invoked_subcommand) + except Exception as e: + console.print( + f"\n\n[bold][[red]FAILED[/red]] Unexpected error: {e} !!![/bold]\n", style="white") + console.print("""This error may be due to a bug. + Please report it to the issue tracker + along with the following stack trace: + """) + console.print_exception() + sys.exit(2) + + +if __name__ == "__main__": + try: + cli() + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(f"An unexpected error occurred: {e}") + exit(2) diff --git a/rocrate_validator/cli/utils.py b/rocrate_validator/cli/utils.py new file mode 100644 index 00000000..5ad48c7c --- /dev/null +++ b/rocrate_validator/cli/utils.py @@ -0,0 +1,77 @@ +# Copyright (c) 2024 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 os +import pydoc +import re +import textwrap +from typing import Any, Optional + +from rich.console import Console as BaseConsole +from rich.padding import Padding +from rich.pager import Pager +from rich.rule import Rule +from rich.text import Text + +from rocrate_validator import log as logging +from rocrate_validator.utils import get_version + +# set up logging +logger = logging.getLogger(__name__) + + +def format_text(text: str, + initial_indent: int = 0, + subsequent_indent: int = 0, + line_width: Optional[int] = None, + skip_initial_indent: bool = False) -> str: + text = re.sub(r"\s+", " ", text).strip() + line_width = line_width or os.get_terminal_size().columns - initial_indent + if line_width: + text = textwrap.fill(text, width=line_width, initial_indent=' ' * + (initial_indent if not skip_initial_indent else 0), + subsequent_indent=' ' * subsequent_indent, + break_long_words=False, + break_on_hyphens=False) + else: + text = textwrap.indent(text, ' ' * initial_indent) + return text + + +def get_app_header_rule() -> Text: + return Padding(Rule(f"\n[bold][cyan]ROCrate Validator[/cyan] (ver. [magenta]{get_version()}[/magenta])[/bold]", + style="bold cyan"), (1, 2)) + + +class SystemPager(Pager): + """Uses the pager installed on the system.""" + + def _pager(self, content: str) -> Any: + return pydoc.pipepager(content, "less -R") + + def show(self, content: str) -> None: + """Use the same pager used by pydoc.""" + self._pager(content) + + +class Console(BaseConsole): + """Rich console that can be disabled.""" + + def __init__(self, *args, disabled: bool = False, **kwargs): + super().__init__(*args, **kwargs) + self.disabled = disabled + + def print(self, *args, **kwargs): + if not self.disabled: + super().print(*args, **kwargs) diff --git a/rocrate_validator/colors.py b/rocrate_validator/colors.py new file mode 100644 index 00000000..3676b2d8 --- /dev/null +++ b/rocrate_validator/colors.py @@ -0,0 +1,54 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Union + +from .models import LevelCollection, Severity + + +def get_severity_color(severity: Union[str, Severity]) -> str: + """ + Get the color for the severity + + :param severity: The severity + :return: The color + """ + if severity == Severity.REQUIRED or severity == "REQUIRED": + return "red" + elif severity == Severity.RECOMMENDED or severity == "RECOMMENDED": + return "orange1" + elif severity == Severity.OPTIONAL or severity == "OPTIONAL": + return "yellow" + else: + return "white" + + +def get_req_level_color(level: LevelCollection) -> str: + """ + Get the color for a LevelCollection + + :return: The color + """ + if level in (LevelCollection.MUST, LevelCollection.SHALL, LevelCollection.REQUIRED): + return "red" + elif level in (LevelCollection.MUST_NOT, LevelCollection.SHALL_NOT): + return "purple" + elif level in (LevelCollection.SHOULD, LevelCollection.RECOMMENDED): + return "orange1" + elif level == LevelCollection.SHOULD_NOT: + return "lightyellow" + elif level in (LevelCollection.MAY, LevelCollection.OPTIONAL): + return "yellow" + else: + return "white" diff --git a/rocrate_validator/config.py b/rocrate_validator/config.py new file mode 100644 index 00000000..7283a922 --- /dev/null +++ b/rocrate_validator/config.py @@ -0,0 +1,41 @@ +# Copyright (c) 2024 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 +import colorlog + + +def configure_logging(level: int = logging.WARNING): + """ + Configure the logging for the package + + :param level: The logging level + """ + log_format = '[%(log_color)s%(asctime)s%(reset)s] %(levelname)s in %(yellow)s%(module)s%(reset)s: '\ + '%(light_white)s%(message)s%(reset)s' + if level == logging.DEBUG: + log_format = '%(log_color)s%(levelname)s%(reset)s:%(yellow)s%(name)s:%(module)s::%(funcName)s%(reset)s '\ + '@ %(light_green)sline: %(lineno)s%(reset)s - %(light_black)s%(message)s%(reset)s' + + colorlog.basicConfig( + level=level, + format=log_format, + log_colors={ + 'DEBUG': 'cyan', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'red,bg_white', + }, + ) diff --git a/rocrate_validator/constants.py b/rocrate_validator/constants.py new file mode 100644 index 00000000..0051afe8 --- /dev/null +++ b/rocrate_validator/constants.py @@ -0,0 +1,84 @@ +# Copyright (c) 2024 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. + +# Define allowed RDF extensions and serialization formats as map +import typing + +from rdflib import Namespace + +# Define SHACL namespace +SHACL_NS = "http://www.w3.org/ns/shacl#" + +# Define the Validator namespace +VALIDATOR_NS = Namespace("https://github.com/crs4/rocrate-validator/") + +# Define the Profiles Vocabulary namespace +PROF_NS = Namespace("http://www.w3.org/ns/dx/prof/") + +# Define the Schema.org namespace +SCHEMA_ORG_NS = Namespace("http://schema.org/") + +# Define RDF syntax namespace +RDF_SYNTAX_NS = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" + +# Define the file name for the profile specification conforms to the Profiles Vocabulary +PROFILE_SPECIFICATION_FILE = "profile.ttl" + +# Define the rocrate-metadata.json file name +ROCRATE_METADATA_FILE = "ro-crate-metadata.json" + +# Define the default profiles name +DEFAULT_PROFILE_IDENTIFIER = "ro-crate-1.1" + +# Define the default profiles path +DEFAULT_PROFILES_PATH = "profiles" + +# Define the default README file name for the RO-Crate profile +DEFAULT_PROFILE_README_FILE = "README.md" + +# Define the list of directories to ignore when loading profiles +IGNORED_PROFILE_DIRECTORIES = ["__pycache__", ".", "README.md", "LICENSE"] + +# Define the list of enabled profile file extensions +PROFILE_FILE_EXTENSIONS = [".ttl", ".py"] + +# Define the default ontology file name +DEFAULT_ONTOLOGY_FILE = "ontology.ttl" + +# Define allowed RDF extensions and serialization formats as map +RDF_SERIALIZATION_FILE_FORMAT_MAP = { + "xml": "xml", + "pretty-xml": "pretty-xml", + "trig": "trig", + "n3": "n3", + "turtle": "ttl", + "nt": "nt", + "json-ld": "json-ld" +} + +# Define allowed RDF serialization formats +RDF_SERIALIZATION_FORMATS_TYPES = typing.Literal[ + "xml", "pretty-xml", "trig", "n3", "turtle", "nt", "json-ld" +] +RDF_SERIALIZATION_FORMATS = typing.get_args(RDF_SERIALIZATION_FORMATS_TYPES) + +# Define allowed inference options +VALID_INFERENCE_OPTIONS_TYPES = typing.Literal["owlrl", "rdfs", "both", None] +VALID_INFERENCE_OPTIONS = typing.get_args(VALID_INFERENCE_OPTIONS_TYPES) + +# Define allowed requirement levels +VALID_REQUIREMENT_LEVELS_TYPES = typing.Literal[ + 'MAY', 'OPTIONAL', 'SHOULD', 'SHOULD_NOT', + 'REQUIRED', 'MUST', 'MUST_NOT', 'SHALL', 'SHALL_NOT', 'RECOMMENDED' +] diff --git a/rocrate_validator/errors.py b/rocrate_validator/errors.py new file mode 100644 index 00000000..92844e87 --- /dev/null +++ b/rocrate_validator/errors.py @@ -0,0 +1,297 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Optional + + +class ROCValidatorError(Exception): + pass + + +class ProfilesDirectoryNotFound(ROCValidatorError): + """Raised when the profiles directory is not found.""" + + def __init__(self, profiles_path: Optional[str] = None): + self._profiles_path = profiles_path + + @property + def profiles_path(self) -> Optional[str]: + """The path to the profiles directory.""" + return self._profiles_path + + def __str__(self) -> str: + return f"Profiles directory not found: {self._profiles_path!r}" + + def __repr__(self): + return f"ProfilesDirectoryNotFound({self._profiles_path!r})" + + +class InvalidProfilePath(ROCValidatorError): + """Raised when an invalid profile path is provided.""" + + def __init__(self, profile_path: Optional[str] = None): + self._profile_path = profile_path + + @property + def profile_path(self) -> Optional[str]: + """The invalid profile path.""" + return self._profile_path + + def __str__(self) -> str: + return f"Invalid profile path: {self._profile_path!r}" + + def __repr__(self): + return f"InvalidProfilePath({self._profile_path!r})" + + +class ProfileNotFound(ROCValidatorError): + """Raised when a profile is not found.""" + + def __init__(self, profile_name: Optional[str] = None, message: Optional[str] = None): + self._profile_name = profile_name + self._message = message + + @property + def profile_name(self) -> Optional[str]: + """The name of the profile.""" + return self._profile_name + + @property + def message(self) -> Optional[str]: + """The error message.""" + return self._message + + def __str__(self) -> str: + return f"Profile not found: {self._profile_name!r}" + + def __repr__(self): + return f"ProfileNotFound({self._profile_name!r})" + + +class ProfileSpecificationNotFound(ROCValidatorError): + """Raised when the profile specification is not found.""" + + def __init__(self, spec_file: Optional[str] = None): + self._spec_file = spec_file + + @property + def spec_file(self) -> Optional[str]: + """The name of the profile specification file.""" + return self._spec_file + + def __str__(self) -> str: + msg = "Unable to find the `profile.ttl` specification" + if self._spec_file: + msg += f" in the file {self._spec_file!r}" + return msg + + def __repr__(self): + return "ProfileSpecificationNotFound()" + + +class ProfileSpecificationError(ROCValidatorError): + """Raised when an error occurs in the profile specification.""" + + def __init__(self, message: Optional[str] = None): + self._message = message + + @property + def message(self) -> Optional[str]: + """The error message.""" + return self._message + + def __str__(self) -> str: + return f"Error in the `profile.ttl` specification: {self._message!r}" + + def __repr__(self): + return f"ProfileSpecificationError({self._message!r})" + + +class DuplicateRequirementCheck(ROCValidatorError): + """Raised when a duplicate requirement check is found.""" + + def __init__(self, check_name: str, profile_name: Optional[str] = None): + self._check_name = check_name + self._profile_name = profile_name + + @property + def check_name(self) -> str: + """The name of the duplicate requirement check.""" + return self._check_name + + @property + def profile_name(self) -> Optional[str]: + """The name of the profile.""" + return self._profile_name + + def __str__(self) -> str: + return f"Duplicate requirement check found: {self._check_name!r} in profile {self._profile_name!r}" + + def __repr__(self): + return f"DuplicateRequirementCheck({self._check_name!r}, {self._profile_name!r})" + + +class InvalidSerializationFormat(ROCValidatorError): + """Raised when an invalid serialization format is provided.""" + + def __init__(self, format: Optional[str] = None): + self._format = format + + @property + def serialization_format(self) -> Optional[str]: + """The invalid serialization format.""" + return self._format + + def __str__(self) -> str: + return f"Invalid serialization format: {self._format!r}" + + def __repr__(self): + return f"InvalidSerializationFormat({self._format!r})" + + +class BadSyntaxError(ROCValidatorError): + """Raised when a syntax error occurs.""" + + def __init__(self, message, path: str = ".", code: int = -1): + self._message = message + self._path = path + self._code = code + + @property + def message(self) -> str: + """The error message.""" + return self._message + + @property + def path(self) -> str: + """The path where the error occurred.""" + return self._path + + @property + def code(self) -> int: + """The error code.""" + return self._code + + def __str__(self) -> str: + return self._message + + def __repr__(self): + return f"BadSyntaxError({self._message!r}, {self._path!r})" + + +class ValidationError(ROCValidatorError): + """Raised when a validation error occurs.""" + + def __init__(self, message, path: str = ".", code: int = -1): + self._message = message + self._path = path + self._code = code + + @property + def message(self) -> str: + """The error message.""" + return self._message + + @property + def path(self) -> str: + """The path where the error occurred.""" + return self._path + + @property + def code(self) -> int: + """The error code.""" + return self._code + + def __str__(self) -> str: + return self._message + + def __repr__(self): + return f"ValidationError({self._message!r}, {self._path!r})" + + +class CheckValidationError(ValidationError): + """Raised when a validation check fails.""" + + def __init__(self, + check, + message, + path: str = ".", + code: int = -1): + super().__init__(message, path, code) + self._check = check + + @property + def check(self): + """The check that failed.""" + return self._check + + def __repr__(self): + return f"CheckValidationError({self._check!r}, {self._message!r}, {self._path!r})" + + +class ROCrateInvalidURIError(ROCValidatorError): + """Raised when an invalid URI is provided.""" + + def __init__(self, uri: Optional[str] = None, message: Optional[str] = None): + self._uri = uri + self._message = message + + @property + def uri(self) -> Optional[str]: + """The invalid URI.""" + return self._uri + + @property + def message(self) -> Optional[str]: + """The error message.""" + return self._message + + def __str__(self) -> str: + if self._message: + return f"Invalid URI \"{self._uri!r}\": {self._message!r}" + else: + return f"Invalid URI \"{self._uri!r}\"" + + def __repr__(self): + return f"ROCrateInvalidURIError({self._uri!r})" + + +class ROCrateMetadataNotFoundError(ROCValidatorError): + """Raised when the RO-Crate metadata is not found.""" + + def __init__(self, message: Optional[str] = None, path: Optional[str] = None): + self._message = message + self._path = path + + @property + def message(self) -> Optional[str]: + """The error message.""" + return self._message + + @property + def path(self) -> Optional[str]: + """The path where the error occurred.""" + return self._path + + def __str__(self) -> str: + if self._path: + if self._message: + return f"RO-Crate metadata not found on '{self._path!r}': {self._message!r}" + else: + return f"RO-Crate metadata not found on '{self._path!r}'" + else: + return "RO-Crate metadata not found" + + def __repr__(self): + return f"ROCrateMetadataNotFoundError({self._path!r},{self._message!r})" diff --git a/rocrate_validator/events.py b/rocrate_validator/events.py new file mode 100644 index 00000000..e6b03023 --- /dev/null +++ b/rocrate_validator/events.py @@ -0,0 +1,73 @@ +# Copyright (c) 2024 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 enum +from abc import ABC, abstractmethod +from functools import total_ordering +from typing import Optional, Union + + +@enum.unique +@total_ordering +class EventType(enum.Enum): + """Enum ordering "strength" of conditions to be verified""" + VALIDATION_START = 0 + VALIDATION_END = 1 + PROFILE_VALIDATION_START = 2 + PROFILE_VALIDATION_END = 3 + REQUIREMENT_VALIDATION_START = 4 + REQUIREMENT_VALIDATION_END = 5 + REQUIREMENT_CHECK_VALIDATION_START = 6 + REQUIREMENT_CHECK_VALIDATION_END = 7 + + def __lt__(self, other): + if self.__class__ is other.__class__: + return self.value < other.value + return NotImplemented + + +class Event: + def __init__(self, event_type: EventType, message: Optional[str] = None): + self.event_type = event_type + self.message = message + + +class Subscriber(ABC): + def __init__(self, name): + self.name = name + + @abstractmethod + def update(self, event: Event): + pass + + +class Publisher: + def __init__(self): + self.__subscribers = set() + + @property + def subscribers(self): + return self.__subscribers + + def add_subscriber(self, subscriber): + self.subscribers.add(subscriber) + + def remove_subscriber(self, subscriber): + self.subscribers.remove(subscriber) + + def notify(self, event: Union[Event, EventType]): + if isinstance(event, EventType): + event = Event(event) + for subscriber in self.subscribers: + subscriber.update(event) diff --git a/rocrate_validator/log.py b/rocrate_validator/log.py new file mode 100644 index 00000000..aeeb4fbb --- /dev/null +++ b/rocrate_validator/log.py @@ -0,0 +1,251 @@ +# Copyright (c) 2024 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 atexit +import sys +import threading +from io import StringIO +from logging import (CRITICAL, DEBUG, ERROR, INFO, WARNING, Logger, + StreamHandler) +from typing import Optional + +import colorlog +from rich.console import Console +from rich.padding import Padding +from rich.rule import Rule +from rich.text import Text + +# set the module to the current module +__module__ = sys.modules[__name__] + + +def get_log_format(level: int): + """Get the log format based on the log level""" + log_format = '[%(log_color)s%(asctime)s%(reset)s] %(levelname)s in %(yellow)s%(module)s%(reset)s: '\ + '%(light_white)s%(message)s%(reset)s' + if level == DEBUG: + log_format = '%(log_color)s%(levelname)s%(reset)s:%(yellow)s%(name)s:%(module)s::%(funcName)s%(reset)s '\ + '@ %(light_green)sline: %(lineno)s%(reset)s - %(light_black)s%(message)s%(reset)s' + return log_format + + +DEFAULT_SETTINGS = { + 'enabled': True, + 'level': WARNING, + 'format': get_log_format(WARNING) +} + + +# _lock is used to serialize access to shared data structures in this module. +# This needs to be an RLock because fileConfig() creates and configures +# Handlers, and so might arbitrary user threads. Since Handler code updates the +# shared dictionary _handlers, it needs to acquire the lock. But if configuring, +# the lock would already have been acquired - so we need an RLock. +# The same argument applies to Loggers and Manager.loggerDict. +# +_lock = threading.RLock() + + +def _acquireLock(): + """ + Acquire the module-level lock for serializing access to shared data. + + This should be released with _releaseLock(). + """ + if _lock: + _lock.acquire() + + +def _releaseLock(): + """ + Release the module-level lock acquired by calling _acquireLock(). + """ + if _lock: + _lock.release() + + +# reference to the list of create loggers +__loggers__ = {} + +# user settings for the loggers +__settings__ = DEFAULT_SETTINGS.copy() + +# store logger handlers (only one handler per logger) +__handlers__ = {} + + +# Create a StringIO stream to capture the logs +__log_stream__ = StringIO() + + +# Define the callback function that will be called on exit +def __print_logs_on_exit__(): + log_contents = __log_stream__.getvalue() + if not log_contents: + return + # print the logs + console = Console() + console.print(Padding(Rule("[bold cyan]Log Report[/bold cyan]", style="bold cyan"), (2, 0, 1, 0))) + console.print(Padding(Text(log_contents), (0, 1))) + console.print(Padding(Rule("", style="bold cyan"), (0, 0, 2, 0))) + # close the stream + __log_stream__.close() + + +# Register the callback with atexit +atexit.register(__print_logs_on_exit__) + + +def __setup_logger__(logger: Logger): + + # prevent the logger from propagating the log messages to the root logger + logger.propagate = False + + # get the settings for the logger + settings = __settings__.get(logger.name, __settings__) + + # parse the log level + level = settings.get('level', __settings__['level']) + if not isinstance(level, int): + level = getattr(__module__, settings['level'].upper(), None) + + # set the log level + logger.setLevel(level) + + # configure the logger handler + ch = __handlers__.get(logger.name, None) + if not ch: + ch = StreamHandler(__log_stream__) + ch.setLevel(level) + ch.setFormatter(colorlog.ColoredFormatter(get_log_format(level))) + logger.addHandler(ch) + + # enable/disable the logger + if settings.get('enabled', __settings__['enabled']): + logger.disabled = False + else: + logger.disabled = True + + +def __create_logger__(name: str) -> Logger: + logger: Logger = None + if not isinstance(name, str): + raise TypeError('A logger name must be a string') + _acquireLock() + try: + if name not in __loggers__: + logger = colorlog.getLogger(name) + __setup_logger__(logger) + __loggers__[name] = logger + finally: + _releaseLock() + return logger + + +def basicConfig(level: int, modules_config: Optional[dict] = None): + """Set the log level and format for the logger""" + _acquireLock() + try: + if not isinstance(level, int): + level = getattr(__module__, level.upper(), None) + + # set the default log level and format + __settings__['level'] = level + __settings__['format'] = get_log_format(level) + + # set the log level for the modules + if modules_config: + __settings__.update(modules_config) + + # initialize the logging module + colorlog.basicConfig( + level=__settings__['level'], + format=__settings__['format'], + log_colors={ + 'DEBUG': 'cyan', + 'INFO': 'green', + 'WARNING': 'yellow', + 'ERROR': 'red', + 'CRITICAL': 'red,bg_white', + }, + handlers=[StreamHandler(__log_stream__)] + ) + + # reconfigure existing loggers + for logger in __loggers__.values(): + __setup_logger__(logger) + + finally: + _releaseLock() + + +def getLogger(name: str) -> Logger: + return LoggerProxy(name) + + +class LoggerProxy: + + """"Define a proxy class for the logger to allow lazy initialization of the logger instance""" + + def __init__(self, name: str): + self.name = name + self._instance = None + + def _initialize(self): + _acquireLock() + try: + if self._instance is None: + self._instance = __create_logger__(self.name) + finally: + _releaseLock() + + def __getattr__(self, name): + self._initialize() + return getattr(self._instance, name) + + +__export__ = [get_log_format, DEFAULT_SETTINGS, Logger, + CRITICAL, DEBUG, ERROR, INFO, WARNING, StreamHandler, Optional] + + +# Example of usage +# if __name__ == '__main__': +# log_config = { +# 'module1': {'enabled': True, 'level': 'DEBUG'}, +# 'module2': {'enabled': False, 'level': 'INFO'}, +# 'module3': {'enabled': True, 'level': 'ERROR'}, +# } +# mgt = LoggerManager(log_config) +# logger1 = mgt.getLogger('module1') +# logger2 = mgt.getLogger('module2') +# logger3 = mgt.getLogger('module3') +# logger4 = mgt.getLogger('module4') + +# logger1.debug('This is a debug message') +# logger1.info('This is an info message') +# logger1.error('This is an error message') + +# logger2.debug('This is a debug message') +# logger2.info('This is an info message') +# logger2.error('This is an error message') + +# logger3.debug('This is a debug message') +# logger3.info('This is an info message') +# logger3.error('This is an error message') +# logger3.critical('This is a critical message') + +# logger4.debug('This is a debug message') +# logger4.info('This is an info message') +# logger4.error('This is an error message') +# logger4.critical('This is a critical message') diff --git a/rocrate_validator/models.py b/rocrate_validator/models.py new file mode 100644 index 00000000..b9bf500b --- /dev/null +++ b/rocrate_validator/models.py @@ -0,0 +1,1615 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import bisect +import enum +import inspect +import json +import re +from abc import ABC, abstractmethod +from collections.abc import Collection +from dataclasses import asdict, dataclass +from functools import total_ordering +from pathlib import Path +from typing import Optional, Tuple, Union + +from rdflib import RDF, RDFS, Graph, Namespace, URIRef + +import rocrate_validator.log as logging +from rocrate_validator.constants import (DEFAULT_ONTOLOGY_FILE, + DEFAULT_PROFILE_IDENTIFIER, + DEFAULT_PROFILE_README_FILE, + IGNORED_PROFILE_DIRECTORIES, PROF_NS, + PROFILE_FILE_EXTENSIONS, + PROFILE_SPECIFICATION_FILE, + RDF_SERIALIZATION_FORMATS_TYPES, + ROCRATE_METADATA_FILE, SCHEMA_ORG_NS, + VALID_INFERENCE_OPTIONS_TYPES) +from rocrate_validator.errors import (DuplicateRequirementCheck, + InvalidProfilePath, ProfileNotFound, + ProfileSpecificationError, + ProfileSpecificationNotFound, + ROCrateMetadataNotFoundError) +from rocrate_validator.events import Event, EventType, Publisher +from rocrate_validator.rocrate import ROCrate +from rocrate_validator.utils import (URI, MapIndex, MultiIndexMap, + get_profiles_path, + get_requirement_name_from_file) + +# set the default profiles path +DEFAULT_PROFILES_PATH = get_profiles_path() + +logger = logging.getLogger(__name__) + +BaseTypes = Union[str, Path, bool, int, None] + + +@enum.unique +@total_ordering +class Severity(enum.Enum): + """Enum ordering "strength" of conditions to be verified""" + OPTIONAL = 0 + RECOMMENDED = 2 + REQUIRED = 4 + + def __lt__(self, other: object) -> bool: + if isinstance(other, Severity): + return self.value < other.value + else: + raise TypeError(f"Comparison not supported between instances of {type(self)} and {type(other)}") + + @staticmethod + def get(name: str) -> Severity: + return getattr(Severity, name.upper()) + + +@total_ordering +@dataclass +class RequirementLevel: + name: str + severity: Severity + + def __eq__(self, other: object) -> bool: + if not isinstance(other, RequirementLevel): + return False + return self.name == other.name and self.severity == other.severity + + def __lt__(self, other: object) -> bool: + # TODO: this ordering is not totally coherent, since for two objects a and b + # with equal Severity but different names you would have + # not a < b, which implies a >= b + # and also a != b and not a > b, which is incoherent with a >= b + if not isinstance(other, RequirementLevel): + raise TypeError(f"Cannot compare {type(self)} with {type(other)}") + return self.severity < other.severity + + def __hash__(self) -> int: + return hash((self.name, self.severity)) + + def __repr__(self) -> str: + return f'RequirementLevel(name={self.name}, severity={self.severity})' + + def __str__(self) -> str: + return self.name + + def __int__(self) -> int: + return self.severity.value + + def __index__(self) -> int: + return self.severity.value + + +class LevelCollection: + """ + * The keywords MUST, MUST NOT, REQUIRED, + * SHALL, SHALL NOT, SHOULD, SHOULD NOT, + * RECOMMENDED, MAY, and OPTIONAL in this document + * are to be interpreted as described in RFC 2119. + """ + OPTIONAL = RequirementLevel('OPTIONAL', Severity.OPTIONAL) + MAY = RequirementLevel('MAY', Severity.OPTIONAL) + + REQUIRED = RequirementLevel('REQUIRED', Severity.REQUIRED) + SHOULD = RequirementLevel('SHOULD', Severity.RECOMMENDED) + SHOULD_NOT = RequirementLevel('SHOULD_NOT', Severity.RECOMMENDED) + RECOMMENDED = RequirementLevel('RECOMMENDED', Severity.RECOMMENDED) + + MUST = RequirementLevel('MUST', Severity.REQUIRED) + MUST_NOT = RequirementLevel('MUST_NOT', Severity.REQUIRED) + SHALL = RequirementLevel('SHALL', Severity.REQUIRED) + SHALL_NOT = RequirementLevel('SHALL_NOT', Severity.REQUIRED) + + def __init__(self): + raise NotImplementedError(f"{type(self)} can't be instantiated") + + @staticmethod + def all() -> list[RequirementLevel]: + return [level for name, level in inspect.getmembers(LevelCollection) + if not inspect.isroutine(level) + and not inspect.isdatadescriptor(level) and not name.startswith('__')] + + @staticmethod + def get(name: str) -> RequirementLevel: + try: + return getattr(LevelCollection, name.upper()) + except AttributeError: + raise ValueError(f"Invalid RequirementLevel: {name}") + + +@total_ordering +class Profile: + + # store the map of profiles: profile URI -> Profile instance + __profiles_map: MultiIndexMap = \ + MultiIndexMap("uri", indexes=[ + MapIndex("name"), MapIndex("token", unique=False), MapIndex("identifier", unique=True) + ]) + + def __init__(self, + profiles_base_path: Path, + profile_path: Path, + requirements: Optional[list[Requirement]] = None, + identifier: str = None, + publicID: Optional[str] = None, + severity: Severity = Severity.REQUIRED): + self._identifier: Optional[str] = identifier + self._profiles_base_path = profiles_base_path + self._profile_path = profile_path + self._name: Optional[str] = None + self._description: Optional[str] = None + self._requirements: list[Requirement] = requirements if requirements is not None else [] + self._publicID = publicID + self._severity = severity + + # init property to store the RDF node which is the root of the profile specification graph + self._profile_node = None + + # init property to store the RDF graph of the profile specification + self._profile_specification_graph = None + + # check if the profile specification file exists + spec_file = self.profile_specification_file_path + if not spec_file or not spec_file.exists(): + raise ProfileSpecificationNotFound(spec_file) + # load the profile specification expressed using the Profiles Vocabulary + profile = Graph() + profile.parse(str(spec_file), format="turtle") + # check that the specification Graph hosts only one profile + profiles = list(profile.subjects(predicate=RDF.type, object=PROF_NS.Profile)) + if len(profiles) == 1: + self._profile_node = profiles[0] + self._profile_specification_graph = profile + # initialize the token and version + self._token, self._version = self.__init_token_version__() + # add the profile to the profiles map + self.__profiles_map.add( + self._profile_node.toPython(), + self, token=self.token, + name=self.name, identifier=self.identifier + ) # add the profile to the profiles map + else: + raise ProfileSpecificationError( + message=f"Profile specification file {spec_file} must contain exactly one profile") + + def __get_specification_property__( + self, property: str, namespace: Namespace, + pop_first: bool = True, as_Python_object: bool = True) -> Union[str, list[Union[str, URIRef]]]: + assert self._profile_specification_graph is not None, "Profile specification graph not loaded" + values = list(self._profile_specification_graph.objects(self._profile_node, namespace[property])) + if values and as_Python_object: + values = [v.toPython() for v in values] + if pop_first: + return values[0] if values and len(values) >= 1 else None + return values + + @property + def path(self): + return self._profile_path + + @property + def identifier(self) -> str: + if not self._identifier: + version = self.version + self._identifier = f"{self.token}-{version}" if version else self.token + return self._identifier + + @property + def name(self): + return self.label or f"Profile {self.uri}" + + @property + def profile_node(self): + return self._profile_node + + @property + def token(self): + return self._token + + @property + def uri(self): + return self._profile_node.toPython() + + @property + def label(self): + return self.__get_specification_property__("label", RDFS) + + @property + def comment(self): + return self.__get_specification_property__("comment", RDFS) + + @property + def version(self): + return self._version + + @property + def is_profile_of(self) -> list[str]: + return self.__get_specification_property__("isProfileOf", PROF_NS, pop_first=False) + + @property + def is_transitive_profile_of(self) -> list[str]: + return self.__get_specification_property__("isTransitiveProfileOf", PROF_NS, pop_first=False) + + @property + def readme_file_path(self) -> Path: + return self.path / DEFAULT_PROFILE_README_FILE + + @property + def profile_specification_file_path(self) -> Path: + return self.path / PROFILE_SPECIFICATION_FILE + + @property + def publicID(self) -> Optional[str]: + return self._publicID + + @property + def severity(self) -> Severity: + return self._severity + + @property + def description(self) -> str: + if not self._description: + if self.path and self.readme_file_path.exists(): + with open(self.readme_file_path, "r") as f: + self._description = f.read() + else: + self._description = self.comment + return self._description + + @property + def requirements(self) -> list[Requirement]: + if not self._requirements: + self._requirements = \ + RequirementLoader.load_requirements(self, severity=self.severity) + return self._requirements + + def get_requirements( + self, severity: Severity = Severity.REQUIRED, + exact_match: bool = False) -> list[Requirement]: + return [requirement for requirement in self.requirements + if (not exact_match and + (not requirement.severity_from_path or requirement.severity_from_path >= severity)) or + (exact_match and requirement.severity_from_path == severity)] + + def get_requirement(self, name: str) -> Optional[Requirement]: + for requirement in self.requirements: + if requirement.name == name: + return requirement + return None + + @classmethod + def __get_nested_profiles__(cls, source: str) -> list[str]: + result = [] + visited = [] + queue = [source] + while len(queue) > 0: + p = queue.pop() + if p not in visited: + visited.append(p) + profile = cls.__profiles_map.get_by_key(p) + inherited_profiles = profile.is_profile_of + if inherited_profiles: + for p in sorted(inherited_profiles, reverse=True): + if p not in visited: + queue.append(p) + if p not in result: + result.insert(0, p) + return result + + @property + def inherited_profiles(self) -> list[Profile]: + inherited_profiles = self.is_transitive_profile_of + if not inherited_profiles or len(inherited_profiles) == 0: + inherited_profiles = Profile.__get_nested_profiles__(self.uri) + profile_keys = self.__profiles_map.keys + return [self.__profiles_map.get_by_key(_) for _ in inherited_profiles if _ in profile_keys] + + def add_requirement(self, requirement: Requirement): + self._requirements.append(requirement) + + def remove_requirement(self, requirement: Requirement): + self._requirements.remove(requirement) + + def __eq__(self, other: object) -> bool: + return isinstance(other, Profile) \ + and self.identifier == other.identifier \ + and self.path == other.path \ + and self.requirements == other.requirements + + def __lt__(self, other: object) -> bool: + if not isinstance(other, Profile): + raise TypeError(f"Cannot compare {type(self)} with {type(other)}") + return self.identifier < other.identifier + + def __hash__(self) -> int: + return hash((self.identifier, self.path, self.requirements)) + + def __repr__(self) -> str: + return ( + f'Profile(identifier={self.identifier}, ' + f'name={self.name}, ' + f'path={self.path}, ' if self.path else '' + f'requirements={self.requirements})' + ) + + def __str__(self) -> str: + return f"{self.name} ({self.identifier})" + + @staticmethod + def __extract_version_from_token__(token: str) -> Optional[str]: + if not token: + return None + pattern = r"\Wv?(\d+(\.\d+(\.\d+)?)?)" + matches = re.findall(pattern, token) + if matches: + return matches[-1][0] + return None + + def __get_consistent_version__(self, candidate_token: str) -> str: + candidates = {_ for _ in [ + self.__get_specification_property__("version", SCHEMA_ORG_NS), + self.__extract_version_from_token__(candidate_token), + self.__extract_version_from_token__(str(self.path.relative_to(self._profiles_base_path))), + self.__extract_version_from_token__(str(self.uri)) + ] if _ is not None} + if len(candidates) > 1: + raise ProfileSpecificationError(f"Inconsistent versions found: {candidates}") + logger.debug("Candidate versions: %s", candidates) + return candidates.pop() if len(candidates) == 1 else None + + def __extract_token_from_path__(self) -> str: + base_path = str(self._profiles_base_path.absolute()) + identifier = str(self.path.absolute()) + # Check if the path starts with the base path + if not identifier.startswith(base_path): + raise ValueError("Path does not start with the base path") + # Remove the base path from the identifier + identifier = identifier.replace(f"{base_path}/", "") + # Replace slashes with hyphens + identifier = identifier.replace('/', '-') + return identifier + + def __init_token_version__(self) -> Tuple[str, str, str]: + # try to extract the token from the specs or the path + candidate_token = self.__get_specification_property__("hasToken", PROF_NS) + if not candidate_token: + candidate_token = self.__extract_token_from_path__() + logger.debug("Candidate token: %s", candidate_token) + + # try to extract the version from the specs or the token or the path or the URI + version = self.__get_consistent_version__(candidate_token) + logger.debug("Extracted version: %s", version) + + # remove the version from the token if it is present + if version: + candidate_token = re.sub(r"[\W|_]+" + re.escape(version) + r"$", "", candidate_token) + + # return the candidate token and version + return candidate_token, version + + @classmethod + def load(cls, profiles_base_path: str, + profile_path: Union[str, Path], + publicID: Optional[str] = None, + severity: Severity = Severity.REQUIRED) -> Profile: + # if the path is a string, convert it to a Path + if isinstance(profile_path, str): + profile_path = Path(profile_path) + # check if the path is a directory + if not profile_path.is_dir(): + raise InvalidProfilePath(profile_path) + # create a new profile + profile = Profile(profiles_base_path=profiles_base_path, + profile_path=profile_path, publicID=publicID, severity=severity) + logger.debug("Loaded profile: %s", profile) + return profile + + @staticmethod + def load_profiles(profiles_path: Union[str, Path], + publicID: Optional[str] = None, + severity: Severity = Severity.REQUIRED, + allow_requirement_check_override: bool = True) -> list[Profile]: + # if the path is a string, convert it to a Path + if isinstance(profiles_path, str): + profiles_path = Path(profiles_path) + # check if the path is a directory + if not profiles_path.is_dir(): + raise InvalidProfilePath(profiles_path) + # initialize the profiles list + profiles = [] + # calculate the list of profiles path as the subdirectories of the profiles path + # where the profile specification file is present + profile_paths = [p.parent for p in profiles_path.rglob('*.*') if p.name == PROFILE_SPECIFICATION_FILE] + + # iterate through the directories and load the profiles + for profile_path in profile_paths: + logger.debug("Checking profile path: %s %s %r", profile_path, + profile_path.is_dir(), IGNORED_PROFILE_DIRECTORIES) + if profile_path.is_dir() and profile_path not in IGNORED_PROFILE_DIRECTORIES: + profile = Profile.load(profiles_path, profile_path, publicID=publicID, severity=severity) + profiles.append(profile) + + # navigate the profiles and check for overridden checks + # if the override is enabled in the settings + # overridden checks should be marked as such + # otherwise, raise an error + profiles_checks = {} + # visit the profiles in reverse order + # (the order is important to visit the most specific profiles first) + for profile in sorted(profiles, reverse=True): + profile_checks = [_ for r in profile.get_requirements() for _ in r.get_checks()] + profile_check_names = [] + for check in profile_checks: + # Β find duplicated checks and raise an error + if check.name in profile_check_names and not allow_requirement_check_override: + raise DuplicateRequirementCheck(check.name, profile.identifier) + # Β add check to the list + profile_check_names.append(check.name) + # Β mark overridden checks + check_chain = profiles_checks.get(check.name, None) + if not check_chain: + profiles_checks[check.name] = [check] + elif allow_requirement_check_override: + check.overridden_by = check_chain[-1] + check_chain.append(check) + logger.debug("Check %s overridden by %s", check.identifier, check.overridden_by.identifier) + else: + raise DuplicateRequirementCheck(check.name, profile.identifier) + + # order profiles according to the number of profiles they depend on: + # i.e, first the profiles that do not depend on any other profile + # then the profiles that depend on the previous ones, and so on + return sorted(profiles, key=lambda x: f"{len(x.inherited_profiles)}_{x.identifier}") + + @classmethod + def get_by_identifier(cls, identifier: str) -> Profile: + return cls.__profiles_map.get_by_index("identifier", identifier) + + @classmethod + def get_by_uri(cls, uri: str) -> Profile: + return cls.__profiles_map.get_by_key(uri) + + @classmethod + def get_by_name(cls, name: str) -> list[Profile]: + return cls.__profiles_map.get_by_index("name", name) + + @classmethod + def get_by_token(cls, token: str) -> Profile: + return cls.__profiles_map.get_by_index("token", token) + + @classmethod + def all(cls) -> list[Profile]: + return cls.__profiles_map.values() + + +class SkipRequirementCheck(Exception): + def __init__(self, check: RequirementCheck, message: str = ""): + self.check = check + self.message = message + + def __str__(self): + return f"SkipRequirementCheck(check={self.check})" + + +@total_ordering +class Requirement(ABC): + + def __init__(self, + profile: Profile, + name: str = "", + description: Optional[str] = None, + path: Optional[Path] = None, + initialize_checks: bool = True): + self._order_number: Optional[int] = None + self._profile = profile + self._description = description + self._path = path # path of code implementing the requirement + self._level_from_path = None + self._checks: list[RequirementCheck] = [] + + if not name and path: + self._name = get_requirement_name_from_file(path) + else: + self._name = name + + # set flag to indicate if the checks have been initialized + self._checks_initialized = False + # initialize the checks if the flag is set + if initialize_checks: + _ = self.__init_checks__() + # assign order numbers to checks + self.__reorder_checks__() + # update the checks initialized flag + self._checks_initialized = True + + @property + def order_number(self) -> int: + assert self._order_number is not None + return self._order_number + + @property + def identifier(self) -> str: + return f"{self.profile.identifier}.{self.relative_identifier}" + + @property + def relative_identifier(self) -> str: + return f"{self.order_number}" + + @property + def name(self) -> str: + return self._name + + @property + def severity_from_path(self) -> Severity: + return self.requirement_level_from_path.severity if self.requirement_level_from_path else None + + @property + def requirement_level_from_path(self) -> RequirementLevel: + if not self._level_from_path: + try: + self._level_from_path = LevelCollection.get(self._path.parent.name) + except ValueError: + logger.debug("The requirement level could not be determined from the path: %s", self._path) + return self._level_from_path + + @property + def profile(self) -> Profile: + return self._profile + + @property + def description(self) -> str: + if not self._description: + self._description = self.__class__.__doc__.strip( + ) if self.__class__.__doc__ else f"Profile Requirement {self.name}" + return self._description + + @property + @abstractmethod + def hidden(self) -> bool: + pass + + @property + def path(self) -> Optional[Path]: + return self._path + + @abstractmethod + def __init_checks__(self) -> list[RequirementCheck]: + pass + + def get_checks(self) -> list[RequirementCheck]: + return self._checks.copy() + + def get_check(self, name: str) -> Optional[RequirementCheck]: + for check in self._checks: + if check.name == name: + return check + return None + + def get_checks_by_level(self, level: RequirementLevel) -> list[RequirementCheck]: + return list({check for check in self._checks if check.level.severity == level.severity}) + + def __reorder_checks__(self) -> None: + for i, check in enumerate(self._checks): + check.order_number = i + 1 + + def __do_validate__(self, context: ValidationContext) -> bool: + """ + Internal method to perform the validation + Returns whether all checks in this requirement passed. + """ + logger.debug("Validating Requirement %s with %s checks", self.name, len(self._checks)) + + logger.debug("Running %s checks for Requirement '%s'", len(self._checks), self.name) + all_passed = True + for check in self._checks: + try: + logger.debug("Running check '%s' - Desc: %s - overridden: %s.%s", + check.name, check.description, check.overridden_by, + check.overridden_by.requirement.profile if check.overridden_by else None) + if check.overridden: + logger.debug("Skipping check '%s' because overridden by '%s'", check.name, check.overridden_by.name) + continue + context.validator.notify(RequirementCheckValidationEvent( + EventType.REQUIREMENT_CHECK_VALIDATION_START, check)) + check_result = check.execute_check(context) + context.result.add_executed_check(check, check_result) + context.validator.notify(RequirementCheckValidationEvent( + EventType.REQUIREMENT_CHECK_VALIDATION_END, check, validation_result=check_result)) + logger.debug("Ran check '%s'. Got result %s", check.name, check_result) + if not isinstance(check_result, bool): + logger.warning("Ignoring the check %s as it returned the value %r instead of a boolean", check.name) + raise RuntimeError(f"Ignoring invalid result from check {check.name}") + all_passed = all_passed and check_result + if not all_passed and context.fail_fast: + break + except SkipRequirementCheck as e: + logger.debug("Skipping check '%s' because: %s", check.name, e) + context.result.add_skipped_check(check) + continue + except Exception as e: + # Ignore the fact that the check failed as far as the validation result is concerned. + logger.warning("Unexpected error during check %s. Exception: %s", check, e) + logger.warning("Consider reporting this as a bug.") + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + + logger.debug("Checks for Requirement '%s' completed. Checks passed? %s", self.name, all_passed) + return all_passed + + def __eq__(self, other: object) -> bool: + if not isinstance(other, Requirement): + raise TypeError(f"Cannot compare {type(self)} with {type(other)}") + return self.name == other.name \ + and self.description == other.description \ + and self.path == other.path + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def __hash__(self): + return hash((self.name, self.description, self.path)) + + def __lt__(self, other: object) -> bool: + if not isinstance(other, Requirement): + raise ValueError(f"Cannot compare Requirement with {type(other)}") + return (self._order_number, self.name) < (other._order_number, other.name) + + def __repr__(self): + return ( + f'ProfileRequirement(' + f'_order_number={self._order_number}, ' + f'name={self.name}, ' + f'description={self.description}' + f', path={self.path}, ' if self.path else '' + ')' + ) + + def __str__(self) -> str: + return self.name + + +class RequirementLoader: + + def __init__(self, profile: Profile): + self._profile = profile + + @property + def profile(self) -> Profile: + return self._profile + + @staticmethod + def __get_requirement_type__(requirement_path: Path) -> str: + if requirement_path.suffix == ".py": + return "python" + elif requirement_path.suffix == ".ttl": + return "shacl" + else: + raise ValueError(f"Unsupported requirement type: {requirement_path.suffix}") + + @classmethod + def __get_requirement_loader__(cls, profile: Profile, requirement_path: Path) -> RequirementLoader: + import importlib + requirement_type = cls.__get_requirement_type__(requirement_path) + loader_instance_name = f"_{requirement_type}_loader_instance" + loader_instance = getattr(profile, loader_instance_name, None) + if loader_instance is None: + module_name = f"rocrate_validator.requirements.{requirement_type}" + logger.debug("Loading module: %s", module_name) + module = importlib.import_module(module_name) + loader_class_name = f"{'Py' if requirement_type == 'python' else 'SHACL'}RequirementLoader" + loader_class = getattr(module, loader_class_name) + loader_instance = loader_class(profile) + setattr(profile, loader_instance_name, loader_instance) + return loader_instance + + @staticmethod + def load_requirements(profile: Profile, severity: Severity = None) -> list[Requirement]: + """ + Load the requirements related to the profile + """ + def ok_file(p: Path) -> bool: + return p.is_file() \ + and p.suffix in PROFILE_FILE_EXTENSIONS \ + and not p.name == DEFAULT_ONTOLOGY_FILE \ + and not p.name == PROFILE_SPECIFICATION_FILE \ + and not p.name.startswith('.') \ + and not p.name.startswith('_') + + files = sorted((p for p in profile.path.rglob('*.*') if ok_file(p)), + key=lambda x: (not x.suffix == '.py', x)) + + # set the requirement level corresponding to the severity + requirement_level = LevelCollection.get(severity.name) + + requirements = [] + for requirement_path in files: + try: + requirement_level_from_path = LevelCollection.get(requirement_path.parent.name) + if requirement_level_from_path < requirement_level: + continue + except ValueError: + logger.debug("The requirement level could not be determined from the path: %s", requirement_path) + requirement_loader = RequirementLoader.__get_requirement_loader__(profile, requirement_path) + for requirement in requirement_loader.load( + profile, requirement_level, + requirement_path, publicID=profile.publicID): + requirements.append(requirement) + # sort the requirements by severity + requirements = sorted(requirements, + key=lambda x: (-x.severity_from_path.value, x.path.name, x.name) + if x.severity_from_path is not None else (0, x.path.name, x.name), + reverse=False) + # assign order numbers to requirements + for i, requirement in enumerate(requirements): + requirement._order_number = i + 1 + # log and return the requirements + logger.debug("Profile %s loaded %s requirements: %s", + profile.identifier, len(requirements), requirements) + return requirements + + +@total_ordering +class RequirementCheck(ABC): + + def __init__(self, + requirement: Requirement, + name: str, + level: Optional[RequirementLevel] = LevelCollection.REQUIRED, + description: Optional[str] = None): + self._requirement: Requirement = requirement + self._order_number = 0 + self._name = name + self._level = level + self._description = description + self._overridden_by: RequirementCheck = None + self._override: RequirementCheck = None + + @property + def order_number(self) -> int: + return self._order_number + + @order_number.setter + def order_number(self, value: int) -> None: + if value < 0: + raise ValueError("order_number can't be < 0") + self._order_number = value + + @property + def identifier(self) -> str: + return f"{self.requirement.identifier}.{self.order_number}" + + @property + def relative_identifier(self) -> str: + return f"{self.level.name} {self.requirement.relative_identifier}.{self.order_number}" + + @property + def name(self) -> str: + if not self._name: + return self.__class__.__name__.replace("Check", "") + return self._name + + @property + def description(self) -> str: + if not self._description: + return self.__class__.__doc__.strip() if self.__class__.__doc__ else f"Check {self.name}" + return self._description + + @property + def requirement(self) -> Requirement: + return self._requirement + + @property + def level(self) -> RequirementLevel: + return self._level or \ + self.requirement.requirement_level_from_path or \ + LevelCollection.REQUIRED + + @property + def severity(self) -> Severity: + return self.level.severity + + @property + def overridden_by(self) -> RequirementCheck: + return self._overridden_by + + @overridden_by.setter + def overridden_by(self, value: RequirementCheck) -> None: + assert value is None or isinstance(value, RequirementCheck) and value != self, \ + f"Invalid value for overridden_by: {value}" + self._overridden_by = value + value._override = self + + @property + def override(self) -> RequirementCheck: + return self._override + + @property + def overridden(self) -> bool: + return self._overridden_by is not None + + @abstractmethod + def execute_check(self, context: ValidationContext) -> bool: + raise NotImplementedError() + + def __eq__(self, other: object) -> bool: + if not isinstance(other, RequirementCheck): + raise ValueError(f"Cannot compare RequirementCheck with {type(other)}") + return self.requirement == other.requirement and self.name == other.name + + def __lt__(self, other: object) -> bool: + if not isinstance(other, RequirementCheck): + raise ValueError(f"Cannot compare RequirementCheck with {type(other)}") + return (self.requirement, self.identifier) < (other.requirement, other.identifier) + + def __ne__(self, other: object) -> bool: + return not self.__eq__(other) + + def __hash__(self) -> int: + return hash((self.requirement, self.name or "")) + + # TODO: delete these? + # + # @property + # def issues(self) -> list[CheckIssue]: + # """Return the issues found during the check""" + # assert self.result, "Issues not set before the check" + # return self.result.get_issues_by_check(self, Severity.OPTIONAL) + + # def get_issues_by_severity(self, severity: Severity = Severity.RECOMMENDED) -> list[CheckIssue]: + # return self.result.get_issues_by_check_and_severity(self, severity) + + +# TODO: delete this? + +# def issue_types(issues: list[Type[CheckIssue]]) -> Type[RequirementCheck]: +# def class_decorator(cls): +# cls.issue_types = issues +# return cls +# return class_decorator + + +@total_ordering +class CheckIssue: + """ + Class to store an issue found during a check + + Attributes: + severity (IssueSeverity): The severity of the issue + message (str): The message + code (int): The code + check (RequirementCheck): The check that generated the issue + """ + + # TODO: + # 2. CheckIssue has the check, so it is able to determine the level and the Severity + # without having it provided through an additional argument. + def __init__(self, severity: Severity, + check: RequirementCheck, + message: Optional[str] = None, + resultPath: Optional[str] = None, + focusNode: Optional[str] = None, + value: Optional[str] = None): + if not isinstance(severity, Severity): + raise TypeError(f"CheckIssue constructed with a severity '{severity}' of type {type(severity)}") + self._severity = severity + self._message = message + self._check: RequirementCheck = check + self._resultPath = resultPath + self._focusNode = focusNode + self._value = value + + @property + def message(self) -> Optional[str]: + """The message associated with the issue""" + return self._message + + @property + def level(self) -> RequirementLevel: + """The level of the issue""" + return self._check.level + + @property + def severity(self) -> Severity: + """Severity of the RequirementLevel associated with this check.""" + return self._severity + + @property + def level_name(self) -> str: + return self.level.name + + @property + def check(self) -> RequirementCheck: + """The check that generated the issue""" + return self._check + + @property + def resultPath(self) -> Optional[str]: + return self._resultPath + + @property + def focusNode(self) -> Optional[str]: + return self._focusNode + + @property + def value(self) -> Optional[str]: + return self._value + + def __eq__(self, other: object) -> bool: + return isinstance(other, CheckIssue) and \ + self._check == other._check and \ + self._severity == other._severity and \ + self._message == other._message + + def __lt__(self, other: object) -> bool: + if not isinstance(other, CheckIssue): + raise TypeError(f"Cannot compare {type(self)} with {type(other)}") + return (self._check, self._severity, self._message) < (other._check, other._severity, other._message) + + def __hash__(self) -> int: + return hash((self._check, self._severity, self._message)) + + def __repr__(self) -> str: + return f'CheckIssue(severity={self.severity}, check={self.check}, message={self.message})' + + def __str__(self) -> str: + return f"{self.severity}: {self.message} ({self.check})" + + def to_dict(self) -> dict: + return { + "severity": self.severity.name, + "message": self.message, + "check": self.check.name, + "resultPath": self.resultPath, + "focusNode": self.focusNode, + "value": self.value + } + + def to_json(self) -> str: + return json.dumps(self.to_dict(), indent=4, cls=CustomEncoder) + + # @property + # def code(self) -> int: + # breakpoint() + # # If the code has not been set, calculate it + # if not self._code: + # """ + # Calculate the code based on the severity, the class name and the message. + # - All issues with the same severity, class name and message will have the same code. + # - All issues with the same severity and class name but different message will have different codes. + # - All issues with the same severity but different class name and message will have different codes. + # - All issues with the same severity should start with the same number. + # - All codes should be positive numbers. + # """ + # # Concatenate the level, class name and message into a single string + # issue_string = self.level.name + self.__class__.__name__ + str(self.message) + # + # # Use the built-in hash function to generate a unique code for this string + # # The modulo operation ensures that the code is a positive number + # self._code = hash(issue_string) % ((1 << 31) - 1) + # # Return the code + # return self._code + + +class ValidationResult: + + def __init__(self, context: ValidationContext): + # reference to the validation context + self._context = context + # reference to the ro-crate path + self._rocrate_path = context.rocrate_path + # reference to the validation settings + self._validation_settings: dict[str, BaseTypes] = context.settings + # keep track of the issues found during the validation + self._issues: list[CheckIssue] = [] + # keep track of the checks that have been executed + self._executed_checks: set[RequirementCheck] = set() + self._executed_checks_results: dict[str, bool] = {} + # keep track of the checks that have been skipped + self._skipped_checks: set[RequirementCheck] = set() + + @property + def context(self) -> ValidationContext: + return self._context + + @property + def rocrate_path(self): + return self._rocrate_path + + @property + def validation_settings(self): + return self._validation_settings + + # --- Checks --- + @property + def executed_checks(self) -> set[RequirementCheck]: + return self._executed_checks + + def add_executed_check(self, check: RequirementCheck, result: bool): + self._executed_checks.add(check) + self._executed_checks_results[check.identifier] = result + # remove the check from the skipped checks if it was skipped + if check in self._skipped_checks: + self._skipped_checks.remove(check) + logger.debug("Removing check '%s' from skipped checks", check.name) + + def get_executed_check_result(self, check: RequirementCheck) -> Optional[bool]: + return self._executed_checks_results.get(check.identifier) + + @property + def skipped_checks(self) -> set[RequirementCheck]: + return self._skipped_checks + + def add_skipped_check(self, check: RequirementCheck): + self._skipped_checks.add(check) + + def remove_skipped_check(self, check: RequirementCheck): + self._skipped_checks.remove(check) + + # --- Issues --- + @property + def issues(self) -> list[CheckIssue]: + return self._issues + + def get_issues(self, min_severity: Optional[Severity] = None) -> list[CheckIssue]: + min_severity = min_severity or self.context.requirement_severity + return [issue for issue in self._issues if issue.severity >= min_severity] + + def get_issues_by_check(self, + check: RequirementCheck, + min_severity: Severity = None) -> list[CheckIssue]: + min_severity = min_severity or self.context.requirement_severity + return [issue for issue in self._issues if issue.check == check and issue.severity >= min_severity] + + # def get_issues_by_check_and_severity(self, check: RequirementCheck, severity: Severity) -> list[CheckIssue]: + # return [issue for issue in self.issues if issue.check == check and issue.severity == severity] + + def has_issues(self, severity: Optional[Severity] = None) -> bool: + severity = severity or self.context.requirement_severity + return any(issue.severity >= severity for issue in self._issues) + + def passed(self, severity: Optional[Severity] = None) -> bool: + severity = severity or self.context.requirement_severity + return not any(issue.severity >= severity for issue in self._issues) + + def add_issue(self, issue: CheckIssue): + bisect.insort(self._issues, issue) + + def add_check_issue(self, + message: str, + check: RequirementCheck, + severity: Optional[Severity] = None, + resultPath: Optional[str] = None, + focusNode: Optional[str] = None, + value: Optional[str] = None) -> CheckIssue: + sev_value = severity if severity is not None else check.severity + c = CheckIssue(sev_value, check, message, resultPath=resultPath, focusNode=focusNode, value=value) + # self._issues.append(c) + bisect.insort(self._issues, c) + return c + + def add_error(self, message: str, check: RequirementCheck) -> CheckIssue: + return self.add_check_issue(message, check, Severity.REQUIRED) + + # --- Requirements --- + @property + def failed_requirements(self) -> Collection[Requirement]: + return set(issue.check.requirement for issue in self._issues) + + # --- Checks --- + @property + def failed_checks(self) -> Collection[RequirementCheck]: + return set(issue.check for issue in self._issues) + + def get_failed_checks_by_requirement(self, requirement: Requirement) -> Collection[RequirementCheck]: + return [check for check in self.failed_checks if check.requirement == requirement] + + def get_failed_checks_by_requirement_and_severity( + self, requirement: Requirement, severity: Severity) -> Collection[RequirementCheck]: + return [check for check in self.failed_checks + if check.requirement == requirement + and check.severity == severity] + + def __str__(self) -> str: + return f"Validation result: {len(self._issues)} issues" + + def __repr__(self): + return f"ValidationResult(issues={self._issues})" + + def __eq__(self, other: object) -> bool: + if not isinstance(other, ValidationResult): + raise TypeError(f"Cannot compare ValidationResult with {type(other)}") + return self._issues == other._issues + + def to_dict(self) -> dict: + allowed_properties = ["data_path", "profiles_path", + "profile_identifier", "inherit_profiles", "requirement_severity", "abort_on_first"] + return { + "rocrate": str(self.rocrate_path), + "validation_settings": {key: self.validation_settings[key] + for key in allowed_properties if key in self.validation_settings}, + "passed": self.passed(self.context.settings["requirement_severity"]), + "issues": [issue.to_dict() for issue in self.issues] + } + + def to_json(self, path: Optional[Path] = None) -> str: + + result = json.dumps(self.to_dict(), indent=4, cls=CustomEncoder) + if path: + with open(path, "w") as f: + f.write(result) + return result + + +class CustomEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, CheckIssue): + return obj.__dict__ + if isinstance(obj, Path): + return str(obj) + if isinstance(obj, Severity): + return obj.name + if isinstance(obj, RequirementCheck): + return obj.identifier + if isinstance(obj, Requirement): + return obj.identifier + if isinstance(obj, RequirementLevel): + return obj.name + + return super().default(obj) + + +@dataclass +class ValidationSettings: + + # Data settings + data_path: Path + # Profile settings + profiles_path: Path = DEFAULT_PROFILES_PATH + profile_identifier: str = DEFAULT_PROFILE_IDENTIFIER + inherit_profiles: bool = True + allow_requirement_check_override: bool = True + disable_check_for_duplicates: bool = False + # Ontology and inference settings + ontology_path: Optional[Path] = None + inference: Optional[VALID_INFERENCE_OPTIONS_TYPES] = None + # Validation strategy settings + advanced: bool = True # enable SHACL Advanced Validation + inplace: Optional[bool] = False + abort_on_first: Optional[bool] = True + inplace: Optional[bool] = False + meta_shacl: bool = False + iterate_rules: bool = True + target_only_validation: bool = True + remote_validation: bool = True + http_cache_timeout: int = 60 + # Requirement severity settings + requirement_severity: Union[str, Severity] = Severity.REQUIRED + requirement_severity_only: bool = False + allow_infos: Optional[bool] = True + allow_warnings: Optional[bool] = True + # Output serialization settings + serialization_output_path: Optional[Path] = None + serialization_output_format: RDF_SERIALIZATION_FORMATS_TYPES = "turtle" + + def __init__(self, **kwargs): + for key, value in kwargs.items(): + setattr(self, key, value) + + # if requirement_severity is a str, convert to Severity + severity = getattr(self, "requirement_severity") + if isinstance(severity, str): + setattr(self, "requirement_severity", Severity[severity]) + + def to_dict(self): + return asdict(self) + + @classmethod + def parse(cls, settings: Union[dict, ValidationSettings]) -> ValidationSettings: + """ + Parse the settings into a ValidationSettings object + + Args: + settings (Union[dict, ValidationSettings]): The settings to parse + + Returns: + ValidationSettings: The parsed settings + + Raises: + ValueError: If the settings type is invalid + """ + if isinstance(settings, dict): + return cls(**settings) + elif isinstance(settings, ValidationSettings): + return settings + else: + raise ValueError(f"Invalid settings type: {type(settings)}") + + +class ValidationEvent(Event): + def __init__(self, event_type: EventType, + validation_result: Optional[ValidationResult] = None, message: Optional[str] = None): + super().__init__(event_type, message) + self._validation_result = validation_result + + @property + def validation_result(self) -> Optional[ValidationResult]: + return self._validation_result + + +class ProfileValidationEvent(Event): + def __init__(self, event_type: EventType, profile: Profile, message: Optional[str] = None): + assert event_type in (EventType.PROFILE_VALIDATION_START, EventType.PROFILE_VALIDATION_END) + super().__init__(event_type, message) + self._profile = profile + + @property + def profile(self) -> Profile: + return self._profile + + +class RequirementValidationEvent(Event): + def __init__(self, + event_type: EventType, + requirement: Requirement, + validation_result: Optional[bool] = None, + message: Optional[str] = None): + assert event_type in (EventType.REQUIREMENT_VALIDATION_START, EventType.REQUIREMENT_VALIDATION_END) + super().__init__(event_type, message) + self._requirement = requirement + self._validation_result = validation_result + + @property + def requirement(self) -> Requirement: + return self._requirement + + @property + def validation_result(self) -> Optional[bool]: + return self._validation_result + + +class RequirementCheckValidationEvent(Event): + def __init__(self, event_type: EventType, + requirement_check: RequirementCheck, + validation_result: Optional[bool] = None, message: Optional[str] = None): + assert event_type in (EventType.REQUIREMENT_CHECK_VALIDATION_START, EventType.REQUIREMENT_CHECK_VALIDATION_END) + super().__init__(event_type, message) + self._requirement_check = requirement_check + self._validation_result = validation_result + + @property + def requirement_check(self) -> RequirementCheck: + return self._requirement_check + + @property + def validation_result(self) -> Optional[bool]: + return self._validation_result + + +class Validator(Publisher): + """ + Can validate conformance to a single Profile (including any requirements + inherited by parent profiles). + """ + + def __init__(self, settings: Union[str, ValidationSettings]): + self._validation_settings = ValidationSettings.parse(settings) + super().__init__() + + @property + def validation_settings(self) -> ValidationSettings: + return self._validation_settings + + def detect_rocrate_profiles(self) -> list[Profile]: + """ + Detect the profiles to validate against + """ + try: + # initialize the validation context + context = ValidationContext(self, self.validation_settings.to_dict()) + candidate_profiles_uris = set() + try: + candidate_profiles_uris.add(context.ro_crate.metadata.get_conforms_to()) + except Exception as e: + logger.debug("Error while getting candidate profiles URIs: %s", e) + try: + candidate_profiles_uris.add(context.ro_crate.metadata.get_root_data_entity_conforms_to()) + except Exception as e: + logger.debug("Error while getting candidate profiles URIs: %s", e) + + logger.debug("Candidate profiles: %s", candidate_profiles_uris) + if not candidate_profiles_uris: + logger.debug("Unable to determine the profile to validate against") + return None + # load the profiles + profiles = [] + candidate_profiles = [] + available_profiles = Profile.load_profiles(context.profiles_path, publicID=context.publicID, + severity=context.requirement_severity) + profiles = [p for p in available_profiles if p.uri in candidate_profiles_uris] + # get the candidate profiles + for profile in profiles: + candidate_profiles.append(profile) + inherited_profiles = profile.inherited_profiles + for inherited_profile in inherited_profiles: + if inherited_profile in candidate_profiles: + candidate_profiles.remove(inherited_profile) + logger.debug("%d Candidate Profiles found: %s", len(candidate_profiles), candidate_profiles) + return candidate_profiles + + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return None + + def validate(self) -> ValidationResult: + return self.__do_validate__() + + def validate_requirements(self, requirements: list[Requirement]) -> ValidationResult: + # check if requirement is an instance of Requirement + assert all(isinstance(requirement, Requirement) for requirement in requirements), \ + "Invalid requirement type" + # perform the requirements validation + return self.__do_validate__(requirements) + + def __do_validate__(self, + requirements: Optional[list[Requirement]] = None) -> ValidationResult: + + # initialize the validation context + context = ValidationContext(self, self.validation_settings.to_dict()) + + # set the profiles to validate against + profiles = context.profiles + assert len(profiles) > 0, "No profiles to validate" + self.notify(EventType.VALIDATION_START) + for profile in profiles: + logger.debug("Validating profile %s (id: %s)", profile.name, profile.identifier) + self.notify(ProfileValidationEvent(EventType.PROFILE_VALIDATION_START, profile=profile)) + # perform the requirements validation + requirements = profile.get_requirements( + context.requirement_severity, exact_match=context.requirement_severity_only) + logger.debug("Validating profile %s with %s requirements", profile.identifier, len(requirements)) + logger.debug("For profile %s, validating these %s requirements: %s", + profile.identifier, len(requirements), requirements) + terminate = False + for requirement in requirements: + self.notify(RequirementValidationEvent( + EventType.REQUIREMENT_VALIDATION_START, requirement=requirement)) + passed = requirement.__do_validate__(context) + self.notify(RequirementValidationEvent( + EventType.REQUIREMENT_VALIDATION_END, requirement=requirement, validation_result=passed)) + if passed: + logger.debug("Validation Requirement passed") + else: + logger.debug(f"Validation Requirement {requirement} failed (profile: {profile.identifier})") + if context.fail_fast: + logger.debug("Aborting on first requirement failure") + terminate = True + break + self.notify(ProfileValidationEvent(EventType.PROFILE_VALIDATION_END, profile=profile)) + if terminate: + break + self.notify(ValidationEvent(EventType.VALIDATION_END, + validation_result=context.result)) + + return context.result + + +class ValidationContext: + + def __init__(self, validator: Validator, settings: dict[str, object]): + # reference to the validator + self._validator = validator + # reference to the settings + self._settings = settings + # reference to the data graph + self._data_graph = None + # reference to the profiles + self._profiles = None + # reference to the validation result + self._result = None + # additional properties for the context + self._properties = {} + + # parse the rocrate path + rocrate_path: URI = URI(settings.get("data_path")) + logger.debug("Validating RO-Crate: %s", rocrate_path) + + # initialize the ROCrate object + self._rocrate = ROCrate.new_instance(rocrate_path) + assert isinstance(self._rocrate, ROCrate), "Invalid RO-Crate instance" + + @property + def ro_crate(self) -> ROCrate: + return self._rocrate + + @property + def validator(self) -> Validator: + return self._validator + + @property + def result(self) -> ValidationResult: + if self._result is None: + self._result = ValidationResult(self) + return self._result + + @property + def settings(self) -> dict[str, object]: + return self._settings + + @property + def publicID(self) -> str: + path = str(self.ro_crate.uri.base_uri) + if not path.endswith("/"): + path = f"{path}/" + return path + + @property + def profiles_path(self) -> Path: + profiles_path = self.settings.get("profiles_path") + if isinstance(profiles_path, str): + profiles_path = Path(profiles_path) + return profiles_path + + @property + def requirement_severity(self) -> Severity: + severity = self.settings.get("requirement_severity", Severity.REQUIRED) + if isinstance(severity, str): + severity = Severity[severity] + elif not isinstance(severity, Severity): + raise ValueError(f"Invalid severity type: {type(severity)}") + return severity + + @property + def requirement_severity_only(self) -> bool: + return self.settings.get("requirement_severity_only", False) + + @property + def rocrate_path(self) -> Path: + return self.settings.get("data_path") + + @property + def fail_fast(self) -> bool: + return self.settings.get("abort_on_first", True) + + @property + def rel_fd_path(self) -> Path: + return Path(ROCRATE_METADATA_FILE) + + def __load_data_graph__(self): + data_graph = Graph() + logger.debug("Loading RO-Crate metadata of: %s", self.ro_crate.uri) + _ = data_graph.parse(data=self.ro_crate.metadata.as_dict(), + format="json-ld", publicID=self.publicID) + logger.debug("RO-Crate metadata loaded: %s", data_graph) + return data_graph + + def get_data_graph(self, refresh: bool = False): + # load the data graph + try: + if not self._data_graph or refresh: + self._data_graph = self.__load_data_graph__() + return self._data_graph + except FileNotFoundError as e: + logger.debug("Error loading data graph: %s", e) + raise ROCrateMetadataNotFoundError(self.rocrate_path) + + @property + def data_graph(self) -> Graph: + return self.get_data_graph() + + @property + def inheritance_enabled(self) -> bool: + return self.settings.get("inherit_profiles", False) + + @property + def profile_identifier(self) -> str: + return self.settings.get("profile_identifier") + + @property + def allow_requirement_check_override(self) -> bool: + return self.settings.get("allow_requirement_check_override", True) + + @property + def disable_check_for_duplicates(self) -> bool: + return self.settings.get("disable_check_for_duplicates", False) + + def __load_profiles__(self) -> list[Profile]: + + # if the inheritance is disabled, load only the target profile + if not self.inheritance_enabled: + profile = Profile.load( + self.profiles_path, + self.profiles_path / self.profile_identifier, + publicID=self.publicID, + severity=self.requirement_severity) + return [profile] + + # load all profiles + profiles = Profile.load_profiles( + self.profiles_path, + publicID=self.publicID, + severity=self.requirement_severity, + allow_requirement_check_override=self.allow_requirement_check_override) + + # Check if the target profile is in the list of profiles + profile = Profile.get_by_identifier(self.profile_identifier) + if not profile: + try: + candidate_profiles = Profile.get_by_token(self.profile_identifier) + logger.debug("Candidate profiles found by token: %s", profile) + if candidate_profiles: + # Find the profile with the highest version number + profile = max(candidate_profiles, key=lambda p: p.version) + self.settings["profile_identifier"] = profile.identifier + logger.debug("Profile with the highest version number: %s", profile) + # if the profile is found by token, set the profile name to the identifier + self.settings["profile_identifier"] = profile.identifier + except AttributeError as e: + # raised when the profile is not found + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + raise ProfileNotFound( + self.profile_identifier, + message=f"Profile '{self.profile_identifier}' not found in '{self.profiles_path}'") from e + + # Set the profiles to validate against as the target profile and its inherited profiles + profiles = profile.inherited_profiles + [profile] + + # if the check for duplicates is disabled, return the profiles + if self.disable_check_for_duplicates: + return profiles + + return profiles + + @property + def profiles(self) -> list[Profile]: + if not self._profiles: + self._profiles = self.__load_profiles__() + return self._profiles.copy() + + @property + def target_profile(self) -> Profile: + profiles = self.profiles + assert len(profiles) > 0, "No profiles to validate" + return self.profiles[-1] + + def get_profile_by_token(self, token: str) -> list[Profile]: + return [p for p in self.profiles if p.token == token] + + def get_profile_by_identifier(self, identifier: str) -> list[Profile]: + for p in self.profiles: + if p.identifier == identifier: + return p + raise ProfileNotFound(identifier) diff --git a/rocrate_validator/profiles/process-run-crate/may/0_software-application.ttl b/rocrate_validator/profiles/process-run-crate/may/0_software-application.ttl new file mode 100644 index 00000000..460c35fa --- /dev/null +++ b/rocrate_validator/profiles/process-run-crate/may/0_software-application.ttl @@ -0,0 +1,45 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix process-run-crate: . +@prefix schema: . +@prefix sh: . +@prefix bioschemas: . + +process-run-crate:ProcRCSoftwareApplicationOptional a sh:NodeShape ; + sh:name "ProcRC SoftwareApplication MAY" ; + sh:description "Optional properties of a Process Run Crate SoftwareApplication" ; + # Avoid performing checks on dependencies + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?this a schema:SoftwareApplication . + FILTER NOT EXISTS { ?other schema:softwareRequirements ?this } . + } + """ + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "SoftwareApplication softwareRequirements" ; + sh:description "The SoftwareApplication MAY have a softwareRequirements that points to a SoftwareApplication" ; + sh:message "The SoftwareApplication MAY have a softwareRequirements that points to a SoftwareApplication" ; + sh:path schema:softwareRequirements ; + sh:class schema:SoftwareApplication ; + sh:minCount 1 ; + ] . diff --git a/rocrate_validator/profiles/process-run-crate/may/1_create_action.ttl b/rocrate_validator/profiles/process-run-crate/may/1_create_action.ttl new file mode 100644 index 00000000..b3ed6db9 --- /dev/null +++ b/rocrate_validator/profiles/process-run-crate/may/1_create_action.ttl @@ -0,0 +1,93 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix process-run-crate: . +@prefix schema: . +@prefix bioschemas: . +@prefix sh: . +@prefix wfrun: . + +process-run-crate:ProcRCActionOptional a sh:NodeShape ; + sh:name "Process Run Crate Action MAY" ; + sh:description "Recommended properties of the Process Run Crate Action" ; + sh:targetClass schema:CreateAction , + schema:ActivateAction , + schema:UpdateAction ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action startTime" ; + sh:description "The Action MAY have a startTime" ; + sh:path schema:startTime ; + sh:minCount 1 ; + sh:message "The Action MAY have a startTime" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action object" ; + sh:description "The Action MAY have an object" ; + sh:path schema:object ; + sh:minCount 1 ; + sh:message "The Action MAY have an object" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action actionStatus" ; + sh:description "The Action MAY have an actionStatus" ; + sh:path schema:actionStatus ; + sh:minCount 1 ; + sh:message "The Action MAY have an actionStatus" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action environment" ; + sh:description "The Action MAY have an environment" ; + sh:path wfrun:environment ; + sh:minCount 1 ; + sh:message "The Action MAY have an environment" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action containerImage" ; + sh:description "The Action MAY have a containerImage" ; + sh:path wfrun:containerImage ; + sh:minCount 1 ; + sh:message "The Action MAY have a containerImage" ; + ] . + + +process-run-crate:ProcRCActionErrorMay a sh:NodeShape ; + sh:name "Process Run Crate Action MAY have error" ; + sh:description "error MAY be specified if actionStatus is set to FailedActionStatus" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + { ?this a schema:CreateAction } UNION + { ?this a schema:ActivateAction } UNION + { ?this a schema:UpdateAction } . + ?this schema:actionStatus ?status . + FILTER(?status = "http://schema.org/FailedActionStatus") . + } + """ + ] ; + sh:property [ + a sh:PropertyShape ; + sh:path schema:error ; + sh:minCount 1 ; + sh:message "error MAY be specified if actionStatus is set to FailedActionStatus" ; + ] . diff --git a/rocrate_validator/profiles/process-run-crate/may/3_container_image.ttl b/rocrate_validator/profiles/process-run-crate/may/3_container_image.ttl new file mode 100644 index 00000000..108afa72 --- /dev/null +++ b/rocrate_validator/profiles/process-run-crate/may/3_container_image.ttl @@ -0,0 +1,41 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix process-run-crate: . +@prefix sh: . +@prefix bioschemas: . +@prefix wfrun: . + +process-run-crate:ProcRCContainerImageOptional a sh:NodeShape ; + sh:name "Process Run Crate ContainerImage MAY" ; + sh:description "Optional properties of the Process Run Crate ContainerImage" ; + sh:targetClass wfrun:ContainerImage ; + sh:property [ + a sh:PropertyShape ; + sh:name "ContainerImage tag" ; + sh:description "The ContainerImage MAY have a tag" ; + sh:path wfrun:tag ; + sh:minCount 1 ; + sh:message "The ContainerImage MAY have a tag" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "ContainerImage sha256" ; + sh:description "The ContainerImage MAY have a sha256" ; + sh:path wfrun:sha256 ; + sh:minCount 1 ; + sh:message "The ContainerImage MAY have a sha256" ; + ] . diff --git a/rocrate_validator/profiles/process-run-crate/must/1_create_action.ttl b/rocrate_validator/profiles/process-run-crate/must/1_create_action.ttl new file mode 100644 index 00000000..bb59410e --- /dev/null +++ b/rocrate_validator/profiles/process-run-crate/must/1_create_action.ttl @@ -0,0 +1,40 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix process-run-crate: . +@prefix schema: . +@prefix sh: . +@prefix bioschemas: . + +process-run-crate:ProcRCActionRequired a sh:NodeShape ; + sh:name "Process Run Crate Action" ; + sh:description "Properties of the Process Run Crate Action" ; + sh:targetClass schema:CreateAction , + schema:ActivateAction , + schema:UpdateAction ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action instrument" ; + sh:description "The Action MUST have an instrument property that references the executed tool" ; + sh:path schema:instrument ; + sh:or ( + [ sh:class schema:SoftwareApplication ; ] + [ sh:class schema:SoftwareSourceCode ; ] + [ sh:class bioschemas:ComputationalWorkflow ; ] + ) ; + sh:minCount 1 ; + sh:message "The Action MUST have an instrument property that references the executed tool" ; + ] . diff --git a/rocrate_validator/profiles/process-run-crate/must/2_root_data_entity_metadata.ttl b/rocrate_validator/profiles/process-run-crate/must/2_root_data_entity_metadata.ttl new file mode 100644 index 00000000..c42413c9 --- /dev/null +++ b/rocrate_validator/profiles/process-run-crate/must/2_root_data_entity_metadata.ttl @@ -0,0 +1,39 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix process-run-crate: . +@prefix dct: . +@prefix schema: . +@prefix sh: . + +process-run-crate:ProcRCRootDataEntityMetadata a sh:NodeShape ; + sh:name "Root Data Entity Metadata" ; + sh:description "Properties of the Root Data Entity" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:property [ + a sh:PropertyShape ; + sh:name "Root Data Entity conformsTo" ; + sh:description "The Root Data Entity MUST reference a CreativeWork entity with an @id URI that is consistent with the versioned permalink of the profile" ; + sh:path dct:conformsTo ; + sh:class schema:CreativeWork; + # At least one value of conformsTo must match the pattern + sh:qualifiedValueShape [ + sh:pattern "^https://w3id.org/ro/wfrun/process/.*" ; + ] ; + sh:qualifiedMinCount 1 ; + sh:minCount 1 ; + sh:message "The Root Data Entity MUST reference a CreativeWork entity with an @id URI that is consistent with the versioned permalink of the profile" ; + ] . diff --git a/rocrate_validator/profiles/process-run-crate/profile.ttl b/rocrate_validator/profiles/process-run-crate/profile.ttl new file mode 100644 index 00000000..29f984fa --- /dev/null +++ b/rocrate_validator/profiles/process-run-crate/profile.ttl @@ -0,0 +1,81 @@ +# Copyright (c) 2024 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 dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Process Run Crate 0.5" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """RO-Crate Metadata Specification."""@en ; + + # URI of the publisher of the Workflow RO-Crate Metadata Specification + dct:publisher ; + + # This profile is an extension of the RO-Crate Metadata Specification 1.1 profile + prof:isProfileOf ; + + # Explicitly state that this profile is a transitive profile of the RO-Crate Metadata Specification 1.1 profile + prof:isTransitiveProfileOf ; + + # 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 refered 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 refered 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 the RO-Crate Metadata Specification 1.1 + prof:isInheritedFrom ; + ] ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "process-run-crate" ; +. diff --git a/rocrate_validator/profiles/process-run-crate/should/0_software-application.ttl b/rocrate_validator/profiles/process-run-crate/should/0_software-application.ttl new file mode 100644 index 00000000..fa73c227 --- /dev/null +++ b/rocrate_validator/profiles/process-run-crate/should/0_software-application.ttl @@ -0,0 +1,111 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix process-run-crate: . +@prefix rdf: . +@prefix schema: . +@prefix bioschemas: . +@prefix sh: . + +process-run-crate:ProcRCApplication a sh:NodeShape ; + sh:name "ProcRC Application" ; + sh:description "Properties of a Process Run Crate Application" ; + sh:targetClass schema:SoftwareApplication, + schema:SoftwareSourceCode, + bioschemas:ComputationalWorkflow; + sh:property [ + a sh:PropertyShape ; + sh:name "Application name" ; + sh:description "The Application SHOULD have a name" ; + sh:path schema:name ; + sh:minCount 1 ; + sh:message "The Application SHOULD have a name" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Application url" ; + sh:description "The Application SHOULD have a url" ; + sh:path schema:url ; + sh:minCount 1 ; + sh:message "The Application SHOULD have a url" ; + ] . + +process-run-crate:ProcRCSoftwareSourceCodeComputationalWorkflow a sh:NodeShape ; + sh:name "ProcRC SoftwareSourceCode or ComputationalWorkflow" ; + sh:description "Properties of a Process Run Crate SoftwareSourceCode or ComputationalWorkflow" ; + sh:targetClass schema:SoftwareSourceCode, + bioschemas:ComputationalWorkflow; + sh:property [ + a sh:PropertyShape ; + sh:name "version" ; + sh:description "The SoftwareSourceCode or ComputationalWorkflow SHOULD have a version" ; + sh:path schema:version ; + sh:minCount 1 ; + sh:message "The SoftwareSourceCode or ComputationalWorkflow SHOULD have a version" ; + ] . + +process-run-crate:ProcRCSoftwareApplication a sh:NodeShape ; + sh:name "ProcRC SoftwareApplication" ; + sh:description "Properties of a Process Run Crate SoftwareApplication" ; + sh:targetClass schema:SoftwareApplication ; + sh:property [ + a sh:PropertyShape ; + sh:name "version or softwareVersion" ; + sh:description "The SoftwareApplication SHOULD have a version or softwareVersion" ; + sh:message "The SoftwareApplication SHOULD have a version or softwareVersion" ; + sh:path [ + sh:alternativePath ( schema:version schema:softwareVersion ) ; + ] ; + sh:minLength 1 ; + sh:minCount 1 ; + ] . + + +process-run-crate:ProcRCSoftwareApplicationSingleVersion a sh:NodeShape ; + sh:name "ProcRC SoftwareApplication SingleVersion" ; + sh:description "Process Run Crate SoftwareApplication should not have both version and softwareVersion" ; + sh:message "Process Run Crate SoftwareApplication should not have both version and softwareVersion" ; + sh:targetClass schema:SoftwareApplication ; + sh:not [ + sh:and ( + [ sh:property [ + a sh:PropertyShape ; + sh:path schema:version ; + sh:minCount 1 ; + ]] + [ sh:property [ + a sh:PropertyShape ; + sh:path schema:softwareVersion ; + sh:minCount 1 ; + ]] + ) ; + ] . + + +process-run-crate:ProcRCSoftwareApplicationID a sh:NodeShape ; + sh:name "ProcRC SoftwareApplication ID" ; + sh:description "Process Run Crate SoftwareApplication ID" ; + sh:targetNode schema:SoftwareApplication , + schema:SoftwareSourceCode, + bioschemas:ComputationalWorkflow; + sh:property [ + a sh:PropertyShape ; + sh:name "SoftwareApplication id" ; + sh:description "The SoftwareApplication id SHOULD be an absolute URI" ; + sh:path [ sh:inversePath rdf:type ] ; + sh:pattern "^http.*" ; + sh:message "The SoftwareApplication id SHOULD be an absolute URI" ; + ] . diff --git a/rocrate_validator/profiles/process-run-crate/should/1_create_action.ttl b/rocrate_validator/profiles/process-run-crate/should/1_create_action.ttl new file mode 100644 index 00000000..06ebfcab --- /dev/null +++ b/rocrate_validator/profiles/process-run-crate/should/1_create_action.ttl @@ -0,0 +1,175 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix process-run-crate: . +@prefix schema: . +@prefix bioschemas: . +@prefix sh: . +@prefix wfrun: . + +process-run-crate:ProcRCActionRecommended a sh:NodeShape ; + sh:name "Process Run Crate Action SHOULD" ; + sh:description "Recommended properties of the Process Run Crate Action" ; + sh:targetClass schema:CreateAction , + schema:ActivateAction , + schema:UpdateAction ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action SHOULD be referenced via mentions from root" ; + sh:description "The Action SHOULD be referenced from the Root Data Entity via mentions" ; + sh:path [ sh:inversePath schema:mentions ] ; + sh:node ro-crate:RootDataEntity ; + sh:minCount 1 ; + sh:message "The Action SHOULD be referenced from the Root Data Entity via mentions" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action name" ; + sh:description "The Action SHOULD have a name" ; + sh:path schema:name ; + sh:minCount 1 ; + sh:message "The Action SHOULD have a name" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action description" ; + sh:description "The Action SHOULD have a description" ; + sh:path schema:description ; + sh:minCount 1 ; + sh:message "The Action SHOULD have a description" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action endTime" ; + sh:description "The Action SHOULD have an endTime in ISO 8601 format" ; + sh:path schema:endTime ; + sh:pattern "^(\\d{4}-\\d{2}-\\d{2})(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?\\+\\d{2}:\\d{2})?$" ; + sh:minCount 1 ; + sh:message "The Action SHOULD have an endTime in ISO 8601 format" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action startTime" ; + sh:description "If present, the Action startTime SHOULD be in ISO 8601 format" ; + sh:path schema:startTime ; + sh:pattern "^(\\d{4}-\\d{2}-\\d{2})(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?\\+\\d{2}:\\d{2})?$" ; + sh:message "If present, the Action startTime SHOULD be in ISO 8601 format" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action agent" ; + sh:description "The Action SHOULD have an agent that is a Person or Organization" ; + sh:path schema:agent ; + sh:or ( + [ sh:class schema:Person ; ] + [ sh:class schema:Organization ; ] + ) ; + sh:minCount 1 ; + sh:message "The Action SHOULD have an agent that is a Person or Organization" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action actionStatus" ; + sh:description "If the Action has an actionStatus, it should be http://schema.org/CompletedActionStatus or http://schema.org/FailedActionStatus" ; + sh:path schema:actionStatus ; + sh:in ( + "http://schema.org/CompletedActionStatus" + "http://schema.org/FailedActionStatus" + ) ; + sh:message "If the Action has an actionStatus, it should be http://schema.org/CompletedActionStatus or http://schema.org/FailedActionStatus" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action environment" ; + sh:description "If the Action has an environment, it should point to entities of type PropertyValue" ; + sh:path wfrun:environment ; + sh:class schema:PropertyValue ; + sh:message "If the Action has an environment, it should point to entities of type PropertyValue" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action containerImage" ; + sh:description "If the Action has a containerImage, it should point to a ContainerImage or a URL" ; + sh:path wfrun:containerImage ; + sh:or ( + [ sh:class wfrun:ContainerImage ; ] + [ sh:pattern "^http.*" ; ] + ) ; + sh:message "If the Action has a containerImage, it should point to a ContainerImage or a URL" ; + ] . + + +process-run-crate:ProcRCCreateUpdateActionRecommended a sh:NodeShape ; + sh:name "Process Run Crate CreateAction UpdateAction SHOULD" ; + sh:description "Recommended properties of the Process Run Crate CreateAction or UpdateAction" ; + sh:targetClass schema:CreateAction , + schema:UpdateAction ; + sh:property [ + a sh:PropertyShape ; + sh:name "Action result" ; + sh:description "The Action SHOULD have a result" ; + sh:path schema:result ; + sh:minCount 1 ; + sh:message "The Action SHOULD have a result" ; + ] . + + +process-run-crate:ProcRCActionError a sh:NodeShape ; + sh:name "Process Run Crate Action error" ; + sh:description "error SHOULD NOT be specified unless actionStatus is set to FailedActionStatus" ; + sh:message "error SHOULD NOT be specified unless actionStatus is set to FailedActionStatus" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + { ?this a schema:CreateAction } UNION + { ?this a schema:ActivateAction } UNION + { ?this a schema:UpdateAction } . + { FILTER NOT EXISTS { ?this schema:actionStatus ?status } } UNION + { ?this schema:actionStatus ?status . + FILTER(?status != "http://schema.org/FailedActionStatus") } + } + """ + ] ; + sh:not [ + a sh:PropertyShape ; + sh:path schema:error ; + sh:minCount 1 ; + ] . + + +process-run-crate:ProcRCActionObjectResultType a sh:NodeShape ; + sh:name "Process Run Crate Action object and result types" ; + sh:description "object and result SHOULD point to entities of type MediaObject, Dataset, Collection, CreativeWork or PropertyValue" ; + sh:targetClass schema:CreateAction , + schema:ActivateAction , + schema:UpdateAction ; + sh:property [ + a sh:PropertyShape ; + sh:path [ + sh:alternativePath (schema:object schema:result) ; + ] ; + sh:or ( + [ sh:class schema:MediaObject ; ] + [ sh:class schema:Dataset ; ] + [ sh:class schema:Collection ; ] + [ sh:class schema:CreativeWork ; ] + [ sh:class schema:PropertyValue ; ] + ); + sh:message "object and result SHOULD point to entities of type MediaObject, Dataset, Collection, CreativeWork or PropertyValue" ; + ] . diff --git a/rocrate_validator/profiles/process-run-crate/should/2_collection.ttl b/rocrate_validator/profiles/process-run-crate/should/2_collection.ttl new file mode 100644 index 00000000..c6e452b6 --- /dev/null +++ b/rocrate_validator/profiles/process-run-crate/should/2_collection.ttl @@ -0,0 +1,65 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix process-run-crate: . +@prefix schema: . +@prefix bioschemas: . +@prefix sh: . +@prefix validator: . + +process-run-crate:ProcRCCollectionRecommended a sh:NodeShape ; + sh:name "Process Run Crate Collection SHOULD" ; + sh:description "Recommended properties of the Process Run Crate Collection" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?this a schema:Collection . + { ?action schema:object ?this } UNION + { ?action schema:result ?this } . + { ?action a schema:CreateAction } UNION + { ?action a schema:ActivateAction } UNION + { ?action a schema:UpdateAction } . + } + """ + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Collection SHOULD be referenced via mentions from root" ; + sh:description "The Collection SHOULD be referenced from the Root Data Entity via mentions" ; + sh:path [ sh:inversePath schema:mentions ] ; + sh:node ro-crate:RootDataEntity ; + sh:minCount 1 ; + sh:message "The Collection SHOULD be referenced from the Root Data Entity via mentions" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Collection hasPart" ; + sh:description "The Collection SHOULD have a hasPart" ; + sh:path schema:hasPart ; + sh:minCount 1 ; + sh:message "The Collection SHOULD have a hasPart" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Collection mainEntity" ; + sh:description "The Collection SHOULD have a mainEntity" ; + sh:path schema:mainEntity ; + sh:minCount 1 ; + sh:message "The Collection SHOULD have a mainEntity" ; + ] . diff --git a/rocrate_validator/profiles/process-run-crate/should/3_container_image.ttl b/rocrate_validator/profiles/process-run-crate/should/3_container_image.ttl new file mode 100644 index 00000000..62c4121c --- /dev/null +++ b/rocrate_validator/profiles/process-run-crate/should/3_container_image.ttl @@ -0,0 +1,54 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix process-run-crate: . +@prefix schema: . +@prefix bioschemas: . +@prefix sh: . +@prefix wfrun: . + +process-run-crate:ProcRCContainerImageRecommended a sh:NodeShape ; + sh:name "Process Run Crate ContainerImage SHOULD" ; + sh:description "Recommended properties of the Process Run Crate ContainerImage" ; + sh:targetClass wfrun:ContainerImage ; + sh:property [ + a sh:PropertyShape ; + sh:name "ContainerImage additionalType" ; + sh:description "The ContainerImage SHOULD have an additionalType pointing to or " ; + sh:path schema:additionalType ; + sh:or ( + [ sh:hasValue wfrun:DockerImage ; ] + [ sh:hasValue wfrun:SIFImage ; ] + ) ; + sh:minCount 1 ; + sh:message "The ContainerImage SHOULD have an additionalType pointing to or " ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "ContainerImage registry" ; + sh:description "The ContainerImage SHOULD have a registry" ; + sh:path wfrun:registry ; + sh:minCount 1 ; + sh:message "The ContainerImage SHOULD have a registry" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "ContainerImage name" ; + sh:description "The ContainerImage SHOULD have a name" ; + sh:path schema:name ; + sh:minCount 1 ; + sh:message "The ContainerImage SHOULD have a name" ; + ] . diff --git a/rocrate_validator/profiles/ro-crate/may/4_data_entity_metadata.ttl b/rocrate_validator/profiles/ro-crate/may/4_data_entity_metadata.ttl new file mode 100644 index 00000000..95dc5cf7 --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/may/4_data_entity_metadata.ttl @@ -0,0 +1,90 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix rdf: . +@prefix sh: . +@prefix xsd: . +@prefix owl: . +@prefix schema: . +@prefix validator: . + +ro-crate:FileDataEntityWebOptionalProperties a sh:NodeShape ; + sh:name "File Data Entity with web presence: OPTIONAL properties" ; + sh:description """A File Data Entity which have a corresponding web presence, + for instance a landing page that describes the file, including persistence identifiers (e.g. DOI), + resolving to an intermediate HTML page instead of the downloadable file directly. + These can included for File Data Entities as additional metadata by using the properties: + `Γ¬dentifier`, `url`, `subjectOf`and `mainEntityOfPage`""" ; + sh:targetClass ro-crate:File ; + # Check if the Web-based Data Entity has a contentSize property + sh:property [ + a sh:PropertyShape ; + sh:minCount 1 ; + sh:name "File Data Entity: optional formal `identifier` (e.g. DOI)" ; + sh:description """Check if the File Data Entity has a formal identifier string such as a DOI""" ; + sh:path schema:identifier ; + sh:datatype xsd:anyURI ; + sh:severity sh:Info ; + sh:message """The File Data Entity MAY have a formal identifier specified through an `identifier` property""" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:minCount 1 ; + sh:name "File Data Entity: optional `url` property" ; + sh:description """Check if the File Data Entity has an optional `download` link""" ; + sh:path schema:url ; + sh:datatype xsd:anyURI ; + sh:severity sh:Info ; + sh:message """The File Data Entity MAY use a `url` property to denote a `download` link""" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:minCount 1 ; + sh:name "File Data Entity: optional `subjectOf` property" ; + sh:description """Check if the File Data Entity includes a `subjectOf` property to link `CreativeWork` instances that mention it.""" ; + sh:path schema:subjectOf ; + sh:class schema:WebPage, schema:CreativeWork ; + sh:severity sh:Info ; + sh:message """The File Data Entity MAY include a `subjectOf` property to link `CreativeWork` instances that mention it.""" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:minCount 1 ; + sh:name "File Data Entity: optional `mainEntityOfPage` property" ; + sh:description """Check if the File Data Entity has a `mainEntityOfPage` property""" ; + sh:path schema:mainEntityOfPage ; + sh:class schema:WebPage, schema:CreativeWork ; + sh:severity sh:Info ; + sh:message """The File Data Entity MAY have a `mainEntityOfPage` property""" ; + ] . + + +ro-crate:DirectoryDataEntityWebOptionalDistribution a sh:NodeShape ; + sh:name "Directory Data Entity: OPTIONAL `distribution` property" ; + sh:description """A Directory Data Entity MAY have a `distribution` property to denote the distribution of the files within the directory""" ; + sh:targetClass ro-crate:File ; + # Check if the Web-based Data Entity has a contentSize property + sh:property [ + a sh:PropertyShape ; + sh:minCount 1 ; + sh:name "Directory Data Entity: optional `distribution` property" ; + sh:description """Check if the Directory Data Entity has a `distribution` property""" ; + sh:path schema:distribution ; + sh:datatype xsd:anyURI ; + sh:severity sh:Info ; + sh:message """The Directory Data Entity MAY have a `distribution` property to denote the distribution of the files within the directory""" ; + ] . + diff --git a/rocrate_validator/profiles/ro-crate/may/61_license_entity.ttl b/rocrate_validator/profiles/ro-crate/may/61_license_entity.ttl new file mode 100644 index 00000000..9e0d4198 --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/may/61_license_entity.ttl @@ -0,0 +1,45 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . + +ro-crate:LicenseDefinition a sh:NodeShape ; + sh:name "License definition" ; + sh:description """Contextual entity representing a license with a name and description."""; + sh:targetClass schema_org:license ; + sh:property [ + a sh:PropertyShape ; + sh:name "License name" ; + sh:description "The license MAY have a name" ; + sh:minCount 1 ; + sh:maxCount 1 ; + sh:nodeKind sh:Literal ; + sh:path schema_org:name ; + sh:message "Missing license name" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "License description" ; + sh:description """The license MAY have a description""" ; + sh:maxCount 1; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path schema_org:description ; + sh:message "Missing license description" ; + ] . + diff --git a/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py b/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py new file mode 100644 index 00000000..fc544da9 --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/must/0_file_descriptor_format.py @@ -0,0 +1,130 @@ +# Copyright (c) 2024 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 rocrate_validator.log as logging +from rocrate_validator.models import ValidationContext +from rocrate_validator.requirements.python import (PyFunctionCheck, check, + requirement) + +# set up logging +logger = logging.getLogger(__name__) + + +@requirement(name="File Descriptor existence") +class FileDescriptorExistence(PyFunctionCheck): + """The file descriptor MUST be present in the RO-Crate and MUST not be empty.""" + + @check(name="File Descriptor Existence") + def test_existence(self, context: ValidationContext) -> bool: + """ + Check if the file descriptor is present in the RO-Crate + """ + if not context.ro_crate.has_descriptor(): + message = f'file descriptor "{context.rel_fd_path}" is not present' + context.result.add_error(message, self) + return False + return True + + @check(name="File Descriptor size check") + def test_size(self, context: ValidationContext) -> bool: + """ + Check if the file descriptor is not empty + """ + if not context.ro_crate.has_descriptor(): + message = f'file descriptor {context.rel_fd_path} is empty' + context.result.add_error(message, self) + return False + if context.ro_crate.metadata.size == 0: + context.result.add_error(f'RO-Crate "{context.rel_fd_path}" file descriptor is empty', self) + return False + return True + + +@requirement(name="File Descriptor JSON format") +class FileDescriptorJsonFormat(PyFunctionCheck): + """ + The file descriptor MUST be a valid JSON file + """ + @check(name="File Descriptor JSON format") + def check(self, context: ValidationContext) -> bool: + """ Check if the file descriptor is in the correct format""" + try: + logger.debug("Checking validity of JSON file at %s", context.ro_crate.metadata) + context.ro_crate.metadata.as_dict() + return True + except Exception as e: + context.result.add_error( + f'RO-Crate file descriptor "{context.rel_fd_path}" is not in the correct format', self) + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return False + + +@requirement(name="File Descriptor JSON-LD format") +class FileDescriptorJsonLdFormat(PyFunctionCheck): + """ + The file descriptor MUST be a valid JSON-LD file + """ + + @check(name="File Descriptor @context property validation") + def check_context(self, context: ValidationContext) -> bool: + """ Check if the file descriptor contains the @context property """ + try: + json_dict = context.ro_crate.metadata.as_dict() + if "@context" not in json_dict: + context.result.add_error( + f'RO-Crate file descriptor "{context.rel_fd_path}" ' + "does not contain a context", self) + return False + return True + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return False + + @check(name="Validation of the @id property of the file descriptor entities") + def check_identifiers(self, context: ValidationContext) -> bool: + """ Check if the file descriptor entities have the @id property """ + try: + json_dict = context.ro_crate.metadata.as_dict() + for entity in json_dict["@graph"]: + if "@id" not in entity: + context.result.add_error( + f"Entity \"{entity.get('name', None) or entity}\" " + f"of RO-Crate \"{context.rel_fd_path}\" " + "file descriptor does not contain the @id attribute", self) + return False + return True + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return False + + @check(name="Validation of the @type property of the file descriptor entities") + def check_types(self, context: ValidationContext) -> bool: + """ Check if the file descriptor entities have the @type property """ + try: + json_dict = context.ro_crate.metadata.as_dict() + for entity in json_dict["@graph"]: + if "@type" not in entity: + context.result.add_error( + f"Entity \"{entity.get('name', None) or entity}\" " + f"of RO-Crate \"{context.rel_fd_path}\" " + "file descriptor does not contain the @type attribute", self) + return False + return True + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return False diff --git a/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl b/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl new file mode 100644 index 00000000..8e23f9ad --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/must/1_file-descriptor_metadata.ttl @@ -0,0 +1,101 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix validator: . + + +ro-crate:FindROCrateMetadataFileDescriptorEntity a sh:NodeShape, validator:HiddenShape; + sh:name "Identify the RO-Crate Metadata File Descriptor" ; + sh:description """The RO-Crate Metadata File Descriptor entity describes the RO-Crate itself, and it is named as `ro-crate-metadata.json`. + It can be identified by name according to the RO-Crate specification + available at [Finding RO-Crate Root in RDF triple stores](https://www.researchobject.org/ro-crate/1.1/appendix/relative-uris.html#finding-ro-crate-root-in-rdf-triple-stores).""" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?this a schema:CreativeWork ; + FILTER(contains(str(?this), "ro-crate-metadata.json")) + } + """ + ] ; + + # Expand data graph with triples from the file data entity + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:ROCrateMetadataFileDescriptor ; + ] . + +ro-crate:ROCrateMetadataFileDescriptorExistence + a sh:NodeShape ; + sh:name "RO-Crate Metadata File Descriptor entity existence" ; + sh:description "The RO-Crate JSON-LD MUST contain a Metadata File Descriptor entity named `ro-crate-metadata.json` and typed as `schema:CreativeWork`" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "RO-Crate Metadata File Descriptor entity existence" ; + sh:description """Check if the RO-Crate Metadata File Descriptor entity exists, + i.e., if there exists an entity with @id `ro-crate-metadata.json` and type `schema:CreativeWork`""" ; + sh:path rdf:type ; + sh:hasValue ro-crate:ROCrateMetadataFileDescriptor ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . + +ro-crate:ROCrateMetadataFileDescriptorRecommendedProperties a sh:NodeShape ; + sh:name "RO-Crate Metadata File Descriptor REQUIRED properties" ; + sh:description """RO-Crate Metadata Descriptor MUST be defined + according with the requirements details defined in + [RO-Crate Metadata File Descriptor](https://www.researchobject.org/ro-crate/1.1/root-data-entity.html#ro-crate-metadata-file-descriptor)"""; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Metadata File Descriptor entity type" ; + sh:description "Check if the RO-Crate Metadata File Descriptor has `@type` CreativeWork, as per schema.org" ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path rdf:type ; + sh:hasValue schema_org:CreativeWork ; + sh:message "The RO-Crate metadata file MUST be a CreativeWork, as per schema.org" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Metadata File Descriptor entity: `about` property" ; + sh:description """Check if the RO-Crate Metadata File Descriptor has an `about` property referencing the Root Data Entity""" ; + sh:maxCount 1; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path schema_org:about ; + sh:class schema_org:Dataset ; + sh:message "The RO-Crate metadata file descriptor MUST have an `about` property referencing the Root Data Entity" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Metadata File Descriptor entity: `conformsTo` property" ; + sh:description """Check if the RO-Crate Metadata File Descriptor has a `conformsTo` property which points to the RO-Crate specification version""" ; + sh:minCount 1 ; + sh:nodeKind sh:IRI ; + sh:path dct:conformsTo ; + sh:hasValue ; + sh:message "The RO-Crate metadata file descriptor MUST have a `conformsTo` property with the RO-Crate specification version" ; + ] . diff --git a/rocrate_validator/profiles/ro-crate/must/2_root_data_entity_metadata.ttl b/rocrate_validator/profiles/ro-crate/must/2_root_data_entity_metadata.ttl new file mode 100644 index 00000000..c269a3f9 --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/must/2_root_data_entity_metadata.ttl @@ -0,0 +1,120 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix validator: . + + +ro-crate:RootDataEntityType + a sh:NodeShape ; + sh:name "RO-Crate Root Data Entity type" ; + sh:description "The Root Data Entity MUST be a `Dataset` (as per `schema.org`)" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?metadatafile schema:about ?this . + FILTER(contains(str(?metadatafile), "ro-crate-metadata.json")) + } + """ + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Root Data Entity type" ; + sh:description "Check if the Root Data Entity is a `Dataset` (as per `schema.org`)" ; + sh:path rdf:type ; + sh:hasValue schema_org:Dataset ; + sh:minCount 1 ; + sh:message """The Root Data Entity MUST be a `Dataset` (as per `schema.org`)""" ; + ] ; + # Validate that if the publisher is specified, it is an Organization or a Person + sh:property [ + sh:path schema_org:publisher ; + sh:severity sh:Violation ; + sh:name "Root Data Entity: `publisher` property" ; + sh:description """Check if the Root Data Entity has a `publisher` property of type `Organization` or `Person`.""" ; + sh:or ( + [ sh:class schema_org:Organization ] + [ sh:class schema_org:Person ] + ) ; + sh:message """The Root Data Entity MUST have a `publisher` property of type `Organization` or `Person`.""" ; + ] . + + +ro-crate:FindRootDataEntity a sh:NodeShape, validator:HiddenShape; + sh:name "Identify the Root Data Entity of the RO-Crate" ; + sh:description """The Root Data Entity is the top-level Data Entity in the RO-Crate and serves as the starting point for the description of the RO-Crate. + It is a schema:Dataset and is indirectly identified by the about property of the resource ro-crate-metadata.json in the RO-Crate + (see the definition at [Finding RO-Crate Root in RDF triple stores](https://www.researchobject.org/ro-crate/1.1/appendix/relative-uris.html#finding-ro-crate-root-in-rdf-triple-stores)). + """ ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?this a schema:Dataset . + ?metadatafile schema:about ?this . + FILTER(contains(str(?metadatafile), "ro-crate-metadata.json")) + } + """ + ] ; + + # Expand data graph with triples from the file data entity + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:RootDataEntity ; + ] . + + +ro-crate:RootDataEntityValueRestriction + a sh:NodeShape ; + sh:name "RO-Crate Root Data Entity value restriction" ; + sh:description "The Root Data Entity MUST end with `/`" ; + sh:targetNode ro-crate:RootDataEntity ; + sh:property [ + a sh:PropertyShape ; + sh:name "Root Data Entity URI value" ; + sh:description "Check if the Root Data Entity URI ends with `/`" ; + sh:path [ sh:inversePath rdf:type ] ; + sh:minCount 1 ; + sh:message """The Root Data Entity URI MUST end with `/`""" ; + sh:pattern "/$" ; + ] . + +ro-crate:RootDataEntityHasPartValueRestriction + a sh:NodeShape ; + sh:name "RO-Crate Root Data Entity: `hasPart` value restriction" ; + sh:description "The Root Data Entity MUST be linked to the declared `File`, `Directory` and other types of instances through the `hasPart` property" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:property [ + a sh:PropertyShape ; + sh:name "RO-Crate Root Data Entity: `hasPart` value restriction" ; + sh:description "Check if the Root Data Entity is linked to the declared `File`, `Directory` and other types of instances through the `hasPart` property" ; + sh:path schema_org:hasPart ; + sh:or ( + [ sh:class ro-crate:File ] + [ sh:class ro-crate:Directory ] + [ sh:class ro-crate:GenericDataEntity ] + ) ; + # sh:message """The Root Data Entity MUST be linked to either File or Directory instances, nothing else""" ; + ] . diff --git a/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.ttl b/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.ttl new file mode 100644 index 00000000..04426096 --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/must/4_data_entity_metadata.ttl @@ -0,0 +1,225 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix owl: . +@prefix validator: . + +ro-crate:DataEntityRequiredProperties a sh:NodeShape ; + sh:name "Data Entity: REQUIRED properties" ; + sh:description """A Data Entity MUST be a `URI Path` relative to the ROCrate root, + or an sbsolute URI""" ; + sh:targetClass ro-crate:DataEntity ; + + sh:property [ + sh:name "Data Entity: @id value restriction" ; + sh:description """Check if the Data Entity has an absolute or relative URI as `@id`""" ; + sh:path [sh:inversePath rdf:type ] ; + sh:nodeKind sh:IRI ; + sh:severity sh:Violation ; + sh:message """Data Entities MUST have an absolute or relative URI as @id.""" ; + ] . + +ro-crate:FileDataEntity a sh:NodeShape ; + sh:name "File Data Entity: REQUIRED properties" ; + sh:description """A File Data Entity MUST be a `File`. + `File` is an RO-Crate alias for the schema.org `MediaObject`. + The term `File` here is liberal, and includes "downloadable" resources where `@id` is an absolute URI. + """ ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?this a schema:MediaObject . + FILTER(?this != ro:ro-crate-metadata.json) + } + """ + ] ; + + sh:property [ + sh:name "File Data Entity: REQUIRED type" ; + sh:description """Check if the File Data Entity has `File` as `@type`. + `File` is an RO-Crate alias for the schema.org `MediaObject`. + """ ; + sh:path rdf:type ; + sh:hasValue ro-crate:File ; + sh:severity sh:Violation ; + sh:message """File Data Entities MUST have "File" as a value for @type.""" ; + ] ; + + # Expand data graph with triples from the file data entity + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:DataEntity ; + ] . + + +ro-crate:DirectoryDataEntity a sh:NodeShape ; + sh:name "Directory Data Entity: REQUIRED properties" ; + sh:description """A Directory Data Entity MUST be of @type `Dataset`. + The term `directory` here includes HTTP file listings where `@id` is an absolute URI. + """ ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?this a schema:Dataset . + ?metadatafile schema:about ?root . + FILTER(contains(str(?metadatafile), "ro-crate-metadata.json")) + FILTER(?this != ?root) + } + """ + ] ; + + # Decomment for debugging + # sh:property [ + # sh:name "Test Directory" ; + # sh:description """Data Entities representing directories MUST have "Directory" as a value for @type.""" ; + # sh:path rdf:type ; + # sh:hasValue ro-crate:File ; + # sh:severity sh:Violation ; + # ] ; + + # Expand data graph with triples from the file data entity + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:Directory ; + ] ; + + # Expand data graph with triples from the directory data entity + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:DataEntity ; + ] ; + + # Ensure that the directory data entity is a dataset + sh:property [ + sh:name "Directory Data Entity: REQUIRED type" ; + sh:description """Check if the Directory Data Entity has `Dataset` as `@type`.""" ; + sh:path rdf:type ; + sh:hasValue schema_org:Dataset ; + sh:severity sh:Violation ; + ] . + +ro-crate:DataEntityRquiredPropertiesShape a sh:NodeShape ; + sh:name "Data Entity: REQUIRED properties" ; + sh:description """A `DataEntity` MUST be linked, either directly or inderectly, from the Root Data Entity""" ; + sh:targetClass ro-crate:DataEntity ; + sh:property + [ + a sh:PropertyShape ; + sh:path [ sh:inversePath schema_org:hasPart ] ; + sh:node schema_org:Dataset ; + sh:minCount 1 ; + sh:name "Data Entity MUST be directly referenced" ; + sh:description """Check if the Data Entity is linked, either directly of inderectly, to the `Root Data Entity` using the `hasPart` (as defined in `schema.org`) property" """ ; + # sh:message "A Data Entity MUST be directly or indirectly linked to the `Root Data Entity` through the `hasPart` property" ; + ] . + +ro-crate:DirectoryDataEntityRequiredValueRestriction a sh:NodeShape ; + sh:name "Directory Data Entity: REQUIRED value restriction" ; + sh:description """A Directory Data Entity MUST end with `/`""" ; + sh:targetNode ro-crate:Directory ; + sh:property [ + a sh:PropertyShape ; + sh:name "Directory Data Entity: REQUIRED value restriction" ; + sh:description """Check if the Directory Data Entity ends with `/`""" ; + sh:path [ sh:inversePath rdf:type ] ; + sh:message """Every Data Entity Directory URI MUST end with `/`""" ; + sh:pattern "/$" ; + ] . + +ro-crate:GenericDataEntityRequiredProperties a sh:NodeShape ; + sh:name "Generic Data Entity: REQUIRED properties" ; + sh:description """A Data Entity other than a File or a Directory MUST be a `DataEntity`""" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?root schema:hasPart ?this . + ?metadatafile schema:about ?root . + FILTER(contains(str(?metadatafile), "ro-crate-metadata.json")) + FILTER(?this != ?root) + FILTER(?this != ?metadatafile) + FILTER NOT EXISTS { + ?this a schema:MediaObject . + ?this a schema:Dataset . + } + } + """ + ] ; + + # Expand data graph with triples to mark the matching entities as GenericDataEntity instances + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:GenericDataEntity ; + ] ; + + # Expand data graph with triples to mark the matching entities as DataEntity instances + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:DataEntity ; + ] . + + +# Uncomment for debugging +# ro-crate:TestGenericDataEntity a sh:NodeShape ; +# sh:disabled true ; +# sh:targetClass ro-crate:GenericDataEntity ; +# sh:name "Generic Data Entity: test invalid property"; +# sh:description """Check if the GenericDataEntity has the invalidProperty property""" ; +# sh:property [ +# sh:minCount 1 ; +# sh:maxCount 1 ; +# sh:path ro-crate:invalidProperty ; +# sh:severity sh:Violation ; +# sh:message "Testing the generic data entity"; +# sh:datatype xsd:string ; +# sh:message "Testing for the invalidProperty of the generic data entity"; +# ] . + + +# Uncomment for debugging +# ro:testDirectory a sh:NodeShape ; +# sh:name "Definition of Test Directory" ; +# sh:description """A Test Directory is a digital object that is stored in a file format""" ; +# sh:targetClass ro-crate:Directory ; + +# sh:property [ +# sh:name "Test Directory instance" ; +# sh:description """Check if the Directory DataEntity instance has the fake property ro-crate:foo""" ; +# sh:path rdf:type ; +# sh:hasValue ro-crate:foo ; +# sh:severity sh:Violation ; +# ] . diff --git a/rocrate_validator/profiles/ro-crate/must/5_web_data_entity_metadata.ttl b/rocrate_validator/profiles/ro-crate/must/5_web_data_entity_metadata.ttl new file mode 100644 index 00000000..3ed7955e --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/must/5_web_data_entity_metadata.ttl @@ -0,0 +1,50 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix rdf: . +@prefix dct: . +@prefix schema_org: . +@prefix sh: . +@prefix owl: . +@prefix xsd: . +@prefix validator: . + + +ro-crate:WebBasedDataEntity a sh:NodeShape, validator:HiddenShape ; + sh:name "Web-based Data Entity: REQUIRED properties" ; + sh:description """A Web-based Data Entity is a `File` identified by an absolute URL""" ; + + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?this a schema:MediaObject . + FILTER(?this != ro:ro-crate-metadata.json) + FILTER regex(str(?this), "^(https?|ftps?)://", "i") + } + """ + ] ; + + # Expand data graph with triples which identify the web-based data entity + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:WebDataEntity ; + ] . + diff --git a/rocrate_validator/profiles/ro-crate/must/6_contextual_entity.ttl b/rocrate_validator/profiles/ro-crate/must/6_contextual_entity.ttl new file mode 100644 index 00000000..d0cf1dd4 --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/must/6_contextual_entity.ttl @@ -0,0 +1,82 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix xsd: . +@prefix owl: . +@prefix validator: . + + +ro-crate:FindLicenseEntity a sh:NodeShape, validator:HiddenShape ; + sh:name "Identify License Entity" ; + sh:description """Mark a license entity any Data Entity referenced by the `schema:licence` property.""" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?subject schema:license ?this . + } + """ + ] ; + + # Expand data graph with triples from the file data entity + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:ContextualEntity ; + ] . + + +ro-crate:WebSiteRecommendedProperties a sh:NodeShape ; + sh:name "WebSite RECOMMENDED Properties" ; + sh:description """A `WebSite` MUST be identified by a valid IRI and MUST have a `name` property.""" ; + sh:targetClass schema:WebSite ; + sh:property [ + sh:path [sh:inversePath rdf:type] ; + sh:datType sh:IRI ; + sh:name "WebSite: value restriction of its identifier" ; + sh:description "Check if the WebSite has a valid IRI" ; + sh:message "A WebSite MUST have a valid IRI" ; + ] ; + sh:property [ + sh:path schema:name ; + sh:minCount 1 ; + sh:dataType xsd:string ; + sh:name "WebSite: REQUIRED `name` property" ; + sh:description "Check if the WebSite has a `name` property" ; + sh:message "A WebSite MUST have a `name` property" ; + ] . + + +ro-crate:CreativeWorkAuthorDefinition a sh:NodeShape, validator:HiddenShape ; + sh:name "CreativeWork Author Definition" ; + sh:description """Define the `CretiveWorkAuthor` as the `Person` object of the `schema:author` predicate.""" ; + sh:targetObjectsOf schema:author ; + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object ro-crate:CreativeWorkAuthor ; + sh:condition [ + sh:property [ sh:path rdf:type ; sh:hasValue schema:Person ; sh:minCount 1 ] ; + ] ; + ] . + diff --git a/rocrate_validator/profiles/ro-crate/ontology.ttl b/rocrate_validator/profiles/ro-crate/ontology.ttl new file mode 100644 index 00000000..4a4535f2 --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/ontology.ttl @@ -0,0 +1,67 @@ +# Copyright (c) 2024 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: . +# @base <./.> . + + rdf:type owl:Ontology ; + owl:versionIRI . + +# # ################################################################# +# # # Classes +# # ################################################################# + +# Declare the RootDataEntity class +ro-crate:RootDataEntity rdf:type owl:Class ; + rdfs:subClassOf schema:Dataset ; + rdfs:label "RootDataEntity"@en . + +### http://schema.org/CreativeWork +schema:CreativeWork rdf:type owl:Class ; + rdfs:label "CreativeWork"@en . + +### http://schema.org/MediaObject +schema:MediaObject rdf:type owl:Class ; + owl:equivalentClass ro-crate:File ; + rdfs:label "MediaObject"@en . + + +### http://schema.org/SoftwareSourceCode +schema:SoftwareSourceCode rdf:type owl:Class ; + rdfs:subClassOf schema:CreativeWork . + + +### https://bioschemas.org/ComputationalWorkflow +bioschemas:ComputationalWorkflow rdf:type owl:Class . + + +### https://w3id.org/ro/crate/1.1/DataEntity +ro-crate:DataEntity rdf:type owl:Class ; + rdfs:subClassOf schema:CreativeWork ; + rdfs:label "DataEntity"@en . + + +# # ### https://w3id.org/ro/crate/1.1/Directory +ro-crate:Directory rdf:type owl:Class ; + rdfs:subClassOf schema:Dataset ; + rdfs:label "Directory"@en . diff --git a/rocrate_validator/profiles/ro-crate/prefixes.ttl b/rocrate_validator/profiles/ro-crate/prefixes.ttl new file mode 100644 index 00000000..e0329f2f --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/prefixes.ttl @@ -0,0 +1,37 @@ +# Copyright (c) 2024 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 sh: . +@prefix xsd: . +@prefix ro-crate: . + +# Define the prefixes used in the SPARQL queries +ro-crate:sparqlPrefixes + sh:declare [ + sh:prefix "schema" ; + sh:namespace "http://schema.org/"^^xsd:anyURI ; + ] ; + sh:declare [ + sh:prefix "bioschemas" ; + sh:namespace "https://bioschemas.org/"^^xsd:anyURI ; + ] ; + sh:declare [ + sh:prefix "rocrate" ; + sh:namespace "https://w3id.org/ro/crate/1.1/"^^xsd:anyURI ; + ] ; + sh:declare [ + sh:prefix "ro" ; + sh:namespace "./"^^xsd:anyURI ; + ] . diff --git a/rocrate_validator/profiles/ro-crate/profile.ttl b/rocrate_validator/profiles/ro-crate/profile.ttl new file mode 100644 index 00000000..64c34837 --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/profile.ttl @@ -0,0 +1,74 @@ +# Copyright (c) 2024 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 dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + # a Profile; it's identifying URI + a prof:Profile ; + + # common metadata for the Profile + + # the Profile's label + rdfs:label "RO-Crate Metadata Specification 1.1" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """RO-Crate Metadata Specification."""@en ; + + # regular metadata, URI of publisher + dct:publisher ; + + # 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 refered 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 refered 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 ; + ] ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "ro-crate" ; +. diff --git a/rocrate_validator/profiles/ro-crate/should/2_root_data_entity_metadata.ttl b/rocrate_validator/profiles/ro-crate/should/2_root_data_entity_metadata.ttl new file mode 100644 index 00000000..b495a024 --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/should/2_root_data_entity_metadata.ttl @@ -0,0 +1,93 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix validator: . + +ro-crate:RootDataEntityDirectRecommendedProperties a sh:NodeShape ; + sh:name "RO-Crate Root Data Entity RECOMMENDED properties" ; + sh:description """The Root Data Entity SHOULD have + the properties `name`, `description` and `license` defined as described + in the RO-Crate specification """; + sh:targetClass ro-crate:RootDataEntity ; + sh:property [ + a sh:PropertyShape ; + sh:name "Root Data Entity: `name` property" ; + sh:description """Check if the Root Data Entity includes a `name` (as specified by schema.org) + to clearly identify the dataset and distinguish it from other datasets.""" ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path schema_org:name; + sh:message "The Root Data Entity SHOULD have a `name` property (as specified by schema.org)" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Root Data Entity: `description` property" ; + sh:description """Check if the Root Data Entity includes a `description` (as specified by schema.org) + to provide a human-readable description of the dataset.""" ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path schema_org:description; + sh:message "The Root Data Entity SHOULD have a `description` property (as specified by schema.org)" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Root Data Entity: `licence` property" ; + sh:description """Check if the Root Data Entity includes a `license` (as specified by schema.org) + to provide information about the license of the dataset.""" ; + sh:nodeKind sh:BlankNodeOrIRI ; + sh:path schema_org:license; + sh:minCount 1 ; + sh:message """The Root Data Entity SHOULD have a link to a Contextual Entity representing the schema_org:license type""" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Root Data Entity: `datePublished` property" ; + sh:description """Check if the Root Data Entity includes a `datePublished` (as specified by schema.org) + to provide the date when the dataset was published. The datePublished MUST be a valid ISO 8601 date. + It SHOULD be specified to at least the day level, but MAY include a time component.""" ; + sh:minCount 1 ; + sh:nodeKind sh:Literal ; + sh:path schema_org:datePublished ; + sh:pattern "^(\\d{4}-\\d{2}-\\d{2})(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?\\+\\d{2}:\\d{2})?$" ; + sh:message "The Root Data Entity SHOULD have a `datePublished` property (as specified by schema.org) with a valid ISO 8601 date and the precision of at least the day level" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Root Data Entity: `author` property" ; + sh:description """Check if the Root Data Entity includes a `author` property (as specified by schema.org) + to provide information about its author.""" ; + sh:or ( + [ sh:class schema_org:Person ;] + [ sh:class schema_org:Organization ;] + ) ; + sh:path schema_org:author; + sh:minCount 1 ; + sh:message """The Root Data Entity SHOULD have a link to a Contextual Entity representing the `author` of the RO-Crate""" ; + ] ; + sh:property [ + sh:minCount 1 ; + sh:maxCount 1 ; + sh:path schema_org:publisher ; + sh:severity sh:Warning ; + sh:name "Root Data Entity: `publisher` property" ; + sh:description """Check if the Root Data Entity has a `publisher` property of type `Organization`.""" ; + sh:message "The `publisher` property of a `Root Data Entity` SHOULD be an `Organization`"; + sh:nodeKind sh:IRI ; + sh:class schema_org:Organization ; + ] . diff --git a/rocrate_validator/profiles/ro-crate/should/2_root_data_entity_relative_uri.py b/rocrate_validator/profiles/ro-crate/should/2_root_data_entity_relative_uri.py new file mode 100644 index 00000000..1bd5bd54 --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/should/2_root_data_entity_relative_uri.py @@ -0,0 +1,42 @@ +# Copyright (c) 2024 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 rocrate_validator.log as logging +from rocrate_validator.models import ValidationContext +from rocrate_validator.requirements.python import (PyFunctionCheck, check, + requirement) + +# set up logging +logger = logging.getLogger(__name__) + + +@requirement(name="RO-Crate Root Data Entity RECOMMENDED value") +class RootDataEntityRelativeURI(PyFunctionCheck): + """ + The Root Data Entity SHOULD be denoted by the string / + """ + + @check(name="Root Data Entity: RECOMMENDED value") + def check_relative_uris(self, context: ValidationContext) -> bool: + """Check if the Root Data Entity is denoted by the string `./` in the file descriptor JSON-LD""" + try: + if not context.ro_crate.metadata.get_root_data_entity().id == './': + context.result.add_error( + 'Root Data Entity URI is not denoted by the string `./`', self) + return False + return True + except Exception as e: + context.result.add_error( + f'Error checking Root Data Entity URI: {str(e)}', self) + return False diff --git a/rocrate_validator/profiles/ro-crate/should/4_data_entity_metadata.ttl b/rocrate_validator/profiles/ro-crate/should/4_data_entity_metadata.ttl new file mode 100644 index 00000000..762d80ab --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/should/4_data_entity_metadata.ttl @@ -0,0 +1,56 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xsd: . + +ro-crate:FileRecommendedProperties a sh:NodeShape ; + sh:targetClass ro-crate:File ; + sh:name "File Data Entity: RECOMMENDED properties"; + sh:description """A `File` Data Entity SHOULD have detailed descriptions encodings through the `encodingFormat` property""" ; + sh:property [ + sh:minCount 1 ; + sh:maxCount 2 ; + sh:path schema_org:encodingFormat ; + sh:severity sh:Warning ; + sh:name "File Data Entity: RECOMMENDED `encodingFormat` property" ; + sh:description """Check if the File Data Entity has a detailed description of encodings through the `encodingFormat` property. + The `encodingFormat` property SHOULD be a PRONOM identifier (e.g., application/pdf) or, + to add more detail, SHOULD be linked using a `PRONOM` to a `Contextual Entity` of type `WebSite` + (see [Adding detailed descriptions of encodings](https://www.researchobject.org/ro-crate/1.1/data-entities.html#adding-detailed-descriptions-of-encodings)). + """ ; + sh:message "Missing or invalid `encodingFormat` linked to the `File Data Entity`"; + sh:or ( + [ + sh:datatype xsd:string ; + sh:pattern "^(\\w*)\\/(\\w[\\w\\.-]*)(?:\\+(\\w[\\w\\.-]*))?(?:;(\\w+=[^;]+))*$" ; + sh:name "File Data Entity: RECOMMENDED `PRONOM` for the `encodingFormat` property" ; + sh:description """Check if the File Data Entity is linked to its `encodingFormat` through a PRONOM identifier + (e.g., application/pdf, application/text, image/svg+xml, image/svg;q=0.9,/;q=0.8,image/svg+xml;q=0.9,/;q=0.8, application/vnd.uplanet.listcmd-wbxml;charset=utf-8). + """ ; + sh:message "The `encodingFormat` SHOULD be linked using a PRONOM identifier (e.g., application/pdf)."; + ] + [ + sh:nodeKind sh:IRI ; + sh:class schema_org:WebSite ; + sh:name "File Data Entity: RECOMMENDED `Contextual Entity` linked to the `encodingFormat` property"; + sh:description "Check if the File Data Entity `encodingFormat` is linked to a `Contextual Entity of type `WebSite`." ; + sh:message "The `encodingFormat` SHOULD be linked to a `Contextual Entity` of type `Web Site`." ; + ] + ) + ] . diff --git a/rocrate_validator/profiles/ro-crate/should/5_web_data_entity_metadata.py b/rocrate_validator/profiles/ro-crate/should/5_web_data_entity_metadata.py new file mode 100644 index 00000000..e58b5fce --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/should/5_web_data_entity_metadata.py @@ -0,0 +1,73 @@ +# Copyright (c) 2024 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 rocrate_validator.log as logging +from rocrate_validator.models import ValidationContext +from rocrate_validator.requirements.python import (PyFunctionCheck, check, + requirement) + +# set up logging +logger = logging.getLogger(__name__) + + +@requirement(name="Web-based Data Entity: RECOMMENDED resource availability") +class WebDataEntityRecommendedChecker(PyFunctionCheck): + """ + Web-based Data Entity instances SHOULD be available + at the URIs specified in the `@id` property of the Web-based Data Entity. + """ + + @check(name="Web-based Data Entity: resource availability") + def check_availability(self, context: ValidationContext) -> bool: + """ + Check if the Web-based Data Entity is directly downloadable + by a simple retrieval (e.g. HTTP GET) permitting redirection and HTTP/HTTPS URIs + """ + result = True + for entity in context.ro_crate.metadata.get_web_data_entities(): + assert entity.id is not None, "Entity has no @id" + try: + if not entity.is_available(): + context.result.add_error( + f'Web-based Data Entity {entity.id} is not available', self) + result = False + except Exception as e: + context.result.add_error( + f'Web-based Data Entity {entity.id} is not available: {e}', self) + result = False + if not result and context.fail_fast: + return result + return result + + @check(name="Web-based Data Entity: `contentSize` property") + def check_content_size(self, context: ValidationContext) -> bool: + """ + Check if the Web-based Data Entity has a `contentSize` property + and if it is set to actual size of the downloadable content + """ + result = True + for entity in context.ro_crate.metadata.get_web_data_entities(): + assert entity.id is not None, "Entity has no @id" + if entity.is_available(): + content_size = entity.get_property("contentSize") + if content_size and int(content_size) != context.ro_crate.get_external_file_size(entity.id): + context.result.add_check_issue( + f'The property contentSize={content_size} of the Web-based Data Entity ' + f'{entity.id} does not match the actual size of ' + f'the downloadable content, i.e., {entity.content_size} (bytes)', self, + focusNode=entity.id, resultPath='contentSize', value=content_size) + result = False + if not result and context.fail_fast: + return result + return result diff --git a/rocrate_validator/profiles/ro-crate/should/5_web_data_entity_metadata.ttl b/rocrate_validator/profiles/ro-crate/should/5_web_data_entity_metadata.ttl new file mode 100644 index 00000000..759c1a9f --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/should/5_web_data_entity_metadata.ttl @@ -0,0 +1,65 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix rdf: . +@prefix dct: . +@prefix schema_org: . +@prefix sh: . +@prefix owl: . +@prefix xsd: . +@prefix validator: . + + +ro-crate:WebBasedDataEntityRequiredValueRestriction a sh:NodeShape ; + sh:name "Web-based Data Entity: RECOMMENDED properties" ; + sh:description """A Web-based Data Entity MUST be identified by an absolute URL and + SHOULD have a `contentSize` and `sdDatePublished` property""" ; + sh:targetClass ro-crate:WebDataEntity ; + # Check if the Web-based Data Entity has a contentSize property + sh:property [ + a sh:PropertyShape ; + sh:minCount 1 ; + sh:name "Web-based Data Entity: `contentSize` property" ; + sh:description """Check if the Web-based Data Entity has a `contentSize` property""" ; + sh:path schema_org:contentSize ; + sh:datatype xsd:string ; + sh:severity sh:Warning ; + sh:message """Web-based Data Entities SHOULD have a `contentSize` property""" ; + sh:sparql [ + sh:message "If the value is a string it must be a string representing an integer." ; + sh:select """ + SELECT ?this ?value + WHERE { + ?this schema:contentSize ?value . + FILTER NOT EXISTS { + FILTER (xsd:integer(?value) = ?value) + } + } + """ ; + ] ; + ] ; + # Check if the Web-based Data Entity has a sdDatePublished property + sh:property [ + a sh:PropertyShape ; + sh:minCount 1 ; + sh:name "Web-based Data Entity: `sdDatePublished` property" ; + sh:description """Check if the Web-based Data Entity has a `sdDatePublished` property""" ; + sh:path schema_org:sdDatePublished ; + # sh:datatype xsd:dateTime ; + sh:pattern "^(\\d{4}-\\d{2}-\\d{2})(T\\d{2}:\\d{2}:\\d{2}(\\.\\d{3})?\\+\\d{2}:\\d{2})?$" ; + sh:severity sh:Warning ; + sh:message """Web-based Data Entities SHOULD have a `sdDatePublished` property""" ; + ] . diff --git a/rocrate_validator/profiles/ro-crate/should/6_contextual_entity_metadata.ttl b/rocrate_validator/profiles/ro-crate/should/6_contextual_entity_metadata.ttl new file mode 100644 index 00000000..1f9d401b --- /dev/null +++ b/rocrate_validator/profiles/ro-crate/should/6_contextual_entity_metadata.ttl @@ -0,0 +1,75 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix schema: . +@prefix sh: . +@prefix xsd: . + +ro-crate:CreativeWorkAuthorMinimuRecommendedProperties a sh:NodeShape ; + sh:name "CreativeWork Author: minimum RECOMMENDED properties" ; + sh:description """The minimum recommended properties for a `CreativeWork Author` are `name` and `affiliation`.""" ; + sh:targetClass ro-crate:CreativeWorkAuthor ; + sh:property [ + sh:path schema:name ; + sh:minCount 1 ; + sh:dataType xsd:string ; + sh:name "CreativeWork Author: RECOMMENDED name property" ; + sh:description "Check if the author has a name." ; + sh:message "The author SHOULD have a name." ; + ] ; + sh:property [ + sh:path schema:affiliation ; + sh:minCount 1 ; + sh:or ( + [ sh:dataType xsd:string ; ] + [ sh:class schema:Organization ;] + ) ; + sh:severity sh:Warning ; + sh:name "CreativeWork Author: RECOMMENDED affiliation property" ; + sh:description "Check if the author has an organizational affiliation." ; + sh:message "The author SHOULD have an organizational affiliation." ; + ] ; + sh:property [ + sh:path schema:affiliation ; + sh:minCount 1 ; + sh:class schema:Organization ; + sh:severity sh:Warning ; + sh:name "CreativeWork Author: RECOMMENDED Contextual Entity linked for the organizational `affiliation` property" ; + sh:description "Check if the author has a Contextual Entity for the organizational `affiliation` property." ; + sh:message "The author SHOULD have a Contextual Entity which specifies the organizational `affiliation`." ; + ] . + + +ro-crate:OrganizationRecommendedProperties a sh:NodeShape ; + sh:name "Organization: RECOMMENDED properties" ; + sh:description """The recommended properties for an `Organization` are `name` and `url`.""" ; + sh:targetClass schema:Organization ; + sh:property [ + sh:path schema:name ; + sh:minCount 1 ; + sh:dataType xsd:string ; + sh:name "Organization: RECOMMENDED name property" ; + sh:description "Check if the `organization` has a name." ; + sh:message "The organization SHOULD have a name." ; + ] ; + sh:property [ + sh:path schema:url ; + sh:minCount 1 ; + sh:dataType xsd:anyURI ; + sh:name "Organization: RECOMMENDED url property" ; + sh:description "Check if the `organization` has a URL." ; + sh:message "The organization SHOULD have a URL." ; + ] . diff --git a/rocrate_validator/profiles/workflow-ro-crate/may/0_main-workflow.ttl b/rocrate_validator/profiles/workflow-ro-crate/may/0_main-workflow.ttl new file mode 100644 index 00000000..850da254 --- /dev/null +++ b/rocrate_validator/profiles/workflow-ro-crate/may/0_main-workflow.ttl @@ -0,0 +1,70 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix workflow-ro-crate: . +@prefix rdf: . +@prefix schema: . +@prefix bioschemas: . +@prefix sh: . +@prefix wroc: . + +workflow-ro-crate:MainWorkflowOptionalProperties a sh:NodeShape ; + sh:name "Main Workflow optional properties" ; + sh:description """Main Workflow properties defined as MAY"""; + sh:targetClass workflow-ro-crate:MainWorkflow ; + sh:property [ + a sh:PropertyShape ; + sh:name "Main Workflow image" ; + sh:description "The Crate MAY contain a Main Workflow Diagram; if present it MUST be referred to via 'image'" ; + sh:path schema:image ; + sh:class schema:MediaObject, schema:ImageObject ; + sh:minCount 1 ; + sh:message "The Crate MAY contain a Main Workflow Diagram; if present it MUST be referred to via 'image'" ; + # sh:severity sh:Info ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Main Workflow subjectOf" ; + sh:description "The Crate MAY contain a Main Workflow CWL Description; if present it MUST be referred to via 'subjectOf'" ; + sh:path schema:subjectOf ; + sh:node workflow-ro-crate:CWLDescriptionProperties ; + sh:minCount 1 ; + sh:message "The Crate MAY contain a Main Workflow CWL Description; if present it MUST be referred to via 'subjectOf'" ; + # sh:severity sh:Info ; + ] . + +workflow-ro-crate:CWLDescriptionProperties a sh:NodeShape ; + sh:name "CWL Description properties" ; + sh:description "Main Workflow CWL Description properties" ; + sh:property [ + a sh:PropertyShape ; + sh:name "CWL Description type" ; + sh:description "The CWL Description type must be File, SoftwareSourceCode, HowTo" ; + sh:path rdf:type ; + sh:hasValue schema:MediaObject, schema:SoftwareSourceCode, schema:HowTo ; + sh:message "The CWL Description type must be File, SoftwareSourceCode, HowTo" ; + # sh:severity sh:Info ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "CWL Description language" ; + sh:description "The CWL Description SHOULD have a language of https://w3id.org/workflowhub/workflow-ro-crate#cwl" ; + sh:path schema:programmingLanguage ; + sh:hasValue wroc:cwl ; + sh:class schema:ComputerLanguage ; + sh:message "The CWL Description SHOULD have a language of https://w3id.org/workflowhub/workflow-ro-crate#cwl" ; + # sh:severity sh:Info ; + ] . diff --git a/rocrate_validator/profiles/workflow-ro-crate/may/1_main_workflow.py b/rocrate_validator/profiles/workflow-ro-crate/may/1_main_workflow.py new file mode 100644 index 00000000..08a3685e --- /dev/null +++ b/rocrate_validator/profiles/workflow-ro-crate/may/1_main_workflow.py @@ -0,0 +1,65 @@ +# Copyright (c) 2024 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 rocrate_validator.log as logging +from rocrate_validator.models import ValidationContext +from rocrate_validator.requirements.python import (PyFunctionCheck, check, + requirement) + +# set up logging +logger = logging.getLogger(__name__) + + +@requirement(name="Workflow-related files existence") +class WorkflowFilesExistence(PyFunctionCheck): + """Checks for workflow-related crate files existence.""" + + @check(name="Workflow diagram existence") + def check_workflow_diagram(self, context: ValidationContext) -> bool: + """Check if the crate contains the workflow diagram.""" + try: + main_workflow = context.ro_crate.metadata.get_main_workflow() + image = main_workflow.get_property("image") + diagram_relpath = image.id if image else None + if not diagram_relpath: + context.result.add_error("main workflow does not have an 'image' property", self) + return False + if not image.is_available(): + context.result.add_error(f"Workflow diagram '{image.id}' not found in crate", self) + return False + return True + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(f"Unexpected error: {e}") + return False + + @check(name="Workflow description existence") + def check_workflow_description(self, context: ValidationContext) -> bool: + """Check if the crate contains the workflow CWL description.""" + try: + main_workflow = context.ro_crate.metadata.get_main_workflow() + main_workflow_subject = main_workflow.get_property("subjectOf") + description_relpath = main_workflow_subject.id if main_workflow_subject else None + if not description_relpath: + context.result.add_error("main workflow does not have a 'subjectOf' property", self) + return False + if not main_workflow_subject.is_available(): + context.result.add_error( + f"Workflow CWL description {main_workflow_subject.id} not found in crate", self) + return False + return True + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(f"Unexpected error: {e}") + return False diff --git a/rocrate_validator/profiles/workflow-ro-crate/may/2_wrroc_crate.ttl b/rocrate_validator/profiles/workflow-ro-crate/may/2_wrroc_crate.ttl new file mode 100644 index 00000000..9cac260e --- /dev/null +++ b/rocrate_validator/profiles/workflow-ro-crate/may/2_wrroc_crate.ttl @@ -0,0 +1,49 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix workflow-ro-crate: . +@prefix rdf: . +@prefix schema: . +@prefix bioschemas: . +@prefix sh: . + +workflow-ro-crate:FindTestDir a sh:NodeShape ; + sh:name "test directory" ; + sh:description "Dataset data entity to hold tests" ; + sh:targetNode ro:test\/ ; + sh:property [ + a sh:PropertyShape ; + sh:name "test/ dir type" ; + sh:description "The test/ dir should be a Dataset" ; + sh:path rdf:type ; + sh:hasValue schema:Dataset ; + sh:minCount 1 ; + sh:message "The test/ dir should be a Dataset" ; + ] . + +workflow-ro-crate:FindExamplesDir a sh:NodeShape ; + sh:name "examples directory" ; + sh:description "Dataset data entity to hold examples" ; + sh:targetNode ro:examples\/ ; + sh:property [ + a sh:PropertyShape ; + sh:name "examples/ dir type" ; + sh:description "The examples/ dir should be a Dataset" ; + sh:path rdf:type ; + sh:hasValue schema:Dataset ; + sh:minCount 1 ; + sh:message "The examples/ dir should be a Dataset" ; + ] . diff --git a/rocrate_validator/profiles/workflow-ro-crate/must/0_main-workflow.ttl b/rocrate_validator/profiles/workflow-ro-crate/must/0_main-workflow.ttl new file mode 100644 index 00000000..dc8f6fc1 --- /dev/null +++ b/rocrate_validator/profiles/workflow-ro-crate/must/0_main-workflow.ttl @@ -0,0 +1,82 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix workflow-ro-crate: . +@prefix bioschemas: . +@prefix rdf: . +@prefix schema: . +@prefix sh: . +@prefix validator: . + +workflow-ro-crate:MainEntityProperyMustExist a sh:NodeShape ; + sh:name "Main Workflow entity existence" ; + sh:description "The Main Workflow must be specified through a `mainEntity` property in the root data entity" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:property [ + a sh:PropertyShape ; + sh:path schema:mainEntity ; + sh:minCount 1 ; + sh:description "Check if the Main Workflow is specified through a `mainEntity` property in the root data entity" ; + sh:message "The Main Workflow must be specified through a `mainEntity` property in the root data entity" ; + ] . + +workflow-ro-crate:FindMainWorkflow a sh:NodeShape, validator:HiddenShape ; + sh:name "Identify Main Workflow" ; + sh:description "Identify the Main Workflow" ; + sh:target [ + a sh:SPARQLTarget ; + sh:prefixes ro-crate:sparqlPrefixes ; + sh:select """ + SELECT ?this + WHERE { + ?root schema:mainEntity ?this . + ?root a schema:Dataset . + ?metadatafile schema:about ?root . + FILTER(contains(str(?metadatafile), "ro-crate-metadata.json")) + } + """ + ] ; + sh:rule [ + a sh:TripleRule ; + sh:subject sh:this ; + sh:predicate rdf:type ; + sh:object workflow-ro-crate:MainWorkflow ; + ] . + +workflow-ro-crate:MainWorkflowRequiredProperties a sh:NodeShape ; + sh:name "Main Workflow definition" ; + sh:description """Main Workflow properties defined as MUST"""; + sh:targetClass workflow-ro-crate:MainWorkflow ; + sh:property [ + a sh:PropertyShape ; + sh:name "Main Workflow type" ; + sh:description "The Main Workflow must have types File, SoftwareSourceCode, ComputationalWorfklow" ; + sh:path rdf:type ; + sh:hasValue schema:MediaObject , + schema:SoftwareSourceCode , + bioschemas:ComputationalWorkflow ; + sh:minCount 1 ; + sh:message "The Main Workflow must have types File, SoftwareSourceCode, ComputationalWorfklow" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "Main Workflow language" ; + sh:description "The Main Workflow must refer to its language via programmingLanguage" ; + sh:path schema:programmingLanguage ; + sh:class schema:ComputerLanguage ; + sh:minCount 1 ; + sh:message "The Main Workflow must refer to its language via programmingLanguage" ; + ] . diff --git a/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py b/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py new file mode 100644 index 00000000..18fbc0d6 --- /dev/null +++ b/rocrate_validator/profiles/workflow-ro-crate/must/0_main_workflow.py @@ -0,0 +1,45 @@ +# Copyright (c) 2024 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 rocrate_validator.log as logging +from rocrate_validator.models import ValidationContext +from rocrate_validator.requirements.python import (PyFunctionCheck, check, + requirement) + +# set up logging +logger = logging.getLogger(__name__) + + +@requirement(name="Main Workflow file existence") +class MainWorkflowFileExistence(PyFunctionCheck): + """Checks for main workflow file existence.""" + + @check(name="Main Workflow file must exist") + def check_workflow(self, context: ValidationContext) -> bool: + """Check if the crate contains the main workflow file.""" + try: + main_workflow = context.ro_crate.metadata.get_main_workflow() + if not main_workflow: + context.result.add_check_issue("main workflow does not exist in metadata file", self) + return False + if not main_workflow.is_available(): + context.result.add_check_issue("Main Workflow {main_workflow.id} not found in crate", self) + return False + return True + except ValueError as e: + context.result.add_check_issue("Unable to check the existence of the main workflow file " + "because the metadata file descriptor doesn't contain a `mainEntity`", self) + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return False diff --git a/rocrate_validator/profiles/workflow-ro-crate/must/1_wroc_root_data_entity.ttl b/rocrate_validator/profiles/workflow-ro-crate/must/1_wroc_root_data_entity.ttl new file mode 100644 index 00000000..0f6a913a --- /dev/null +++ b/rocrate_validator/profiles/workflow-ro-crate/must/1_wroc_root_data_entity.ttl @@ -0,0 +1,39 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix workflow-ro-crate: . +@prefix schema: . +@prefix bioschemas: . +@prefix sh: . +@prefix xsd: . +@prefix validator: . + +workflow-ro-crate:WROCRootDataEntityRequiredProperties a sh:NodeShape ; + sh:name "WROC Root Data Entity Required Properties" ; + sh:description """Root Data Entity properties defined as MUST""" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:property [ + a sh:PropertyShape ; + sh:name "Crate license" ; + sh:description "The Crate must specify a license" ; + sh:path schema:license ; + sh:or ( + [ sh:nodeKind sh:Literal; sh:datatype xsd:string ; ] + [ sh:nodeKind sh:IRI ; ] + ) ; + sh:minCount 1 ; + sh:message "The Crate (Root Data Entity) must specify a license, which should be a URL but can also be a string" ; + ] . diff --git a/rocrate_validator/profiles/workflow-ro-crate/profile.ttl b/rocrate_validator/profiles/workflow-ro-crate/profile.ttl new file mode 100644 index 00000000..30d8b32f --- /dev/null +++ b/rocrate_validator/profiles/workflow-ro-crate/profile.ttl @@ -0,0 +1,81 @@ +# Copyright (c) 2024 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 dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Workflow RO-Crate Metadata Specification 1.0" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Workflow RO-Crate Metadata Specification."""@en ; + + # URI of the publisher of the Workflow RO-Crate Metadata Specification + dct:publisher ; + + # This profile is an extension of the RO-Crate Metadata Specification 1.1 profile + prof:isProfileOf ; + + # Explicitly state that this profile is a transitive profile of the RO-Crate Metadata Specification 1.1 profile + prof:isTransitiveProfileOf ; + + # 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 refered 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 refered 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 the RO-Crate Metadata Specification 1.1 + prof:isInheritedFrom ; + ] ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "workflow-ro-crate" ; +. diff --git a/rocrate_validator/profiles/workflow-ro-crate/should/1_wroc_crate.ttl b/rocrate_validator/profiles/workflow-ro-crate/should/1_wroc_crate.ttl new file mode 100644 index 00000000..5c1daf32 --- /dev/null +++ b/rocrate_validator/profiles/workflow-ro-crate/should/1_wroc_crate.ttl @@ -0,0 +1,59 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix workflow-ro-crate: . +@prefix dct: . +@prefix bioschemas: . +@prefix schema: . +@prefix sh: . + +workflow-ro-crate:DescriptorProperties a sh:NodeShape ; + sh:name "WROC Metadata File Descriptor properties" ; + sh:description "Properties of the WROC Metadata File Descriptor" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:propertyShape ; + sh:name "Metadata File Descriptor conformsTo" ; + sh:description "The Metadata File Descriptor conformsTo SHOULD contain https://w3id.org/ro/crate/1.1 and https://w3id.org/workflowhub/workflow-ro-crate/1.0" ; + sh:path dct:conformsTo ; + sh:hasValue , + ; + sh:minCount 1 ; + sh:message "The Metadata File Descriptor conformsTo SHOULD contain https://w3id.org/ro/crate/1.1 and https://w3id.org/workflowhub/workflow-ro-crate/1.0" ; + ] . + +workflow-ro-crate:FindReadme a sh:NodeShape ; + sh:name "README.md properties" ; + sh:description "README file for the Workflow RO-Crate" ; + sh:targetNode ro:README.md ; + sh:property [ + a sh:PropertyShape ; + sh:name "README.md about" ; + sh:description "The README.md SHOULD be about the crate" ; + sh:path schema:about ; + sh:class ro-crate:RootDataEntity ; + sh:minCount 1 ; + sh:message "The README.md SHOULD be about the crate" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "README.md encodingFormat" ; + sh:description "The README.md SHOULD have text/markdown as its encodingFormat" ; + sh:path schema:encodingFormat ; + sh:hasValue "text/markdown" ; + sh:minCount 1 ; + sh:message "The README.md SHOULD have text/markdown as its encodingFormat" ; + ] . diff --git a/rocrate_validator/profiles/workflow-ro-crate/should/2_main-workflow.ttl b/rocrate_validator/profiles/workflow-ro-crate/should/2_main-workflow.ttl new file mode 100644 index 00000000..834e8473 --- /dev/null +++ b/rocrate_validator/profiles/workflow-ro-crate/should/2_main-workflow.ttl @@ -0,0 +1,35 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix workflow-ro-crate: . +@prefix dct: . +@prefix sh: . +@prefix bioschemas: . + +workflow-ro-crate:MainWorkflowRecommendedProperties a sh:NodeShape ; + sh:name "Main Workflow recommended properties" ; + sh:description """Main Workflow properties defined as SHOULD"""; + sh:targetClass workflow-ro-crate:MainWorkflow ; + sh:property [ + a sh:PropertyShape ; + sh:name "Main Workflow Bioschemas compliance" ; + sh:description "The Main Workflow SHOULD comply with Bioschemas ComputationalWorkflow profile version 1.0 or later" ; + sh:path dct:conformsTo ; + sh:pattern "https://bioschemas.org/profiles/ComputationalWorkflow/[1-9].*" ; + sh:minCount 1 ; + sh:message "The Main Workflow SHOULD comply with Bioschemas ComputationalWorkflow profile version 1.0 or later" ; + sh:severity sh:Warning ; + ] . diff --git a/rocrate_validator/profiles/workflow-testing-ro-crate/must/1_test_suite.ttl b/rocrate_validator/profiles/workflow-testing-ro-crate/must/1_test_suite.ttl new file mode 100644 index 00000000..d431ebec --- /dev/null +++ b/rocrate_validator/profiles/workflow-testing-ro-crate/must/1_test_suite.ttl @@ -0,0 +1,56 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix workflow-testing-ro-crate: . +@prefix schema: . +@prefix bioschemas: . +@prefix sh: . +@prefix wftest: . + +workflow-testing-ro-crate:WTROCTestSuiteRequired a sh:NodeShape ; + sh:name "Workflow Testing RO-Crate TestSuite MUST" ; + sh:description "Required properties of the Workflow Testing RO-Crate TestSuite" ; + sh:targetClass wftest:TestSuite ; + sh:property [ + a sh:PropertyShape ; + sh:name "TestSuite MUST be referenced via mentions from root" ; + sh:description "The TestSuite MUST be referenced from the Root Data Entity via mentions" ; + sh:path [ sh:inversePath schema:mentions ] ; + sh:node ro-crate:RootDataEntity ; + sh:minCount 1 ; + sh:message "The TestSuite MUST be referenced from the Root Data Entity via mentions" ; + ] . + + +workflow-testing-ro-crate:WTROCTestSuiteInstanceOrDefinition a sh:NodeShape ; + sh:name "TestSuite instance or definition" ; + sh:description "The TestSuite MUST refer to a TestInstance or TestDefinition" ; + sh:message "The TestSuite MUST refer to a TestInstance or TestDefinition" ; + sh:targetClass wftest:TestSuite ; + sh:or ( + [ sh:property [ + a sh:PropertyShape ; + sh:path wftest:instance ; + sh:class wftest:TestInstance ; + sh:minCount 1 ; + ]] + [ sh:property [ + a sh:PropertyShape ; + sh:path wftest:definition ; + sh:class wftest:TestDefinition ; + sh:minCount 1 ; + ]] + ) . diff --git a/rocrate_validator/profiles/workflow-testing-ro-crate/must/2_root_data_entity_metadata.ttl b/rocrate_validator/profiles/workflow-testing-ro-crate/must/2_root_data_entity_metadata.ttl new file mode 100644 index 00000000..40d91845 --- /dev/null +++ b/rocrate_validator/profiles/workflow-testing-ro-crate/must/2_root_data_entity_metadata.ttl @@ -0,0 +1,34 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix schema: . +@prefix sh: . +@prefix wftest: . +@prefix workflow-testing-ro-crate: . + +workflow-testing-ro-crate:WTROCRootDataEntityMetadata a sh:NodeShape ; + sh:name "Root Data Entity Metadata" ; + sh:description "Properties of the Root Data Entity" ; + sh:targetClass ro-crate:RootDataEntity ; + sh:property [ + a sh:PropertyShape ; + sh:name "Root Data Entity mentions" ; + sh:description "The Root Data Entity MUST refer to one or more test suites via mentions" ; + sh:path schema:mentions ; + sh:class wftest:TestSuite; + sh:minCount 1 ; + sh:message "The Root Data Entity MUST refer to one or more test suites via mentions" ; + ] . diff --git a/rocrate_validator/profiles/workflow-testing-ro-crate/must/2_test_instance.ttl b/rocrate_validator/profiles/workflow-testing-ro-crate/must/2_test_instance.ttl new file mode 100644 index 00000000..6c31c7cf --- /dev/null +++ b/rocrate_validator/profiles/workflow-testing-ro-crate/must/2_test_instance.ttl @@ -0,0 +1,59 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix xsd: . +@prefix workflow-testing-ro-crate: . +@prefix schema: . +@prefix sh: . +@prefix wroc: . +@prefix wftest: . + +workflow-testing-ro-crate:WTROCTestInstanceRequired a sh:NodeShape ; + sh:name "Workflow Testing RO-Crate TestInstance MUST" ; + sh:description "Required properties of the Workflow Testing RO-Crate TestInstance" ; + sh:targetClass wftest:TestInstance ; + sh:property [ + a sh:PropertyShape ; + sh:name "TestInstance runsOn" ; + sh:description "The TestInstance MUST refer to a TestService via runsOn" ; + sh:path wftest:runsOn ; + sh:class wftest:TestService ; + sh:or ( + [ sh:hasValue wftest:GithubService ; ] + [ sh:hasValue wftest:TravisService ; ] + [ sh:hasValue wftest:JenkinsService ; ] + ) ; + sh:minCount 1 ; + sh:message "The TestInstance MUST refer to a TestService via runsOn" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "TestInstance url" ; + sh:description "The TestInstance MUST refer to the test service base URL via url" ; + sh:path schema:url ; + sh:datatype xsd:string ; + sh:pattern "^http.*" ; + sh:minCount 1 ; + sh:message "The TestInstance MUST refer to the test service base URL via url" ; + ] ; sh:property [ + a sh:PropertyShape ; + sh:name "TestInstance resource" ; + sh:description "The TestInstance MUST refer to the relative URL of the test project via resource" ; + sh:path wftest:resource ; + sh:datatype xsd:string ; + sh:minCount 1 ; + sh:message "The TestInstance MUST refer to the relative URL of the test project via resource" ; + ] . diff --git a/rocrate_validator/profiles/workflow-testing-ro-crate/must/3_test_definition.ttl b/rocrate_validator/profiles/workflow-testing-ro-crate/must/3_test_definition.ttl new file mode 100644 index 00000000..1b9f4b8c --- /dev/null +++ b/rocrate_validator/profiles/workflow-testing-ro-crate/must/3_test_definition.ttl @@ -0,0 +1,56 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix rdf: . +@prefix xsd: . +@prefix dct: . +@prefix workflow-testing-ro-crate: . +@prefix schema: . +@prefix sh: . +@prefix wroc: . +@prefix wftest: . + +workflow-testing-ro-crate:WTROCTestDefinitionRequired a sh:NodeShape ; + sh:name "Workflow Testing RO-Crate TestDefinition MUST" ; + sh:description "Required properties of the Workflow Testing RO-Crate TestDefinition" ; + sh:targetClass wftest:TestDefinition ; + sh:property [ + a sh:PropertyShape ; + sh:name "TestDefinition type" ; + sh:description "The TestDefinition MUST have types TestDefinition and File" ; + sh:path rdf:type ; + sh:hasValue schema:MediaObject, wftest:TestDefinition ; + sh:minCount 1 ; + sh:message "The TestDefinition MUST have types TestDefinition and File" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "TestDefinition conformsTo" ; + sh:description "The TestDefinition MUST refer to the test engine it is written for via conformsTo" ; + sh:path dct:conformsTo ; + sh:class schema:SoftwareApplication ; + sh:minCount 1 ; + sh:message "The TestDefinition MUST refer to the test engine it is written for via conformsTo" ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "TestDefinition engineVersion" ; + sh:description "The TestDefinition MUST refer to the test engine version via engineVersion" ; + sh:path wftest:engineVersion ; + sh:datatype xsd:string ; + sh:minCount 1 ; + sh:message "The TestDefinition MUST refer to the test engine version via engineVersion" ; + ] . diff --git a/rocrate_validator/profiles/workflow-testing-ro-crate/profile.ttl b/rocrate_validator/profiles/workflow-testing-ro-crate/profile.ttl new file mode 100644 index 00000000..7e0c5160 --- /dev/null +++ b/rocrate_validator/profiles/workflow-testing-ro-crate/profile.ttl @@ -0,0 +1,80 @@ +# Copyright (c) 2024 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 dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Workflow Testing RO-Crate 0.1" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Workflow Testing RO-Crate Metadata Specification 0.1"""@en ; + + # URI of the publisher of the Specification + dct:publisher ; + + # This profile is an extension of Workflow RO-Crate + prof:isProfileOf ; + + # This profile is a transitive profile of Workflow RO-Crate and the RO-Crate Metadata Specification + prof:isTransitiveProfileOf , + ; + + # 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 refered 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 refered 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 RO-Crate + prof:isInheritedFrom ; + ] ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "workflow-testing-ro-crate" ; +. diff --git a/rocrate_validator/profiles/workflow-testing-ro-crate/should/1_test_suite.ttl b/rocrate_validator/profiles/workflow-testing-ro-crate/should/1_test_suite.ttl new file mode 100644 index 00000000..f1af48c6 --- /dev/null +++ b/rocrate_validator/profiles/workflow-testing-ro-crate/should/1_test_suite.ttl @@ -0,0 +1,38 @@ +# Copyright (c) 2024 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 ro-crate: . +@prefix workflow-testing-ro-crate: . +@prefix schema: . +@prefix bioschemas: . +@prefix sh: . +@prefix wroc: . +@prefix wftest: . + +workflow-testing-ro-crate:WTROCTestSuiteRecommended a sh:NodeShape ; + sh:name "Workflow Testing RO-Crate TestSuite SHOULD" ; + sh:description "Recommended properties of the Workflow Testing RO-Crate TestSuite" ; + sh:targetClass wftest:TestSuite ; + sh:property [ + a sh:PropertyShape ; + sh:name "TestSuite mainEntity" ; + sh:description "The TestSuite SHOULD refer to the tested workflow via mainEntity" ; + sh:path schema:mainEntity ; + sh:class schema:MediaObject , + schema:SoftwareSourceCode , + bioschemas:ComputationalWorkflow ; + sh:minCount 1 ; + sh:message "The TestSuite SHOULD refer to the tested workflow via mainEntity" ; + ] . diff --git a/rocrate_validator/requirements/python/__init__.py b/rocrate_validator/requirements/python/__init__.py new file mode 100644 index 00000000..fa927524 --- /dev/null +++ b/rocrate_validator/requirements/python/__init__.py @@ -0,0 +1,178 @@ +# Copyright (c) 2024 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 inspect +import re +from pathlib import Path +from typing import Callable, Optional, Type + +import rocrate_validator.log as logging + +from ...models import (LevelCollection, Profile, Requirement, RequirementCheck, + RequirementLevel, RequirementLoader, Severity, ValidationContext) +from ...utils import get_classes_from_file + +# set up logging +logger = logging.getLogger(__name__) + + +class PyFunctionCheck(RequirementCheck): + """ + Concrete class that implements a check that calls a function. + """ + + def __init__(self, + requirement: Requirement, + name: str, + check_function: Callable[[RequirementCheck, ValidationContext], bool], + description: Optional[str] = None, + level: Optional[LevelCollection] = LevelCollection.REQUIRED): + """ + check_function: a function that accepts an instance of PyFunctionCheck and a ValidationContext. + """ + super().__init__(requirement, name, description=description, level=level) + + sig = inspect.signature(check_function) + if len(sig.parameters) != 2: + raise RuntimeError("Invalid PyFunctionCheck function. Checks are expected to accept " + f"arguments [RequirementCheck, ValidationContext] but this has signature {sig}") + if sig.return_annotation not in (bool, inspect.Signature.empty): + raise RuntimeError("Invalid PyFunctionCheck function. Checks are expected to " + f"return bool but this only returns {sig.return_annotation}") + + self._check_function = check_function + + def execute_check(self, context: ValidationContext) -> bool: + return self._check_function(self, context) + + +class PyRequirement(Requirement): + + def __init__(self, + profile: Profile, + requirement_check_class: Type[PyFunctionCheck], + name: str = "", + description: Optional[str] = None, + path: Optional[Path] = None): + self.requirement_check_class = requirement_check_class + super().__init__(profile, name, description, path, initialize_checks=True) + + def __init_checks__(self): + # initialize the list of checks + checks = [] + for name, member in inspect.getmembers(self.requirement_check_class, inspect.isfunction): + # verify that the attribute set by the check decorator is present + if hasattr(member, "check") and member.check is True: + check_name = None + try: + check_name = member.name.strip() + except Exception: + check_name = name.strip() + check_description = member.__doc__.strip() if member.__doc__ else "" + # init the check with the requirement level + severity = None + try: + severity = member.severity + except Exception: + severity = self.severity_from_path or Severity.REQUIRED + check = self.requirement_check_class(self, + check_name, + member, + description=check_description, + level=LevelCollection.get(severity.name) if severity else None) + self._checks.append(check) + logger.debug("Added check: %s %r", check_name, check) + + return checks + + @property + def hidden(self) -> bool: + return getattr(self.requirement_check_class, "hidden", False) + + +def requirement(name: str, description: Optional[str] = None): + """ + A decorator to mark functions as "requirements" (by setting an attribute + `requirement=True`) and annotating them with a human-legible name. + """ + def decorator(cls): + if name: + cls.__rq_name__ = name + if description: + cls.__rq_description__ = description + return cls + + return decorator + + +def check(name: Optional[str] = None, severity: Optional[Severity] = None): + """ + A decorator to mark functions as "checks" (by setting an attribute + `check=True`) and optionally annotating them with a human-legible name. + """ + def decorator(func): + check_name = name if name else func.__name__ + sig = inspect.signature(func) + if len(sig.parameters) != 2: + raise RuntimeError(f"Invalid check {check_name}. Checks are expected to " + f"accept two arguments but this only takes {len(sig.parameters)}") + if sig.return_annotation not in (bool, inspect.Signature.empty): + raise RuntimeError(f"Invalid check {check_name}. Checks are expected to " + f"return bool but this only returns {sig.return_annotation}") + func.check = True + func.name = check_name + func.severity = severity + return func + return decorator + + +class PyRequirementLoader(RequirementLoader): + + def load(self, profile: Profile, + requirement_level: RequirementLevel, + file_path: Path, + publicID: Optional[str] = None) -> list[Requirement]: + # instantiate a list to store the requirements + requirements: list[Requirement] = [] + + # Get the classes in the file that are subclasses of RequirementCheck + classes = get_classes_from_file(file_path, filter_class=PyFunctionCheck) + logger.debug("Classes: %r", classes) + + # instantiate a requirement for each class + for requirement_name, check_class in classes.items(): + # set default requirement name and description + rq = {} + rq["name"] = " ".join(re.findall(r'[A-Z](?:[a-z]+|[A-Z]*(?=[A-Z]|$))', + requirement_name.strip())) if requirement_name else "" + rq["description"] = check_class.__doc__.strip() if check_class.__doc__ else "" + # handle default overrides via decorators + for pn in ("name", "description"): + try: + pv = getattr(check_class, f"__rq_{pn}__", None) + if pv and isinstance(pv, str): + rq[pn] = pv + except AttributeError: + pass + logger.debug("Processing requirement: %r", requirement_name) + r = PyRequirement( + profile, + requirement_check_class=check_class, + name=rq["name"], + description=rq["description"], + path=file_path) + logger.debug("Created requirement: %r", r) + requirements.append(r) + + return requirements diff --git a/rocrate_validator/requirements/shacl/__init__.py b/rocrate_validator/requirements/shacl/__init__.py new file mode 100644 index 00000000..68cca36f --- /dev/null +++ b/rocrate_validator/requirements/shacl/__init__.py @@ -0,0 +1,21 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .checks import SHACLCheck +from .errors import SHACLValidationError +from .requirements import SHACLRequirement, SHACLRequirementLoader +from .validator import SHACLValidationResult, SHACLValidator + +__all__ = ["SHACLCheck", "SHACLValidator", "SHACLValidationResult", + "SHACLValidationError", "SHACLRequirement", "SHACLRequirementLoader"] diff --git a/rocrate_validator/requirements/shacl/checks.py b/rocrate_validator/requirements/shacl/checks.py new file mode 100644 index 00000000..409946ce --- /dev/null +++ b/rocrate_validator/requirements/shacl/checks.py @@ -0,0 +1,281 @@ +# Copyright (c) 2024 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 json +from timeit import default_timer as timer +from typing import Optional + +import rocrate_validator.log as logging +from rocrate_validator.errors import ROCrateMetadataNotFoundError +from rocrate_validator.events import EventType +from rocrate_validator.models import (LevelCollection, Requirement, + RequirementCheck, + RequirementCheckValidationEvent, + SkipRequirementCheck, ValidationContext) +from rocrate_validator.requirements.shacl.models import Shape +from rocrate_validator.requirements.shacl.utils import make_uris_relative + +from .validator import (SHACLValidationAlreadyProcessed, + SHACLValidationContext, SHACLValidationContextManager, + SHACLValidationSkip, SHACLValidator, SHACLViolation) + +logger = logging.getLogger(__name__) + + +class SHACLCheck(RequirementCheck): + """ + A SHACL check for a specific shape property + """ + + # Map shape to requirement check instances + __instances__ = {} + + def __init__(self, + requirement: Requirement, + shape: Shape) -> None: + self._shape = shape + # init the check + super().__init__(requirement, + shape.name if shape and shape.name + else shape.parent.name if shape.parent + else None, + shape.description if shape and shape.description + else shape.parent.description if shape.parent + else None) + # store the instance + SHACLCheck.__add_instance__(shape, self) + + # set the check level + requirement_level_from_path = self.requirement.requirement_level_from_path + if requirement_level_from_path: + declared_level = shape.get_declared_level() + if declared_level: + if shape.level.severity != requirement_level_from_path.severity: + logger.warning("Mismatch in requirement level for check \"%s\": " + "shape level %s does not match the level from the containing folder %s. " + "Consider moving the shape property or removing the severity property.", + self.name, shape.level, requirement_level_from_path) + self._level = None + + @property + def shape(self) -> Shape: + return self._shape + + @property + def description(self) -> str: + return self._shape.description + + def __compute_requirement_level__(self) -> LevelCollection: + if self._shape and self._shape.get_declared_level(): + return self._shape.get_declared_level() + if self.requirement and self.requirement.requirement_level_from_path: + return self.requirement.requirement_level_from_path + return LevelCollection.REQUIRED + + @property + def level(self) -> str: + if not self._level: + self._level = self.__compute_requirement_level__() + return self._level + + @property + def severity(self) -> str: + return self.level.severity + + def execute_check(self, context: ValidationContext): + logger.debug("Starting check %s", self) + try: + logger.debug("SHACL Validation of profile %s requirement %s started", + self.requirement.profile.identifier, self.identifier) + with SHACLValidationContextManager(self, context) as ctx: + # The check is executed only if the profile is the most specific one + logger.debug("SHACL Validation of profile %s requirement %s started", + self.requirement.profile.identifier, self.identifier) + result = self.__do_execute_check__(ctx) + ctx.current_validation_result = self not in result + return ctx.current_validation_result + except SHACLValidationAlreadyProcessed: + logger.debug("SHACL Validation of profile %s already processed", self.requirement.profile.identifier) + # The check belongs to a profile which has already been processed + # so we can skip the validation and return the specific result for the check + return self not in [i.check for i in context.result.get_issues()] + except SHACLValidationSkip as e: + logger.debug("SHACL Validation of profile %s requirement %s skipped", + self.requirement.profile.identifier, self.identifier) + # The validation is postponed to the more specific profiles + # so the check is not considered as failed. + raise SkipRequirementCheck(self, str(e)) + except ROCrateMetadataNotFoundError as e: + logger.debug("Unable to perform metadata validation due to missing metadata file: %s", e) + return False + + def __do_execute_check__(self, shacl_context: SHACLValidationContext): + # get the shapes registry + shapes_registry = shacl_context.shapes_registry + + # set up the input data for the validator + start_time = timer() + ontology_graph = shacl_context.ontology_graph + end_time = timer() + logger.debug(f"Execution time for getting ontology graph: {end_time - start_time} seconds") + + data_graph = None + try: + start_time = timer() + data_graph = shacl_context.data_graph + end_time = timer() + logger.debug(f"Execution time for getting data graph: {end_time - start_time} seconds") + except json.decoder.JSONDecodeError as e: + logger.debug("Unable to perform metadata validation " + "due to one or more errors in the JSON-LD data file: %s", e) + shacl_context.result.add_error( + "Unable to perform metadata validation due to one or more errors in the JSON-LD data file", self) + raise ROCrateMetadataNotFoundError( + "Unable to perform metadata validation due to one or more errors in the JSON-LD data file") + + # Begin the timer + start_time = timer() + shapes_graph = shapes_registry.shapes_graph + end_time = timer() + logger.debug(f"Execution time for getting shapes: {end_time - start_time} seconds") + + # # uncomment to save the graphs to the logs folder (for debugging purposes) + # start_time = timer() + # data_graph.serialize("logs/data_graph.ttl", format="turtle") + # shapes_graph.serialize("logs/shapes_graph.ttl", format="turtle") + # if ontology_graph: + # ontology_graph.serialize("logs/ontology_graph.ttl", format="turtle") + # end_time = timer() + # logger.debug(f"Execution time for saving graphs: {end_time - start_time} seconds") + + # validate the data graph + start_time = timer() + shacl_validator = SHACLValidator(shapes_graph=shapes_graph, ont_graph=ontology_graph) + shacl_result = shacl_validator.validate( + data_graph=data_graph, ontology_graph=ontology_graph, **shacl_context.settings) + # parse the validation result + end_time = timer() + logger.debug("Validation '%s' conforms: %s", self.name, shacl_result.conforms) + logger.debug("Number of violations: %s", len(shacl_result.violations)) + logger.debug(f"Execution time for validating the data graph: {end_time - start_time} seconds") + + # store the validation result in the context + start_time = timer() + # if the validation fails, process the failed checks + failed_requirements_checks = set() + failed_requirements_checks_violations: dict[str, SHACLViolation] = {} + failed_requirement_checks_notified = [] + logger.debug("Parsing Validation with result: %s", shacl_result) + # process the failed checks to extract the requirement checks involved + for violation in shacl_result.violations: + shape = shapes_registry.get_shape(Shape.compute_key(shapes_graph, violation.sourceShape)) + assert shape is not None, "Unable to map the violation to a shape" + requirementCheck = SHACLCheck.get_instance(shape) + assert requirementCheck is not None, "The requirement check cannot be None" + failed_requirements_checks.add(requirementCheck) + violations = failed_requirements_checks_violations.get(requirementCheck.identifier, None) + if violations is None: + failed_requirements_checks_violations[requirementCheck.identifier] = violations = [] + violations.append(violation) + # sort the failed checks by identifier and severity + # to ensure a consistent order of the issues + # and to make the fail fast mode deterministic + for requirementCheck in sorted(failed_requirements_checks, key=lambda x: (x.identifier, x.severity)): + # add only the issues for the current profile when the `target_profile_only` mode is disabled + # (issues related to other profiles will be added by the corresponding profile validation) + if requirementCheck.requirement.profile == shacl_context.current_validation_profile or \ + shacl_context.settings.get("target_only_validation", False): + for violation in failed_requirements_checks_violations[requirementCheck.identifier]: + c = shacl_context.result.add_check_issue( + message=violation.get_result_message(shacl_context.rocrate_path), + check=requirementCheck, + severity=violation.get_result_severity(), + resultPath=violation.resultPath.toPython() if violation.resultPath else None, + focusNode=make_uris_relative( + violation.focusNode.toPython(), shacl_context.publicID), + value=violation.value) + # if the fail fast mode is enabled, stop the validation after the first issue + if shacl_context.fail_fast: + break + + # If the fail fast mode is disabled, notify all the validation issues + # related to profiles other than the current one. + # They are issues which have not been notified yet because skipped during + # the validation of their corresponding profile because SHACL checks are executed + # all together and not profile by profile + if requirementCheck.identifier not in failed_requirement_checks_notified: + # + if requirementCheck.requirement.profile != shacl_context.current_validation_profile: + failed_requirement_checks_notified.append(requirementCheck.identifier) + shacl_context.result.add_executed_check(requirementCheck, False) + shacl_context.validator.notify(RequirementCheckValidationEvent( + EventType.REQUIREMENT_CHECK_VALIDATION_END, requirementCheck, validation_result=False)) + logger.debug("Added validation issue to the context: %s", c) + + # if the fail fast mode is enabled, stop the validation after the first failed check + if shacl_context.fail_fast: + break + + # As above, but for skipped checks which are not failed + logger.debug("Skipped checks: %s", len(shacl_context.result.skipped_checks)) + for requirementCheck in list(shacl_context.result.skipped_checks): + logger.debug("Processing skipped check: %s", requirementCheck.identifier) + if not isinstance(requirementCheck, SHACLCheck): + logger.debug("Skipped check is not a SHACLCheck: %s", requirementCheck.identifier) + continue + if requirementCheck.requirement.profile != shacl_context.current_validation_profile and \ + requirementCheck not in failed_requirements_checks and \ + requirementCheck.identifier not in failed_requirement_checks_notified: + failed_requirement_checks_notified.append(requirementCheck.identifier) + shacl_context.result.add_executed_check(requirementCheck, True) + shacl_context.validator.notify(RequirementCheckValidationEvent( + EventType.REQUIREMENT_CHECK_VALIDATION_END, requirementCheck, validation_result=True)) + logger.debug("Added skipped check to the context: %s", requirementCheck.identifier) + + logger.debug("Remaining skipped checks: %r", len(shacl_context.result.skipped_checks)) + for requirementCheck in shacl_context.result.skipped_checks: + logger.debug("Remaining skipped check: %r - %s", requirementCheck.identifier, requirementCheck.name) + end_time = timer() + logger.debug(f"Execution time for parsing the validation result: {end_time - start_time} seconds") + + return failed_requirements_checks + + def __str__(self) -> str: + return super().__str__() + (f" - {self._shape}" if self._shape else "") + + def __repr__(self) -> str: + return super().__repr__() + (f" - {self._shape}" if self._shape else "") + + def __eq__(self, __value: object) -> bool: + if not isinstance(__value, type(self)): + return NotImplemented + return super().__eq__(__value) and self._shape == getattr(__value, '_shape', None) + + def __hash__(self) -> int: + return super().__hash__() + (hash(self._shape) if self._shape else 0) + + @classmethod + def get_instance(cls, shape: Shape) -> Optional["SHACLCheck"]: + return cls.__instances__.get(hash(shape), None) + + @classmethod + def __add_instance__(cls, shape: Shape, check: "SHACLCheck") -> None: + cls.__instances__[hash(shape)] = check + + @classmethod + def clear_instances(cls) -> None: + cls.__instances__.clear() + + +__all__ = ["SHACLCheck"] diff --git a/rocrate_validator/requirements/shacl/errors.py b/rocrate_validator/requirements/shacl/errors.py new file mode 100644 index 00000000..4b70f6c4 --- /dev/null +++ b/rocrate_validator/requirements/shacl/errors.py @@ -0,0 +1,38 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ...errors import ValidationError +from .validator import SHACLValidationResult + + +class SHACLValidationError(ValidationError): + + def __init__( + self, + result: SHACLValidationResult = None, + message: str = "Document does not conform to SHACL shapes.", + path: str = ".", + code: int = 500, + ): + super().__init__(message, path, code) + self._result = result + + @property + def result(self) -> SHACLValidationResult: + return self._result + + def __repr__(self): + return ( + f"SHACLValidationError({self._message!r}, {self._path!r}, {self.result!r})" + ) diff --git a/rocrate_validator/requirements/shacl/models.py b/rocrate_validator/requirements/shacl/models.py new file mode 100644 index 00000000..f0412426 --- /dev/null +++ b/rocrate_validator/requirements/shacl/models.py @@ -0,0 +1,433 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from pathlib import Path +from typing import Optional, Union + +from rdflib import Graph, Namespace, URIRef +from rdflib.term import Node + +from rocrate_validator.constants import SHACL_NS +import rocrate_validator.log as logging +from rocrate_validator.models import LevelCollection, RequirementLevel, Severity +from rocrate_validator.requirements.shacl.utils import (ShapesList, + compute_key, + inject_attributes) + +# set up logging +logger = logging.getLogger(__name__) + + +class SHACLNode: + + # define default values + _name: str = None + _description: str = None + severity: str = None + + def __init__(self, node: Node, graph: Graph, parent: Optional[SHACLNode] = None): + + # store the shape key + self._key = None + # store the shape node + self._node = node + # store the shapes graph + self._graph = graph + # cache the hash + self._hash = None + # store the parent shape + self._parent = parent + + # inject attributes of the shape to the object + inject_attributes(self, graph, node) + + @property + def name(self) -> str: + """Return the name of the shape""" + if not self._name: + self._name = self._node.split("#")[-1] if "#" in self.node else self._node.split("/")[-1] + return self._name or self._node.split("/")[-1] + + @name.setter + def name(self, value: str): + self._name = value + + @property + def description(self) -> str: + """Return the description of the shape""" + return self._description + + @description.setter + def description(self, value: str): + self._description = value + + @property + def key(self) -> str: + """Return the key of the shape""" + if self._key is None: + return compute_key(self.graph, self.node) + return self._key + + @property + def node(self): + """Return the node of the shape""" + return self._node + + @property + def graph(self): + """Return the subgraph of the shape""" + return self._graph + + @property + def parent(self) -> Optional[SHACLNode]: + """Return the parent shape of the shape""" + return self._parent + + @property + def level(self) -> RequirementLevel: + """Return the requirement level of the shape""" + return self.get_declared_level() or LevelCollection.REQUIRED + + def get_declared_level(self) -> Optional[RequirementLevel]: + """Return the declared level of the shape""" + severity = self.get_declared_severity() + if severity: + try: + return LevelCollection.get(severity.name) + except ValueError: + pass + return None + + def get_declared_severity(self) -> Optional[Severity]: + """Return the declared severity of the shape""" + severity = getattr(self, "severity", None) + if severity == f"{SHACL_NS}Violation": + return Severity.REQUIRED + elif severity == f"{SHACL_NS}Warning": + return Severity.RECOMMENDED + elif severity == f"{SHACL_NS}Info": + return Severity.OPTIONAL + return None + + def __str__(self): + class_name = self.__class__.__name__ + if self.name and self.description: + return f"{class_name} - {self.name}: {self.description} ({hash(self)})" + elif self.name: + return f"{class_name} - {self.name} ({hash(self)})" + elif self.description: + return f"{class_name} - {self.description} ({hash(self)})" + else: + return f"{class_name} ({hash(self)})" + + def __repr__(self): + return f"{ self.__class__.__name__}({hash(self)})" + + def __eq__(self, other): + if not isinstance(other, Shape): + return False + return self._node == other._node + + def __hash__(self): + if self._hash is None: + self._hash = hash(self.key) + return self._hash + + @staticmethod + def compute_key(graph: Graph, node: Node) -> str: + return compute_key(graph, node) + + @staticmethod + def compute_hash(graph: Graph, node: Node) -> int: + return hash(compute_key(graph, node)) + + +class SHACLNodeCollection(SHACLNode): + + def __init__(self, node: Node, graph: Graph, properties: list[PropertyShape] = None): + super().__init__(node, graph) + # store the properties + self._properties = properties if properties else [] + + @property + def properties(self) -> list[PropertyShape]: + """Return the properties of the shape""" + return self._properties.copy() + + def get_property(self, name) -> PropertyShape: + """Return the property of the shape with the given name""" + for prop in self._properties: + if prop.name == name: + return prop + return None + + def get_property_index(self, name) -> int: + """Return the index of the property with the given name""" + for i, prop in enumerate(self._properties): + if prop.name == name: + return i + return -1 + + def add_property(self, property: PropertyShape): + """Add a property to the shape""" + self._properties.append(property) + + def remove_property(self, property: PropertyShape): + """Remove a property from the shape""" + self._properties.remove(property) + + +class Shape(SHACLNode): + pass + + +class PropertyGroup(SHACLNodeCollection): + pass + + +class PropertyShape(Shape): + + # define default values + _name: str = None + _short_name: str = None + _description: str = None + group: str = None + defaultValue: str = None + order: int = 0 + # store the reference to the property group + _property_group: PropertyGroup = None + + def __init__(self, + node: Node, + graph: Graph, + parent: Optional[Shape] = None): + # call the parent constructor + super().__init__(node, graph) + # store the parent shape + self._parent = parent + + @property + def name(self) -> str: + """Return the name of the shape property""" + if not self._name: + # get the object of the predicate sh:path + shacl_ns = Namespace(SHACL_NS) + path = self.graph.value(subject=self.node, predicate=shacl_ns.path) + if path: + self._short_name = path.split("#")[-1] if "#" in path else path.split("/")[-1] + if self.parent: + self._name = f"{self._short_name} of {self.parent.name}" + return self._name + + @name.setter + def name(self, value: str): + self._name = value + + @property + def description(self) -> str: + """Return the description of the shape property""" + if not self._description: + # get the object of the predicate sh:description + if not self._description: + property_name = self.name + if self._short_name: + property_name = self._short_name + self._description = f"Check the property \"**{property_name}**\"" + if self.parent and self.parent.name not in property_name: + self._description += f" of the entity \"**{self.parent.name}**\"" + return self._description + + @description.setter + def description(self, value: str): + self._description = value + + @property + def node(self) -> Node: + """Return the node of the shape property""" + return self._node + + @property + def graph(self) -> Graph: + """Return the graph of the shape property""" + return self._graph + + @property + def parent(self) -> Optional[Shape]: + """Return the parent shape of the shape property""" + return self._parent + + @property + def propertyGroup(self) -> PropertyGroup: + """Return the group of the shape property""" + return self._property_group + + +class NodeShape(Shape, SHACLNodeCollection): + + @property + def property_groups(self) -> list[PropertyGroup]: + """Return the property groups of the shape""" + groups = set() + for prop in self.properties: + if prop.propertyGroup: + groups.add(prop.propertyGroup) + return list(groups) + + @property + def grouped_properties(self) -> list[PropertyShape]: + """Return the properties that are in a group""" + return [prop for prop in self.properties if prop.propertyGroup] + + @property + def ungrouped_properties(self) -> list[PropertyShape]: + """Return the properties that are not in a group""" + return [prop for prop in self.properties if not prop.propertyGroup] + + +class ShapesRegistry: + + def __init__(self): + self._shapes = {} + self._shapes_graph: Graph = Graph() + + def add_shape(self, shape: Shape): + assert isinstance(shape, Shape), "Invalid shape" + self._shapes[shape.key] = shape + + def remove_shape(self, shape: Shape): + assert isinstance(shape, Shape), "Invalid shape" + self._shapes.pop(shape.key, None) + self._shapes_graph -= shape.graph + + def get_shape(self, shape_key: str) -> Optional[Shape]: + logger.debug("Searching for shape %s in the registry: %s", shape_key, self._shapes) + result = self._shapes.get(shape_key, None) + if not result: + logger.debug(f"Shape {shape_key} not found in the registry") + raise ValueError(f"Shape not found in the registry: {shape_key}") + return result + + def extend(self, shapes: dict[str, Shape], graph: Graph) -> None: + self._shapes.update(shapes) + self._shapes_graph += graph + + def get_shape_by_name(self, name: str) -> Optional[Shape]: + for shape in self._shapes.values(): + if shape.name == name: + return shape + return None + + def get_shapes(self) -> dict[str, Shape]: + return self._shapes.copy() + + @property + def shapes_graph(self) -> Graph: + g = Graph() + g += self._shapes_graph + return g + + def load_shapes(self, shapes_path: Union[str, Path], publicID: Optional[str] = None) -> list[Shape]: + """ + Load the shapes from the graph + """ + logger.debug(f"Loading shapes from: {shapes_path}") + # load shapes (nodes and properties) from the shapes graph + shapes_list: ShapesList = ShapesList.load_from_file(shapes_path, publicID) + logger.debug(f"Shapes List: {shapes_list}") + + # append the partial shapes graph to the global shapes graph + self._shapes_graph += shapes_list.shapes_graph + + # list of instantiated shapes + shapes = [] + + # list of property groups + property_groups = {} + + # register Node Shapes + for node_shape in shapes_list.node_shapes: + # flag to check if the nested properties are in a group + grouped = False + # list of properties ungrouped + ungrouped_properties = [] + # get the shape graph + node_graph = shapes_list.get_shape_graph(node_shape) + # create a node shape object + shape = NodeShape(node_shape, node_graph) + # load the nested properties + shacl_ns = Namespace(SHACL_NS) + nested_properties = node_graph.objects(subject=node_shape, predicate=shacl_ns.property) + for property_shape in nested_properties: + property_graph = shapes_list.get_shape_property_graph(node_shape, property_shape) + p_shape = PropertyShape( + property_shape, property_graph, shape) + shape.add_property(p_shape) + group = __process_property_group__(property_groups, p_shape) + if group and group not in shapes: + grouped = True + shapes.append(group) + if not group: + ungrouped_properties.append(p_shape) + + # store the property shape in the registry + self.add_shape(p_shape) + # store the node shape in the registry + self.add_shape(shape) + + # Β store the node in the list of shapes + if not grouped: + shapes.append(shape) + else: + for prop in ungrouped_properties: + shapes.append(prop) + + # register Property Shapes + for property_shape in shapes_list.property_shapes: + shape = PropertyShape(property_shape, shapes_list.get_shape_graph(property_shape)) + self.add_shape(shape) + shapes.append(shape) + + return shapes + + def __str__(self): + return f"ShapesRegistry: {self._shapes}" + + def __repr__(self): + return f"ShapesRegistry({self._shapes})" + + def clear(self): + self._shapes.clear() + self._shapes_graph = Graph() + + @classmethod + def get_instance(cls, ctx: object): + instance = getattr(ctx, "_shapes_registry_instance", None) + if not instance: + instance = cls() + setattr(ctx, "_shapes_registry_instance", instance) + return instance + + +def __process_property_group__(groups: dict[str, PropertyGroup], property_shape: PropertyShape) -> PropertyGroup: + group_name = property_shape.group + if group_name: + if group_name not in groups: + groups[group_name] = PropertyGroup(URIRef(property_shape.group), property_shape.graph) + property_shape.graph.serialize("logs/property_shape.ttl", format="turtle") + groups[group_name].add_property(property_shape) + property_shape._property_group = groups[group_name] + return groups[group_name] + return None diff --git a/rocrate_validator/requirements/shacl/requirements.py b/rocrate_validator/requirements/shacl/requirements.py new file mode 100644 index 00000000..28dec2a0 --- /dev/null +++ b/rocrate_validator/requirements/shacl/requirements.py @@ -0,0 +1,99 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from typing import Optional + +from rdflib import RDF + +import rocrate_validator.log as logging +from rocrate_validator.constants import VALIDATOR_NS + +from ...models import (Profile, Requirement, RequirementCheck, + RequirementLevel, RequirementLoader) +from .checks import SHACLCheck +from .models import Shape, ShapesRegistry + +# set up logging +logger = logging.getLogger(__name__) + + +class SHACLRequirement(Requirement): + + def __init__(self, + shape: Shape, + profile: Profile, + path: Path): + self._shape = shape + super().__init__(profile, + shape.name if shape.name else "", + shape.description if shape.description else "", + path) + # init checks + self._checks = self.__init_checks__() + # assign check IDs + self.__reorder_checks__() + + def __init_checks__(self) -> list[RequirementCheck]: + # assign a check to each property of the shape + checks = [] + # create a check for each property if the shape has nested properties + if hasattr(self.shape, "properties"): + for prop in self.shape.properties: + logger.debug("Creating check for property %s %s", prop.name, prop.description) + property_check = SHACLCheck(self, prop) + logger.debug("Property check %s: %s", property_check.name, property_check.description) + checks.append(property_check) + + # if no property checks, add a generic one + assert self.shape is not None, "The shape cannot be None" + if len(checks) == 0 and self.shape is not None and self.shape.node is not None: + checks.append(SHACLCheck(self, self.shape)) + return checks + + @property + def shape(self) -> Shape: + return self._shape + + @property + def hidden(self) -> bool: + if self.shape.node is not None: + if (self.shape.node, RDF.type, VALIDATOR_NS.HiddenShape) in self.shape.graph: + return True + return False + + +class SHACLRequirementLoader(RequirementLoader): + + def __init__(self, profile: Profile): + super().__init__(profile) + self._shape_registry = ShapesRegistry.get_instance(profile) + # reset the shapes registry + self._shape_registry.clear() # should be removed + + @property + def shapes_registry(self) -> ShapesRegistry: + return self._shape_registry + + def load(self, profile: Profile, + requirement_level: RequirementLevel, + file_path: Path, publicID: Optional[str] = None) -> list[Requirement]: + assert file_path is not None, "The file path cannot be None" + shapes: list[Shape] = self.shapes_registry.load_shapes(file_path, publicID) + logger.debug("Loaded %s shapes: %s", len(shapes), shapes) + requirements = [] + for shape in shapes: + if shape is not None and shape.level >= requirement_level: + requirements.append(SHACLRequirement(shape, profile, file_path)) + return requirements diff --git a/rocrate_validator/requirements/shacl/utils.py b/rocrate_validator/requirements/shacl/utils.py new file mode 100644 index 00000000..bc009dca --- /dev/null +++ b/rocrate_validator/requirements/shacl/utils.py @@ -0,0 +1,298 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import hashlib +from pathlib import Path +from typing import Optional, Union + +from rdflib import RDF, BNode, Graph, Namespace +from rdflib.term import Node + +import rocrate_validator.log as logging +from rocrate_validator.constants import RDF_SYNTAX_NS, SHACL_NS +from rocrate_validator.errors import BadSyntaxError +from rocrate_validator.models import Severity + +# set up logging +logger = logging.getLogger(__name__) + + +def build_node_subgraph(graph: Graph, node: Node) -> Graph: + shape_graph = Graph() + shape_graph += graph.triples((node, None, None)) + + # add BNodes + for _, _, o in shape_graph: + shape_graph += graph.triples((o, None, None)) + + # Use the triples method to get all triples that are part of a list + RDF = Namespace(RDF_SYNTAX_NS) + first_predicate = RDF.first + rest_predicate = RDF.rest + shape_graph += graph.triples((None, first_predicate, None)) + shape_graph += graph.triples((None, rest_predicate, None)) + for _, _, object in shape_graph: + shape_graph += graph.triples((object, None, None)) + + # return the subgraph + return shape_graph + + +def map_severity(shacl_severity: str) -> Severity: + """ + Map the SHACL severity term to our Severity enum values + """ + if f"{SHACL_NS}Violation" == shacl_severity: + return Severity.REQUIRED + elif f"{SHACL_NS}Warning" == shacl_severity: + return Severity.RECOMMENDED + elif f"{SHACL_NS}Info" == shacl_severity: + return Severity.OPTIONAL + else: + raise RuntimeError(f"Unrecognized SHACL severity term {shacl_severity}") + + +def make_uris_relative(text: str, ro_crate_path: Union[Path, str]) -> str: + # globally replace the string "file://" with "./ + return text.replace(str(ro_crate_path), './') + + +def inject_attributes(obj: object, node_graph: Graph, node: Node, exclude: Optional[list] = None) -> object: + # inject attributes of the shape property + # logger.debug("Injecting attributes of node %s", node) + skip_properties = ["node"] if exclude is None else exclude + ["node"] + triples = node_graph.triples((node, None, None)) + for node, p, o in triples: + predicate_as_string = p.toPython() + # logger.debug(f"Processing {predicate_as_string} of property graph {node}") + if predicate_as_string.startswith(SHACL_NS): + property_name = predicate_as_string.split("#")[-1] + if property_name in skip_properties: + continue + try: + setattr(obj, property_name, o.toPython()) + except AttributeError as e: + logger.error(f"Error injecting attribute {property_name}: {e}") + # logger.debug("Injected attribute %s: %s", property_name, o.toPython()) + # logger.debug("Injected attributes ig node %s: %s", node, len(list(triples))) + # return the object + return obj + + +def __compute_values__(g: Graph, s: Node) -> list[tuple]: + """ + Compute the values of the triples in the graph (excluding BNodes) + starting from the given subject node `s`. + """ + + # Collect the values of the triples in the graph (excluding BNodes) + values = [] + # Assuming the list of triples values is stored in a variable called 'triples_values' + triples_values = list([(_, x, _) for (_, x, _) in g.triples((s, None, None)) if x != RDF.type]) + + for (s, p, o) in triples_values: + if isinstance(o, BNode): + values.extend(__compute_values__(g, o)) + else: + values.append((s, p, o) if not isinstance(s, BNode) else (p, o)) + return values + + +def compute_hash(g: Graph, s: Node): + """ + Compute the hash of the triples in the graph (including BNodes) + starting from the given subject node `s`. + """ + + # Collect the values of the triples in the graph (excluding BNodes) + triples_values = sorted(__compute_values__(g, s)) + # Convert the list of triples values to a string representation + triples_string = str(triples_values) + # Calculate the hash of the triples string + hash_value = hashlib.sha256(triples_string.encode()).hexdigest() + # Return the hash value + return hash_value + + +def compute_key(g: Graph, s: Node) -> str: + """ + Compute the key of the node `s` in the graph `g`. + If the node is a URI, return the URI as a string. + If the node is a BNode, return the hash of the triples in the graph starting from the BNode. + """ + + if isinstance(s, BNode): + return compute_hash(g, s) + else: + return s.toPython() + + +class ShapesList: + def __init__(self, + node_shapes: list[Node], + property_shapes: list[Node], + shapes_graphs: dict[Node, Graph], + shapes_graph: Graph): + self._node_shapes = node_shapes + self._property_shapes = property_shapes + self._shapes_graph = shapes_graph + self._shapes_graphs = shapes_graphs + + @property + def node_shapes(self) -> list[Node]: + """ + Get all the node shapes + """ + return self._node_shapes.copy() + + @property + def property_shapes(self) -> list[Node]: + """ + Get all the property shapes + """ + return self._property_shapes.copy() + + @property + def shapes(self) -> list[Node]: + """ + Get all the shapes + """ + return self._node_shapes + self._property_shapes + + @property + def shapes_graph(self) -> Graph: + """ + Get the graph containing all the shapes + """ + return self._shapes_graph + + def get_shape_graph(self, shape_node: Node) -> Graph: + """ + Get the subgraph of the given shape node + """ + return self._shapes_graphs[shape_node] + + def get_shape_property_graph(self, shape_node: Node, shape_property: Node) -> Graph: + """ + Get the subgraph of the given shape node excluding the given property + """ + node_graph = self.get_shape_graph(shape_node) + assert node_graph is not None, "The shape graph cannot be None" + + property_graph = Graph() + shacl_ns = Namespace(SHACL_NS) + nested_properties_to_exclude = [o for (_, _, o) in node_graph.triples( + (shape_node, shacl_ns.property, None)) if o != shape_property] + triples_to_exclude = [(s, _, o) for (s, _, o) in node_graph.triples((None, None, None)) + if s in nested_properties_to_exclude + or o in nested_properties_to_exclude] + + property_graph += node_graph - triples_to_exclude + + return property_graph + + @classmethod + def load_from_file(cls, file_path: str, publicID: str = None) -> ShapesList: + """ + Load the shapes from the file + + :param file_path: the path to the file containing the shapes + :param publicID: the public ID to use + + :return: the list of shapes + """ + return load_shapes_from_file(file_path, publicID) + + @classmethod + def load_from_graph(cls, graph: Graph) -> ShapesList: + """ + Load the shapes from the graph + + :param graph: the graph containing the shapes + :param target_node: the target node to extract the shapes from + + :return: the list of shapes + """ + return load_shapes_from_graph(graph) + + +def __extract_related_triples__(graph, subject_node, processed_nodes=None): + """ + Recursively extract all triples related to a given shape. + """ + + related_triples = [] + + processed_nodes = processed_nodes if processed_nodes is not None else set() + + # Skip the current node if it has already been processed + if subject_node in processed_nodes: + return related_triples + + # Add the current node to the processed nodes + processed_nodes.add(subject_node) + + # Directly related triples + related_triples.extend((_, p, o) for (_, p, o) in graph.triples((subject_node, None, None))) + + # Recursively find triples related to nested shapes + for _, _, object_node in related_triples: + if isinstance(object_node, Node): + related_triples.extend(__extract_related_triples__(graph, object_node, processed_nodes)) + + return related_triples + + +def load_shapes_from_file(file_path: str, publicID: str = None) -> ShapesList: + try: + # Check the file path is not None + assert file_path is not None, "The file path cannot be None" + # Load the graph from the file + g = Graph() + g.parse(file_path, format="turtle", publicID=publicID) + # Extract the shapes from the graph + return load_shapes_from_graph(g) + except Exception as e: + raise BadSyntaxError(str(e), file_path) from e + + +def load_shapes_from_graph(g: Graph) -> ShapesList: + # define the SHACL namespace + SHACL = Namespace(SHACL_NS) + # find all NodeShapes + node_shapes = [s for (s, _, _) in g.triples( + (None, RDF.type, SHACL.NodeShape)) if not isinstance(s, BNode)] + logger.debug("Loaded Node Shapes: %s", node_shapes) + # find all PropertyShapes + property_shapes = [s for (s, _, _) in g.triples((None, RDF.type, SHACL.PropertyShape)) + if not isinstance(s, BNode)] + logger.debug("Loaded Property Shapes: %s", property_shapes) + # define the list of shapes to extract + shapes = node_shapes + property_shapes + + # Split the graph into subgraphs for each shape + subgraphs = {} + count = 0 + for shape in shapes: + count += 1 + subgraph = Graph() + # Extract all related triples for the current shape + related_triples = __extract_related_triples__(g, shape) + for s, p, o in related_triples: + subgraph.add((s, p, o)) + subgraphs[shape] = subgraph + + return ShapesList(node_shapes, property_shapes, subgraphs, g) diff --git a/rocrate_validator/requirements/shacl/validator.py b/rocrate_validator/requirements/shacl/validator.py new file mode 100644 index 00000000..a4d5a65b --- /dev/null +++ b/rocrate_validator/requirements/shacl/validator.py @@ -0,0 +1,485 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import os +from pathlib import Path +from typing import Optional, Union + +import pyshacl +from pyshacl.pytypes import GraphLike +from rdflib import BNode, Graph +from rdflib.term import Node, URIRef + +import rocrate_validator.log as logging +from rocrate_validator.models import (Profile, RequirementCheck, Severity, + ValidationContext, ValidationResult) +from rocrate_validator.requirements.shacl.utils import (make_uris_relative, + map_severity) + +from ...constants import (DEFAULT_ONTOLOGY_FILE, RDF_SERIALIZATION_FORMATS, + RDF_SERIALIZATION_FORMATS_TYPES, SHACL_NS, + VALID_INFERENCE_OPTIONS, + VALID_INFERENCE_OPTIONS_TYPES) +from .models import ShapesRegistry + +# set up logging +logger = logging.getLogger(__name__) + + +class SHACLValidationSkip(Exception): + pass + + +class SHACLValidationAlreadyProcessed(Exception): + + def __init__(self, profile_identifier: str, result: SHACLValidationResult) -> None: + super().__init__(f"Profile {profile_identifier} has already been processed") + self.result = result + + +class SHACLValidationContextManager: + + def __init__(self, check: RequirementCheck, context: ValidationContext) -> None: + self._check = check + self._profile = check.requirement.profile + self._context = context + self._shacl_context = SHACLValidationContext.get_instance(context) + + def __enter__(self) -> SHACLValidationContext: + logger.debug("Entering SHACLValidationContextManager") + if not self._shacl_context.__set_current_validation_profile__(self._profile): + raise SHACLValidationAlreadyProcessed( + self._profile.identifier, self._shacl_context.get_validation_result(self._profile)) + logger.debug("Processing profile: %s (id: %s)", self._profile.name, self._profile.identifier) + if self._context.settings.get("target_only_validation", False) and \ + self._profile.identifier != self._context.settings.get("profile_identifier", None): + logger.debug("Skipping validation of profile %s", self._profile.identifier) + self.context.result.add_skipped_check(self._check) + raise SHACLValidationSkip(f"Skipping validation of profile {self._profile.identifier}") + logger.debug("ValidationContext of profile %s initialized", self._profile.identifier) + return self._shacl_context + + def __exit__(self, exc_type, exc_val, exc_tb) -> None: + self._shacl_context.__unset_current_validation_profile__() + logger.debug("Exiting SHACLValidationContextManager") + + @property + def context(self) -> ValidationContext: + return self._context + + @property + def shacl_context(self) -> SHACLValidationContext: + return self._shacl_context + + @property + def check(self) -> RequirementCheck: + return self._check + + +class SHACLValidationContext(ValidationContext): + + def __init__(self, context: ValidationContext): + super().__init__(context.validator, context.settings) + self._base_context: ValidationContext = context + # reference to the ontology path + self._ontology_path: Path = None + + # reference to the contextual ShapeRegistry instance + self._shapes_registry: ShapesRegistry = ShapesRegistry() + + # processed profiles + self._processed_profiles: dict[str, bool] = {} + + # reference to the current validation profile + self._current_validation_profile: Profile = None + + # store the validation result of the current profile + self._validation_result: SHACLValidationResult = None + + # reference to the contextual ontology graph + self._ontology_graph: Graph = Graph() + + def __set_current_validation_profile__(self, profile: Profile) -> bool: + if profile.identifier not in self._processed_profiles: + # augment the ontology graph with the profile ontology + ontology_graph = self.__load_ontology_graph__(profile.path) + if ontology_graph: + self._ontology_graph += ontology_graph + # augment the shapes registry with the profile shapes + profile_registry = ShapesRegistry.get_instance(profile) + profile_shapes = profile_registry.get_shapes() + profile_shapes_graph = profile_registry.shapes_graph + logger.debug("Loaded shapes: %s", profile_shapes) + + # enable overriding of checks + if self.settings.get("allow_requirement_check_override", True): + from rocrate_validator.requirements.shacl.requirements import \ + SHACLRequirement + for requirement in [_ for _ in profile.requirements if isinstance(_, SHACLRequirement)]: + logger.debug("Processing requirement: %s", requirement.name) + for check in requirement.get_checks(): + logger.debug("Processing check: %s", check) + if check.overridden and check.requirement.profile != self.target_profile: + logger.debug("Overridden check: %s", check) + profile_shapes_graph -= check.shape.graph + profile_shapes.pop(check.shape.key) + + # add the shapes to the registry + self._shapes_registry.extend(profile_shapes, profile_shapes_graph) + # set the current validation profile + self._current_validation_profile = profile + # return True if the profile should be processed + return True + # return False if the profile has already been processed + return False + + def __unset_current_validation_profile__(self) -> None: + self._current_validation_profile = None + + @property + def base_context(self) -> ValidationContext: + return self._base_context + + @property + def current_validation_profile(self) -> Profile: + return self._current_validation_profile + + @property + def current_validation_result(self) -> SHACLValidationResult: + return self._validation_result + + @current_validation_result.setter + def current_validation_result(self, result: ValidationResult): + assert self._current_validation_profile is not None, "Invalid state: current profile not set" + # store the validation result + self._validation_result = result + # mark the profile as processed and store the result + self._processed_profiles[self._current_validation_profile.identifier] = result + + def get_validation_result(self, profile: Profile) -> Optional[bool]: + assert profile is not None, "Invalid profile" + return self._processed_profiles.get(profile.identifier, None) + + @property + def result(self) -> ValidationResult: + return self.base_context.result + + @property + def shapes_registry(self) -> ShapesRegistry: + return self._shapes_registry + + @property + def shapes_graph(self) -> Graph: + return self.shapes_registry.shapes_graph + + def __get_ontology_path__(self, profile_path: Path, ontology_filename: str = DEFAULT_ONTOLOGY_FILE) -> Path: + if not self._ontology_path: + supported_path = f"{profile_path}/{ontology_filename}" + if self.settings.get("ontology_path", None): + logger.warning("Detected an ontology path. Custom ontology file is not yet supported." + f"Use {supported_path} to provide an ontology for your profile.") + # overwrite the ontology path if the custom ontology file is provided + self._ontology_path = Path(supported_path) + return self._ontology_path + + def __load_ontology_graph__(self, profile_path: Path, + ontology_filename: Optional[str] = DEFAULT_ONTOLOGY_FILE) -> Graph: + # load the graph of ontologies + ontology_graph = None + ontology_path = self.__get_ontology_path__(profile_path, ontology_filename) + if os.path.exists(ontology_path): + logger.debug("Loading ontologies: %s", ontology_path) + ontology_graph = Graph() + ontology_graph.parse(ontology_path, format="ttl", + publicID=self.publicID) + logger.debug("Ontologies loaded: %s", ontology_graph) + return ontology_graph + + @property + def ontology_graph(self) -> Graph: + return self._ontology_graph + + @classmethod + def get_instance(cls, context: ValidationContext) -> SHACLValidationContext: + instance = getattr(context, "_shacl_validation_context", None) + if not instance: + instance = SHACLValidationContext(context) + setattr(context, "_shacl_validation_context", instance) + return instance + + +class SHACLViolation: + + def __init__(self, result: ValidationResult, violation_node: Node, graph: Graph) -> None: + # check the input + assert result is not None, "Invalid result" + assert isinstance(violation_node, Node), "Invalid violation node" + assert isinstance(graph, Graph), "Invalid graph" + + # store the input + self._result = result + self._violation_node = violation_node + self._graph = graph + + # initialize the properties for lazy loading + self._focus_node = None + self._result_message = None + self._result_path = None + self._severity = None + self._source_constraint_component = None + self._source_shape = None + self._source_shape_node = None + self._value = None + + @property + def node(self) -> Node: + return self._violation_node + + @property + def graph(self) -> Graph: + return self._graph + + @property + def focusNode(self) -> Node: + if not self._focus_node: + self._focus_node = self.graph.value(self._violation_node, URIRef(f"{SHACL_NS}focusNode")) + assert self._focus_node is not None, f"Unable to get focus node from violation node {self._violation_node}" + return self._focus_node + + @property + def resultPath(self): + if not self._result_path: + self._result_path = self.graph.value(self._violation_node, URIRef(f"{SHACL_NS}resultPath")) + return self._result_path + + @property + def value(self): + if not self._value: + self._value = self.graph.value(self._violation_node, URIRef(f"{SHACL_NS}value")) + return self._value + + def get_result_severity(self) -> Severity: + if not self._severity: + severity = self.graph.value(self._violation_node, URIRef(f"{SHACL_NS}resultSeverity")) + assert severity is not None, f"Unable to get severity from violation node {self._violation_node}" + # we need to map the SHACL severity term to our Severity enum values + self._severity = map_severity(severity.toPython()) + return self._severity + + @property + def sourceConstraintComponent(self): + if not self._source_constraint_component: + self._source_constraint_component = self.graph.value( + self._violation_node, URIRef(f"{SHACL_NS}sourceConstraintComponent")) + assert self._source_constraint_component is not None, \ + f"Unable to get source constraint component from violation node {self._violation_node}" + return self._source_constraint_component + + def get_result_message(self, ro_crate_path: Union[Path, str]) -> str: + if not self._result_message: + message = self.graph.value(self._violation_node, URIRef(f"{SHACL_NS}resultMessage")) + assert message is not None, f"Unable to get result message from violation node {self._violation_node}" + self._result_message = make_uris_relative(message.toPython(), ro_crate_path) + return self._result_message + + @property + def sourceShape(self) -> Union[URIRef, BNode]: + if not self._source_shape_node: + self._source_shape_node = self.graph.value(self._violation_node, URIRef(f"{SHACL_NS}sourceShape")) + assert self._source_shape_node is not None, \ + f"Unable to get source shape node from violation node {self._violation_node}" + return self._source_shape_node + + +class SHACLValidationResult: + + def __init__(self, results_graph: Graph, + results_text: str = None) -> None: + # validate the results graph input + assert results_graph is not None, "Invalid graph" + assert isinstance(results_graph, Graph), "Invalid graph type" + # check if the graph is valid ValidationReport + assert (None, URIRef(f"{SHACL_NS}conforms"), + None) in results_graph, "Invalid ValidationReport" + # store the input properties + self.results_graph = results_graph + self._text = results_text + # parse the results graph + self._violations = self._parse_results_graph(results_graph) + # initialize the conforms property + self._conforms = len(self._violations) == 0 + + logger.debug("Validation report. N. violations: %s, Conforms: %s; Text: %s", + len(self._violations), self._conforms, self._text) + + def _parse_results_graph(self, results_graph: Graph): + # parse the violations from the results graph + violations = [] + for violation_node in results_graph.subjects(predicate=URIRef(f"{SHACL_NS}resultMessage")): + violation = SHACLViolation(self, violation_node, results_graph) + violations.append(violation) + + return violations + + @property + def conforms(self) -> bool: + return self._conforms + + @property + def violations(self) -> list[SHACLViolation]: + return self._violations + + @property + def text(self) -> str: + return self._text + + +class SHACLValidator: + + def __init__( + self, + shapes_graph: Optional[Union[GraphLike, str, bytes]], + ont_graph: Optional[Union[GraphLike, str, bytes]] = None, + ) -> None: + """ + Create a new SHACLValidator instance. + + :param shacl_graph: rdflib.Graph or file path or web url + of the SHACL Shapes graph to use to + validate the data graph + :type shacl_graph: rdflib.Graph | str | bytes + :param ont_graph: rdflib.Graph or file path or web url + of an extra ontology document to mix into the data graph + :type ont_graph: rdflib.Graph | str | bytes + """ + self._shapes_graph = shapes_graph + self._ont_graph = ont_graph + + @property + def shapes_graph(self) -> Optional[Union[GraphLike, str, bytes]]: + return self._shapes_graph + + @property + def ont_graph(self) -> Optional[Union[GraphLike, str, bytes]]: + return self._ont_graph + + def validate( + self, + # data to validate + data_graph: Union[GraphLike, str, bytes], + # validation settings + abort_on_first: Optional[bool] = True, + advanced: Optional[bool] = True, + inference: Optional[VALID_INFERENCE_OPTIONS_TYPES] = None, + inplace: Optional[bool] = False, + meta_shacl: bool = False, + iterate_rules: bool = True, + # SHACL validation severity + allow_infos: Optional[bool] = False, + allow_warnings: Optional[bool] = False, + # serialization settings + serialization_output_path: Optional[str] = None, + serialization_output_format: + Optional[RDF_SERIALIZATION_FORMATS_TYPES] = "turtle", + **kwargs, + ) -> SHACLValidationResult: + f""" + Validate a data graph using SHACL shapes as constraints + + :param data_graph: rdflib.Graph or file path or web url + of the data to validate + :type data_graph: rdflib.Graph | str | bytes + :param advanced: Enable advanced SHACL features, default=False + :type advanced: bool | None + :param inference: One of {VALID_INFERENCE_OPTIONS} + :type inference: str | None + :param inplace: If this is enabled, do not clone the datagraph, + manipulate it inplace + :type inplace: bool + :param abort_on_first: Stop evaluating constraints after first + violation is found + :type abort_on_first: bool | None + :param allow_infos: Shapes marked with severity of sh:Info + will not cause result to be invalid. + :type allow_infos: bool | None + :param allow_warnings: Shapes marked with severity of sh:Warning + or sh:Info will not cause result to be invalid. + :type allow_warnings: bool | None + :param serialization_output_format: Literal[ + {RDF_SERIALIZATION_FORMATS} + ] + :param kwargs: Additional keyword arguments to pass to pyshacl.validate + """ + + # Validate data_graph + if not isinstance(data_graph, (Graph, str, bytes)): + raise ValueError( + "data_graph must be an instance of Graph, str, or bytes") + + # Validate inference + if inference and inference not in VALID_INFERENCE_OPTIONS: + raise ValueError( + f"inference must be one of {VALID_INFERENCE_OPTIONS}") + + # Validate serialization_output_format + if serialization_output_format and \ + serialization_output_format not in RDF_SERIALIZATION_FORMATS: + raise ValueError( + "serialization_output_format must be one of " + f"{RDF_SERIALIZATION_FORMATS}") + + assert inference in (None, "rdfs", "owlrl", "both"), "Invalid inference option" + + # validate the data graph using pyshacl.validate + conforms, results_graph, results_text = pyshacl.validate( + data_graph, + shacl_graph=self.shapes_graph, + ont_graph=self.ont_graph, + inference=inference if inference else "owlrl" if self.ont_graph else None, + inplace=inplace, + abort_on_first=abort_on_first, + allow_infos=allow_infos, + allow_warnings=allow_warnings, + meta_shacl=meta_shacl, + iterate_rules=iterate_rules, + advanced=advanced, + js=False, + debug=False, + **kwargs, + ) + # log the validation results + logger.debug("pyshacl.validate result: Conforms: %r", conforms) + logger.debug("pyshacl.validate result: Results Graph: %r", results_graph) + logger.debug("pyshacl.validate result: Results Text: %r", results_text) + + # serialize the results graph + if serialization_output_path: + assert serialization_output_format in [ + "turtle", + "n3", + "nt", + "xml", + "rdf", + "json-ld", + ], "Invalid serialization output format" + results_graph.serialize( + serialization_output_path, format=serialization_output_format + ) + # return the validation result + return SHACLValidationResult(results_graph, results_text) + + +__all__ = ["SHACLValidator", "SHACLValidationResult", "SHACLViolation"] diff --git a/rocrate_validator/rocrate.py b/rocrate_validator/rocrate.py new file mode 100644 index 00000000..38d8aa0f --- /dev/null +++ b/rocrate_validator/rocrate.py @@ -0,0 +1,552 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import io +import json +import struct +import zipfile +from abc import ABC, abstractmethod +from pathlib import Path +from typing import Optional, Union + +import requests +from rdflib import Graph + +from rocrate_validator import log as logging +from rocrate_validator.errors import ROCrateInvalidURIError +from rocrate_validator.utils import URI + +# set up logging +logger = logging.getLogger(__name__) + + +class ROCrateEntity: + + def __init__(self, metadata: ROCrateMetadata, raw_data: object) -> None: + self._raw_data = raw_data + self._metadata = metadata + + @property + def id(self) -> str: + return self._raw_data.get('@id') + + @property + def type(self) -> Union[str, list[str]]: + return self._raw_data.get('@type') + + @property + def name(self) -> str: + return self._raw_data.get('name') + + @property + def metadata(self) -> ROCrateMetadata: + return self._metadata + + @property + def ro_crate(self) -> ROCrate: + return self.metadata.ro_crate + + def has_type(self, entity_type: str) -> bool: + assert isinstance(entity_type, str), "Entity type must be a string" + e_types = self.type if isinstance(self.type, list) else [self.type] + return entity_type in e_types + + def has_types(self, entity_types: list[str]) -> bool: + assert isinstance(entity_types, list), "Entity types must be a list" + e_types = self.type if isinstance(self.type, list) else [self.type] + return any([t in e_types for t in entity_types]) + + def __process_property__(self, name: str, data: object) -> object: + if isinstance(data, dict) and '@id' in data: + entity = self.metadata.get_entity(data['@id']) + if entity is None: + return ROCrateEntity(self, data) + return entity + return data + + def get_property(self, name: str, default=None) -> Union[str, ROCrateEntity]: + data = self._raw_data.get(name, default) + if data is None: + return None + if isinstance(data, list): + return [self.__process_property__(name, _) for _ in data] + return self.__process_property__(name, data) + + @property + def raw_data(self) -> object: + return self._raw_data + + def is_available(self) -> bool: + try: + # check if the entity points to an external file + if self.id.startswith("http"): + return ROCrate.get_external_file_size(self.id) > 0 + + # check if the entity is part of the local RO-Crate + if self.ro_crate.uri.is_local_resource(): + # check if the file exists in the local file system + if isinstance(self.ro_crate, ROCrateLocalFolder): + logger.debug("Checking local folder: %s", self.ro_crate.uri.as_path().absolute() / self.id) + return self.ro_crate.has_file( + self.ro_crate.uri.as_path().absolute() / self.id) \ + or self.ro_crate.has_directory( + self.ro_crate.uri.as_path().absolute() / self.id) + # check if the file exists in the local zip file + if isinstance(self.ro_crate, ROCrateLocalZip): + if self.id in [str(_) for _ in self.ro_crate.list_files()]: + return self.ro_crate.get_file_size(Path(self.id)) > 0 + + # check if the entity is part of the remote RO-Crate + if self.ro_crate.uri.is_remote_resource(): + return self.ro_crate.get_file_size(Path(self.id)) > 0 + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return False + + raise ROCrateInvalidURIError(uri=self.id, message="Could not determine the availability of the entity") + + def get_size(self) -> int: + try: + return self.metadata.ro_crate.get_file_size(Path(self.id)) + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return 0 + + def __str__(self) -> str: + return f"Entity({self.id})" + + def __repr__(self) -> str: + return str(self) + + def __eq__(self, other: ROCrateEntity) -> bool: + if not isinstance(other, ROCrateEntity): + return False + return self.id == other.id + + +class ROCrateMetadata: + + METADATA_FILE_DESCRIPTOR = 'ro-crate-metadata.json' + + def __init__(self, ro_crate: ROCrate) -> None: + self._ro_crate = ro_crate + self._dict = None + self._json: str = None + + @property + def ro_crate(self) -> ROCrate: + return self._ro_crate + + @property + def size(self) -> int: + try: + return len(self.as_json()) + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return 0 + + def get_file_descriptor_entity(self) -> ROCrateEntity: + metadata_file_descriptor = self.get_entity(self.METADATA_FILE_DESCRIPTOR) + if not metadata_file_descriptor: + raise ValueError("no metadata file descriptor in crate") + return metadata_file_descriptor + + def get_root_data_entity(self) -> ROCrateEntity: + metadata_file_descriptor = self.get_file_descriptor_entity() + main_entity = metadata_file_descriptor.get_property('about') + if not main_entity: + raise ValueError("no main entity in metadata file descriptor") + return main_entity + + def get_root_data_entity_conforms_to(self) -> Optional[list[str]]: + try: + root_data_entity = self.get_root_data_entity() + result = root_data_entity.get_property('conformsTo', []) + if result is None: + return None + if not isinstance(result, list): + result = [result] + return [_.id for _ in result] + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return None + + def get_main_workflow(self) -> ROCrateEntity: + root_data_entity = self.get_root_data_entity() + main_workflow = root_data_entity.get_property('mainEntity') + if not main_workflow: + raise ValueError("no main workflow in metadata file descriptor") + return main_workflow + + def get_entity(self, entity_id: str) -> ROCrateEntity: + for entity in self.as_dict().get('@graph', []): + if entity.get('@id') == entity_id: + return ROCrateEntity(self, entity) + return None + + def get_entities(self) -> list[ROCrateEntity]: + entities = [] + for entity in self.as_dict().get('@graph', []): + entities.append(ROCrateEntity(self, entity)) + return entities + + def get_entities_by_type(self, entity_type: Union[str, list[str]]) -> list[ROCrateEntity]: + entities = [] + for e in self.get_entities(): + if e.has_type(entity_type): + entities.append(e) + return entities + + def get_dataset_entities(self) -> list[ROCrateEntity]: + return self.get_entities_by_type('Dataset') + + def get_file_entities(self) -> list[ROCrateEntity]: + return self.get_entities_by_type('File') + + def get_web_data_entities(self) -> list[ROCrateEntity]: + entities = [] + for entity in self.get_entities(): + if entity.has_type('File') or entity.has_type('Dataset'): + if entity.id.startswith("http"): + entities.append(entity) + return entities + + def get_conforms_to(self) -> Optional[list[str]]: + try: + file_descriptor = self.get_file_descriptor_entity() + result = file_descriptor.get_property('conformsTo', []) + if result is None: + return None + if not isinstance(result, list): + result = [result] + return [_.id for _ in result] + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return None + + def as_json(self) -> str: + if not self._json: + self._json = self.ro_crate.get_file_content( + Path(self.METADATA_FILE_DESCRIPTOR), binary_mode=False) + return self._json + + def as_dict(self) -> dict: + if not self._dict: + # if the dictionary is not cached, load it + self._dict = json.loads(self.as_json()) + return self._dict + + def as_graph(self, publicID: str = None) -> Graph: + if not self._graph: + # if the graph is not cached, load it + self._graph = Graph(base=publicID or self.ro_crate.uri) + self._graph.parse(data=self.as_json, format='json-ld') + return self._graph + + def __str__(self) -> str: + return f"Metadata({self.ro_crate})" + + def __repr__(self) -> str: + return str(self) + + def __eq__(self, other: ROCrateMetadata) -> bool: + if not isinstance(other, ROCrateMetadata): + return False + return self.ro_crate == other.ro_crate + + +class ROCrate(ABC): + + def __init__(self, path: Union[str, Path, URI]): + + # store the path to the crate + self._uri = URI(path) + + # cache the list of files + self._files = None + + # initialize variables to cache the data + self._dict: dict = None + self._graph = None + + self._metadata = None + + @property + def uri(self) -> URI: + return self._uri + + @property + def metadata(self) -> ROCrateMetadata: + if not self._metadata: + self._metadata = ROCrateMetadata(self) + return self._metadata + + @abstractmethod + def size(self) -> int: + pass + + @property + @abstractmethod + def list_files(self) -> list[Path]: + pass + + def __parse_path__(self, path: Path) -> Path: + assert path, "Path cannot be None" + # if the path is absolute, return it + if path.is_absolute(): + return path + try: + # if the path is relative, try to resolve it + return self.uri.as_path().absolute() / path.relative_to(self.uri.as_path()) + except ValueError: + # if the path cannot be resolved, return the absolute path + return self.uri.as_path().absolute() / path + + def has_descriptor(self) -> bool: + return (self.uri.as_path().absolute() / self.metadata.METADATA_FILE_DESCRIPTOR).is_file() + + def has_file(self, path: Path) -> bool: + try: + return self.__parse_path__(path).is_file() + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return False + + def has_directory(self, path: Path) -> bool: + try: + return self.__parse_path__(path).is_dir() + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + return False + + @abstractmethod + def get_file_size(self, path: Path) -> int: + pass + + @abstractmethod + def get_file_content(self, path: Path, binary_mode: bool = True) -> Union[str, bytes]: + pass + + @staticmethod + def get_external_file_content(uri: str, binary_mode: bool = True) -> Union[str, bytes]: + response = requests.get(str(uri)) + response.raise_for_status() + return response.content if binary_mode else response.text + + @staticmethod + def get_external_file_size(uri: str) -> int: + response = requests.head(str(uri)) + response.raise_for_status() + return int(response.headers.get('Content-Length')) + + @staticmethod + def new_instance(uri: Union[str, Path, URI]) -> 'ROCrate': + # check if the URI is valid + if not uri: + raise ValueError("Invalid URI") + if not isinstance(uri, URI): + uri = URI(uri) + # check if the URI is a local directory + if uri.is_local_directory(): + return ROCrateLocalFolder(uri) + # check if the URI is a local zip file + if uri.is_local_file(): + return ROCrateLocalZip(uri) + # check if the URI is a remote zip file + if uri.is_remote_resource(): + return ROCrateRemoteZip(uri) + # if the URI is not supported, raise an error + raise ROCrateInvalidURIError(uri=uri, message="Unsupported URI") + + +class ROCrateLocalFolder(ROCrate): + + def __init__(self, path: Union[str, Path, URI]): + super().__init__(path) + + # cache the list of files + self._files = None + + # check if the path is a directory + if not self.has_directory(self.uri.as_path()): + raise ROCrateInvalidURIError(uri=path) + + @property + def size(self) -> int: + return sum(f.stat().st_size for f in self.list_files() if f.is_file()) + + def list_files(self) -> list[Path]: + if not self._files: + self._files = [] + base_path = self.uri.as_path() + for file in base_path.rglob('*'): + if file.is_file(): + self._files.append(base_path / file) + return self._files + + def get_file_size(self, path: Path) -> int: + path = self.__parse_path__(path) + if not self.has_file(path): + raise FileNotFoundError(f"File not found: {path}") + return path.stat().st_size + + def get_file_content(self, path: Path, binary_mode: bool = True) -> Union[str, bytes]: + path = self.__parse_path__(path) + if not self.has_file(path): + raise FileNotFoundError(f"File not found: {path}") + return path.read_bytes() if binary_mode else path.read_text() + + +class ROCrateLocalZip(ROCrate): + + def __init__(self, path: Union[str, Path, URI], init_zip: bool = True): + super().__init__(path) + + # initialize the zip reference + self._zipref = None + if init_zip: + self.__init_zip_reference__() + + # cache the list of files + self._files = None + + def __del__(self): + if self._zipref and self._zipref.fp is not None: + self._zipref.close() + del self._zipref + + @property + def size(self) -> int: + return self.uri.as_path().stat().st_size + + def __init_zip_reference__(self): + path = self.uri.as_path() + # check if the path is a file + if not self.uri.as_path().is_file(): + raise ROCrateInvalidURIError(uri=path) + # check if the file is a zip file + if not self.uri.as_path().suffix == '.zip': + raise ROCrateInvalidURIError(uri=path) + self._zipref = zipfile.ZipFile(path) + logger.debug("Initialized zip reference: %s", self._zipref) + + def __get_file_info__(self, path: Path) -> zipfile.ZipInfo: + return self._zipref.getinfo(str(path)) + + def has_descriptor(self) -> bool: + return ROCrateMetadata.METADATA_FILE_DESCRIPTOR in [str(_.name) for _ in self.list_files()] + + def has_file(self, path: Path) -> bool: + if path in self.list_files(): + info = self.__get_file_info__(path) + return not info.is_dir() + return False + + def has_directory(self, path: Path) -> bool: + if path in self.list_files(): + info = self.__get_file_info__(path) + return info.is_dir() + return False + + def list_files(self) -> list[Path]: + if not self._files: + self._files = [] + for file in self._zipref.namelist(): + self._files.append(Path(file)) + return self._files + + def get_file_size(self, path: Path) -> int: + return self._zipref.getinfo(str(path)).file_size + + def get_file_content(self, path: Path, binary_mode: bool = True) -> Union[str, bytes]: + if not self.has_file(path): + raise FileNotFoundError(f"File not found: {path}") + data = self._zipref.read(str(path)) + return data if binary_mode else data.decode('utf-8') + + +class ROCrateRemoteZip(ROCrateLocalZip): + + def __init__(self, path: Union[str, Path, URI]): + super().__init__(path, init_zip=False) + + logger.debug("Size: %s", self.size) + + # # initialize the zip reference + self.__init_zip_reference__() + + def __init_zip_reference__(self): + url = str(self.uri) + + # check if the URI is available + if not self.uri.is_available(): + raise ROCrateInvalidURIError(uri=url, message="URI is not available") + + # Step 1: Fetch the last 22 bytes to find the EOCD record + eocd_data = self.__fetch_range__(url, -22, '') + + # Step 2: Find the EOCD record + eocd_offset = self.__find_eocd__(eocd_data) + + # Step 3: Fetch the EOCD and parse it + eocd_full_data = self.__fetch_range__(url, -22 - eocd_offset, -1) + central_directory_offset, central_directory_size = self.__parse_eocd__(eocd_full_data) + + # Step 4: Fetch the central directory + central_directory_data = self.__fetch_range__(url, central_directory_offset, + central_directory_offset + central_directory_size - 1) + # Step 5: Parse the central directory and return the zip file + self._zipref = zipfile.ZipFile(io.BytesIO(central_directory_data)) + + @property + def size(self) -> int: + response = requests.head(str(self.uri)) + response.raise_for_status() # Check if the request was successful + file_size = response.headers.get('Content-Length') + if file_size is not None: + return int(file_size) + else: + raise Exception("Could not determine the file size from the headers") + + @staticmethod + def __fetch_range__(uri: str, start, end): + headers = {'Range': f'bytes={start}-{end}'} + response = requests.get(uri, headers=headers) + response.raise_for_status() + return response.content + + @staticmethod + def __find_eocd__(data): + eocd_signature = b'PK\x05\x06' + eocd_offset = data.rfind(eocd_signature) + if eocd_offset == -1: + raise Exception("EOCD not found") + return eocd_offset + + @staticmethod + def __parse_eocd__(data): + eocd_size = struct.calcsize('<4s4H2LH') + eocd = struct.unpack('<4s4H2LH', data[-eocd_size:]) + central_directory_size = eocd[5] + central_directory_offset = eocd[6] + return central_directory_offset, central_directory_size diff --git a/rocrate_validator/services.py b/rocrate_validator/services.py new file mode 100644 index 00000000..346d8a62 --- /dev/null +++ b/rocrate_validator/services.py @@ -0,0 +1,191 @@ +# Copyright (c) 2024 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 shutil +import tempfile +import zipfile +from pathlib import Path +from typing import Optional, Union + +import requests +import requests_cache + +import rocrate_validator.log as logging +from rocrate_validator.constants import DEFAULT_PROFILE_IDENTIFIER +from rocrate_validator.errors import ProfileNotFound +from rocrate_validator.events import Subscriber +from rocrate_validator.models import (Profile, Severity, ValidationResult, + ValidationSettings, Validator) +from rocrate_validator.utils import URI, get_profiles_path + +# set the default profiles path +DEFAULT_PROFILES_PATH = get_profiles_path() + +# set up logging +logger = logging.getLogger(__name__) + + +def detect_profiles(settings: Union[dict, ValidationSettings]) -> list[Profile]: + # initialize the validator + validator = __initialise_validator__(settings) + # detect the profiles + profiles = validator.detect_rocrate_profiles() + logger.debug("Profiles detected: %s", profiles) + return profiles + + +def validate(settings: Union[dict, ValidationSettings], + subscribers: Optional[list[Subscriber]] = None) -> ValidationResult: + """ + Validate a RO-Crate against a profile + """ + # initialize the validator + validator = __initialise_validator__(settings, subscribers) + # validate the RO-Crate + result = validator.validate() + logger.debug("Validation completed: %s", result) + return result + + +def __initialise_validator__(settings: Union[dict, ValidationSettings], + subscribers: Optional[list[Subscriber]] = None) -> Validator: + """ + Validate a RO-Crate against a profile + """ + # if settings is a dict, convert to ValidationSettings + settings = ValidationSettings.parse(settings) + + # parse the rocrate path + rocrate_path: URI = URI(settings.data_path) + logger.debug("Validating RO-Crate: %s", rocrate_path) + + # check if the RO-Crate exists + if not rocrate_path.is_available(): + raise FileNotFoundError(f"RO-Crate not found: {rocrate_path}") + + # check if the requests cache is enabled + if settings.http_cache_timeout > 0: + # Set up requests cache + requests_cache.install_cache( + '/tmp/rocrate_validator_cache', + expire_after=settings.http_cache_timeout, # Cache expiration time in seconds + backend='sqlite', # Use SQLite backend + allowable_methods=('GET',), # Cache GET + allowable_codes=(200, 302, 404) # Cache responses with these status codes + ) + + # check if remote validation is enabled + remote_validation = settings.remote_validation + logger.debug("Remote validation: %s", remote_validation) + if remote_validation: + # create a validator + validator = Validator(settings) + logger.debug("Validator created. Starting validation...") + if subscribers: + for subscriber in subscribers: + validator.add_subscriber(subscriber) + return validator + + def __init_validator__(settings: ValidationSettings) -> Validator: + # create a validator + validator = Validator(settings) + logger.debug("Validator created. Starting validation...") + if subscribers: + for subscriber in subscribers: + validator.add_subscriber(subscriber) + return validator + + def __extract_and_validate_rocrate__(rocrate_path: Path): + # store the original data path + original_data_path = settings.data_path + + with tempfile.TemporaryDirectory() as tmp_dir: + try: + # extract the RO-Crate to the temporary directory + with zipfile.ZipFile(rocrate_path, "r") as zip_ref: + zip_ref.extractall(tmp_dir) + logger.debug("RO-Crate extracted to temporary directory: %s", tmp_dir) + # update the data path to point to the temporary directory + settings.data_path = Path(tmp_dir) + # continue with the validation process + return __init_validator__(settings) + finally: + # restore the original data path + settings.data_path = original_data_path + logger.debug("Original data path restored: %s", original_data_path) + + # check if the RO-Crate is a remote RO-Crate, + # i.e., if the RO-Crate is a URL. If so, download the RO-Crate + # and extract it to a temporary directory. We support either http or https + # or ftp protocols to download the remote RO-Crate. + if rocrate_path.scheme in ('http', 'https', 'ftp'): + logger.debug("RO-Crate is a remote RO-Crate") + # create a temp folder to store the downloaded RO-Crate + with tempfile.NamedTemporaryFile(delete=False) as tmp_file: + # download the remote RO-Crate + with requests.get(rocrate_path.uri, stream=True, allow_redirects=True) as r: + with open(tmp_file.name, 'wb') as f: + shutil.copyfileobj(r.raw, f) + logger.debug("RO-Crate downloaded to temporary file: %s", tmp_file.name) + # continue with the validation process by extracting the RO-Crate and validating it + return __extract_and_validate_rocrate__(Path(tmp_file.name)) + + # check if the RO-Crate is a ZIP file + elif rocrate_path.as_path().suffix == ".zip": + logger.debug("RO-Crate is a local ZIP file") + # continue with the validation process by extracting the RO-Crate and validating it + return __extract_and_validate_rocrate__(rocrate_path.as_path()) + + # if the RO-Crate is not a ZIP file, directly validate the RO-Crate + elif rocrate_path.is_local_directory(): + logger.debug("RO-Crate is a local directory") + settings.data_path = rocrate_path.as_path() + return __init_validator__(settings) + else: + raise ValueError( + f"Invalid RO-Crate URI: {rocrate_path}. " + "It MUST be a local directory or a ZIP file (local or remote).") + + +def get_profiles(profiles_path: Path = DEFAULT_PROFILES_PATH, + publicID: str = None, + severity=Severity.OPTIONAL, + allow_requirement_check_override: bool = + ValidationSettings.allow_requirement_check_override) -> list[Profile]: + """ + Load the profiles from the given path + """ + profiles = Profile.load_profiles(profiles_path, publicID=publicID, + severity=severity, + allow_requirement_check_override=allow_requirement_check_override) + logger.debug("Profiles loaded: %s", profiles) + return profiles + + +def get_profile(profiles_path: Path = DEFAULT_PROFILES_PATH, + profile_identifier: str = DEFAULT_PROFILE_IDENTIFIER, + publicID: str = None, + severity=Severity.OPTIONAL, + allow_requirement_check_override: bool = + ValidationSettings.allow_requirement_check_override) -> Profile: + """ + Load the profiles from the given path + """ + profiles = get_profiles(profiles_path, publicID=publicID, severity=severity, + allow_requirement_check_override=allow_requirement_check_override) + profile = next((p for p in profiles if p.identifier == profile_identifier), None) or \ + next((p for p in profiles if str(p.identifier).replace(f"-{p.version}", '') == profile_identifier), None) + if not profile: + raise ProfileNotFound(profile_identifier) + return profile diff --git a/rocrate_validator/utils.py b/rocrate_validator/utils.py new file mode 100644 index 00000000..a128722f --- /dev/null +++ b/rocrate_validator/utils.py @@ -0,0 +1,504 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import inspect +import os +import re +import sys +from importlib import import_module +from pathlib import Path +from typing import Optional, Union +from urllib.parse import ParseResult, parse_qsl, urlparse + +import requests +import toml +from rdflib import Graph + +import rocrate_validator.log as logging + +from . import constants, errors + +# current directory +CURRENT_DIR = os.path.dirname(os.path.realpath(__file__)) + + +# set up logging +logger = logging.getLogger(__name__) + +# Read the pyproject.toml file +config = toml.load(Path(CURRENT_DIR).parent / "pyproject.toml") + + +def run_git_command(command: list[str]) -> Optional[str]: + """ + Run a git command and return the output + + :param command: The git command + :return: The output of the command + """ + import subprocess + + try: + output = subprocess.check_output(command).decode().strip() + return output + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.debug(e) + return None + + +def get_git_commit() -> str: + """ + Get the git commit hash + + :return: The git commit hash + """ + return run_git_command(['git', 'rev-parse', '--short', 'HEAD']) + + +def is_release_tag(git_sha: str) -> bool: + """ + Check whether a git sha corresponds to a release tag + + :param git_sha: The git sha + :return: True if the sha corresponds to a release tag, False otherwise + """ + tags = run_git_command(['git', 'tag', '--points-at', git_sha]) + return bool(tags) + + +def get_commit_distance(tag: Optional[str] = None) -> int: + """ + Get the distance in commits between the current commit and the last tag + + :return: The distance in commits + """ + if not tag: + tag = get_last_tag() + return int(run_git_command(['git', 'rev-list', '--count', 'HEAD' if not tag else f"{tag}..HEAD"])) + + +def get_last_tag() -> str: + """ + Get the last tag in the git repository + + :return: The last tag + """ + return run_git_command(['git', 'describe', '--tags', '--abbrev=0']) + +# write a function to checks whether the are any uncommitted changes in the repository + + +def has_uncommitted_changes() -> bool: + """ + Check whether there are any uncommitted changes in the repository + + :return: True if there are uncommitted changes, False otherwise + """ + return bool(run_git_command(['git', 'status', '--porcelain'])) + + +def get_version() -> str: + """ + Get the version of the package + + :return: The version + """ + version = None + declared_version = config["tool"]["poetry"]["version"] + commit_sha = get_git_commit() + is_release = is_release_tag(commit_sha) + latest_tag = get_last_tag() + if is_release: + if declared_version != latest_tag: + logger.warning("The declared version %s is different from the last tag %s", declared_version, latest_tag) + version = latest_tag + else: + commit_distance = get_commit_distance(latest_tag) + if commit_sha: + version = f"{declared_version}_{commit_sha}+{commit_distance}" + else: + version = declared_version + dirty = has_uncommitted_changes() + return f"{version}-dirty" if dirty else version + + +def get_config(property: Optional[str] = None) -> dict: + """ + Get the configuration for the package or a specific property + + :param property_name: The property name + :return: The configuration + """ + if property: + return config["tool"]["rocrate_validator"][property] + return config["tool"]["rocrate_validator"] + + +def get_file_descriptor_path(rocrate_path: Path) -> Path: + """ + Get the path to the metadata file in the RO-Crate + + :param rocrate_path: The path to the RO-Crate + :return: The path to the metadata file + """ + return Path(rocrate_path) / constants.ROCRATE_METADATA_FILE + + +def get_profiles_path() -> Path: + """ + Get the path to the profiles directory from the default paths + + :param not_exist_ok: If True, return the path even if it does not exist + + :return: The path to the profiles directory + """ + return Path(CURRENT_DIR) / constants.DEFAULT_PROFILES_PATH + + +def get_format_extension(serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES) -> str: + """ + Get the file extension for the RDF serialization format + + :param format: The RDF serialization format + :return: The file extension + + :raises InvalidSerializationFormat: If the format is not valid + """ + try: + return constants.RDF_SERIALIZATION_FILE_FORMAT_MAP[serialization_format] + except KeyError as exc: + logger.error("Invalid RDF serialization format: %s", serialization_format) + raise errors.InvalidSerializationFormat(serialization_format) from exc + + +def get_all_files( + directory: str = '.', + serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES = "turtle") -> list[str]: + """ + Get all the files in the directory matching the format. + + :param directory: The directory to search + :param format: The RDF serialization format + :return: A list of file paths + """ + # initialize an empty list to store the file paths + file_paths = [] + + # extension + extension = get_format_extension(serialization_format) + + # iterate through the directory and subdirectories + for root, _, files in os.walk(directory): + # iterate through the files + for file in files: + # check if the file has a .ttl extension + if file.endswith(extension): + # append the file path to the list + file_paths.append(os.path.join(root, file)) + # return the list of file paths + return file_paths + + +def get_graphs_paths(graphs_dir: str = CURRENT_DIR, + serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES = "turtle") -> list[str]: + """ + Get the paths to all the graphs in the directory + + :param graphs_dir: The directory containing the graphs + :param format: The RDF serialization format + :return: A list of graph paths + """ + return get_all_files(directory=graphs_dir, serialization_format=serialization_format) + + +def get_full_graph( + graphs_dir: str, + serialization_format: constants.RDF_SERIALIZATION_FORMATS_TYPES = "turtle", + publicID: str = ".") -> Graph: + """ + Get the full graph from the directory + + :param graphs_dir: The directory containing the graphs + :param format: The RDF serialization format + :param publicID: The public ID + :return: The full graph + """ + full_graph = Graph() + graphs_paths = get_graphs_paths(graphs_dir, serialization_format=serialization_format) + for graph_path in graphs_paths: + full_graph.parse(graph_path, format="turtle", publicID=publicID) + logger.debug("Loaded triples from %s", graph_path) + return full_graph + + +def get_classes_from_file(file_path: Path, + filter_class: Optional[type] = None, + class_name_suffix: Optional[str] = None) -> dict[str, type]: + """Get all classes in a Python file """ + # ensure the file path is a Path object + assert file_path, "The file path is required" + if not isinstance(file_path, Path): + file_path = Path(file_path) + + # Check if the file is a Python file + if not file_path.exists(): + raise ValueError("The file does not exist") + + # Check if the file is a Python file + if file_path.suffix != ".py": + raise ValueError("The file is not a Python file") + + # Get the module name from the file path + module_name = file_path.stem + logger.debug("Module: %r", module_name) + + # Add the directory containing the file to the system path + sys.path.insert(0, os.path.dirname(file_path)) + + # Import the module + module = import_module(module_name) + logger.debug("Module: %r", module) + + # Get all classes in the module that are subclasses of filter_class + classes = {name: cls for name, cls in inspect.getmembers(module, inspect.isclass) + if cls.__module__ == module_name + and (not class_name_suffix or cls.__name__.endswith(class_name_suffix)) + and (not filter_class or (issubclass(cls, filter_class) and cls != filter_class))} + + return classes + + +def get_requirement_name_from_file(file: Path, check_name: Optional[str] = None) -> str: + """ + Get the requirement name from the file + + :param file: The file + :return: The requirement name + """ + assert file, "The file is required" + if not isinstance(file, Path): + file = Path(file) + base_name = to_camel_case(file.stem) + if check_name: + return f"{base_name}.{check_name.replace('Check', '')}" + return base_name + + +def get_requirement_class_by_name(requirement_name: str) -> type: + """ + Dynamically load the module of the class and return the class""" + + # Split the requirement name into module and class + module_name, class_name = requirement_name.rsplit(".", 1) + logger.debug("Module: %r", module_name) + logger.debug("Class: %r", class_name) + + # convert the module name to a path + module_path = module_name.replace(".", "/") + # add the path to the system path + sys.path.insert(0, os.path.dirname(module_path)) + + # Import the module + module = import_module(module_name) + + # Get the class from the module + return getattr(module, class_name) + + +def to_camel_case(snake_str: str) -> str: + """ + Convert a snake case string to camel case + + :param snake_str: The snake case string + :return: The camel case string + """ + components = re.split('_|-', snake_str) + return components[0].capitalize() + ''.join(x.title() for x in components[1:]) + + +class URI: + + REMOTE_SUPPORTED_SCHEMA = ('http', 'https', 'ftp') + + def __init__(self, uri: Union[str, Path]): + self._uri = uri = str(uri) + try: + # map local path to URI with file scheme + if not re.match(r'^\w+://', uri): + uri = f"file://{uri}" + # parse the value to extract the scheme + self._parse_result = urlparse(uri) + assert self.scheme in self.REMOTE_SUPPORTED_SCHEMA + ('file',), "Invalid URI scheme" + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.debug(e) + raise ValueError("Invalid URI: %s", uri) + + @property + def uri(self) -> str: + return self._uri + + @property + def base_uri(self) -> str: + return f"{self.scheme}://{self._parse_result.netloc}{self._parse_result.path}" + + @property + def parse_result(self) -> ParseResult: + return self._parse_result + + @property + def scheme(self) -> str: + return self._parse_result.scheme + + @property + def fragment(self) -> Optional[str]: + fragment = self._parse_result.fragment + return fragment if fragment else None + + def get_scheme(self) -> str: + return self._parse_result.scheme + + def get_netloc(self) -> str: + return self._parse_result.netloc + + def get_path(self) -> str: + return self._parse_result.path + + def get_query_string(self) -> str: + return self._parse_result.query + + def get_query_param(self, param: str) -> Optional[str]: + query_params = dict(parse_qsl(self._parse_result.query)) + return query_params.get(param) + + def as_path(self) -> Path: + if not self.is_local_resource(): + raise ValueError("URI is not a local resource") + return Path(self._uri) + + def is_remote_resource(self) -> bool: + return self.scheme in self.REMOTE_SUPPORTED_SCHEMA + + def is_local_resource(self) -> bool: + return not self.is_remote_resource() + + def is_local_directory(self) -> bool: + return self.is_local_resource() and self.as_path().is_dir() + + def is_local_file(self) -> bool: + return self.is_local_resource() and self.as_path().is_file() + + def is_available(self) -> bool: + """Check if the resource is available""" + if self.is_remote_resource(): + try: + response = requests.head(self._uri, allow_redirects=True) + return response.status_code in (200, 302) + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.debug(e) + return False + return Path(self._uri).exists() + + def __str__(self): + return self._uri + + def __repr__(self): + return f"URI(uri={self._uri})" + + def __eq__(self, other): + if isinstance(other, URI): + return self._uri == other.uri + return False + + def __hash__(self): + return hash(self._uri) + + +class MapIndex: + + def __init__(self, name: str, unique: bool = False): + self.name = name + self.unique = unique + + +class MultiIndexMap: + def __init__(self, key: str = "id", indexes: list[MapIndex] = None): + self._key = key + # initialize an empty dictionary to store the indexes + self._indices: list[MapIndex] = {} + if indexes: + for index in indexes: + self.add_index(index) + # initialize an empty dictionary to store the data + self._data = {} + + @property + def key(self) -> str: + return self._key + + @property + def keys(self) -> list[str]: + return list(self._data.keys()) + + @property + def indices(self) -> list[str]: + return list(self._indices.keys()) + + def add_index(self, index: MapIndex): + self._indices[index.name] = {"__meta__": index} + + def remove_index(self, index_name: str): + self._indices.pop(index_name) + + def get_index(self, index_name: str) -> MapIndex: + return self._indices.get(index_name)["__meta__"] + + def add(self, key, obj, **indices): + self._data[key] = obj + for index_name, index_value in indices.items(): + index = self.get_index(index_name) + assert isinstance(index, MapIndex), f"Index {index_name} does not exist" + if index_name in self._indices: + if index_value not in self._indices[index_name]: + self._indices[index_name][index_value] = set() if not index.unique else key + if not index.unique: + self._indices[index_name][index_value].add(key) + + def remove(self, key): + obj = self._data.pop(key) + for index_name, index in self._indices.items(): + index_value = getattr(obj, index_name) + if index_value in index: + index[index_value].remove(key) + + def values(self): + return self._data.values() + + def get_by_key(self, key): + return self._data.get(key) + + def get_by_index(self, index_name, index_value): + if index_name == self._key: + return self._data.get(index_value) + index = self.get_index(index_name) + assert isinstance(index, MapIndex), f"Index {index_name} does not exist" + if index.unique: + key = self._indices.get(index_name, {}).get(index_value) + return self._data.get(key) + keys = self._indices.get(index_name, {}).get(index_value, set()) + return [self._data[key] for key in keys] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..68664469 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,11 @@ +[flake8] +max-line-length = 120 +exclude = + .git + .github + .vscode + .venv + __pycache__ + build + dist + rocrate_validator.egg-info diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 00000000..47a867cf --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,107 @@ +# Copyright (c) 2024 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. + +# calculate the absolute path of the rocrate-validator package +# and add it to the system path +import os + +from pytest import fixture + +import rocrate_validator.log as logging + +# set up logging +logging.basicConfig( + level="warning", + modules_config={ + # "rocrate_validator.models": {"level": logging.DEBUG} + } +) + +CURRENT_PATH = os.path.dirname(os.path.realpath(__file__)) + +# test data paths +TEST_DATA_PATH = os.path.abspath(os.path.join(CURRENT_PATH, "data")) + +# profiles paths +PROFILES_PATH = os.path.abspath(f"{CURRENT_PATH}/../rocrate_validator/profiles") + + +@fixture +def random_path(): + return "/tmp/random_path" + + +@fixture +def ro_crates_path(): + return f"{TEST_DATA_PATH}/crates" + + +@fixture +def fake_profiles_path(): + return f"{TEST_DATA_PATH}/profiles/fake" + + +@fixture +def profiles_requirement_loading(): + return f"{TEST_DATA_PATH}/profiles/requirement_loading" + + +@fixture +def profiles_with_free_folder_structure_path(): + return f"{TEST_DATA_PATH}/profiles/free_folder_structure" + + +@fixture +def fake_versioned_profiles_path(): + return f"{TEST_DATA_PATH}/profiles/fake_versioned_profiles" + + +@fixture +def fake_conflicting_versioned_profiles_path(): + return f"{TEST_DATA_PATH}/profiles/conflicting_versions" + + +@fixture +def graphs_path(): + return f"{TEST_DATA_PATH}/graphs" + + +@fixture +def profiles_path(): + return PROFILES_PATH + + +@fixture +def graph_books_path(): + return f"{TEST_DATA_PATH}/graphs/books" + + +@fixture +def ro_crate_profile_path(profiles_path): + return os.path.join(profiles_path, "ro-crate") + + +@fixture +def ro_crate_profile_must_path(ro_crate_profile_path): + return os.path.join(ro_crate_profile_path, "must") + + +@fixture +def ro_crate_profile_should_path(ro_crate_profile_path): + return os.path.join(ro_crate_profile_path, "should") + + +@fixture +def ro_crate_profile_may_path(ro_crate_profile_path): + return os.path.join(ro_crate_profile_path, "may") diff --git a/tests/data/crates/invalid/0_file_descriptor_format/invalid_json_format/ro-crate-metadata.json b/tests/data/crates/invalid/0_file_descriptor_format/invalid_json_format/ro-crate-metadata.json new file mode 100644 index 00000000..0cb1f1e8 --- /dev/null +++ b/tests/data/crates/invalid/0_file_descriptor_format/invalid_json_format/ro-crate-metadata.json @@ -0,0 +1,2 @@ +This is a not valid JSON file. Please check the file and try again. +} diff --git a/tests/data/crates/invalid/0_file_descriptor_format/invalid_jsonld_format/missing_context/ro-crate-metadata.json b/tests/data/crates/invalid/0_file_descriptor_format/invalid_jsonld_format/missing_context/ro-crate-metadata.json new file mode 100644 index 00000000..de1793da --- /dev/null +++ b/tests/data/crates/invalid/0_file_descriptor_format/invalid_jsonld_format/missing_context/ro-crate-metadata.json @@ -0,0 +1,129 @@ +{ + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": "MIT", + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/0_file_descriptor_format/invalid_jsonld_format/missing_id/ro-crate-metadata.json b/tests/data/crates/invalid/0_file_descriptor_format/invalid_jsonld_format/missing_id/ro-crate-metadata.json new file mode 100644 index 00000000..c024fd72 --- /dev/null +++ b/tests/data/crates/invalid/0_file_descriptor_format/invalid_jsonld_format/missing_id/ro-crate-metadata.json @@ -0,0 +1,154 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "should_be_the_id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "should_be_the_id": "my-workflow.ga" + }, + { + "should_be_the_id": "my-workflow-test.yml" + }, + { + "should_be_the_id": "test-data/" + }, + { + "should_be_the_id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": "MIT", + "mainEntity": { + "should_be_the_id": "my-workflow.ga" + }, + "mentions": [ + { + "should_be_the_id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "should_be_the_id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "should_be_the_id": "./" + }, + "conformsTo": [ + { + "should_be_the_id": "https://w3id.org/ro/crate/1.1" + }, + { + "should_be_the_id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "should_be_the_id": "my-workflow.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "name": "MyWorkflow", + "programmingLanguage": { + "should_be_the_id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "should_be_the_id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "should_be_the_id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "should_be_the_id": "https://galaxyproject.org/" + } + }, + { + "should_be_the_id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "should_be_the_id": "my-workflow-test.yml" + }, + "instance": [ + { + "should_be_the_id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "should_be_the_id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "should_be_the_id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "should_be_the_id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "should_be_the_id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "should_be_the_id": "https://github.com" + } + }, + { + "should_be_the_id": "my-workflow-test.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "should_be_the_id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "should_be_the_id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "should_be_the_id": "https://github.com/galaxyproject/planemo" + } + }, + { + "should_be_the_id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "should_be_the_id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/0_file_descriptor_format/invalid_jsonld_format/missing_type/ro-crate-metadata.json b/tests/data/crates/invalid/0_file_descriptor_format/invalid_jsonld_format/missing_type/ro-crate-metadata.json new file mode 100644 index 00000000..17f43385 --- /dev/null +++ b/tests/data/crates/invalid/0_file_descriptor_format/invalid_jsonld_format/missing_type/ro-crate-metadata.json @@ -0,0 +1,151 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "./", + "should_be_the_type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": "MIT", + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "should_be_the_type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "my-workflow.ga", + "should_be_the_type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "should_be_the_type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "should_be_the_type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "should_be_the_type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "should_be_the_type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "should_be_the_type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "should_be_the_type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "should_be_the_type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "should_be_the_type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/0_main_workflow/main_workflow_bad_conformsto/ro-crate-metadata.json b/tests/data/crates/invalid/0_main_workflow/main_workflow_bad_conformsto/ro-crate-metadata.json new file mode 100644 index 00000000..0f454e80 --- /dev/null +++ b/tests/data/crates/invalid/0_main_workflow/main_workflow_bad_conformsto/ro-crate-metadata.json @@ -0,0 +1,137 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + }, + { + "@id": "test/" + }, + { + "@id": "examples/" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/0.5-DRAFT-2020_07_21" + }, + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + }, + { + "@id": "test/", + "@type": "Dataset" + }, + { + "@id": "examples/", + "@type": "Dataset" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/0_main_workflow/main_workflow_bad_conformsto/sort-and-change-case.ga b/tests/data/crates/invalid/0_main_workflow/main_workflow_bad_conformsto/sort-and-change-case.ga new file mode 100644 index 00000000..5a199969 --- /dev/null +++ b/tests/data/crates/invalid/0_main_workflow/main_workflow_bad_conformsto/sort-and-change-case.ga @@ -0,0 +1,118 @@ +{ + "uuid": "e2a8566c-c025-4181-9e90-7ed29d4e4df1", + "tags": [], + "format-version": "0.1", + "name": "sort-and-change-case", + "version": 0, + "steps": { + "0": { + "tool_id": null, + "tool_version": null, + "outputs": [], + "workflow_outputs": [], + "input_connections": {}, + "tool_state": "{}", + "id": 0, + "uuid": "5a36fad2-66c7-4b9e-8759-0fbcae9b8541", + "errors": null, + "name": "Input dataset", + "label": "bed_input", + "inputs": [], + "position": { + "top": 200, + "left": 200 + }, + "annotation": "", + "content_id": null, + "type": "data_input" + }, + "1": { + "tool_id": "sort1", + "tool_version": "1.1.0", + "outputs": [ + { + "type": "input", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "8237f71a-bc2a-494e-a63c-09c1e65ef7c8", + "label": "sorted_bed" + } + ], + "input_connections": { + "input": { + "output_name": "output", + "id": 0 + } + }, + "tool_state": "{\"__page__\": null, \"style\": \"\\\"alpha\\\"\", \"column\": \"\\\"1\\\"\", \"__rerun_remap_job_id__\": null, \"column_set\": \"[]\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\", \"header_lines\": \"\\\"0\\\"\", \"order\": \"\\\"ASC\\\"\"}", + "id": 1, + "uuid": "0b6b3cda-c75f-452b-85b1-8ae4f3302ba4", + "errors": null, + "name": "Sort", + "post_job_actions": {}, + "label": "sort", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Sort" + } + ], + "position": { + "top": 200, + "left": 420 + }, + "annotation": "", + "content_id": "sort1", + "type": "tool" + }, + "2": { + "tool_id": "ChangeCase", + "tool_version": "1.0.0", + "outputs": [ + { + "type": "tabular", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "c31cd733-dab6-4d50-9fec-b644d162397b", + "label": "uppercase_bed" + } + ], + "input_connections": { + "input": { + "output_name": "out_file1", + "id": 1 + } + }, + "tool_state": "{\"__page__\": null, \"casing\": \"\\\"up\\\"\", \"__rerun_remap_job_id__\": null, \"cols\": \"\\\"c1\\\"\", \"delimiter\": \"\\\"TAB\\\"\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\"}", + "id": 2, + "uuid": "9698bcde-0729-48fe-b88d-ccfb6f6153b4", + "errors": null, + "name": "Change Case", + "post_job_actions": {}, + "label": "change_case", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Change Case" + } + ], + "position": { + "top": 200, + "left": 640 + }, + "annotation": "", + "content_id": "ChangeCase", + "type": "tool" + } + }, + "annotation": "", + "a_galaxy_workflow": "true" +} diff --git a/tests/data/crates/invalid/0_main_workflow/main_workflow_bad_type/ro-crate-metadata.json b/tests/data/crates/invalid/0_main_workflow/main_workflow_bad_type/ro-crate-metadata.json new file mode 100644 index 00000000..b6d3d9cb --- /dev/null +++ b/tests/data/crates/invalid/0_main_workflow/main_workflow_bad_type/ro-crate-metadata.json @@ -0,0 +1,113 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "https://spdx.org/licenses/Apache-2.0.html", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "Action", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/0_main_workflow/main_workflow_cwl_desc_bad_type/ro-crate-metadata.json b/tests/data/crates/invalid/0_main_workflow/main_workflow_cwl_desc_bad_type/ro-crate-metadata.json new file mode 100644 index 00000000..51cee962 --- /dev/null +++ b/tests/data/crates/invalid/0_main_workflow/main_workflow_cwl_desc_bad_type/ro-crate-metadata.json @@ -0,0 +1,111 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "https://spdx.org/licenses/Apache-2.0.html", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/0_main_workflow/main_workflow_cwl_desc_no_lang/ro-crate-metadata.json b/tests/data/crates/invalid/0_main_workflow/main_workflow_cwl_desc_no_lang/ro-crate-metadata.json new file mode 100644 index 00000000..513e57ad --- /dev/null +++ b/tests/data/crates/invalid/0_main_workflow/main_workflow_cwl_desc_no_lang/ro-crate-metadata.json @@ -0,0 +1,98 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "https://spdx.org/licenses/Apache-2.0.html", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case" + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/0_main_workflow/main_workflow_no_cwl_desc/ro-crate-metadata.json b/tests/data/crates/invalid/0_main_workflow/main_workflow_no_cwl_desc/ro-crate-metadata.json new file mode 100644 index 00000000..60a60937 --- /dev/null +++ b/tests/data/crates/invalid/0_main_workflow/main_workflow_no_cwl_desc/ro-crate-metadata.json @@ -0,0 +1,86 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "https://spdx.org/licenses/Apache-2.0.html", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/0_main_workflow/main_workflow_no_image/ro-crate-metadata.json b/tests/data/crates/invalid/0_main_workflow/main_workflow_no_image/ro-crate-metadata.json new file mode 100644 index 00000000..b746084b --- /dev/null +++ b/tests/data/crates/invalid/0_main_workflow/main_workflow_no_image/ro-crate-metadata.json @@ -0,0 +1,103 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "https://spdx.org/licenses/Apache-2.0.html", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/0_main_workflow/main_workflow_no_lang/ro-crate-metadata.json b/tests/data/crates/invalid/0_main_workflow/main_workflow_no_lang/ro-crate-metadata.json new file mode 100644 index 00000000..f9ca8cb3 --- /dev/null +++ b/tests/data/crates/invalid/0_main_workflow/main_workflow_no_lang/ro-crate-metadata.json @@ -0,0 +1,99 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "https://spdx.org/licenses/Apache-2.0.html", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/0_main_workflow/no_files/ro-crate-metadata.json b/tests/data/crates/invalid/0_main_workflow/no_files/ro-crate-metadata.json new file mode 100644 index 00000000..4bb00a12 --- /dev/null +++ b/tests/data/crates/invalid/0_main_workflow/no_files/ro-crate-metadata.json @@ -0,0 +1,113 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "https://spdx.org/licenses/Apache-2.0.html", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_conforms_to/ro-crate-metadata.json b/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_conforms_to/ro-crate-metadata.json new file mode 100644 index 00000000..860a50a6 --- /dev/null +++ b/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_conforms_to/ro-crate-metadata.json @@ -0,0 +1,155 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "description": "RO-Crate for MyWorkflow", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1-invalid" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + }, + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + } + ] +} diff --git a/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_entity_about/ro-crate-metadata.json b/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_entity_about/ro-crate-metadata.json new file mode 100644 index 00000000..6d2533a5 --- /dev/null +++ b/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_entity_about/ro-crate-metadata.json @@ -0,0 +1,151 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "description": "RO-Crate for MyWorkflow", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "test-data/" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_entity_about_type/ro-crate-metadata.json b/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_entity_about_type/ro-crate-metadata.json new file mode 100644 index 00000000..cb32f4d1 --- /dev/null +++ b/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_entity_about_type/ro-crate-metadata.json @@ -0,0 +1,151 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "description": "RO-Crate for MyWorkflow", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "my-workflow.ga" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_entity_type/ro-crate-metadata.json b/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_entity_type/ro-crate-metadata.json new file mode 100644 index 00000000..993e7e7d --- /dev/null +++ b/tests/data/crates/invalid/1_file_descriptor_metadata/invalid_entity_type/ro-crate-metadata.json @@ -0,0 +1,151 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "description": "RO-Crate for MyWorkflow", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWorkInvalid", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/1_file_descriptor_metadata/missing_conforms_to/ro-crate-metadata.json b/tests/data/crates/invalid/1_file_descriptor_metadata/missing_conforms_to/ro-crate-metadata.json new file mode 100644 index 00000000..aac08965 --- /dev/null +++ b/tests/data/crates/invalid/1_file_descriptor_metadata/missing_conforms_to/ro-crate-metadata.json @@ -0,0 +1,155 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "description": "RO-Crate for MyWorkflow", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "missing-conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + }, + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + } + ] +} diff --git a/tests/data/crates/invalid/1_file_descriptor_metadata/missing_entity/ro-crate-metadata.json b/tests/data/crates/invalid/1_file_descriptor_metadata/missing_entity/ro-crate-metadata.json new file mode 100644 index 00000000..0a174f69 --- /dev/null +++ b/tests/data/crates/invalid/1_file_descriptor_metadata/missing_entity/ro-crate-metadata.json @@ -0,0 +1,143 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "description": "RO-Crate for MyWorkflow", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + }, + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + } + ] +} diff --git a/tests/data/crates/invalid/1_file_descriptor_metadata/missing_entity_about/ro-crate-metadata.json b/tests/data/crates/invalid/1_file_descriptor_metadata/missing_entity_about/ro-crate-metadata.json new file mode 100644 index 00000000..172bb709 --- /dev/null +++ b/tests/data/crates/invalid/1_file_descriptor_metadata/missing_entity_about/ro-crate-metadata.json @@ -0,0 +1,151 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "description": "RO-Crate for MyWorkflow", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWorkInvalid", + "missing-about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/1_wroc_crate/no_license/ro-crate-metadata.json b/tests/data/crates/invalid/1_wroc_crate/no_license/ro-crate-metadata.json new file mode 100644 index 00000000..7f490d69 --- /dev/null +++ b/tests/data/crates/invalid/1_wroc_crate/no_license/ro-crate-metadata.json @@ -0,0 +1,112 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/1_wroc_crate/no_license/sort-and-change-case.ga b/tests/data/crates/invalid/1_wroc_crate/no_license/sort-and-change-case.ga new file mode 100644 index 00000000..5a199969 --- /dev/null +++ b/tests/data/crates/invalid/1_wroc_crate/no_license/sort-and-change-case.ga @@ -0,0 +1,118 @@ +{ + "uuid": "e2a8566c-c025-4181-9e90-7ed29d4e4df1", + "tags": [], + "format-version": "0.1", + "name": "sort-and-change-case", + "version": 0, + "steps": { + "0": { + "tool_id": null, + "tool_version": null, + "outputs": [], + "workflow_outputs": [], + "input_connections": {}, + "tool_state": "{}", + "id": 0, + "uuid": "5a36fad2-66c7-4b9e-8759-0fbcae9b8541", + "errors": null, + "name": "Input dataset", + "label": "bed_input", + "inputs": [], + "position": { + "top": 200, + "left": 200 + }, + "annotation": "", + "content_id": null, + "type": "data_input" + }, + "1": { + "tool_id": "sort1", + "tool_version": "1.1.0", + "outputs": [ + { + "type": "input", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "8237f71a-bc2a-494e-a63c-09c1e65ef7c8", + "label": "sorted_bed" + } + ], + "input_connections": { + "input": { + "output_name": "output", + "id": 0 + } + }, + "tool_state": "{\"__page__\": null, \"style\": \"\\\"alpha\\\"\", \"column\": \"\\\"1\\\"\", \"__rerun_remap_job_id__\": null, \"column_set\": \"[]\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\", \"header_lines\": \"\\\"0\\\"\", \"order\": \"\\\"ASC\\\"\"}", + "id": 1, + "uuid": "0b6b3cda-c75f-452b-85b1-8ae4f3302ba4", + "errors": null, + "name": "Sort", + "post_job_actions": {}, + "label": "sort", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Sort" + } + ], + "position": { + "top": 200, + "left": 420 + }, + "annotation": "", + "content_id": "sort1", + "type": "tool" + }, + "2": { + "tool_id": "ChangeCase", + "tool_version": "1.0.0", + "outputs": [ + { + "type": "tabular", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "c31cd733-dab6-4d50-9fec-b644d162397b", + "label": "uppercase_bed" + } + ], + "input_connections": { + "input": { + "output_name": "out_file1", + "id": 1 + } + }, + "tool_state": "{\"__page__\": null, \"casing\": \"\\\"up\\\"\", \"__rerun_remap_job_id__\": null, \"cols\": \"\\\"c1\\\"\", \"delimiter\": \"\\\"TAB\\\"\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\"}", + "id": 2, + "uuid": "9698bcde-0729-48fe-b88d-ccfb6f6153b4", + "errors": null, + "name": "Change Case", + "post_job_actions": {}, + "label": "change_case", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Change Case" + } + ], + "position": { + "top": 200, + "left": 640 + }, + "annotation": "", + "content_id": "ChangeCase", + "type": "tool" + } + }, + "annotation": "", + "a_galaxy_workflow": "true" +} diff --git a/tests/data/crates/invalid/1_wroc_crate/no_mainentity/ro-crate-metadata.json b/tests/data/crates/invalid/1_wroc_crate/no_mainentity/ro-crate-metadata.json new file mode 100644 index 00000000..6169df3a --- /dev/null +++ b/tests/data/crates/invalid/1_wroc_crate/no_mainentity/ro-crate-metadata.json @@ -0,0 +1,134 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + }, + { + "@id": "test/" + }, + { + "@id": "examples/" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + } + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + }, + { + "@id": "test/", + "@type": "Dataset" + }, + { + "@id": "examples/", + "@type": "Dataset" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/1_wroc_crate/readme_not_about_crate/ro-crate-metadata.json b/tests/data/crates/invalid/1_wroc_crate/readme_not_about_crate/ro-crate-metadata.json new file mode 100644 index 00000000..ad81e806 --- /dev/null +++ b/tests/data/crates/invalid/1_wroc_crate/readme_not_about_crate/ro-crate-metadata.json @@ -0,0 +1,110 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "https://spdx.org/licenses/Apache-2.0.html", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/1_wroc_crate/readme_wrong_encoding_format/ro-crate-metadata.json b/tests/data/crates/invalid/1_wroc_crate/readme_wrong_encoding_format/ro-crate-metadata.json new file mode 100644 index 00000000..2669fb9f --- /dev/null +++ b/tests/data/crates/invalid/1_wroc_crate/readme_wrong_encoding_format/ro-crate-metadata.json @@ -0,0 +1,113 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "https://spdx.org/licenses/Apache-2.0.html", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/csv" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/invalid_root_date/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/invalid_root_date/ro-crate-metadata.json new file mode 100644 index 00000000..db778f91 --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/invalid_root_date/ro-crate-metadata.json @@ -0,0 +1,152 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024 Jan 01", + "description": "This RO Crate contains the workflow MyWorkflow", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": "MIT", + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/invalid_root_type/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/invalid_root_type/ro-crate-metadata.json new file mode 100644 index 00000000..a965b219 --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/invalid_root_type/ro-crate-metadata.json @@ -0,0 +1,154 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "./", + "@type": "WrongDatasetType", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": "MIT", + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/invalid_root_value/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/invalid_root_value/ro-crate-metadata.json new file mode 100644 index 00000000..a1ca75e0 --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/invalid_root_value/ro-crate-metadata.json @@ -0,0 +1,154 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "./invalidRootValue", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": "MIT", + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./invalidRootValue" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_description/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_description/ro-crate-metadata.json new file mode 100644 index 00000000..7a974b9c --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_description/ro-crate-metadata.json @@ -0,0 +1,150 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "name": "MyWorkflow", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ] + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + }, + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + } + ] +} diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_entity/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_entity/ro-crate-metadata.json new file mode 100644 index 00000000..92325fbd --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_entity/ro-crate-metadata.json @@ -0,0 +1,114 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_license/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_license/ro-crate-metadata.json new file mode 100644 index 00000000..77a5bf31 --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_license/ro-crate-metadata.json @@ -0,0 +1,151 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "name": "MyWorkflow", + "description": "A simple workflow for testing RO-Crate", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ] + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_license_description/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_license_description/ro-crate-metadata.json new file mode 100644 index 00000000..712c91f3 --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_license_description/ro-crate-metadata.json @@ -0,0 +1,152 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "name": "MyWorkflow RO-Crate", + "description": "RO-Crate for MyWorkflow", + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ] + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + }, + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license", + "name": "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Australia" + } + ] +} diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_license_name/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_license_name/ro-crate-metadata.json new file mode 100644 index 00000000..6487b7d3 --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_license_name/ro-crate-metadata.json @@ -0,0 +1,152 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "name": "MyWorkflow RO-Crate", + "description": "RO-Crate for MyWorkflow", + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ] + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + }, + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license", + "description": "Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Australia" + } + ] +} diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_name/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_name/ro-crate-metadata.json new file mode 100644 index 00000000..32cf0d2b --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/missing_root_name/ro-crate-metadata.json @@ -0,0 +1,150 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "description": "RO-Crate for MyWorkflow", + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/" + }, + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ] + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": ["File", "TestDefinition"], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + }, + { + "@id": "https://creativecommons.org/licenses/by-nc-sa/3.0/au/", + "@type": "license" + } + ] +} diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/recommended_root_value/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/recommended_root_value/ro-crate-metadata.json new file mode 100644 index 00000000..fd5d3b61 --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/recommended_root_value/ro-crate-metadata.json @@ -0,0 +1,154 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "GithubService": "https://w3id.org/ro/terms/test#GithubService", + "JenkinsService": "https://w3id.org/ro/terms/test#JenkinsService", + "PlanemoEngine": "https://w3id.org/ro/terms/test#PlanemoEngine", + "TestDefinition": "https://w3id.org/ro/terms/test#TestDefinition", + "TestInstance": "https://w3id.org/ro/terms/test#TestInstance", + "TestService": "https://w3id.org/ro/terms/test#TestService", + "TestSuite": "https://w3id.org/ro/terms/test#TestSuite", + "TravisService": "https://w3id.org/ro/terms/test#TravisService", + "definition": "https://w3id.org/ro/terms/test#definition", + "engineVersion": "https://w3id.org/ro/terms/test#engineVersion", + "instance": "https://w3id.org/ro/terms/test#instance", + "resource": "https://w3id.org/ro/terms/test#resource", + "runsOn": "https://w3id.org/ro/terms/test#runsOn" + } + ], + "@graph": [ + { + "@id": "https://TheROCrateRoot/", + "@type": "Dataset", + "datePublished": "2024-01-22T15:36:43+00:00", + "hasPart": [ + { + "@id": "my-workflow.ga" + }, + { + "@id": "my-workflow-test.yml" + }, + { + "@id": "test-data/" + }, + { + "@id": "README.md" + } + ], + "isBasedOn": "https://github.com/kikkomep/myworkflow", + "license": "MIT", + "mainEntity": { + "@id": "my-workflow.ga" + }, + "mentions": [ + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02" + } + ], + "name": "MyWorkflow" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "https://TheROCrateRoot/" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "my-workflow.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "name": "MyWorkflow", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "url": "https://github.com/kikkomep/myworkflow", + "version": "main" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#1d230a09-a465-411a-82bb-d7d4f3f1be02", + "@type": "TestSuite", + "definition": { + "@id": "my-workflow-test.yml" + }, + "instance": [ + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9" + } + ], + "mainEntity": { + "@id": "my-workflow.ga" + }, + "name": "Test suite for MyWorkflow" + }, + { + "@id": "#350f2567-6ed2-4080-b354-a0921f49a4a9", + "@type": "TestInstance", + "name": "GitHub Actions workflow for testing MyWorkflow", + "resource": "repos/kikkomep/myworkflow/actions/workflows/main.yml", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#GithubService" + }, + "url": "https://api.github.com" + }, + { + "@id": "https://w3id.org/ro/terms/test#GithubService", + "@type": "TestService", + "name": "Github Actions", + "url": { + "@id": "https://github.com" + } + }, + { + "@id": "my-workflow-test.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + }, + { + "@id": "test-data/", + "@type": "Dataset", + "description": "Data files for testing the workflow" + }, + { + "@id": "README.md", + "@type": "File", + "description": "Workflow documentation" + } + ] +} diff --git a/tests/data/crates/invalid/2_root_data_entity_metadata/valid_referenced_generic_data_entities/ro-crate-metadata.json b/tests/data/crates/invalid/2_root_data_entity_metadata/valid_referenced_generic_data_entities/ro-crate-metadata.json new file mode 100644 index 00000000..cfe2ab8f --- /dev/null +++ b/tests/data/crates/invalid/2_root_data_entity_metadata/valid_referenced_generic_data_entities/ro-crate-metadata.json @@ -0,0 +1,119 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Invalid RO Crate: referenced entities should be files of directories", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "foo/" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": "https://spdx.org/licenses/Apache-2.0.html" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": ["File", "SoftwareSourceCode", "HowTo"], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "foo/", + "@type": "UnknownCollection", + "hasPart": [ + { + "@id": "foo/xxx" + } + ] + }, + { + "@id": "foo/xxx", + "@type": "File" + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} diff --git a/tests/data/crates/invalid/2_wroc_descriptor/wroc_descriptor_bad_conforms_to/ro-crate-metadata.json b/tests/data/crates/invalid/2_wroc_descriptor/wroc_descriptor_bad_conforms_to/ro-crate-metadata.json new file mode 100644 index 00000000..70a0f622 --- /dev/null +++ b/tests/data/crates/invalid/2_wroc_descriptor/wroc_descriptor_bad_conforms_to/ro-crate-metadata.json @@ -0,0 +1,108 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "https://spdx.org/licenses/Apache-2.0.html", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + } + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/invalid/3_process_run_crate/action_bad_actionstatus/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_bad_actionstatus/ro-crate-metadata.json new file mode 100644 index 00000000..9bf81ab1 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_bad_actionstatus/ro-crate-metadata.json @@ -0,0 +1,102 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/Integer", + "error": "this is just to test the error property" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_bad_agent/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_bad_agent/ro-crate-metadata.json new file mode 100644 index 00000000..b0f92114 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_bad_agent/ro-crate-metadata.json @@ -0,0 +1,99 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://example.com/foo" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://example.com/foo", + "@type": "MedicalEntity", + "name": "Foo" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_bad_containerimage_type/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_bad_containerimage_type/ro-crate-metadata.json new file mode 100644 index 00000000..b448c7c8 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_bad_containerimage_type/ro-crate-metadata.json @@ -0,0 +1,139 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": { + "@id": "pics/sepia_fence.jpg" + } + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_bad_containerimage_url/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_bad_containerimage_url/ro-crate-metadata.json new file mode 100644 index 00000000..4b258f0d --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_bad_containerimage_url/ro-crate-metadata.json @@ -0,0 +1,137 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": "imagemagick" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_bad_endtime/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_bad_endtime/ro-crate-metadata.json new file mode 100644 index 00000000..cb643d09 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_bad_endtime/ro-crate-metadata.json @@ -0,0 +1,99 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "May 17 2024", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_bad_environment/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_bad_environment/ro-crate-metadata.json new file mode 100644 index 00000000..471f01fb --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_bad_environment/ro-crate-metadata.json @@ -0,0 +1,134 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ] + }, + { + "@id": "#width-limit-pv", + "@type": "CreativeWork" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_bad_starttime/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_bad_starttime/ro-crate-metadata.json new file mode 100644 index 00000000..dd836838 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_bad_starttime/ro-crate-metadata.json @@ -0,0 +1,100 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "May 17 2024", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_error_no_status/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_error_no_status/ro-crate-metadata.json new file mode 100644 index 00000000..9d4e8cbd --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_error_no_status/ro-crate-metadata.json @@ -0,0 +1,101 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "error": "this is just to test the error property" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_error_not_failed_status/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_error_not_failed_status/ro-crate-metadata.json new file mode 100644 index 00000000..49d624bf --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_error_not_failed_status/ro-crate-metadata.json @@ -0,0 +1,102 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/CompletedActionStatus", + "error": "this is just to test the error property" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_instrument_bad_type/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_instrument_bad_type/ro-crate-metadata.json new file mode 100644 index 00000000..0a0a25ae --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_instrument_bad_type/ro-crate-metadata.json @@ -0,0 +1,99 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "Thing", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_actionstatus/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_actionstatus/ro-crate-metadata.json new file mode 100644 index 00000000..9d4e8cbd --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_actionstatus/ro-crate-metadata.json @@ -0,0 +1,101 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "error": "this is just to test the error property" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_agent/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_agent/ro-crate-metadata.json new file mode 100644 index 00000000..8ed91563 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_agent/ro-crate-metadata.json @@ -0,0 +1,91 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_containerimage/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_containerimage/ro-crate-metadata.json new file mode 100644 index 00000000..d3e5bffa --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_containerimage/ro-crate-metadata.json @@ -0,0 +1,136 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ] + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_description/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_description/ro-crate-metadata.json new file mode 100644 index 00000000..d707b86a --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_description/ro-crate-metadata.json @@ -0,0 +1,98 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_endtime/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_endtime/ro-crate-metadata.json new file mode 100644 index 00000000..e6321d60 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_endtime/ro-crate-metadata.json @@ -0,0 +1,98 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_environment/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_environment/ro-crate-metadata.json new file mode 100644 index 00000000..7804b6c5 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_environment/ro-crate-metadata.json @@ -0,0 +1,116 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_error/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_error/ro-crate-metadata.json new file mode 100644 index 00000000..733b2535 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_error/ro-crate-metadata.json @@ -0,0 +1,101 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_instrument/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_instrument/ro-crate-metadata.json new file mode 100644 index 00000000..097c9dcb --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_instrument/ro-crate-metadata.json @@ -0,0 +1,96 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_name/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_name/ro-crate-metadata.json new file mode 100644 index 00000000..2023bbf3 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_name/ro-crate-metadata.json @@ -0,0 +1,98 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_object/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_object/ro-crate-metadata.json new file mode 100644 index 00000000..7a87f972 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_object/ro-crate-metadata.json @@ -0,0 +1,86 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property" + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_result/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_result/ro-crate-metadata.json new file mode 100644 index 00000000..97e1662e --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_result/ro-crate-metadata.json @@ -0,0 +1,86 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_no_starttime/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_no_starttime/ro-crate-metadata.json new file mode 100644 index 00000000..0a02e2d6 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_no_starttime/ro-crate-metadata.json @@ -0,0 +1,99 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_not_mentioned/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_not_mentioned/ro-crate-metadata.json new file mode 100644 index 00000000..6ca0ed38 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_not_mentioned/ro-crate-metadata.json @@ -0,0 +1,96 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/action_obj_res_bad_type/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/action_obj_res_bad_type/ro-crate-metadata.json new file mode 100644 index 00000000..87309910 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/action_obj_res_bad_type/ro-crate-metadata.json @@ -0,0 +1,113 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/application_id_no_absoluteuri/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/application_id_no_absoluteuri/ro-crate-metadata.json new file mode 100644 index 00000000..21203178 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/application_id_no_absoluteuri/ro-crate-metadata.json @@ -0,0 +1,99 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "#imagemagick", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/application_no_name/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/application_no_name/ro-crate-metadata.json new file mode 100644 index 00000000..46fc2a37 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/application_no_name/ro-crate-metadata.json @@ -0,0 +1,98 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/application_no_url/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/application_no_url/ro-crate-metadata.json new file mode 100644 index 00000000..a689bff5 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/application_no_url/ro-crate-metadata.json @@ -0,0 +1,98 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/application_no_version/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/application_no_version/ro-crate-metadata.json new file mode 100644 index 00000000..920f0ba3 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/application_no_version/ro-crate-metadata.json @@ -0,0 +1,98 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/application_version_softwareVersion/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/application_version_softwareVersion/ro-crate-metadata.json new file mode 100644 index 00000000..0b6da817 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/application_version_softwareVersion/ro-crate-metadata.json @@ -0,0 +1,100 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "version": "6.9.7-4", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/collection_no_haspart/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/collection_no_haspart/ro-crate-metadata.json new file mode 100644 index 00000000..899652c1 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/collection_no_haspart/ro-crate-metadata.json @@ -0,0 +1,120 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + } + ], + "hasPart": [ + { + "@id": "pics/in_01.jpg" + }, + { + "@id": "pics/in_02.jpg" + }, + { + "@id": "pics/in_main.jpg" + }, + { + "@id": "pics/out_01.jpg" + }, + { + "@id": "pics/out_02.jpg" + }, + { + "@id": "pics/out_main.jpg" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0" + }, + "mentions": [ + { + "@id": "#Conversion" + }, + { + "@id": "#InCollection" + }, + { + "@id": "#OutCollection" + } + ], + "name": "Test Collections" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#Conversion", + "@type": "CreateAction", + "name": "Convert image collections", + "description": "Convert image collections", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "#InCollection" + }, + "result": { + "@id": "#OutCollection" + }, + "agent": { + "@id": "https://orcid.org/0000-0002-1825-0097" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property" + }, + { + "@id": "#InCollection", + "@type": "Collection", + "mainEntity": "pics/in_main.jpg" + }, + { + "@id": "#OutCollection", + "@type": "Collection", + "mainEntity": "pics/out_main.jpg", + "hasPart": [ + { + "@id": "pics/out_01.jpg" + }, + { + "@id": "pics/out_02.jpg" + }, + { + "@id": "pics/out_main.jpg" + } + ] + }, + { + "@id": "https://orcid.org/0000-0002-1825-0097", + "@type": "Person", + "name": "Josiah Carberry" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/collection_no_mainentity/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/collection_no_mainentity/ro-crate-metadata.json new file mode 100644 index 00000000..5a38e1b7 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/collection_no_mainentity/ro-crate-metadata.json @@ -0,0 +1,130 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + } + ], + "hasPart": [ + { + "@id": "pics/in_01.jpg" + }, + { + "@id": "pics/in_02.jpg" + }, + { + "@id": "pics/in_main.jpg" + }, + { + "@id": "pics/out_01.jpg" + }, + { + "@id": "pics/out_02.jpg" + }, + { + "@id": "pics/out_main.jpg" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0" + }, + "mentions": [ + { + "@id": "#Conversion" + }, + { + "@id": "#InCollection" + }, + { + "@id": "#OutCollection" + } + ], + "name": "Test Collections" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#Conversion", + "@type": "CreateAction", + "name": "Convert image collections", + "description": "Convert image collections", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "#InCollection" + }, + "result": { + "@id": "#OutCollection" + }, + "agent": { + "@id": "https://orcid.org/0000-0002-1825-0097" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property" + }, + { + "@id": "#InCollection", + "@type": "Collection", + "hasPart": [ + { + "@id": "pics/in_01.jpg" + }, + { + "@id": "pics/in_02.jpg" + }, + { + "@id": "pics/in_main.jpg" + } + ] + }, + { + "@id": "#OutCollection", + "@type": "Collection", + "mainEntity": "pics/out_main.jpg", + "hasPart": [ + { + "@id": "pics/out_01.jpg" + }, + { + "@id": "pics/out_02.jpg" + }, + { + "@id": "pics/out_main.jpg" + } + ] + }, + { + "@id": "https://orcid.org/0000-0002-1825-0097", + "@type": "Person", + "name": "Josiah Carberry" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/collection_not_mentioned/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/collection_not_mentioned/ro-crate-metadata.json new file mode 100644 index 00000000..3d668e58 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/collection_not_mentioned/ro-crate-metadata.json @@ -0,0 +1,125 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + } + ], + "hasPart": [ + { + "@id": "pics/in_01.jpg" + }, + { + "@id": "pics/in_02.jpg" + }, + { + "@id": "pics/in_main.jpg" + }, + { + "@id": "pics/out_01.jpg" + }, + { + "@id": "pics/out_02.jpg" + }, + { + "@id": "pics/out_main.jpg" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0" + }, + "mentions": [ + { + "@id": "#Conversion" + } + ], + "name": "Test Collections" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#Conversion", + "@type": "CreateAction", + "name": "Convert image collections", + "description": "Convert image collections", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "#InCollection" + }, + "result": { + "@id": "#OutCollection" + }, + "agent": { + "@id": "https://orcid.org/0000-0002-1825-0097" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property" + }, + { + "@id": "#InCollection", + "@type": "Collection", + "mainEntity": "pics/in_main.jpg", + "hasPart": [ + { + "@id": "pics/in_01.jpg" + }, + { + "@id": "pics/in_02.jpg" + }, + { + "@id": "pics/in_main.jpg" + } + ] + }, + { + "@id": "#OutCollection", + "@type": "Collection", + "mainEntity": "pics/out_main.jpg", + "hasPart": [ + { + "@id": "pics/out_01.jpg" + }, + { + "@id": "pics/out_02.jpg" + }, + { + "@id": "pics/out_main.jpg" + } + ] + }, + { + "@id": "https://orcid.org/0000-0002-1825-0097", + "@type": "Person", + "name": "Josiah Carberry" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/conformsto_bad_profile/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/conformsto_bad_profile/ro-crate-metadata.json new file mode 100644 index 00000000..6fd1c2aa --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/conformsto_bad_profile/ro-crate-metadata.json @@ -0,0 +1,99 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/foobar/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/foobar/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/conformsto_bad_type/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/conformsto_bad_type/ro-crate-metadata.json new file mode 100644 index 00000000..4d416a39 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/conformsto_bad_type/ro-crate-metadata.json @@ -0,0 +1,97 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "Action" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/containerimage_bad_additionaltype/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/containerimage_bad_additionaltype/ro-crate-metadata.json new file mode 100644 index 00000000..8ba88a1f --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/containerimage_bad_additionaltype/ro-crate-metadata.json @@ -0,0 +1,150 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": { + "@id": "#imagemagick-image" + } + }, + { + "@id": "#imagemagick-image", + "@type": "ContainerImage", + "additionalType": { + "@id": "http://schema.org/Integer" + }, + "registry": "docker.io", + "name": "simleo/imagemagick", + "tag": "6.9.12-98", + "sha256": "dc6161af5a230ec48b7c9469c81c32b2e7cc2ae510b32dacc43bfc658a2bca1a" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/containerimage_no_additionaltype/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/containerimage_no_additionaltype/ro-crate-metadata.json new file mode 100644 index 00000000..34a44682 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/containerimage_no_additionaltype/ro-crate-metadata.json @@ -0,0 +1,147 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": { + "@id": "#imagemagick-image" + } + }, + { + "@id": "#imagemagick-image", + "@type": "ContainerImage", + "registry": "docker.io", + "name": "simleo/imagemagick", + "tag": "6.9.12-98", + "sha256": "dc6161af5a230ec48b7c9469c81c32b2e7cc2ae510b32dacc43bfc658a2bca1a" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/containerimage_no_name/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/containerimage_no_name/ro-crate-metadata.json new file mode 100644 index 00000000..d91b9340 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/containerimage_no_name/ro-crate-metadata.json @@ -0,0 +1,149 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": { + "@id": "#imagemagick-image" + } + }, + { + "@id": "#imagemagick-image", + "@type": "ContainerImage", + "additionalType": { + "@id": "https://w3id.org/ro/terms/workflow-run#DockerImage" + }, + "registry": "docker.io", + "tag": "6.9.12-98", + "sha256": "dc6161af5a230ec48b7c9469c81c32b2e7cc2ae510b32dacc43bfc658a2bca1a" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/containerimage_no_registry/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/containerimage_no_registry/ro-crate-metadata.json new file mode 100644 index 00000000..5e12cc7c --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/containerimage_no_registry/ro-crate-metadata.json @@ -0,0 +1,149 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": { + "@id": "#imagemagick-image" + } + }, + { + "@id": "#imagemagick-image", + "@type": "ContainerImage", + "additionalType": { + "@id": "https://w3id.org/ro/terms/workflow-run#DockerImage" + }, + "name": "simleo/imagemagick", + "tag": "6.9.12-98", + "sha256": "dc6161af5a230ec48b7c9469c81c32b2e7cc2ae510b32dacc43bfc658a2bca1a" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/containerimage_no_sha256/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/containerimage_no_sha256/ro-crate-metadata.json new file mode 100644 index 00000000..0b54c68b --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/containerimage_no_sha256/ro-crate-metadata.json @@ -0,0 +1,149 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": { + "@id": "#imagemagick-image" + } + }, + { + "@id": "#imagemagick-image", + "@type": "ContainerImage", + "additionalType": { + "@id": "https://w3id.org/ro/terms/workflow-run#DockerImage" + }, + "registry": "docker.io", + "name": "simleo/imagemagick", + "tag": "6.9.12-98" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/containerimage_no_tag/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/containerimage_no_tag/ro-crate-metadata.json new file mode 100644 index 00000000..fce440f0 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/containerimage_no_tag/ro-crate-metadata.json @@ -0,0 +1,149 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": { + "@id": "#imagemagick-image" + } + }, + { + "@id": "#imagemagick-image", + "@type": "ContainerImage", + "additionalType": { + "@id": "https://w3id.org/ro/terms/workflow-run#DockerImage" + }, + "registry": "docker.io", + "name": "simleo/imagemagick", + "sha256": "dc6161af5a230ec48b7c9469c81c32b2e7cc2ae510b32dacc43bfc658a2bca1a" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/softwareapplication_bad_softwarerequirements/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/softwareapplication_bad_softwarerequirements/ro-crate-metadata.json new file mode 100644 index 00000000..61391f69 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/softwareapplication_bad_softwarerequirements/ro-crate-metadata.json @@ -0,0 +1,144 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4", + "softwareRequirements": { + "@id": "https://example.com/foobar/" + } + }, + { + "@id": "https://example.com/foobar/", + "@type": "CreativeWork" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": "https://example.com/imagemagick.sif" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/softwareapplication_no_softwarerequirements/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/softwareapplication_no_softwarerequirements/ro-crate-metadata.json new file mode 100644 index 00000000..7d636aa5 --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/softwareapplication_no_softwarerequirements/ro-crate-metadata.json @@ -0,0 +1,137 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": "https://example.com/imagemagick.sif" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/3_process_run_crate/softwaresourcecode_no_version/ro-crate-metadata.json b/tests/data/crates/invalid/3_process_run_crate/softwaresourcecode_no_version/ro-crate-metadata.json new file mode 100644 index 00000000..e87874db --- /dev/null +++ b/tests/data/crates/invalid/3_process_run_crate/softwaresourcecode_no_version/ro-crate-metadata.json @@ -0,0 +1,99 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareSourceCode", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/invalid/4_data_entity_metadata/directory_data_entity_wo_trailing_slash/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/directory_data_entity_wo_trailing_slash/ro-crate-metadata.json new file mode 100644 index 00000000..7662be93 --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/directory_data_entity_wo_trailing_slash/ro-crate-metadata.json @@ -0,0 +1,124 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Valid RO Crate with foo/xxx indirectly referenced", + "description": "This RO Crate contains a foo Dataset (directory) which doesn't have a trailing slash in its @id", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "foo" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": "https://spdx.org/licenses/Apache-2.0.html" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": ["File", "SoftwareSourceCode", "HowTo"], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "foo", + "@type": "Dataset", + "hasPart": [ + { + "@id": "foo/xxx" + } + ] + }, + { + "@id": "foo/xxx", + "@type": "File" + }, + { + "@id": "bar", + "@type": "Dataset" + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} diff --git a/tests/data/crates/invalid/4_data_entity_metadata/invalid_encoding_format_ctx_entity_missing_ws_name/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/invalid_encoding_format_ctx_entity_missing_ws_name/ro-crate-metadata.json new file mode 100644 index 00000000..cdf42c6c --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/invalid_encoding_format_ctx_entity_missing_ws_name/ro-crate-metadata.json @@ -0,0 +1,97 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Valid RO Crate with foo/xxx indirectly referenced", + "description": "This RO Crate contains a foo Dataset (directory) which doesn't have a trailing slash in its @id", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": [ + { + "@id": "https://spdx.org/licenses/MIT.html" + } + ] + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + }, + "encodingFormat": ["application/galaxy"] + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + }, + "encodingFormat": ["application/galaxy"] + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"], + "encodingFormat": [ + { + "@id": "https://www.iana.org/assignments/media-types/image/png" + } + ] + }, + { + "@id": "https://www.iana.org/assignments/media-types/image/png", + "@type": "WebSite" + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": ["text/markdown"] + }, + { "@id": "https://spdx.org/licenses/MIT.html", "@type": "license" } + ] +} diff --git a/tests/data/crates/invalid/4_data_entity_metadata/invalid_encoding_format_ctx_entity_missing_ws_type/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/invalid_encoding_format_ctx_entity_missing_ws_type/ro-crate-metadata.json new file mode 100644 index 00000000..2a315b6e --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/invalid_encoding_format_ctx_entity_missing_ws_type/ro-crate-metadata.json @@ -0,0 +1,93 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Valid RO Crate with foo/xxx indirectly referenced", + "description": "This RO Crate contains a foo Dataset (directory) which doesn't have a trailing slash in its @id", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": [ + { + "@id": "https://spdx.org/licenses/MIT.html" + } + ] + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + }, + "encodingFormat": ["application/galaxy-workflow+yaml"] + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + }, + "encodingFormat": ["application/galaxy-workflow+yaml"] + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"], + "encodingFormat": [ + { + "@id": "https://www.iana.org/assignments/media-types/image/png" + } + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": ["text/markdown"] + }, + { "@id": "https://spdx.org/licenses/MIT.html", "@type": "license" } + ] +} diff --git a/tests/data/crates/invalid/4_data_entity_metadata/invalid_encoding_format_pronom/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/invalid_encoding_format_pronom/ro-crate-metadata.json new file mode 100644 index 00000000..384d0cef --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/invalid_encoding_format_pronom/ro-crate-metadata.json @@ -0,0 +1,89 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Valid RO Crate with foo/xxx indirectly referenced", + "description": "This RO Crate contains a foo Dataset (directory) which doesn't have a trailing slash in its @id", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": [ + { + "@id": "https://spdx.org/licenses/MIT.html" + } + ] + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + }, + "encodingFormat": ["application/galaxy-workflow+yaml"] + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + }, + "encodingFormat": ["application/galaxy-workflow+yaml"] + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"], + "encodingFormat": 1234566 + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": ["text/markdown"] + }, + { "@id": "https://spdx.org/licenses/MIT.html", "@type": "license" } + ] +} diff --git a/tests/data/crates/invalid/4_data_entity_metadata/invalid_missing_hasPart_reference/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/invalid_missing_hasPart_reference/ro-crate-metadata.json new file mode 100644 index 00000000..16bb00f9 --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/invalid_missing_hasPart_reference/ro-crate-metadata.json @@ -0,0 +1,114 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Missing direct or indirect references on the Root Data Entity", + "description": "sort-and-change-case.ga and foo/xxx are not referenced by the Root Data Entity", + "hasPart": [ + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "foo/" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": "https://spdx.org/licenses/Apache-2.0.html" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": ["File", "SoftwareSourceCode", "HowTo"], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "foo/", + "@type": "Dataset" + + }, + { + "@id": "foo/xxx", + "@type": "File" + + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} diff --git a/tests/data/crates/invalid/4_data_entity_metadata/missing_encoding_format/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/missing_encoding_format/ro-crate-metadata.json new file mode 100644 index 00000000..3744b2cf --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/missing_encoding_format/ro-crate-metadata.json @@ -0,0 +1,120 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Valid RO Crate with foo/xxx indirectly referenced", + "description": "This RO Crate contains a foo Dataset (directory) which doesn't have a trailing slash in its @id", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "foo/" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": "https://spdx.org/licenses/Apache-2.0.html" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": ["File", "SoftwareSourceCode", "HowTo"], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "foo/", + "@type": "Dataset", + "hasPart": [ + { + "@id": "foo/xxx" + } + ] + }, + { + "@id": "foo/xxx", + "@type": "File", + "name": "xxx" + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + } + } + ] +} diff --git a/tests/data/crates/invalid/4_data_entity_metadata/valid_direct_hasPart_reference/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/valid_direct_hasPart_reference/ro-crate-metadata.json new file mode 100644 index 00000000..0c36dfff --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/valid_direct_hasPart_reference/ro-crate-metadata.json @@ -0,0 +1,110 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Valid RO Crate with foo/ Dataset directly referenced", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "foo/" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": "https://spdx.org/licenses/Apache-2.0.html" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": ["File", "SoftwareSourceCode", "HowTo"], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "foo/", + "@type": "Dataset" + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} diff --git a/tests/data/crates/invalid/4_data_entity_metadata/valid_encoding_format_ctx_entity/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/valid_encoding_format_ctx_entity/ro-crate-metadata.json new file mode 100644 index 00000000..e8fa1ae0 --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/valid_encoding_format_ctx_entity/ro-crate-metadata.json @@ -0,0 +1,123 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Valid RO Crate with foo/xxx indirectly referenced", + "description": "This RO Crate contains a foo Dataset (directory) which doesn't have a trailing slash in its @id", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": [ + { + "@id": "https://spdx.org/licenses/MIT.html" + } + ], + "author": { + "@id": "https://orcid.org/0000-0002-1825-0097" + }, + "publisher": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "https://orcid.org/0000-0002-1825-0097", + "@type": "Person", + "name": "Stian Soiland-Reyes", + "affiliation": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "url": { + "@id": "https://orcid.org/0000-0002-1825-0097" + } + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Organization", + "name": "The University of Manchester", + "url": { + "@id": "https://www.manchester.ac.uk/" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + }, + "encodingFormat": ["application/galaxy"] + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + }, + "encodingFormat": ["application/galaxy"] + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"], + "encodingFormat": [ + { + "@id": "https://www.iana.org/assignments/media-types/image/png" + } + ] + }, + { + "@id": "https://www.iana.org/assignments/media-types/image/png", + "@type": "WebSite", + "name": "image/png" + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": ["text/markdown"] + }, + { "@id": "https://spdx.org/licenses/MIT.html", "@type": "license" } + ] +} diff --git a/tests/data/crates/invalid/4_data_entity_metadata/valid_encoding_format_pronom/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/valid_encoding_format_pronom/ro-crate-metadata.json new file mode 100644 index 00000000..5c479161 --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/valid_encoding_format_pronom/ro-crate-metadata.json @@ -0,0 +1,114 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Valid RO Crate with foo/xxx indirectly referenced", + "description": "This RO Crate contains a foo Dataset (directory) which doesn't have a trailing slash in its @id", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": [ + { + "@id": "https://spdx.org/licenses/MIT.html" + } + ], + "author": { + "@id": "https://orcid.org/0000-0002-1825-0097" + }, + "publisher": { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + }, + { + "@id": "https://orcid.org/0000-0002-1825-0097", + "@type": "Person", + "name": "Stian Soiland-Reyes", + "affiliation": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "url": { + "@id": "https://orcid.org/0000-0002-1825-0097" + } + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Organization", + "name": "The University of Manchester", + "url": { + "@id": "https://www.manchester.ac.uk/" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + }, + "encodingFormat": ["application/galaxy"] + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + }, + "encodingFormat": ["application/galaxy"] + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"], + "encodingFormat": ["image/png"] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": ["text/markdown"] + }, + { "@id": "https://spdx.org/licenses/MIT.html", "@type": "license" } + ] +} diff --git a/tests/data/crates/invalid/4_data_entity_metadata/valid_indirect_hasPart_reference/ro-crate-metadata.json b/tests/data/crates/invalid/4_data_entity_metadata/valid_indirect_hasPart_reference/ro-crate-metadata.json new file mode 100644 index 00000000..9f1fa13d --- /dev/null +++ b/tests/data/crates/invalid/4_data_entity_metadata/valid_indirect_hasPart_reference/ro-crate-metadata.json @@ -0,0 +1,120 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "name": "Valid RO Crate with foo/xxx indirectly referenced", + "description": "This RO Crate contains a file foo/xxx that is not directly referenced in the metadata, but is included in a subdirectory of a Dataset and it is referenced by the `hasPart` property of that Dataset.", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "foo/" + }, + { + "@id": "README.md" + } + ], + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "license": "https://spdx.org/licenses/Apache-2.0.html" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": ["File", "SoftwareSourceCode", "ComputationalWorkflow"], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": ["File", "SoftwareSourceCode", "HowTo"], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "foo/", + "@type": "Dataset", + "hasPart": [ + { + "@id": "foo/xxx" + } + ] + }, + { + "@id": "foo/xxx", + "@type": "File" + }, + { + "@id": "blank.png", + "@type": ["File", "ImageObject"] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_bad_conformsto/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_bad_conformsto/ro-crate-metadata.json new file mode 100644 index 00000000..87291855 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_bad_conformsto/ro-crate-metadata.json @@ -0,0 +1,133 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_bad_engineversion/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_bad_engineversion/ro-crate-metadata.json new file mode 100644 index 00000000..15655700 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_bad_engineversion/ro-crate-metadata.json @@ -0,0 +1,135 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": { + "@id": "http://example.com/foobar" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_bad_type/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_bad_type/ro-crate-metadata.json new file mode 100644 index 00000000..daeb9e29 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_bad_type/ro-crate-metadata.json @@ -0,0 +1,130 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": "TestDefinition", + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_no_engine/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_no_engine/ro-crate-metadata.json new file mode 100644 index 00000000..78ca9ef7 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_no_engine/ro-crate-metadata.json @@ -0,0 +1,122 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "engineVersion": ">=0.70" + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_no_engineversion/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_no_engineversion/ro-crate-metadata.json new file mode 100644 index 00000000..0742cea9 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testdefinition_no_engineversion/ro-crate-metadata.json @@ -0,0 +1,132 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_bad_resource/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_bad_resource/ro-crate-metadata.json new file mode 100644 index 00000000..57530bee --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_bad_resource/ro-crate-metadata.json @@ -0,0 +1,135 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + } + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_bad_runson/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_bad_runson/ro-crate-metadata.json new file mode 100644 index 00000000..1eb769f4 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_bad_runson/ro-crate-metadata.json @@ -0,0 +1,125 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "sort-and-change-case.ga" + }, + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_bad_url/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_bad_url/ro-crate-metadata.json new file mode 100644 index 00000000..dc4fc7b4 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_bad_url/ro-crate-metadata.json @@ -0,0 +1,133 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "foobar", + "resource": "job/tests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_no_resource/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_no_resource/ro-crate-metadata.json new file mode 100644 index 00000000..80fae221 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_no_resource/ro-crate-metadata.json @@ -0,0 +1,132 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_no_service/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_no_service/ro-crate-metadata.json new file mode 100644 index 00000000..ad734119 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_no_service/ro-crate-metadata.json @@ -0,0 +1,122 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_no_url/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_no_url/ro-crate-metadata.json new file mode 100644 index 00000000..f302958c --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testinstance_no_url/ro-crate-metadata.json @@ -0,0 +1,132 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "resource": "job/tests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_bad_definition/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_bad_definition/ro-crate-metadata.json new file mode 100644 index 00000000..be902fd9 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_bad_definition/ro-crate-metadata.json @@ -0,0 +1,91 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "definition": { + "@id": "sort-and-change-case.ga" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_bad_instance/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_bad_instance/ro-crate-metadata.json new file mode 100644 index 00000000..87ad51ba --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_bad_instance/ro-crate-metadata.json @@ -0,0 +1,93 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "sort-and-change-case.ga" + } + ] + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_bad_mainentity/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_bad_mainentity/ro-crate-metadata.json new file mode 100644 index 00000000..d29f7df9 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_bad_mainentity/ro-crate-metadata.json @@ -0,0 +1,133 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_no_instance_no_def/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_no_instance_no_def/ro-crate-metadata.json new file mode 100644 index 00000000..3ecf4dd3 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_no_instance_no_def/ro-crate-metadata.json @@ -0,0 +1,88 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_no_mainentity/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_no_mainentity/ro-crate-metadata.json new file mode 100644 index 00000000..82a22990 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_no_mainentity/ro-crate-metadata.json @@ -0,0 +1,130 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_not_mentioned/ro-crate-metadata.json b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_not_mentioned/ro-crate-metadata.json new file mode 100644 index 00000000..8428b451 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_not_mentioned/ro-crate-metadata.json @@ -0,0 +1,156 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test2", + "name": "test2", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test2_1" + } + ] + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "#test2_1", + "name": "test2_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": "job/moretests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_not_mentioned/sort-and-change-case.ga b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_not_mentioned/sort-and-change-case.ga new file mode 100644 index 00000000..5a199969 --- /dev/null +++ b/tests/data/crates/invalid/5_workflow_testing_ro_crate/testsuite_not_mentioned/sort-and-change-case.ga @@ -0,0 +1,118 @@ +{ + "uuid": "e2a8566c-c025-4181-9e90-7ed29d4e4df1", + "tags": [], + "format-version": "0.1", + "name": "sort-and-change-case", + "version": 0, + "steps": { + "0": { + "tool_id": null, + "tool_version": null, + "outputs": [], + "workflow_outputs": [], + "input_connections": {}, + "tool_state": "{}", + "id": 0, + "uuid": "5a36fad2-66c7-4b9e-8759-0fbcae9b8541", + "errors": null, + "name": "Input dataset", + "label": "bed_input", + "inputs": [], + "position": { + "top": 200, + "left": 200 + }, + "annotation": "", + "content_id": null, + "type": "data_input" + }, + "1": { + "tool_id": "sort1", + "tool_version": "1.1.0", + "outputs": [ + { + "type": "input", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "8237f71a-bc2a-494e-a63c-09c1e65ef7c8", + "label": "sorted_bed" + } + ], + "input_connections": { + "input": { + "output_name": "output", + "id": 0 + } + }, + "tool_state": "{\"__page__\": null, \"style\": \"\\\"alpha\\\"\", \"column\": \"\\\"1\\\"\", \"__rerun_remap_job_id__\": null, \"column_set\": \"[]\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\", \"header_lines\": \"\\\"0\\\"\", \"order\": \"\\\"ASC\\\"\"}", + "id": 1, + "uuid": "0b6b3cda-c75f-452b-85b1-8ae4f3302ba4", + "errors": null, + "name": "Sort", + "post_job_actions": {}, + "label": "sort", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Sort" + } + ], + "position": { + "top": 200, + "left": 420 + }, + "annotation": "", + "content_id": "sort1", + "type": "tool" + }, + "2": { + "tool_id": "ChangeCase", + "tool_version": "1.0.0", + "outputs": [ + { + "type": "tabular", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "c31cd733-dab6-4d50-9fec-b644d162397b", + "label": "uppercase_bed" + } + ], + "input_connections": { + "input": { + "output_name": "out_file1", + "id": 1 + } + }, + "tool_state": "{\"__page__\": null, \"casing\": \"\\\"up\\\"\", \"__rerun_remap_job_id__\": null, \"cols\": \"\\\"c1\\\"\", \"delimiter\": \"\\\"TAB\\\"\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\"}", + "id": 2, + "uuid": "9698bcde-0729-48fe-b88d-ccfb6f6153b4", + "errors": null, + "name": "Change Case", + "post_job_actions": {}, + "label": "change_case", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Change Case" + } + ], + "position": { + "top": 200, + "left": 640 + }, + "annotation": "", + "content_id": "ChangeCase", + "type": "tool" + } + }, + "annotation": "", + "a_galaxy_workflow": "true" +} diff --git a/tests/data/crates/valid/process-run-crate-collections/ro-crate-metadata.json b/tests/data/crates/valid/process-run-crate-collections/ro-crate-metadata.json new file mode 100644 index 00000000..736785b3 --- /dev/null +++ b/tests/data/crates/valid/process-run-crate-collections/ro-crate-metadata.json @@ -0,0 +1,131 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + } + ], + "hasPart": [ + { + "@id": "pics/in_01.jpg" + }, + { + "@id": "pics/in_02.jpg" + }, + { + "@id": "pics/in_main.jpg" + }, + { + "@id": "pics/out_01.jpg" + }, + { + "@id": "pics/out_02.jpg" + }, + { + "@id": "pics/out_main.jpg" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0" + }, + "mentions": [ + { + "@id": "#Conversion" + }, + { + "@id": "#InCollection" + }, + { + "@id": "#OutCollection" + } + ], + "name": "Test Collections" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#Conversion", + "@type": "CreateAction", + "name": "Convert image collections", + "description": "Convert image collections", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "#InCollection" + }, + "result": { + "@id": "#OutCollection" + }, + "agent": { + "@id": "https://orcid.org/0000-0002-1825-0097" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property" + }, + { + "@id": "#InCollection", + "@type": "Collection", + "mainEntity": "pics/in_main.jpg", + "hasPart": [ + { + "@id": "pics/in_01.jpg" + }, + { + "@id": "pics/in_02.jpg" + }, + { + "@id": "pics/in_main.jpg" + } + ] + }, + { + "@id": "#OutCollection", + "@type": "Collection", + "mainEntity": "pics/out_main.jpg", + "hasPart": [ + { + "@id": "pics/out_01.jpg" + }, + { + "@id": "pics/out_02.jpg" + }, + { + "@id": "pics/out_main.jpg" + } + ] + }, + { + "@id": "https://orcid.org/0000-0002-1825-0097", + "@type": "Person", + "name": "Josiah Carberry" + } + ] +} diff --git a/tests/data/crates/valid/process-run-crate-containerimage/ro-crate-metadata.json b/tests/data/crates/valid/process-run-crate-containerimage/ro-crate-metadata.json new file mode 100644 index 00000000..941b133f --- /dev/null +++ b/tests/data/crates/valid/process-run-crate-containerimage/ro-crate-metadata.json @@ -0,0 +1,150 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": { + "@id": "#imagemagick-image" + } + }, + { + "@id": "#imagemagick-image", + "@type": "ContainerImage", + "additionalType": { + "@id": "https://w3id.org/ro/terms/workflow-run#DockerImage" + }, + "registry": "docker.io", + "name": "simleo/imagemagick", + "tag": "6.9.12-98", + "sha256": "dc6161af5a230ec48b7c9469c81c32b2e7cc2ae510b32dacc43bfc658a2bca1a" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/valid/process-run-crate/ro-crate-metadata.json b/tests/data/crates/valid/process-run-crate/ro-crate-metadata.json new file mode 100644 index 00000000..5beab1c1 --- /dev/null +++ b/tests/data/crates/valid/process-run-crate/ro-crate-metadata.json @@ -0,0 +1,146 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + } + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1" + } + ], + "hasPart": [ + { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + { + "@id": "pics/sepia_fence.jpg" + } + ], + "isBasedOn": { + "@id": "https://doi.org/10.5281/zenodo.1009240" + }, + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mentions": { + "@id": "#SepiaConversion_1" + }, + "name": "My Pictures" + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.5", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.5" + }, + { + "@id": "https://example.com/otherprofile/0.1", + "@type": "CreativeWork", + "name": "Other Profile", + "version": "0.1" + }, + { + "@id": "https://www.imagemagick.org/", + "@type": "SoftwareApplication", + "url": "https://www.imagemagick.org/", + "name": "ImageMagick", + "softwareVersion": "6.9.7-4", + "softwareRequirements": { + "@id": "https://example.com/foobar/1.0.0/" + } + }, + { + "@id": "https://example.com/foobar/1.0.0/", + "@type": "SoftwareApplication", + "name": "foobar", + "softwareVersion": "1.0.0" + }, + { + "@id": "#SepiaConversion_1", + "@type": "CreateAction", + "name": "Convert dog image to sepia", + "description": "convert -sepia-tone 80% pics/2017-06-11\\ 12.56.14.jpg pics/sepia_fence.jpg", + "startTime": "2024-05-17T01:04:50+01:00", + "endTime": "2024-05-17T01:04:52+01:00", + "instrument": { + "@id": "https://www.imagemagick.org/" + }, + "object": { + "@id": "pics/2017-06-11%2012.56.14.jpg" + }, + "result": { + "@id": "pics/sepia_fence.jpg" + }, + "agent": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "actionStatus": "http://schema.org/FailedActionStatus", + "error": "this is just to test the error property", + "environment": [ + { + "@id": "#height-limit-pv" + }, + { + "@id": "#width-limit-pv" + } + ], + "containerImage": "https://example.com/imagemagick.sif" + }, + { + "@id": "#width-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_WIDTH_LIMIT", + "value": "4096" + }, + { + "@id": "#height-limit-pv", + "@type": "PropertyValue", + "name": "MAGICK_HEIGHT_LIMIT", + "value": "3072" + }, + { + "@id": "pics/2017-06-11%2012.56.14.jpg", + "@type": "File", + "description": "Original image", + "encodingFormat": "image/jpeg", + "name": "2017-06-11 12.56.14.jpg (input)", + "author": { + "@id": "https://orcid.org/0000-0002-3545-944X" + } + }, + { + "@id": "pics/sepia_fence.jpg", + "@type": "File", + "description": "The converted picture, now sepia-colored", + "encodingFormat": "image/jpeg", + "name": "sepia_fence (output)" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://orcid.org/0000-0002-3545-944X", + "@type": "Person", + "name": "Peter Sefton" + } + ] +} diff --git a/tests/data/crates/valid/sortchangecase.crate.zip b/tests/data/crates/valid/sortchangecase.crate.zip new file mode 100644 index 00000000..cae3882c Binary files /dev/null and b/tests/data/crates/valid/sortchangecase.crate.zip differ diff --git a/tests/data/crates/valid/workflow-roc-string-license/ro-crate-metadata.json b/tests/data/crates/valid/workflow-roc-string-license/ro-crate-metadata.json new file mode 100644 index 00000000..4e2e350f --- /dev/null +++ b/tests/data/crates/valid/workflow-roc-string-license/ro-crate-metadata.json @@ -0,0 +1,113 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + } + ], + "license": "Apache-2.0", + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/valid/workflow-roc-string-license/sort-and-change-case.ga b/tests/data/crates/valid/workflow-roc-string-license/sort-and-change-case.ga new file mode 100644 index 00000000..5a199969 --- /dev/null +++ b/tests/data/crates/valid/workflow-roc-string-license/sort-and-change-case.ga @@ -0,0 +1,118 @@ +{ + "uuid": "e2a8566c-c025-4181-9e90-7ed29d4e4df1", + "tags": [], + "format-version": "0.1", + "name": "sort-and-change-case", + "version": 0, + "steps": { + "0": { + "tool_id": null, + "tool_version": null, + "outputs": [], + "workflow_outputs": [], + "input_connections": {}, + "tool_state": "{}", + "id": 0, + "uuid": "5a36fad2-66c7-4b9e-8759-0fbcae9b8541", + "errors": null, + "name": "Input dataset", + "label": "bed_input", + "inputs": [], + "position": { + "top": 200, + "left": 200 + }, + "annotation": "", + "content_id": null, + "type": "data_input" + }, + "1": { + "tool_id": "sort1", + "tool_version": "1.1.0", + "outputs": [ + { + "type": "input", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "8237f71a-bc2a-494e-a63c-09c1e65ef7c8", + "label": "sorted_bed" + } + ], + "input_connections": { + "input": { + "output_name": "output", + "id": 0 + } + }, + "tool_state": "{\"__page__\": null, \"style\": \"\\\"alpha\\\"\", \"column\": \"\\\"1\\\"\", \"__rerun_remap_job_id__\": null, \"column_set\": \"[]\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\", \"header_lines\": \"\\\"0\\\"\", \"order\": \"\\\"ASC\\\"\"}", + "id": 1, + "uuid": "0b6b3cda-c75f-452b-85b1-8ae4f3302ba4", + "errors": null, + "name": "Sort", + "post_job_actions": {}, + "label": "sort", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Sort" + } + ], + "position": { + "top": 200, + "left": 420 + }, + "annotation": "", + "content_id": "sort1", + "type": "tool" + }, + "2": { + "tool_id": "ChangeCase", + "tool_version": "1.0.0", + "outputs": [ + { + "type": "tabular", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "c31cd733-dab6-4d50-9fec-b644d162397b", + "label": "uppercase_bed" + } + ], + "input_connections": { + "input": { + "output_name": "out_file1", + "id": 1 + } + }, + "tool_state": "{\"__page__\": null, \"casing\": \"\\\"up\\\"\", \"__rerun_remap_job_id__\": null, \"cols\": \"\\\"c1\\\"\", \"delimiter\": \"\\\"TAB\\\"\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\"}", + "id": 2, + "uuid": "9698bcde-0729-48fe-b88d-ccfb6f6153b4", + "errors": null, + "name": "Change Case", + "post_job_actions": {}, + "label": "change_case", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Change Case" + } + ], + "position": { + "top": 200, + "left": 640 + }, + "annotation": "", + "content_id": "ChangeCase", + "type": "tool" + } + }, + "annotation": "", + "a_galaxy_workflow": "true" +} diff --git a/tests/data/crates/valid/workflow-roc/README.md b/tests/data/crates/valid/workflow-roc/README.md new file mode 100644 index 00000000..33fbaf72 --- /dev/null +++ b/tests/data/crates/valid/workflow-roc/README.md @@ -0,0 +1,3 @@ +# Example of a valid Workflow RO-Crate + +The Galaxy workflow has been copied from the LifeMonitor repository: [sort-and-change-case.ga](https://github.com/crs4/life_monitor/blob/b6206ca348701817756e133e72174826a5c3df4a/interaction_experiments/workflow_examples/galaxy/sort-and-change-case/sort-and-change-case.ga). The abstract CWL version of the workflow, `sort-and-change-case.cwl` has been generated by converting the Galaxy one with [ro-crate-py](https://github.com/ResearchObject/ro-crate-py), which, in turn, uses [galaxy2cwl](https://github.com/workflowhub-eu/galaxy2cwl). diff --git a/tests/data/crates/valid/workflow-roc/blank.png b/tests/data/crates/valid/workflow-roc/blank.png new file mode 100644 index 00000000..053a72f9 Binary files /dev/null and b/tests/data/crates/valid/workflow-roc/blank.png differ diff --git a/tests/data/crates/valid/workflow-roc/make_crate.py b/tests/data/crates/valid/workflow-roc/make_crate.py new file mode 100644 index 00000000..1143cb4c --- /dev/null +++ b/tests/data/crates/valid/workflow-roc/make_crate.py @@ -0,0 +1,44 @@ +"""\ +(Re)generate the RO-Crate metadata. Requires +https://github.com/ResearchObject/ro-crate-py. +""" + +from pathlib import Path + +from rocrate.rocrate import ROCrate + + +THIS_DIR = Path(__file__).absolute().parent +WF = THIS_DIR / "sort-and-change-case.ga" +CWL_DESC_WF = THIS_DIR / "sort-and-change-case.cwl" +DIAGRAM = "blank.png" +README = "README.md" +WF_LICENSE = "https://spdx.org/licenses/MIT.html" +CRATE_LICENSE = "https://spdx.org/licenses/Apache-2.0.html" + + +def main(): + crate = ROCrate(gen_preview=False) + crate.root_dataset["license"] = CRATE_LICENSE + wf = crate.add_workflow(WF, main=True, lang="galaxy", gen_cwl=True, properties={ + "license": WF_LICENSE, + "name": "sort-and-change-case", + "description": "sort lines and change text to upper case", + }) + cwl_desc_wf = crate.add_workflow(CWL_DESC_WF, main=False, lang="cwl", properties={ + "@type": ["File", "SoftwareSourceCode", "HowTo"], + }) + wf["subjectOf"] = cwl_desc_wf + diagram = crate.add_file(DIAGRAM, properties={ + "@type": ["File", "ImageObject"], + }) + wf["image"] = diagram + readme = crate.add_file(README, properties={ + "encodingFormat": "text/markdown", + }) + readme["about"] = crate.root_dataset + crate.metadata.write(THIS_DIR) + + +if __name__ == "__main__": + main() diff --git a/tests/data/crates/valid/workflow-roc/ro-crate-metadata.json b/tests/data/crates/valid/workflow-roc/ro-crate-metadata.json new file mode 100644 index 00000000..22014d97 --- /dev/null +++ b/tests/data/crates/valid/workflow-roc/ro-crate-metadata.json @@ -0,0 +1,137 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-04-17T13:39:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case.cwl" + }, + { + "@id": "blank.png" + }, + { + "@id": "README.md" + }, + { + "@id": "test/" + }, + { + "@id": "examples/" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + } + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "image": { + "@id": "blank.png" + }, + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "subjectOf": { + "@id": "sort-and-change-case.cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "sort-and-change-case.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "HowTo" + ], + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + } + }, + { + "@id": "blank.png", + "@type": [ + "File", + "ImageObject" + ] + }, + { + "@id": "README.md", + "@type": "File", + "about": { + "@id": "./" + }, + "encodingFormat": "text/markdown" + }, + { + "@id": "test/", + "@type": "Dataset" + }, + { + "@id": "examples/", + "@type": "Dataset" + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/valid/workflow-roc/sort-and-change-case.cwl b/tests/data/crates/valid/workflow-roc/sort-and-change-case.cwl new file mode 100644 index 00000000..679fb292 --- /dev/null +++ b/tests/data/crates/valid/workflow-roc/sort-and-change-case.cwl @@ -0,0 +1,42 @@ +class: Workflow +cwlVersion: v1.2.0-dev2 +doc: 'Abstract CWL Automatically generated from the Galaxy workflow file: sort-and-change-case' +inputs: + 0_Input Dataset: + format: data + type: File +outputs: {} +steps: + 1_Sort: + in: + input: 0_Input Dataset + out: + - out_file1 + run: + class: Operation + id: sort1 + inputs: + input: + format: Any + type: File + outputs: + out_file1: + doc: input + type: File + 2_Change Case: + in: + input: 1_Sort/out_file1 + out: + - out_file1 + run: + class: Operation + id: ChangeCase + inputs: + input: + format: Any + type: File + outputs: + out_file1: + doc: tabular + type: File + diff --git a/tests/data/crates/valid/workflow-roc/sort-and-change-case.ga b/tests/data/crates/valid/workflow-roc/sort-and-change-case.ga new file mode 100644 index 00000000..5a199969 --- /dev/null +++ b/tests/data/crates/valid/workflow-roc/sort-and-change-case.ga @@ -0,0 +1,118 @@ +{ + "uuid": "e2a8566c-c025-4181-9e90-7ed29d4e4df1", + "tags": [], + "format-version": "0.1", + "name": "sort-and-change-case", + "version": 0, + "steps": { + "0": { + "tool_id": null, + "tool_version": null, + "outputs": [], + "workflow_outputs": [], + "input_connections": {}, + "tool_state": "{}", + "id": 0, + "uuid": "5a36fad2-66c7-4b9e-8759-0fbcae9b8541", + "errors": null, + "name": "Input dataset", + "label": "bed_input", + "inputs": [], + "position": { + "top": 200, + "left": 200 + }, + "annotation": "", + "content_id": null, + "type": "data_input" + }, + "1": { + "tool_id": "sort1", + "tool_version": "1.1.0", + "outputs": [ + { + "type": "input", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "8237f71a-bc2a-494e-a63c-09c1e65ef7c8", + "label": "sorted_bed" + } + ], + "input_connections": { + "input": { + "output_name": "output", + "id": 0 + } + }, + "tool_state": "{\"__page__\": null, \"style\": \"\\\"alpha\\\"\", \"column\": \"\\\"1\\\"\", \"__rerun_remap_job_id__\": null, \"column_set\": \"[]\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\", \"header_lines\": \"\\\"0\\\"\", \"order\": \"\\\"ASC\\\"\"}", + "id": 1, + "uuid": "0b6b3cda-c75f-452b-85b1-8ae4f3302ba4", + "errors": null, + "name": "Sort", + "post_job_actions": {}, + "label": "sort", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Sort" + } + ], + "position": { + "top": 200, + "left": 420 + }, + "annotation": "", + "content_id": "sort1", + "type": "tool" + }, + "2": { + "tool_id": "ChangeCase", + "tool_version": "1.0.0", + "outputs": [ + { + "type": "tabular", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "c31cd733-dab6-4d50-9fec-b644d162397b", + "label": "uppercase_bed" + } + ], + "input_connections": { + "input": { + "output_name": "out_file1", + "id": 1 + } + }, + "tool_state": "{\"__page__\": null, \"casing\": \"\\\"up\\\"\", \"__rerun_remap_job_id__\": null, \"cols\": \"\\\"c1\\\"\", \"delimiter\": \"\\\"TAB\\\"\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\"}", + "id": 2, + "uuid": "9698bcde-0729-48fe-b88d-ccfb6f6153b4", + "errors": null, + "name": "Change Case", + "post_job_actions": {}, + "label": "change_case", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Change Case" + } + ], + "position": { + "top": 200, + "left": 640 + }, + "annotation": "", + "content_id": "ChangeCase", + "type": "tool" + } + }, + "annotation": "", + "a_galaxy_workflow": "true" +} diff --git a/tests/data/crates/valid/workflow-run-crate/Galaxy-Workflow-Hello_World.ga b/tests/data/crates/valid/workflow-run-crate/Galaxy-Workflow-Hello_World.ga new file mode 100644 index 00000000..8bd9b1ba --- /dev/null +++ b/tests/data/crates/valid/workflow-run-crate/Galaxy-Workflow-Hello_World.ga @@ -0,0 +1,157 @@ +{ + "a_galaxy_workflow": "true", + "annotation": "From https://training.galaxyproject.org/training-material/topics/galaxy-interface/tutorials/workflow-editor/tutorial.html#creating-a-new-workflow", + "creator": [ + { + "class": "Person", + "identifier": "https://orcid.org/0000-0001-9842-9718", + "name": "Stian Soiland-Reyes" + } + ], + "format-version": "0.1", + "license": "CC0-1.0", + "name": "Hello World", + "steps": { + "0": { + "annotation": "A simple set of lines in a text file", + "content_id": null, + "errors": null, + "id": 0, + "input_connections": {}, + "inputs": [ + { + "description": "A simple set of lines in a text file", + "name": "simple_input" + } + ], + "label": "simple_input", + "name": "Input dataset", + "outputs": [], + "position": { + "bottom": 519.227779812283, + "height": 55.616668701171875, + "left": 626.0000271267361, + "right": 806.0000271267361, + "top": 463.6111111111111, + "width": 180, + "x": 626.0000271267361, + "y": 463.6111111111111 + }, + "tool_id": null, + "tool_state": "{\"optional\": false}", + "tool_version": null, + "type": "data_input", + "uuid": "75e4b93c-1b01-4332-8e2d-974bc03870b2", + "workflow_outputs": [] + }, + "1": { + "annotation": "Return all the lines of a text file reversed, last to first", + "content_id": "toolshed.g2.bx.psu.edu/repos/bgruening/text_processing/tp_tac/1.1.0", + "errors": null, + "id": 1, + "input_connections": { + "infile": { + "id": 0, + "output_name": "output" + } + }, + "inputs": [ + { + "description": "runtime parameter for tool tac", + "name": "infile" + } + ], + "label": "Reverse dataset", + "name": "tac", + "outputs": [ + { + "name": "outfile", + "type": "input" + } + ], + "position": { + "bottom": 669.8444400363499, + "height": 102.23332214355469, + "left": 883.9999728732639, + "right": 1063.999972873264, + "top": 567.6111178927952, + "width": 180, + "x": 883.9999728732639, + "y": 567.6111178927952 + }, + "post_job_actions": {}, + "tool_id": "toolshed.g2.bx.psu.edu/repos/bgruening/text_processing/tp_tac/1.1.0", + "tool_shed_repository": { + "changeset_revision": "ddf54b12c295", + "name": "text_processing", + "owner": "bgruening", + "tool_shed": "toolshed.g2.bx.psu.edu" + }, + "tool_state": "{\"infile\": {\"__class__\": \"RuntimeValue\"}, \"separator\": {\"separator_select\": \"no\", \"__current_case__\": 0}, \"__page__\": null, \"__rerun_remap_job_id__\": null}", + "tool_version": "1.1.0", + "type": "tool", + "uuid": "1e2bcc37-edad-4d9d-9ae8-a27e183ee55a", + "workflow_outputs": [ + { + "label": "reversed", + "output_name": "outfile", + "uuid": "bb56259b-0460-4187-a4a1-2b7b3a868d6d" + } + ] + }, + "2": { + "annotation": "The last lines of workflow input are the first lines of the reversed input.", + "content_id": "Show beginning1", + "errors": null, + "id": 2, + "input_connections": { + "input": { + "id": 1, + "output_name": "outfile" + } + }, + "inputs": [ + { + "description": "runtime parameter for tool Select first", + "name": "input" + } + ], + "label": "Select last lines", + "name": "Select first", + "outputs": [ + { + "name": "out_file1", + "type": "input" + } + ], + "position": { + "bottom": 819.8444061279297, + "height": 102.23332214355469, + "left": 1168.999972873264, + "right": 1348.999972873264, + "top": 717.611083984375, + "width": 180, + "x": 1168.999972873264, + "y": 717.611083984375 + }, + "post_job_actions": {}, + "tool_id": "Show beginning1", + "tool_state": "{\"header\": \"false\", \"input\": {\"__class__\": \"RuntimeValue\"}, \"lineNum\": \"2\", \"__page__\": null, \"__rerun_remap_job_id__\": null}", + "tool_version": "1.0.1", + "type": "tool", + "uuid": "b378a19a-2126-4302-aace-c3311b7ef64e", + "workflow_outputs": [ + { + "label": "last_lines", + "output_name": "out_file1", + "uuid": "8fe82179-555b-4ace-ad8b-ab3a6587aea8" + } + ] + } + }, + "tags": [ + "example" + ], + "uuid": "576ba0e9-b112-47f0-845e-32d8af3a1f35", + "version": 3 +} \ No newline at end of file diff --git a/tests/data/crates/valid/workflow-run-crate/ro-crate-metadata.json b/tests/data/crates/valid/workflow-run-crate/ro-crate-metadata.json new file mode 100644 index 00000000..7583a63e --- /dev/null +++ b/tests/data/crates/valid/workflow-run-crate/ro-crate-metadata.json @@ -0,0 +1,270 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/workflow-run/context" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "conformsTo": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.1" + }, + { + "@id": "https://w3id.org/ro/wfrun/workflow/0.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ], + "hasPart": [ + { + "@id": "Galaxy-Workflow-Hello_World.ga" + }, + { + "@id": "inputs/abcdef.txt" + }, + { + "@id": "outputs/Select_first_on_data_1_2.txt" + }, + { + "@id": "outputs/tac_on_data_360_1.txt" + } + ], + "license": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "mainEntity": { + "@id": "Galaxy-Workflow-Hello_World.ga" + }, + "mentions": { + "@id": "#wfrun-5a5970ab-4375-444d-9a87-a764a66e3a47" + } + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.1", + "@type": "CreativeWork", + "name": "Process Run Crate", + "version": "0.1" + }, + { + "@id": "https://w3id.org/ro/wfrun/workflow/0.1", + "@type": "CreativeWork", + "name": "Workflow Run Crate", + "version": "0.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0", + "@type": "CreativeWork", + "name": "Workflow RO-Crate", + "version": "1.0" + }, + { + "@id": "Galaxy-Workflow-Hello_World.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "name": "Hello World (Galaxy Workflow)", + "author": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "creator": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + }, + "input": [ + { + "@id": "#simple_input" + }, + { + "@id": "#verbose-param" + } + ], + "output": [ + { + "@id": "#reversed" + }, + { + "@id": "#last_lines" + } + ] + }, + { + "@id": "#simple_input", + "@type": "FormalParameter", + "additionalType": "File", + "conformsTo": { + "@id": "https://bioschemas.org/profiles/FormalParameter/1.0-RELEASE" + }, + "description": "A simple set of lines in a text file", + "encodingFormat": [ + "text/plain", + { + "@id": "http://edamontology.org/format_2330" + } + ], + "workExample": { + "@id": "inputs/abcdef.txt" + }, + "name": "simple_input", + "valueRequired": "True" + }, + { + "@id": "#verbose-param", + "@type": "FormalParameter", + "additionalType": "Boolean", + "conformsTo": { + "@id": "https://bioschemas.org/profiles/FormalParameter/1.0-RELEASE" + }, + "description": "Increase logging output", + "workExample": { + "@id": "#verbose-pv" + }, + "name": "verbose", + "valueRequired": "False" + }, + { + "@id": "#reversed", + "@type": "FormalParameter", + "additionalType": "File", + "conformsTo": { + "@id": "https://bioschemas.org/profiles/FormalParameter/1.0-RELEASE" + }, + "description": "All the lines, reversed", + "encodingFormat": [ + "text/plain", + { + "@id": "http://edamontology.org/format_2330" + } + ], + "name": "reversed", + "workExample": { + "@id": "outputs/tac_on_data_360_1.txt" + } + }, + { + "@id": "#last_lines", + "@type": "FormalParameter", + "additionalType": "File", + "conformsTo": { + "@id": "https://bioschemas.org/profiles/FormalParameter/1.0-RELEASE" + }, + "description": "The last lines of workflow input are the first lines of the reversed input", + "encodingFormat": [ + "text/plain", + { + "@id": "http://edamontology.org/format_2330" + } + ], + "name": "last_lines", + "workExample": { + "@id": "outputs/Select_first_on_data_1_2.txt" + } + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": "https://galaxyproject.org/", + "name": "Galaxy", + "url": "https://galaxyproject.org/" + }, + { + "@id": "#wfrun-5a5970ab-4375-444d-9a87-a764a66e3a47", + "@type": "CreateAction", + "name": "Galaxy workflow run 5a5970ab-4375-444d-9a87-a764a66e3a47", + "endTime": "2018-09-19T17:01:07+10:00", + "instrument": { + "@id": "Galaxy-Workflow-Hello_World.ga" + }, + "subjectOf": { + "@id": "https://usegalaxy.eu/u/5dbf7f05329e49c98b31243b5f35045c/p/invocation-report-a3a1d27edb703e5c" + }, + "object": [ + { + "@id": "inputs/abcdef.txt" + }, + { + "@id": "#verbose-pv" + } + ], + "result": [ + { + "@id": "outputs/Select_first_on_data_1_2.txt" + }, + { + "@id": "outputs/tac_on_data_360_1.txt" + } + ] + }, + { + "@id": "inputs/abcdef.txt", + "@type": "File", + "description": "Example input, a simple text file", + "encodingFormat": "text/plain", + "exampleOfWork": { + "@id": "#simple_input" + } + }, + { + "@id": "#verbose-pv", + "@type": "PropertyValue", + "exampleOfWork": { + "@id": "#verbose-param" + }, + "name": "verbose", + "value": "True" + }, + { + "@id": "outputs/Select_first_on_data_1_2.txt", + "@type": "File", + "name": "Select_first_on_data_1_2 (output)", + "description": "Example output of the last (aka first of reversed) lines", + "encodingFormat": "text/plain", + "exampleOfWork": { + "@id": "#last_lines" + } + }, + { + "@id": "outputs/tac_on_data_360_1.txt", + "@type": "File", + "name": "tac_on_data_360_1 (output)", + "description": "Example output of the reversed lines", + "encodingFormat": "text/plain", + "exampleOfWork": { + "@id": "#reversed" + } + }, + { + "@id": "https://usegalaxy.eu/u/5dbf7f05329e49c98b31243b5f35045c/p/invocation-report-a3a1d27edb703e5c", + "@type": "CreativeWork", + "encodingFormat": "text/html", + "datePublished": "2021-11-18T02:02:00Z", + "name": "Workflow Execution Summary of Hello World" + } + ] +} diff --git a/tests/data/crates/valid/workflow-testing-ro-crate/ro-crate-metadata.json b/tests/data/crates/valid/workflow-testing-ro-crate/ro-crate-metadata.json new file mode 100644 index 00000000..465d12d6 --- /dev/null +++ b/tests/data/crates/valid/workflow-testing-ro-crate/ro-crate-metadata.json @@ -0,0 +1,133 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + "https://w3id.org/ro/terms/test" + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2024-09-17T11:09:44+00:00", + "hasPart": [ + { + "@id": "sort-and-change-case.ga" + }, + { + "@id": "sort-and-change-case-tests.yml" + } + ], + "license": { + "@id": "https://spdx.org/licenses/Apache-2.0.html" + }, + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "mentions": [ + { + "@id": "#test1" + } + ] + }, + { + "@id": "https://spdx.org/licenses/Apache-2.0.html", + "@type": "CreativeWork", + "name": "Apache 2.0 license" + }, + { + "@id": "sort-and-change-case.ga", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow" + ], + "conformsTo": { + "@id": "https://bioschemas.org/profiles/ComputationalWorkflow/1.0-RELEASE" + }, + "description": "sort lines and change text to upper case", + "license": "https://spdx.org/licenses/MIT.html", + "name": "sort-and-change-case", + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#galaxy", + "@type": "ComputerLanguage", + "identifier": { + "@id": "https://galaxyproject.org/" + }, + "name": "Galaxy", + "url": { + "@id": "https://galaxyproject.org/" + } + }, + { + "@id": "#test1", + "name": "test1", + "@type": "TestSuite", + "mainEntity": { + "@id": "sort-and-change-case.ga" + }, + "instance": [ + { + "@id": "#test1_1" + } + ], + "definition": { + "@id": "sort-and-change-case-tests.yml" + } + }, + { + "@id": "#test1_1", + "name": "test1_1", + "@type": "TestInstance", + "runsOn": { + "@id": "https://w3id.org/ro/terms/test#JenkinsService" + }, + "url": "http://example.org/jenkins", + "resource": "job/tests/" + }, + { + "@id": "https://w3id.org/ro/terms/test#JenkinsService", + "@type": "TestService", + "name": "Jenkins", + "url": { + "@id": "https://www.jenkins.io" + } + }, + { + "@id": "sort-and-change-case-tests.yml", + "@type": [ + "File", + "TestDefinition" + ], + "conformsTo": { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine" + }, + "engineVersion": ">=0.70" + }, + { + "@id": "https://w3id.org/ro/terms/test#PlanemoEngine", + "@type": "SoftwareApplication", + "name": "Planemo", + "url": { + "@id": "https://github.com/galaxyproject/planemo" + } + } + ] +} diff --git a/tests/data/crates/valid/workflow-testing-ro-crate/sort-and-change-case-tests.yml b/tests/data/crates/valid/workflow-testing-ro-crate/sort-and-change-case-tests.yml new file mode 100644 index 00000000..9f24ba69 --- /dev/null +++ b/tests/data/crates/valid/workflow-testing-ro-crate/sort-and-change-case-tests.yml @@ -0,0 +1,8 @@ +- doc: test with a small input + job: + bed_input: + class: File + path: http://example.com/data/input.bed + outputs: + uppercase_bed: + path: http://example.com/data/output_exp.bed diff --git a/tests/data/crates/valid/workflow-testing-ro-crate/sort-and-change-case.ga b/tests/data/crates/valid/workflow-testing-ro-crate/sort-and-change-case.ga new file mode 100644 index 00000000..5a199969 --- /dev/null +++ b/tests/data/crates/valid/workflow-testing-ro-crate/sort-and-change-case.ga @@ -0,0 +1,118 @@ +{ + "uuid": "e2a8566c-c025-4181-9e90-7ed29d4e4df1", + "tags": [], + "format-version": "0.1", + "name": "sort-and-change-case", + "version": 0, + "steps": { + "0": { + "tool_id": null, + "tool_version": null, + "outputs": [], + "workflow_outputs": [], + "input_connections": {}, + "tool_state": "{}", + "id": 0, + "uuid": "5a36fad2-66c7-4b9e-8759-0fbcae9b8541", + "errors": null, + "name": "Input dataset", + "label": "bed_input", + "inputs": [], + "position": { + "top": 200, + "left": 200 + }, + "annotation": "", + "content_id": null, + "type": "data_input" + }, + "1": { + "tool_id": "sort1", + "tool_version": "1.1.0", + "outputs": [ + { + "type": "input", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "8237f71a-bc2a-494e-a63c-09c1e65ef7c8", + "label": "sorted_bed" + } + ], + "input_connections": { + "input": { + "output_name": "output", + "id": 0 + } + }, + "tool_state": "{\"__page__\": null, \"style\": \"\\\"alpha\\\"\", \"column\": \"\\\"1\\\"\", \"__rerun_remap_job_id__\": null, \"column_set\": \"[]\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\", \"header_lines\": \"\\\"0\\\"\", \"order\": \"\\\"ASC\\\"\"}", + "id": 1, + "uuid": "0b6b3cda-c75f-452b-85b1-8ae4f3302ba4", + "errors": null, + "name": "Sort", + "post_job_actions": {}, + "label": "sort", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Sort" + } + ], + "position": { + "top": 200, + "left": 420 + }, + "annotation": "", + "content_id": "sort1", + "type": "tool" + }, + "2": { + "tool_id": "ChangeCase", + "tool_version": "1.0.0", + "outputs": [ + { + "type": "tabular", + "name": "out_file1" + } + ], + "workflow_outputs": [ + { + "output_name": "out_file1", + "uuid": "c31cd733-dab6-4d50-9fec-b644d162397b", + "label": "uppercase_bed" + } + ], + "input_connections": { + "input": { + "output_name": "out_file1", + "id": 1 + } + }, + "tool_state": "{\"__page__\": null, \"casing\": \"\\\"up\\\"\", \"__rerun_remap_job_id__\": null, \"cols\": \"\\\"c1\\\"\", \"delimiter\": \"\\\"TAB\\\"\", \"input\": \"{\\\"__class__\\\": \\\"RuntimeValue\\\"}\"}", + "id": 2, + "uuid": "9698bcde-0729-48fe-b88d-ccfb6f6153b4", + "errors": null, + "name": "Change Case", + "post_job_actions": {}, + "label": "change_case", + "inputs": [ + { + "name": "input", + "description": "runtime parameter for tool Change Case" + } + ], + "position": { + "top": 200, + "left": 640 + }, + "annotation": "", + "content_id": "ChangeCase", + "type": "tool" + } + }, + "annotation": "", + "a_galaxy_workflow": "true" +} diff --git a/tests/data/crates/valid/wrroc-paper-long-date/index.html b/tests/data/crates/valid/wrroc-paper-long-date/index.html new file mode 120000 index 00000000..a79e58d6 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper-long-date/index.html @@ -0,0 +1 @@ +../wrroc-paper/index.html \ No newline at end of file diff --git a/tests/data/crates/valid/wrroc-paper-long-date/mapping b/tests/data/crates/valid/wrroc-paper-long-date/mapping new file mode 120000 index 00000000..3853ffbd --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper-long-date/mapping @@ -0,0 +1 @@ +../wrroc-paper/mapping/ \ No newline at end of file diff --git a/tests/data/crates/valid/wrroc-paper-long-date/ro-crate-metadata.json b/tests/data/crates/valid/wrroc-paper-long-date/ro-crate-metadata.json new file mode 100644 index 00000000..0ca2fa9f --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper-long-date/ro-crate-metadata.json @@ -0,0 +1,814 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "Standard": "http://purl.org/dc/terms/Standard", + "Profile": "http://www.w3.org/ns/dx/prof/Profile", + "MappingSet": "https://w3id.org/sssom/schema/MappingSet" + }, + { + "copyrightNotice": "http://schema.org/copyrightNotice", + "interpretedAsClaim": "http://schema.org/interpretedAsClaim", + "archivedAt": "http://schema.org/archivedAt", + "creditText": "http://schema.org/creditText" + } + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + }, + "author": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "license": { + "@id": "https://creativecommons.org/publicdomain/zero/1.0/" + } + }, + { + "@id": "./", + "identifier": { + "@id": "https://doi.org/10.5281/zenodo.10368990" + }, + "url": "https://w3id.org/ro/doi/10.5281/zenodo.10368989", + "@type": "Dataset", + "about": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "author": [ + { + "@id": "https://orcid.org/0000-0001-8271-5429" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024" + }, + { + "@id": "https://orcid.org/0000-0002-4663-5613" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140" + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017" + }, + { + "@id": "https://orcid.org/0000-0002-1119-1792" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049" + }, + { + "@id": "https://orcid.org/0000-0002-0309-604X" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + ], + "description": "RO-Crate for the manuscript that describes Workflow Run Crate, includes mapping to PROV using SKOS/SSSOM", + "hasPart": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/workflow/0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/provenance/0.3" + }, + { + "@id": "mapping/" + } + ], + "name": "Recording provenance of workflow runs with RO-Crate (RO-Crate and mapping)", + "datePublished": "2023-12-12T12:00:00+06:00", + "license": { + "@id": "https://www.apache.org/licenses/LICENSE-2.0" + }, + "creator": [] + }, + { + "@id": "https://doi.org/10.5281/zenodo.10368990", + "@type": "PropertyValue", + "name": "doi", + "propertyID": "https://registry.identifiers.org/registry/doi", + "value": "doi:10.5281/zenodo.10368990", + "url": "https://doi.org/10.5281/zenodo.10368990" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "affiliation": [ + "Department of Computer Science, The University of Manchester, Manchester, United Kingdom", + "Informatics Institute, University of Amsterdam, Amsterdam, The Netherlands" + ], + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://researchobject.org/workflow-run-crate/", + "@type": "Project", + "member": [ + { + "@id": "https://orcid.org/0000-0001-8271-5429" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + { + "@id": "https://orcid.org/0000-0002-5432-2748" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140" + }, + { + "@id": "https://orcid.org/0000-0003-3156-2105" + }, + { + "@id": "https://orcid.org/0000-0002-6190-122X" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670" + }, + { + "@id": "https://orcid.org/0000-0003-3986-0510" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024" + }, + { + "@id": "https://orcid.org/0000-0002-9464-6640" + }, + { + "@id": "https://orcid.org/0000-0001-5845-8880" + }, + { + "@id": "https://orcid.org/0000-0003-4894-4660" + }, + { + "@id": "https://orcid.org/0000-0002-4405-6802" + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017" + }, + { + "@id": "https://orcid.org/0000-0003-0617-9219" + }, + { + "@id": "https://orcid.org/0000-0001-9228-2882" + }, + { + "@id": "https://orcid.org/0000-0003-3898-9451" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049" + }, + { + "@id": "https://orcid.org/0000-0001-9818-9320" + }, + { + "@id": "https://orcid.org/0000-0002-8122-9522" + }, + { + "@id": "https://orcid.org/0000-0002-8330-4071" + }, + { + "@id": "https://orcid.org/0000-0003-4073-7456" + }, + { + "@id": "https://orcid.org/0000-0003-1361-7301" + }, + { + "@id": "https://orcid.org/0000-0002-5358-616X" + }, + { + "@id": "https://orcid.org/0000-0002-5477-287X" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086" + }, + { + "@id": "https://orcid.org/0000-0001-8172-8981" + }, + { + "@id": "https://orcid.org/0000-0001-6740-9212" + } + ], + "name": "Workflow Run Crate task force", + "parentOrganization": { + "@id": "https://www.researchobject.org/ro-crate/community" + } + }, + { + "@id": "https://orcid.org/0000-0001-8271-5429", + "@type": "Person", + "affiliation": "Center for Advanced Studies, Research, and Development in Sardinia (CRS4), Pula, Sardinia, Italy", + "name": "Simone Leo" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670", + "@type": "Person", + "affiliation": [ + "Vrije Universiteit Amsterdam, Amsterdam, The Netherlands", + "DTL Projects, The Netherlands", + "Forschungszentrum JΓΌlich, Germany" + ], + "name": "Michael R Crusoe" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": "Laura RodrΓ­guez-Navas" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": "RaΓΌl Sirvent" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652", + "@type": "Person", + "affiliation": [ + "Biozentrum, University of Basel, Basel, Switzerland", + "Swiss Institute of Bioinformatics, Lausanne, Switzerland" + ], + "name": "Alexander Kanitz" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946", + "@type": "Person", + "affiliation": "VIB-UGent Center for Plant Systems Biology, Gent, Belgium", + "name": "Paul De Geest" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024", + "@type": "Person", + "affiliation": [ + "Faculty of Informatics, Masaryk Universit, Brno, Czech Republic", + "Institute of Computer Science, Masaryk University, Brno, Czech Republic", + "BBMRI-ERIC, Graz, Austria" + ], + "name": "Rudolf Wittner" + }, + { + "@id": "https://orcid.org/0000-0002-4663-5613", + "@type": "Person", + "affiliation": "Center for Advanced Studies, Research, and Development in Sardinia (CRS4), Pula, Sardinia, Italy", + "name": "Luca Pireddu" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145", + "@type": "Person", + "affiliation": "Ontology Engineering Group, Universidad PolitΓ©cnica de Madrid, Madrid, Spain", + "name": "Daniel Garijo" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": [ + "JosΓ© MarΓ­a FernΓ‘ndez", + "JosΓ© M. FernΓ‘ndez" + ] + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017", + "@type": "Person", + "affiliation": "Computer Science Dept., UniversitΓ  degli Studi di Torino, Torino, Italy", + "name": "Iacopo Colonnelli" + }, + { + "@id": "https://orcid.org/0000-0002-1119-1792", + "@type": "Person", + "affiliation": "Faculty of Informatics, Masaryk University, Brno, Czech Republic", + "name": "Matej Gallo" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945", + "@type": "Person", + "affiliation": [ + "Database Center for Life Science, Joint Support-Center for Data Science Research, Research Organization of Information and Systems, Shizuoka, Japan", + "Institute for Advanced Academic Research, Chiba University, Chiba, Japan" + ], + "name": "Tazro Ohta" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049", + "@type": "Person", + "affiliation": "Sator Inc., Tokyo, Japan", + "name": "Hirotaka Suetake" + }, + { + "@id": "https://orcid.org/0000-0002-0309-604X", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": "Salvador Capella-Gutierrez" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086", + "@type": "Person", + "affiliation": "Vrije Universiteit Amsterdam, Amsterdam, The Netherlands", + "name": "Renske de Wit" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": [ + "Bruno P. Kinoshita", + "Bruno de Paula Kinoshita" + ] + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.3", + "@type": "Profile", + "author": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "name": "Process Run Crate", + "version": "0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/workflow/0.3", + "@type": "Profile", + "author": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "name": "Workflow Run Crate", + "version": "0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/provenance/0.3", + "@type": "Profile", + "author": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "name": "Provenance Run Crate", + "version": "0.3" + }, + { + "@id": "mapping/environment.yml", + "@type": "File", + "conformsTo": { + "@id": "https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually" + }, + "encodingFormat": [ + "application/yaml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/818" + } + ], + "name": "Conda environment for sssom" + }, + { + "@id": "mapping/environment.lock.yml", + "@type": "File", + "conformsTo": { + "@id": "https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually" + }, + "encodingFormat": [ + "application/yaml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/818" + } + ], + "name": "Conda environment for sssom, version-pinned" + }, + { + "@id": "mapping/prov-mapping.tsv", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": [ + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#tsv" + } + ], + "author": [ + { + "@id": "https://orcid.org/0000-0003-0454-7145" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + ], + "creator": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "encodingFormat": [ + "text/tab-separated-values", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/x-fmt/13" + } + ], + "name": "PROV mapping to Workflow Run Crate (SSSOM TSV)" + }, + { + "@id": "mapping/prov-mapping.yml", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": { + "@id": "https://w3id.org/sssom/" + }, + "encodingFormat": [ + "text/yaml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/818" + } + ], + "name": "PROV mapping to Workflow Run Crate (SSSOM metadata)" + }, + { + "@id": "mapping/prov-mapping-w-metadata.tsv", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": [ + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#tsv" + } + ], + "encodingFormat": "text/tab-separated-values", + "isBasedOn": [ + { + "@id": "mapping/prov-mapping.tsv" + }, + { + "@id": "mapping/prov-mapping.yml" + } + ], + "name": "PROV mapping to Workflow Run Crate (SSSOM TSV with metadata)" + }, + { + "@id": "mapping/prov-mapping.ttl", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": [ + { + "@id": "http://www.w3.org/TR/owl2-mapping-to-rdf/" + }, + { + "@id": "http://www.w3.org/TR/skos-reference" + } + ], + "encodingFormat": [ + "text/turtle", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/874" + } + ], + "isBasedOn": { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + "name": "PROV to Workflow Run Crate (SKOS and SSSOM OWL axioms)" + }, + { + "@id": "mapping/prov-mapping.rdf", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": [ + { + "@id": "http://www.w3.org/TR/owl2-mapping-to-rdf/" + }, + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#rdfxml-serialised-re-ified-owl-axioms" + } + ], + "encodingFormat": [ + "application/rdf+xml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/875" + } + ], + "isBasedOn": { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + "name": "PROV mapping to Workflow Run Crate (SSSOM OWL axioms)" + }, + { + "@id": "mapping/prov-mapping.json", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": [ + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#json" + } + ], + "encodingFormat": [ + "application/json", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/817" + } + ], + "isBasedOn": { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + "name": "PROV mapping to Workflow Run Crate (SSSOM JSON)" + }, + { + "@id": "http://www.w3.org/TR/owl2-mapping-to-rdf/", + "@type": "CreativeWork", + "name": "OWL 2 (in RDF)" + }, + { + "@id": "http://www.w3.org/TR/skos-reference", + "@type": "CreativeWork", + "alternateName": "SKOS Simple Knowledge Organization System Reference", + "name": "SKOS" + }, + { + "@id": "http://www.w3.org/ns/dx/prof/Profile", + "@type": "DefinedTerm", + "name": "Profile", + "url": "https://www.w3.org/TR/dx-prof/" + }, + { + "@id": "https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually", + "@type": "CreativeWork", + "name": "Conda environment file format" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#json", + "@type": "WebPageElement", + "name": "SSSOM JSON" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#rdfxml-serialised-re-ified-owl-axioms", + "@type": "WebPageElement", + "name": "SSSOM RDF/XML serialised re-ified OWL axioms" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#tsv", + "@type": "WebPageElement", + "name": "SSSOM TSV" + }, + { + "@id": "https://orcid.org/0000-0001-5845-8880", + "@type": "Person", + "name": "Sebastiaan Huber" + }, + { + "@id": "https://orcid.org/0000-0001-6740-9212", + "@type": "Person", + "name": "Samuel Lampa" + }, + { + "@id": "https://orcid.org/0000-0001-8172-8981", + "@type": "Person", + "name": "Jasper Koehorst" + }, + { + "@id": "https://orcid.org/0000-0001-9228-2882", + "@type": "Person", + "name": "Abigail Miller" + }, + { + "@id": "https://orcid.org/0000-0001-9818-9320", + "@type": "Person", + "name": "Johannes KΓΆster" + }, + { + "@id": "https://orcid.org/0000-0002-4405-6802", + "@type": "Person", + "name": "Haris Zafeiropoulos" + }, + { + "@id": "https://orcid.org/0000-0002-5358-616X", + "@type": "Person", + "name": "Petr Holub" + }, + { + "@id": "https://orcid.org/0000-0002-5432-2748", + "@type": "Person", + "name": "Paul Brack" + }, + { + "@id": "https://orcid.org/0000-0002-5477-287X", + "@type": "Person", + "name": "Milan Markovic" + }, + { + "@id": "https://orcid.org/0000-0002-6190-122X", + "@type": "Person", + "name": "Ignacio Eguinoa" + }, + { + "@id": "https://orcid.org/0000-0002-8122-9522", + "@type": "Person", + "name": "Luiz Gadelha" + }, + { + "@id": "https://orcid.org/0000-0002-8330-4071", + "@type": "Person", + "name": "Mahnoor Zulfiqar" + }, + { + "@id": "https://orcid.org/0000-0002-9464-6640", + "@type": "Person", + "name": "Wolfgang Maier" + }, + { + "@id": "https://orcid.org/0000-0003-0617-9219", + "@type": "Person", + "name": "Jake Emerson" + }, + { + "@id": "https://orcid.org/0000-0003-1361-7301", + "@type": "Person", + "name": "Maciek BΔ…k" + }, + { + "@id": "https://orcid.org/0000-0003-3156-2105", + "@type": "Person", + "name": "Alan R Williams" + }, + { + "@id": "https://orcid.org/0000-0003-3898-9451", + "@type": "Person", + "name": "Stelios Ninidakis" + }, + { + "@id": "https://orcid.org/0000-0003-3986-0510", + "@type": "Person", + "name": "LJ Garcia Castro" + }, + { + "@id": "https://orcid.org/0000-0003-4073-7456", + "@type": "Person", + "name": "Romain David" + }, + { + "@id": "https://orcid.org/0000-0003-4894-4660", + "@type": "Person", + "name": "Kevin Jablonka" + }, + { + "@id": "https://www.researchobject.org/ro-crate/community", + "@type": "Project", + "name": "RO-Crate Community" + }, + { + "@id": "https://w3id.org/sssom/", + "@type": [ + "WebPage", + "Standard" + ], + "alternateName": "Simple Standard for Sharing Ontological Mappings", + "name": "SSSOM", + "version": "0.15.0" + }, + { + "@id": "https://w3id.org/sssom/schema/MappingSet", + "@type": "DefinedTerm", + "url": "https://mapping-commons.github.io/sssom/MappingSet/" + }, + { + "@id": "https://www.apache.org/licenses/LICENSE-2.0", + "@type": "CreativeWork", + "name": "Apache License, Version 2.0", + "version": "2.0", + "identifier": { + "@id": "http://spdx.org/licenses/Apache-2.0" + } + }, + { + "@id": "https://creativecommons.org/publicdomain/zero/1.0/", + "@type": "CreativeWork", + "identifier": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "name": "Creative Commons Zero v1.0 Universal", + "version": "1.0" + }, + { + "@id": "https://creativecommons.org/licenses/by/4.0/", + "@type": "CreativeWork", + "identifier": { + "@id": "http://spdx.org/licenses/CC-BY-4.0" + }, + "name": "Creative Commons Attribution 4.0 International", + "version": "4.0" + }, + { + "@id": "http://spdx.org/licenses/Apache-2.0", + "@type": "PropertyValue", + "propertyID": "http://spdx.org/rdf/terms#licenseId", + "name": "spdx", + "value": "Apache-2.0" + }, + { + "@id": "http://spdx.org/licenses/CC0-1.0", + "@type": "PropertyValue", + "propertyID": "http://spdx.org/rdf/terms#licenseId", + "name": "spdx", + "value": "CC0-1.0" + }, + { + "@id": "mapping/", + "@type": "Dataset", + "name": "PROV mapping to Workflow Run Crate", + "description": "Mapping using SKOS and SSSOM", + "hasPart": [ + { + "@id": "mapping/environment.yml" + }, + { + "@id": "mapping/environment.lock.yml" + }, + { + "@id": "mapping/prov-mapping.tsv" + }, + { + "@id": "mapping/prov-mapping.yml" + }, + { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + { + "@id": "mapping/prov-mapping.ttl" + }, + { + "@id": "mapping/prov-mapping.rdf" + }, + { + "@id": "mapping/prov-mapping.json" + } + ] + } + ] +} diff --git a/tests/data/crates/valid/wrroc-paper-long-date/ro-crate-metadata.jsonld b/tests/data/crates/valid/wrroc-paper-long-date/ro-crate-metadata.jsonld new file mode 120000 index 00000000..3c4380a3 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper-long-date/ro-crate-metadata.jsonld @@ -0,0 +1 @@ +../wrroc-paper/ro-crate-metadata.jsonld \ No newline at end of file diff --git a/tests/data/crates/valid/wrroc-paper-long-date/ro-crate-preview.html b/tests/data/crates/valid/wrroc-paper-long-date/ro-crate-preview.html new file mode 120000 index 00000000..f7e328f9 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper-long-date/ro-crate-preview.html @@ -0,0 +1 @@ +../wrroc-paper/ro-crate-preview.html \ No newline at end of file diff --git a/tests/data/crates/valid/wrroc-paper/index.html b/tests/data/crates/valid/wrroc-paper/index.html new file mode 100644 index 00000000..7a2fc2a8 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/index.html @@ -0,0 +1,2530 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+

Recording provenance of workflow runs with RO-Crate (RO-Crate and mapping)

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Simone Leo

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Michael R Crusoe

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Laura RodrΓ­guez-Navas

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: RaΓΌl Sirvent

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Alexander Kanitz

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Paul De Geest

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Rudolf Wittner

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Luca Pireddu

+ + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Daniel Garijo

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: JosΓ© MarΓ­a FernΓ‘ndez,JosΓ© M. FernΓ‘ndez

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Iacopo Colonnelli

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Matej Gallo

+ + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Tazro Ohta

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Hirotaka Suetake

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Salvador Capella-Gutierrez

+ + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Renske de Wit

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Bruno P. Kinoshita,Bruno de Paula Kinoshita

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Stian Soiland-Reyes

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: doi

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Workflow Run Crate task force

+ + + + + + +
+


+
+

Go to: Process Run Crate

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Workflow Run Crate

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Provenance Run Crate

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+ +


+
+

Go to: Apache License, Version 2.0

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+ + + + + + + diff --git a/tests/data/crates/valid/wrroc-paper/mapping/Makefile b/tests/data/crates/valid/wrroc-paper/mapping/Makefile new file mode 100644 index 00000000..82719940 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/mapping/Makefile @@ -0,0 +1,18 @@ +.PHONY: clean + +all: prov-mapping.rdf prov-mapping.json prov-mapping.ttl + +clean: + rm -f prov-mapping-w-metadata.tsv prov-mapping.rdf prov-mapping.ttl prov-mapping.json + +prov-mapping-w-metadata.tsv: prov-mapping.yml prov-mapping.tsv + sssom parse -m prov-mapping.yml prov-mapping.tsv > prov-mapping-w-metadata.tsv + +prov-mapping.rdf: prov-mapping-w-metadata.tsv + sssom convert prov-mapping-w-metadata.tsv --output-format rdf --output prov-mapping.rdf + +prov-mapping.ttl: prov-mapping-w-metadata.tsv + sssom convert prov-mapping-w-metadata.tsv --output-format owl --output prov-mapping.ttl + +prov-mapping.json: prov-mapping-w-metadata.tsv + sssom convert prov-mapping-w-metadata.tsv --output-format json --output prov-mapping.json diff --git a/tests/data/crates/valid/wrroc-paper/mapping/README.md b/tests/data/crates/valid/wrroc-paper/mapping/README.md new file mode 100644 index 00000000..49bef88a --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/mapping/README.md @@ -0,0 +1,37 @@ +# SSSOM mapping from PROV to Workflow Run Crate + +## About SSSOM + +SSSOM is a way to specify semantic mappings, typically based on SKOS. + +* https://mapping-commons.github.io/sssom/spec/ +* https://mapping-commons.github.io/sssom/tutorial/ +* https://www.w3.org/TR/skos-primer/ + +SSSOM mapping ar typically edited collaboratively as tab-separated text files, which can then be converted to OWL assertions using the [SSSOM toolkit](https://mapping-commons.github.io/sssom-py/). + + + +## Editing + +Please edit [prov-mapping.tsv](prov-mapping.tsv) taking care not to break the tabular characters. You may use the _Rainbow CSV_ extension in Visual Studio Code, or a spreadsheet software. + +The metadata headers are maintained in [prov-mapping.yml](prov-mapping.yml) as well as in [ro-crate-metadata.json](../ro-crate-metadata.json). + +## Validating/converting + +Install the [SSSOM toolkit](https://mapping-commons.github.io/sssom-py/installation.html). + +If you use Conda, you can use: + +``` +conda env create -f environment.yml +conda activate sssom +``` + +Then to generate the converted file formats `prov-mapping.rdf prov-mapping.json prov-mapping.ttl` run: + +``` +make +``` + diff --git a/tests/data/crates/valid/wrroc-paper/mapping/environment.lock.yml b/tests/data/crates/valid/wrroc-paper/mapping/environment.lock.yml new file mode 100644 index 00000000..81c27174 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/mapping/environment.lock.yml @@ -0,0 +1,111 @@ +name: sssom +channels: + - conda-forge + - bioconda + - defaults +dependencies: + - _libgcc_mutex=0.1=conda_forge + - _openmp_mutex=4.5=2_gnu + - bzip2=1.0.8=h7f98852_4 + - ca-certificates=2023.7.22=hbcca054_0 + - ld_impl_linux-64=2.40=h41732ed_0 + - libexpat=2.5.0=hcb278e6_1 + - libffi=3.4.2=h7f98852_5 + - libgcc-ng=13.2.0=h807b86a_2 + - libgomp=13.2.0=h807b86a_2 + - libnsl=2.0.1=hd590300_0 + - libsqlite=3.43.2=h2797004_0 + - libuuid=2.38.1=h0b41bf4_0 + - libzlib=1.2.13=hd590300_5 + - ncurses=6.4=hcb278e6_0 + - openssl=3.1.3=hd590300_0 + - pip=23.3.1=pyhd8ed1ab_0 + - python=3.12.0=hab00c5b_0_cpython + - readline=8.2=h8228510_1 + - setuptools=68.2.2=pyhd8ed1ab_0 + - tk=8.6.13=h2797004_0 + - wheel=0.41.2=pyhd8ed1ab_0 + - xz=5.2.6=h166bdaf_0 + - pip: + - attrs==23.1.0 + - babel==2.13.0 + - beautifulsoup4==4.12.2 + - cachetools==5.3.1 + - certifi==2023.7.22 + - chardet==5.2.0 + - charset-normalizer==3.3.1 + - click==8.1.7 + - colorama==0.4.6 + - curies==0.6.6 + - deprecated==1.2.14 + - deprecation==2.1.0 + - distlib==0.3.7 + - editorconfig==0.12.3 + - filelock==3.12.4 + - ghp-import==2.1.0 + - greenlet==3.0.0 + - hbreader==0.9.1 + - idna==3.4 + - importlib-metadata==6.8.0 + - iniconfig==2.0.0 + - isodate==0.6.1 + - jinja2==3.1.2 + - jsbeautifier==1.14.9 + - json-flattener==0.1.9 + - jsonasobj2==1.0.4 + - jsonschema==4.19.1 + - jsonschema-specifications==2023.7.1 + - linkml-runtime==1.6.0 + - markdown==3.5 + - markupsafe==2.1.3 + - mergedeep==1.3.4 + - mkdocs==1.5.3 + - mkdocs-material==9.4.6 + - mkdocs-material-extensions==1.3 + - mkdocs-mermaid2-plugin==0.6.0 + - networkx==3.2 + - numpy==1.26.1 + - packaging==23.2 + - paginate==0.5.6 + - pandas==2.1.1 + - pansql==0.0.1 + - pathspec==0.11.2 + - platformdirs==3.11.0 + - pluggy==1.3.0 + - prefixcommons==0.1.12 + - prefixmaps==0.1.7 + - pydantic==1.10.13 + - pygments==2.16.1 + - pymdown-extensions==10.3.1 + - pyparsing==3.1.1 + - pyproject-api==1.6.1 + - pytest==7.4.2 + - pytest-logging==2015.11.4 + - python-dateutil==2.8.2 + - pytrie==0.4.0 + - pytz==2023.3.post1 + - pyyaml==6.0.1 + - pyyaml-env-tag==0.1 + - rdflib==7.0.0 + - referencing==0.30.2 + - regex==2023.10.3 + - requests==2.31.0 + - rpds-py==0.10.6 + - scipy==1.11.3 + - six==1.16.0 + - sortedcontainers==2.4.0 + - soupsieve==2.5 + - sparqlwrapper==2.0.0 + - sqlalchemy==2.0.22 + - sssom==0.3.41 + - sssom-schema==0.15.0 + - tox==4.11.3 + - typing-extensions==4.8.0 + - tzdata==2023.3 + - urllib3==2.0.7 + - validators==0.22.0 + - virtualenv==20.24.5 + - watchdog==3.0.0 + - wrapt==1.15.0 + - zipp==3.17.0 +prefix: /home/stain/miniconda3/envs/sssom diff --git a/tests/data/crates/valid/wrroc-paper/mapping/environment.yml b/tests/data/crates/valid/wrroc-paper/mapping/environment.yml new file mode 100644 index 00000000..9e6d1209 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/mapping/environment.yml @@ -0,0 +1,10 @@ +name: sssom +channels: + - conda-forge + - bioconda + - defaults +dependencies: + - python>3.8 + - pip + - pip: + - sssom>0.3 diff --git a/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping-w-metadata.tsv b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping-w-metadata.tsv new file mode 100644 index 00000000..cc71ad32 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping-w-metadata.tsv @@ -0,0 +1,46 @@ +# curie_map: +# bioschema: https://bioschemas.org/ +# orcid: https://orcid.org/ +# owl: http://www.w3.org/2002/07/owl# +# prov: http://www.w3.org/ns/prov# +# rdf: http://www.w3.org/1999/02/22-rdf-syntax-ns# +# rdfs: http://www.w3.org/2000/01/rdf-schema# +# schema: http://schema.org/ +# semapv: https://w3id.org/semapv/vocab/ +# skos: http://www.w3.org/2004/02/skos/core# +# sssom: https://w3id.org/sssom/ +# license: https://creativecommons.org/publicdomain/zero/1.0/ +# mapping_set_group: researchobject.org +# mapping_set_id: prov_wfrun +# mapping_set_title: Mapping PROV to Workflow Run RO-Crate +# object_source: https://w3id.org/ro/wfrun/provenance/0.3 +# object_source_version: 0.3 +# subject_source: http://www.w3.org/ns/prov-o# +# subject_source_version: 20130430 +subject_id subject_label predicate_id object_id object_label mapping_justification creator_id author_id mapping_date confidence comment +prov:Activity Activity skos:narrowMatch schema:CreateAction Create action semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 Assuming activity is workflow/process execution +prov:Activity Activity skos:narrowMatch schema:OrganizeAction Organize action semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 Assuming activity is workflow/process execution +prov:Agent Agent skos:narrowMatch schema:Organization Organization semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0001-9842-9718 2023-10-22 1.0 +prov:Agent Agent skos:narrowMatch schema:Person Person semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0001-9842-9718 2023-10-22 1.0 +prov:Agent Agent skos:relatedMatch schema:SoftwareApplication Software application semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0001-9842-9718 2023-10-22 0.75 +prov:Entity Entity skos:narrowMatch schema:Dataset Dataset semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 Assuming non-Plan entity +prov:Entity Entity skos:narrowMatch schema:MediaObject File (Media object) semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 Assuming non-Plan entity +prov:Entity Entity skos:narrowMatch schema:PropertyValue Property value semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 Assuming non-Plan entity +prov:Organization Organization skos:exactMatch schema:Organization Organization semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 +prov:Person Person skos:exactMatch schema:Person Person semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 +prov:Plan Plan skos:narrowMatch bioschema:ComputationalWorkflow Computational workflow semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 +prov:Plan Plan skos:narrowMatch schema:HowTo How-to semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 +prov:Plan Plan skos:narrowMatch schema:SoftwareApplication Software application semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 +prov:SoftwareAgent Software agent skos:relatedMatch schema:SoftwareApplication Software application semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 +prov:agent agent skos:narrowMatch schema:instrument instrument semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 Assuming agent is a workflow management system +prov:agent agent skos:relatedMatch schema:agent agent semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 Complex mapping: an agent implies the existence of a qualified association (prov:Association) linked to a prov:Agent through prov:agent +prov:endedAtTime ended at time skos:closeMatch schema:endTime end time semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0001-9842-9718 2023-10-22 0.95 +prov:hadPlan hadPlan skos:relatedMatch schema:instrument instrument semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 Complex mapping: an instrument implies the existence of a qualified association (prov:Association) linked to a prov:Plan through prov:hadPlan +prov:startedAtTime started at time skos:closeMatch schema:startTime start time semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0001-9842-9718 2023-10-22 0.95 +prov:used used skos:exactMatch schema:object object semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 +prov:wasAssociatedWith was associated with skos:narrowMatch schema:agent agent semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 +prov:wasAssociatedWith was associated with skos:narrowMatch schema:instrument instrument semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 +prov:wasEndedBy was ended by skos:relatedMatch schema:agent agent semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145|orcid:0000-0001-9842-9718 2023-10-22 0.95 +prov:wasGeneratedBy was generated by skos:closeMatch schema:result object_label semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 2023-10-22 0.95 Note inverse properties: :ent prov:wasGeneratedBy :act vs :act schema:result :ent +prov:wasStartedBy was started by skos:relatedMatch schema:agent agent semapv:ManualMappingCuration orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145|orcid:0000-0001-9842-9718 2023-10-22 0.95 + diff --git a/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.json b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.json new file mode 100644 index 00000000..d8592e21 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.json @@ -0,0 +1,610 @@ +{ + "mapping_set_id": "prov_wfrun", + "license": "https://creativecommons.org/publicdomain/zero/1.0/", + "mappings": [ + { + "subject_id": "prov:Activity", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:CreateAction", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Activity", + "object_label": "Create action", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95, + "comment": "Assuming activity is workflow/process execution" + }, + { + "subject_id": "prov:Activity", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:OrganizeAction", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Activity", + "object_label": "Organize action", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95, + "comment": "Assuming activity is workflow/process execution" + }, + { + "subject_id": "prov:Agent", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:Organization", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Agent", + "object_label": "Organization", + "author_id": [ + "orcid:0000-0001-9842-9718" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 1.0 + }, + { + "subject_id": "prov:Agent", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:Person", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Agent", + "object_label": "Person", + "author_id": [ + "orcid:0000-0001-9842-9718" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 1.0 + }, + { + "subject_id": "prov:Agent", + "predicate_id": "skos:relatedMatch", + "object_id": "schema:SoftwareApplication", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Agent", + "object_label": "Software application", + "author_id": [ + "orcid:0000-0001-9842-9718" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.75 + }, + { + "subject_id": "prov:Entity", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:Dataset", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Entity", + "object_label": "Dataset", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95, + "comment": "Assuming non-Plan entity" + }, + { + "subject_id": "prov:Entity", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:MediaObject", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Entity", + "object_label": "File (Media object)", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95, + "comment": "Assuming non-Plan entity" + }, + { + "subject_id": "prov:Entity", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:PropertyValue", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Entity", + "object_label": "Property value", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95, + "comment": "Assuming non-Plan entity" + }, + { + "subject_id": "prov:Organization", + "predicate_id": "skos:exactMatch", + "object_id": "schema:Organization", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Organization", + "object_label": "Organization", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:Person", + "predicate_id": "skos:exactMatch", + "object_id": "schema:Person", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Person", + "object_label": "Person", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:Plan", + "predicate_id": "skos:narrowMatch", + "object_id": "bioschema:ComputationalWorkflow", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Plan", + "object_label": "Computational workflow", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:Plan", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:HowTo", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Plan", + "object_label": "How-to", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:Plan", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:SoftwareApplication", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Plan", + "object_label": "Software application", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:SoftwareAgent", + "predicate_id": "skos:relatedMatch", + "object_id": "schema:SoftwareApplication", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "Software agent", + "object_label": "Software application", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:agent", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:instrument", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "agent", + "object_label": "instrument", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95, + "comment": "Assuming agent is a workflow management system" + }, + { + "subject_id": "prov:agent", + "predicate_id": "skos:relatedMatch", + "object_id": "schema:agent", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "agent", + "object_label": "agent", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95, + "comment": "Complex mapping: an agent implies the existence of a qualified association (prov:Association) linked to a prov:Agent through prov:agent" + }, + { + "subject_id": "prov:endedAtTime", + "predicate_id": "skos:closeMatch", + "object_id": "schema:endTime", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "ended at time", + "object_label": "end time", + "author_id": [ + "orcid:0000-0001-9842-9718" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:hadPlan", + "predicate_id": "skos:relatedMatch", + "object_id": "schema:instrument", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "hadPlan", + "object_label": "instrument", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95, + "comment": "Complex mapping: an instrument implies the existence of a qualified association (prov:Association) linked to a prov:Plan through prov:hadPlan" + }, + { + "subject_id": "prov:startedAtTime", + "predicate_id": "skos:closeMatch", + "object_id": "schema:startTime", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "started at time", + "object_label": "start time", + "author_id": [ + "orcid:0000-0001-9842-9718" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:used", + "predicate_id": "skos:exactMatch", + "object_id": "schema:object", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "used", + "object_label": "object", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:wasAssociatedWith", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:agent", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "was associated with", + "object_label": "agent", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:wasAssociatedWith", + "predicate_id": "skos:narrowMatch", + "object_id": "schema:instrument", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "was associated with", + "object_label": "instrument", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:wasEndedBy", + "predicate_id": "skos:relatedMatch", + "object_id": "schema:agent", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "was ended by", + "object_label": "agent", + "author_id": [ + "orcid:0000-0003-0454-7145", + "orcid:0000-0001-9842-9718" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + }, + { + "subject_id": "prov:wasGeneratedBy", + "predicate_id": "skos:closeMatch", + "object_id": "schema:result", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "was generated by", + "object_label": "object_label", + "author_id": [ + "orcid:0000-0003-0454-7145" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95, + "comment": "Note inverse properties: :ent prov:wasGeneratedBy :act vs :act schema:result :ent" + }, + { + "subject_id": "prov:wasStartedBy", + "predicate_id": "skos:relatedMatch", + "object_id": "schema:agent", + "mapping_justification": "semapv:ManualMappingCuration", + "subject_label": "was started by", + "object_label": "agent", + "author_id": [ + "orcid:0000-0003-0454-7145", + "orcid:0000-0001-9842-9718" + ], + "creator_id": [ + "orcid:0000-0001-9842-9718" + ], + "mapping_date": "2023-10-22", + "confidence": 0.95 + } + ], + "mapping_set_title": "Mapping PROV to Workflow Run RO-Crate", + "subject_source": "http://www.w3.org/ns/prov-o#", + "subject_source_version": 20130430, + "object_source": "https://w3id.org/ro/wfrun/provenance/0.3", + "object_source_version": 0.3, + "mapping_set_group": "researchobject.org", + "@type": "MappingSet", + "@context": { + "dcterms": "http://purl.org/dc/terms/", + "linkml": "https://w3id.org/linkml/", + "oboInOwl": "http://www.geneontology.org/formats/oboInOwl#", + "owl": "http://www.w3.org/2002/07/owl#", + "pav": "http://purl.org/pav/", + "prov": "http://www.w3.org/ns/prov#", + "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#", + "semapv": "https://w3id.org/semapv/vocab/", + "skos": "http://www.w3.org/2004/02/skos/core#", + "sssom": "https://w3id.org/sssom/", + "@vocab": "https://w3id.org/sssom/", + "author_id": { + "@type": "rdfs:Resource", + "@id": "pav:authoredBy" + }, + "comment": { + "@id": "rdfs:comment" + }, + "confidence": { + "@type": "xsd:double" + }, + "creator_id": { + "@type": "rdfs:Resource", + "@id": "dcterms:creator" + }, + "curation_rule": { + "@type": "rdfs:Resource" + }, + "documentation": { + "@type": "@id" + }, + "homepage": { + "@type": "@id" + }, + "imports": { + "@type": "@id" + }, + "issue_tracker": { + "@type": "@id" + }, + "issue_tracker_item": { + "@type": "rdfs:Resource" + }, + "last_updated": { + "@type": "xsd:date" + }, + "license": { + "@type": "@id", + "@id": "dcterms:license" + }, + "mapping_cardinality": { + "@context": { + "@vocab": "@null", + "text": "skos:notation", + "description": "skos:prefLabel", + "meaning": "@id" + } + }, + "mapping_date": { + "@type": "xsd:date", + "@id": "pav:authoredOn" + }, + "mapping_justification": { + "@type": "rdfs:Resource" + }, + "mapping_provider": { + "@type": "@id" + }, + "mapping_registry_id": { + "@type": "rdfs:Resource" + }, + "mapping_set_description": { + "@id": "dcterms:description" + }, + "mapping_set_id": { + "@type": "@id" + }, + "mapping_set_references": { + "@type": "@id" + }, + "mapping_set_source": { + "@type": "@id", + "@id": "prov:wasDerivedFrom" + }, + "mapping_set_title": { + "@id": "dcterms:title" + }, + "mapping_set_version": { + "@id": "owl:versionInfo" + }, + "mapping_source": { + "@type": "rdfs:Resource" + }, + "mappings": { + "@type": "@id" + }, + "mirror_from": { + "@type": "@id" + }, + "object_id": { + "@type": "rdfs:Resource", + "@id": "owl:annotatedTarget" + }, + "object_match_field": { + "@type": "rdfs:Resource" + }, + "object_preprocessing": { + "@type": "rdfs:Resource" + }, + "object_source": { + "@type": "rdfs:Resource" + }, + "object_type": { + "@context": { + "@vocab": "@null", + "text": "skos:notation", + "description": "skos:prefLabel", + "meaning": "@id" + } + }, + "predicate_id": { + "@type": "rdfs:Resource", + "@id": "owl:annotatedProperty" + }, + "predicate_modifier": { + "@context": { + "@vocab": "@null", + "text": "skos:notation", + "description": "skos:prefLabel", + "meaning": "@id" + } + }, + "predicate_type": { + "@context": { + "@vocab": "@null", + "text": "skos:notation", + "description": "skos:prefLabel", + "meaning": "@id" + } + }, + "publication_date": { + "@type": "xsd:date", + "@id": "dcterms:created" + }, + "registry_confidence": { + "@type": "xsd:double" + }, + "reviewer_id": { + "@type": "rdfs:Resource" + }, + "see_also": { + "@id": "rdfs:seeAlso" + }, + "semantic_similarity_score": { + "@type": "xsd:double" + }, + "subject_id": { + "@type": "rdfs:Resource", + "@id": "owl:annotatedSource" + }, + "subject_match_field": { + "@type": "rdfs:Resource" + }, + "subject_preprocessing": { + "@type": "rdfs:Resource" + }, + "subject_source": { + "@type": "rdfs:Resource" + }, + "subject_type": { + "@context": { + "@vocab": "@null", + "text": "skos:notation", + "description": "skos:prefLabel", + "meaning": "@id" + } + }, + "Mapping": { + "@id": "owl:Axiom" + }, + "bioschema": "https://bioschemas.org/", + "orcid": "https://orcid.org/", + "schema": "http://schema.org/" + } +} \ No newline at end of file diff --git a/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.rdf b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.rdf new file mode 100644 index 00000000..6311784d --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.rdf @@ -0,0 +1,310 @@ +@prefix bioschema: . +@prefix dc1: . +@prefix orcid: . +@prefix owl: . +@prefix pav: . +@prefix prov: . +@prefix rdfs: . +@prefix schema1: . +@prefix semapv: . +@prefix skos: . +@prefix sssom: . +@prefix xsd: . + +[] a sssom:MappingSet ; + dc1:license "https://creativecommons.org/publicdomain/zero/1.0/"^^xsd:anyURI ; + dc1:title "Mapping PROV to Workflow Run RO-Crate" ; + sssom:mapping_set_group "researchobject.org" ; + sssom:mapping_set_id "prov_wfrun"^^xsd:anyURI ; + sssom:mappings [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:exactMatch ; + owl:annotatedSource prov:Organization ; + owl:annotatedTarget schema1:Organization ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Organization" ; + sssom:subject_label "Organization" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Complex mapping: an instrument implies the existence of a qualified association (prov:Association) linked to a prov:Plan through prov:hadPlan" ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:hadPlan ; + owl:annotatedTarget schema1:instrument ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "instrument" ; + sssom:subject_label "hadPlan" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming non-Plan entity" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Entity ; + owl:annotatedTarget schema1:MediaObject ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "File (Media object)" ; + sssom:subject_label "Entity" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Agent ; + owl:annotatedTarget schema1:Organization ; + sssom:confidence 1e+00 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Organization" ; + sssom:subject_label "Agent" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Note inverse properties: :ent prov:wasGeneratedBy :act vs :act schema:result :ent" ; + owl:annotatedProperty skos:closeMatch ; + owl:annotatedSource prov:wasGeneratedBy ; + owl:annotatedTarget schema1:result ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "object_label" ; + sssom:subject_label "was generated by" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:closeMatch ; + owl:annotatedSource prov:endedAtTime ; + owl:annotatedTarget schema1:endTime ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "end time" ; + sssom:subject_label "ended at time" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718, + orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:wasStartedBy ; + owl:annotatedTarget schema1:agent ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "agent" ; + sssom:subject_label "was started by" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:Agent ; + owl:annotatedTarget schema1:SoftwareApplication ; + sssom:confidence 7.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Software application" ; + sssom:subject_label "Agent" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Plan ; + owl:annotatedTarget schema1:HowTo ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "How-to" ; + sssom:subject_label "Plan" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:exactMatch ; + owl:annotatedSource prov:Person ; + owl:annotatedTarget schema1:Person ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Person" ; + sssom:subject_label "Person" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:SoftwareAgent ; + owl:annotatedTarget schema1:SoftwareApplication ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Software application" ; + sssom:subject_label "Software agent" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming agent is a workflow management system" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:agent ; + owl:annotatedTarget schema1:instrument ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "instrument" ; + sssom:subject_label "agent" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Complex mapping: an agent implies the existence of a qualified association (prov:Association) linked to a prov:Agent through prov:agent" ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:agent ; + owl:annotatedTarget schema1:agent ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "agent" ; + sssom:subject_label "agent" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718, + orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:wasEndedBy ; + owl:annotatedTarget schema1:agent ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "agent" ; + sssom:subject_label "was ended by" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming activity is workflow/process execution" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Activity ; + owl:annotatedTarget schema1:OrganizeAction ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Organize action" ; + sssom:subject_label "Activity" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Agent ; + owl:annotatedTarget schema1:Person ; + sssom:confidence 1e+00 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Person" ; + sssom:subject_label "Agent" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Plan ; + owl:annotatedTarget bioschema:ComputationalWorkflow ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Computational workflow" ; + sssom:subject_label "Plan" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:exactMatch ; + owl:annotatedSource prov:used ; + owl:annotatedTarget schema1:object ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "object" ; + sssom:subject_label "used" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming non-Plan entity" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Entity ; + owl:annotatedTarget schema1:Dataset ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Dataset" ; + sssom:subject_label "Entity" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming non-Plan entity" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Entity ; + owl:annotatedTarget schema1:PropertyValue ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Property value" ; + sssom:subject_label "Entity" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming activity is workflow/process execution" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Activity ; + owl:annotatedTarget schema1:CreateAction ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Create action" ; + sssom:subject_label "Activity" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:wasAssociatedWith ; + owl:annotatedTarget schema1:instrument ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "instrument" ; + sssom:subject_label "was associated with" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:closeMatch ; + owl:annotatedSource prov:startedAtTime ; + owl:annotatedTarget schema1:startTime ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "start time" ; + sssom:subject_label "started at time" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:wasAssociatedWith ; + owl:annotatedTarget schema1:agent ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "agent" ; + sssom:subject_label "was associated with" ], + [ a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Plan ; + owl:annotatedTarget schema1:SoftwareApplication ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Software application" ; + sssom:subject_label "Plan" ] ; + sssom:object_source ; + sssom:object_source_version 3e-01 ; + sssom:subject_source ; + sssom:subject_source_version 20130430 . + + diff --git a/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.tsv b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.tsv new file mode 100644 index 00000000..3444964d --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.tsv @@ -0,0 +1,26 @@ +subject_id subject_label predicate_id object_id object_label mapping_justification mapping_date creator_id author_id confidence comment +prov:Activity Activity skos:narrowMatch schema:CreateAction Create action semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 Assuming activity is workflow/process execution +prov:Activity Activity skos:narrowMatch schema:OrganizeAction Organize action semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 Assuming activity is workflow/process execution +prov:agent agent skos:narrowMatch schema:instrument instrument semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 Assuming agent is a workflow management system +prov:Agent Agent skos:narrowMatch schema:Person Person semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0001-9842-9718 1 +prov:Agent Agent skos:narrowMatch schema:Organization Organization semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0001-9842-9718 1 +prov:Agent Agent skos:relatedMatch schema:SoftwareApplication Software application semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0001-9842-9718 0.75 +prov:Person Person skos:exactMatch schema:Person Person semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 +prov:Organization Organization skos:exactMatch schema:Organization Organization semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 +prov:SoftwareAgent Software agent skos:relatedMatch schema:SoftwareApplication Software application semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 +prov:Plan Plan skos:narrowMatch bioschema:ComputationalWorkflow Computational workflow semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 +prov:Plan Plan skos:narrowMatch schema:SoftwareApplication Software application semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 +prov:Plan Plan skos:narrowMatch schema:HowTo How-to semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 +prov:Entity Entity skos:narrowMatch schema:MediaObject File (Media object) semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 Assuming non-Plan entity +prov:Entity Entity skos:narrowMatch schema:Dataset Dataset semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 Assuming non-Plan entity +prov:Entity Entity skos:narrowMatch schema:PropertyValue Property value semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 Assuming non-Plan entity +prov:wasStartedBy was started by skos:relatedMatch schema:agent agent semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145|orcid:0000-0001-9842-9718 0.95 +prov:startedAtTime started at time skos:closeMatch schema:startTime start time semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0001-9842-9718 0.95 +prov:wasEndedBy was ended by skos:relatedMatch schema:agent agent semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145|orcid:0000-0001-9842-9718 0.95 +prov:endedAtTime ended at time skos:closeMatch schema:endTime end time semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0001-9842-9718 0.95 +prov:wasAssociatedWith was associated with skos:narrowMatch schema:agent agent semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 +prov:wasAssociatedWith was associated with skos:narrowMatch schema:instrument instrument semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 +prov:hadPlan hadPlan skos:relatedMatch schema:instrument instrument semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 Complex mapping: an instrument implies the existence of a qualified association (prov:Association) linked to a prov:Plan through prov:hadPlan +prov:agent agent skos:relatedMatch schema:agent agent semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 Complex mapping: an agent implies the existence of a qualified association (prov:Association) linked to a prov:Agent through prov:agent +prov:used used skos:exactMatch schema:object object semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 +prov:wasGeneratedBy was generated by skos:closeMatch schema:result object_label semapv:ManualMappingCuration 2023-10-22 orcid:0000-0001-9842-9718 orcid:0000-0003-0454-7145 0.95 Note inverse properties: :ent prov:wasGeneratedBy :act vs :act schema:result :ent diff --git a/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.ttl b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.ttl new file mode 100644 index 00000000..3a482647 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.ttl @@ -0,0 +1,392 @@ +@prefix bioschema: . +@prefix dc1: . +@prefix orcid: . +@prefix owl: . +@prefix pav: . +@prefix prov: . +@prefix rdfs: . +@prefix schema1: . +@prefix semapv: . +@prefix skos: . +@prefix sssom: . +@prefix xsd: . + +dc1:creator a owl:AnnotationProperty . + +pav:authoredBy a owl:AnnotationProperty . + +pav:authoredOn a owl:AnnotationProperty . + +rdfs:comment a owl:AnnotationProperty . + +sssom:confidence a owl:AnnotationProperty . + +sssom:mapping_justification a owl:AnnotationProperty . + +sssom:object_label a owl:AnnotationProperty . + +sssom:subject_label a owl:AnnotationProperty . + +prov:Organization skos:exactMatch schema1:Organization . + +prov:Person skos:exactMatch schema1:Person . + +prov:SoftwareAgent skos:relatedMatch schema1:SoftwareApplication . + +prov:endedAtTime skos:closeMatch schema1:endTime . + +prov:hadPlan skos:relatedMatch schema1:instrument . + +prov:startedAtTime skos:closeMatch schema1:startTime . + +prov:used skos:exactMatch schema1:object . + +prov:wasEndedBy skos:relatedMatch schema1:agent . + +prov:wasGeneratedBy skos:closeMatch schema1:result . + +prov:wasStartedBy skos:relatedMatch schema1:agent . + +prov:Activity skos:narrowMatch schema1:CreateAction, + schema1:OrganizeAction . + +prov:agent skos:narrowMatch schema1:instrument ; + skos:relatedMatch schema1:agent . + +prov:wasAssociatedWith skos:narrowMatch schema1:agent, + schema1:instrument . + +prov:Agent skos:narrowMatch schema1:Organization, + schema1:Person ; + skos:relatedMatch schema1:SoftwareApplication . + +prov:Entity skos:narrowMatch schema1:Dataset, + schema1:MediaObject, + schema1:PropertyValue . + +prov:Plan skos:narrowMatch schema1:HowTo, + schema1:SoftwareApplication, + bioschema:ComputationalWorkflow . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:wasAssociatedWith ; + owl:annotatedTarget schema1:instrument ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "instrument" ; + sssom:subject_label "was associated with" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Agent ; + owl:annotatedTarget schema1:Person ; + sssom:confidence 1e+00 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Person" ; + sssom:subject_label "Agent" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:exactMatch ; + owl:annotatedSource prov:Person ; + owl:annotatedTarget schema1:Person ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Person" ; + sssom:subject_label "Person" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:exactMatch ; + owl:annotatedSource prov:used ; + owl:annotatedTarget schema1:object ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "object" ; + sssom:subject_label "used" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming non-Plan entity" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Entity ; + owl:annotatedTarget schema1:PropertyValue ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Property value" ; + sssom:subject_label "Entity" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Note inverse properties: :ent prov:wasGeneratedBy :act vs :act schema:result :ent" ; + owl:annotatedProperty skos:closeMatch ; + owl:annotatedSource prov:wasGeneratedBy ; + owl:annotatedTarget schema1:result ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "object_label" ; + sssom:subject_label "was generated by" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:closeMatch ; + owl:annotatedSource prov:startedAtTime ; + owl:annotatedTarget schema1:startTime ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "start time" ; + sssom:subject_label "started at time" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:closeMatch ; + owl:annotatedSource prov:endedAtTime ; + owl:annotatedTarget schema1:endTime ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "end time" ; + sssom:subject_label "ended at time" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming activity is workflow/process execution" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Activity ; + owl:annotatedTarget schema1:OrganizeAction ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Organize action" ; + sssom:subject_label "Activity" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:wasAssociatedWith ; + owl:annotatedTarget schema1:agent ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "agent" ; + sssom:subject_label "was associated with" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:Agent ; + owl:annotatedTarget schema1:SoftwareApplication ; + sssom:confidence 7.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Software application" ; + sssom:subject_label "Agent" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:exactMatch ; + owl:annotatedSource prov:Organization ; + owl:annotatedTarget schema1:Organization ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Organization" ; + sssom:subject_label "Organization" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Agent ; + owl:annotatedTarget schema1:Organization ; + sssom:confidence 1e+00 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Organization" ; + sssom:subject_label "Agent" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:SoftwareAgent ; + owl:annotatedTarget schema1:SoftwareApplication ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Software application" ; + sssom:subject_label "Software agent" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming activity is workflow/process execution" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Activity ; + owl:annotatedTarget schema1:CreateAction ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Create action" ; + sssom:subject_label "Activity" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Complex mapping: an agent implies the existence of a qualified association (prov:Association) linked to a prov:Agent through prov:agent" ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:agent ; + owl:annotatedTarget schema1:agent ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "agent" ; + sssom:subject_label "agent" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Plan ; + owl:annotatedTarget schema1:SoftwareApplication ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Software application" ; + sssom:subject_label "Plan" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming non-Plan entity" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Entity ; + owl:annotatedTarget schema1:Dataset ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Dataset" ; + sssom:subject_label "Entity" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming non-Plan entity" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Entity ; + owl:annotatedTarget schema1:MediaObject ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "File (Media object)" ; + sssom:subject_label "Entity" . + +[] a owl:Ontology ; + dc1:license "https://creativecommons.org/publicdomain/zero/1.0/"^^xsd:anyURI ; + dc1:title "Mapping PROV to Workflow Run RO-Crate" ; + sssom:mapping_set_group "researchobject.org" ; + sssom:mapping_set_id "prov_wfrun"^^xsd:anyURI ; + sssom:object_source ; + sssom:object_source_version 3e-01 ; + sssom:subject_source ; + sssom:subject_source_version 20130430 . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718, + orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:wasEndedBy ; + owl:annotatedTarget schema1:agent ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "agent" ; + sssom:subject_label "was ended by" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Plan ; + owl:annotatedTarget schema1:HowTo ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "How-to" ; + sssom:subject_label "Plan" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Assuming agent is a workflow management system" ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:agent ; + owl:annotatedTarget schema1:instrument ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "instrument" ; + sssom:subject_label "agent" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:narrowMatch ; + owl:annotatedSource prov:Plan ; + owl:annotatedTarget bioschema:ComputationalWorkflow ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "Computational workflow" ; + sssom:subject_label "Plan" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + rdfs:comment "Complex mapping: an instrument implies the existence of a qualified association (prov:Association) linked to a prov:Plan through prov:hadPlan" ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:hadPlan ; + owl:annotatedTarget schema1:instrument ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "instrument" ; + sssom:subject_label "hadPlan" . + +[] a owl:Axiom ; + dc1:creator orcid:0000-0001-9842-9718 ; + pav:authoredBy orcid:0000-0001-9842-9718, + orcid:0000-0003-0454-7145 ; + pav:authoredOn "2023-10-22"^^xsd:date ; + owl:annotatedProperty skos:relatedMatch ; + owl:annotatedSource prov:wasStartedBy ; + owl:annotatedTarget schema1:agent ; + sssom:confidence 9.5e-01 ; + sssom:mapping_justification semapv:ManualMappingCuration ; + sssom:object_label "agent" ; + sssom:subject_label "was started by" . + + diff --git a/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.yml b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.yml new file mode 100644 index 00000000..15e882d2 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/mapping/prov-mapping.yml @@ -0,0 +1,15 @@ +curie_map: + prov: http://www.w3.org/ns/prov# + schema: http://schema.org/ + wfrun: https://w3id.org/ro/terms/workflow-run + skos: http://www.w3.org/2004/02/skos/core# + bioschema: https://bioschemas.org/ + semapv: https://w3id.org/semapv/vocab/ +license: https://creativecommons.org/publicdomain/zero/1.0/ +mapping_set_group: researchobject.org +mapping_set_id: prov_wfrun +mapping_set_title: 'Mapping PROV to Workflow Run RO-Crate' +object_source: https://w3id.org/ro/wfrun/provenance/0.3 +object_source_version: 0.3 +subject_source: http://www.w3.org/ns/prov-o# +subject_source_version: 20130430 diff --git a/tests/data/crates/valid/wrroc-paper/ro-crate-metadata.json b/tests/data/crates/valid/wrroc-paper/ro-crate-metadata.json new file mode 100644 index 00000000..3f99ec94 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/ro-crate-metadata.json @@ -0,0 +1,769 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "Standard": "http://purl.org/dc/terms/Standard", + "Profile": "http://www.w3.org/ns/dx/prof/Profile", + "MappingSet": "https://w3id.org/sssom/schema/MappingSet" + }, + { + "copyrightNotice": "http://schema.org/copyrightNotice", + "interpretedAsClaim": "http://schema.org/interpretedAsClaim", + "archivedAt": "http://schema.org/archivedAt", + "creditText": "http://schema.org/creditText" + } + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + }, + "author": { + "@id": "https://orcid.org/0000-0001-8271-5429" + }, + "license": { + "@id": "https://creativecommons.org/publicdomain/zero/1.0/" + } + }, + { + "@id": "./", + "identifier": { + "@id": "https://doi.org/10.5281/zenodo.10368990" + }, + "url": "https://w3id.org/ro/doi/10.5281/zenodo.10368989", + "@type": "Dataset", + "about": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "author": [ + { + "@id": "https://orcid.org/0000-0001-8271-5429" + } + ], + "description": "RO-Crate for the manuscript that describes Workflow Run Crate, includes mapping to PROV using SKOS/SSSOM", + "hasPart": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/workflow/0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/provenance/0.3" + }, + { + "@id": "mapping/" + } + ], + "name": "Recording provenance of workflow runs with RO-Crate (RO-Crate and mapping)", + "datePublished": "2023-12-12", + "license": { + "@id": "https://www.apache.org/licenses/LICENSE-2.0" + }, + "creator": [], + "publisher": { + "@id": "https://crs4.it" + } + }, + { + "@id": "https://doi.org/10.5281/zenodo.10368990", + "@type": "PropertyValue", + "name": "doi", + "propertyID": "https://registry.identifiers.org/registry/doi", + "value": "doi:10.5281/zenodo.10368990", + "url": "https://doi.org/10.5281/zenodo.10368990" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "affiliation": [ + "Department of Computer Science, The University of Manchester, Manchester, United Kingdom", + "Informatics Institute, University of Amsterdam, Amsterdam, The Netherlands" + ], + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://researchobject.org/workflow-run-crate/", + "@type": "Project", + "member": [ + { + "@id": "https://orcid.org/0000-0001-8271-5429" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + { + "@id": "https://orcid.org/0000-0002-5432-2748" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140" + }, + { + "@id": "https://orcid.org/0000-0003-3156-2105" + }, + { + "@id": "https://orcid.org/0000-0002-6190-122X" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670" + }, + { + "@id": "https://orcid.org/0000-0003-3986-0510" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024" + }, + { + "@id": "https://orcid.org/0000-0002-9464-6640" + }, + { + "@id": "https://orcid.org/0000-0001-5845-8880" + }, + { + "@id": "https://orcid.org/0000-0003-4894-4660" + }, + { + "@id": "https://orcid.org/0000-0002-4405-6802" + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017" + }, + { + "@id": "https://orcid.org/0000-0003-0617-9219" + }, + { + "@id": "https://orcid.org/0000-0001-9228-2882" + }, + { + "@id": "https://orcid.org/0000-0003-3898-9451" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049" + }, + { + "@id": "https://orcid.org/0000-0001-9818-9320" + }, + { + "@id": "https://orcid.org/0000-0002-8122-9522" + }, + { + "@id": "https://orcid.org/0000-0002-8330-4071" + }, + { + "@id": "https://orcid.org/0000-0003-4073-7456" + }, + { + "@id": "https://orcid.org/0000-0003-1361-7301" + }, + { + "@id": "https://orcid.org/0000-0002-5358-616X" + }, + { + "@id": "https://orcid.org/0000-0002-5477-287X" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086" + }, + { + "@id": "https://orcid.org/0000-0001-8172-8981" + }, + { + "@id": "https://orcid.org/0000-0001-6740-9212" + } + ], + "name": "Workflow Run Crate task force", + "parentOrganization": { + "@id": "https://www.researchobject.org/ro-crate/community" + } + }, + { + "@id": "https://orcid.org/0000-0001-8271-5429", + "@type": "Person", + "affiliation": { + "@id": "https://crs4.it" + }, + "name": "Simone Leo" + }, + { + "@id": "https://crs4.it", + "@type": "Organization", + "name": "Center for Advanced Studies, Research, and Development in Sardinia (CRS4)", + "url": "https://crs4.it" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670", + "@type": "Person", + "affiliation": [ + "Vrije Universiteit Amsterdam, Amsterdam, The Netherlands", + "DTL Projects, The Netherlands", + "Forschungszentrum JΓΌlich, Germany" + ], + "name": "Michael R Crusoe" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": "Laura RodrΓ­guez-Navas" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": "RaΓΌl Sirvent" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652", + "@type": "Person", + "affiliation": [ + "Biozentrum, University of Basel, Basel, Switzerland", + "Swiss Institute of Bioinformatics, Lausanne, Switzerland" + ], + "name": "Alexander Kanitz" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946", + "@type": "Person", + "affiliation": "VIB-UGent Center for Plant Systems Biology, Gent, Belgium", + "name": "Paul De Geest" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024", + "@type": "Person", + "affiliation": [ + "Faculty of Informatics, Masaryk Universit, Brno, Czech Republic", + "Institute of Computer Science, Masaryk University, Brno, Czech Republic", + "BBMRI-ERIC, Graz, Austria" + ], + "name": "Rudolf Wittner" + }, + { + "@id": "https://orcid.org/0000-0002-4663-5613", + "@type": "Person", + "affiliation": "Center for Advanced Studies, Research, and Development in Sardinia (CRS4), Pula, Sardinia, Italy", + "name": "Luca Pireddu" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145", + "@type": "Person", + "affiliation": "Ontology Engineering Group, Universidad PolitΓ©cnica de Madrid, Madrid, Spain", + "name": "Daniel Garijo" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": ["JosΓ© MarΓ­a FernΓ‘ndez", "JosΓ© M. FernΓ‘ndez"] + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017", + "@type": "Person", + "affiliation": "Computer Science Dept., UniversitΓ  degli Studi di Torino, Torino, Italy", + "name": "Iacopo Colonnelli" + }, + { + "@id": "https://orcid.org/0000-0002-1119-1792", + "@type": "Person", + "affiliation": "Faculty of Informatics, Masaryk University, Brno, Czech Republic", + "name": "Matej Gallo" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945", + "@type": "Person", + "affiliation": [ + "Database Center for Life Science, Joint Support-Center for Data Science Research, Research Organization of Information and Systems, Shizuoka, Japan", + "Institute for Advanced Academic Research, Chiba University, Chiba, Japan" + ], + "name": "Tazro Ohta" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049", + "@type": "Person", + "affiliation": "Sator Inc., Tokyo, Japan", + "name": "Hirotaka Suetake" + }, + { + "@id": "https://orcid.org/0000-0002-0309-604X", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": "Salvador Capella-Gutierrez" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086", + "@type": "Person", + "affiliation": "Vrije Universiteit Amsterdam, Amsterdam, The Netherlands", + "name": "Renske de Wit" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": ["Bruno P. Kinoshita", "Bruno de Paula Kinoshita"] + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.3", + "@type": "Profile", + "author": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "name": "Process Run Crate", + "version": "0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/workflow/0.3", + "@type": "Profile", + "author": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "name": "Workflow Run Crate", + "version": "0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/provenance/0.3", + "@type": "Profile", + "author": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "name": "Provenance Run Crate", + "version": "0.3" + }, + { + "@id": "mapping/environment.yml", + "@type": "File", + "conformsTo": { + "@id": "https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually" + }, + "encodingFormat": [ + "application/yaml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/818" + } + ], + "name": "Conda environment for sssom" + }, + { + "@id": "mapping/environment.lock.yml", + "@type": "File", + "conformsTo": { + "@id": "https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually" + }, + "encodingFormat": [ + "application/yaml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/818" + } + ], + "name": "Conda environment for sssom, version-pinned" + }, + { + "@id": "mapping/prov-mapping.tsv", + "@type": ["File", "MappingSet"], + "conformsTo": [ + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#tsv" + } + ], + "author": [ + { + "@id": "https://orcid.org/0000-0001-8271-5429" + } + ], + "creator": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "encodingFormat": [ + "text/tab-separated-values", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/x-fmt/13" + } + ], + "name": "PROV mapping to Workflow Run Crate (SSSOM TSV)" + }, + { + "@id": "mapping/prov-mapping.yml", + "@type": ["File", "MappingSet"], + "conformsTo": { + "@id": "https://w3id.org/sssom/" + }, + "encodingFormat": [ + "text/yaml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/818" + } + ], + "name": "PROV mapping to Workflow Run Crate (SSSOM metadata)" + }, + { + "@id": "mapping/prov-mapping-w-metadata.tsv", + "@type": ["File", "MappingSet"], + "conformsTo": [ + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#tsv" + } + ], + "encodingFormat": "text/tab-separated-values", + "isBasedOn": [ + { + "@id": "mapping/prov-mapping.tsv" + }, + { + "@id": "mapping/prov-mapping.yml" + } + ], + "name": "PROV mapping to Workflow Run Crate (SSSOM TSV with metadata)" + }, + { + "@id": "mapping/prov-mapping.ttl", + "@type": ["File", "MappingSet"], + "conformsTo": [ + { + "@id": "http://www.w3.org/TR/owl2-mapping-to-rdf/" + }, + { + "@id": "http://www.w3.org/TR/skos-reference" + } + ], + "encodingFormat": [ + "text/turtle", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/874" + } + ], + "isBasedOn": { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + "name": "PROV to Workflow Run Crate (SKOS and SSSOM OWL axioms)" + }, + { + "@id": "mapping/prov-mapping.rdf", + "@type": ["File", "MappingSet"], + "conformsTo": [ + { + "@id": "http://www.w3.org/TR/owl2-mapping-to-rdf/" + }, + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#rdfxml-serialised-re-ified-owl-axioms" + } + ], + "encodingFormat": [ + "application/rdf+xml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/875" + } + ], + "isBasedOn": { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + "name": "PROV mapping to Workflow Run Crate (SSSOM OWL axioms)" + }, + { + "@id": "mapping/prov-mapping.json", + "@type": ["File", "MappingSet"], + "conformsTo": [ + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#json" + } + ], + "encodingFormat": [ + "application/json", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/817" + } + ], + "isBasedOn": { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + "name": "PROV mapping to Workflow Run Crate (SSSOM JSON)" + }, + { + "@id": "http://www.w3.org/TR/owl2-mapping-to-rdf/", + "@type": "CreativeWork", + "name": "OWL 2 (in RDF)" + }, + { + "@id": "http://www.w3.org/TR/skos-reference", + "@type": "CreativeWork", + "alternateName": "SKOS Simple Knowledge Organization System Reference", + "name": "SKOS" + }, + { + "@id": "http://www.w3.org/ns/dx/prof/Profile", + "@type": "DefinedTerm", + "name": "Profile", + "url": "https://www.w3.org/TR/dx-prof/" + }, + { + "@id": "https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually", + "@type": "CreativeWork", + "name": "Conda environment file format" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#json", + "@type": "WebPageElement", + "name": "SSSOM JSON" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#rdfxml-serialised-re-ified-owl-axioms", + "@type": "WebPageElement", + "name": "SSSOM RDF/XML serialised re-ified OWL axioms" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#tsv", + "@type": "WebPageElement", + "name": "SSSOM TSV" + }, + { + "@id": "https://orcid.org/0000-0001-5845-8880", + "@type": "Person", + "name": "Sebastiaan Huber" + }, + { + "@id": "https://orcid.org/0000-0001-6740-9212", + "@type": "Person", + "name": "Samuel Lampa" + }, + { + "@id": "https://orcid.org/0000-0001-8172-8981", + "@type": "Person", + "name": "Jasper Koehorst" + }, + { + "@id": "https://orcid.org/0000-0001-9228-2882", + "@type": "Person", + "name": "Abigail Miller" + }, + { + "@id": "https://orcid.org/0000-0001-9818-9320", + "@type": "Person", + "name": "Johannes KΓΆster" + }, + { + "@id": "https://orcid.org/0000-0002-4405-6802", + "@type": "Person", + "name": "Haris Zafeiropoulos" + }, + { + "@id": "https://orcid.org/0000-0002-5358-616X", + "@type": "Person", + "name": "Petr Holub" + }, + { + "@id": "https://orcid.org/0000-0002-5432-2748", + "@type": "Person", + "name": "Paul Brack" + }, + { + "@id": "https://orcid.org/0000-0002-5477-287X", + "@type": "Person", + "name": "Milan Markovic" + }, + { + "@id": "https://orcid.org/0000-0002-6190-122X", + "@type": "Person", + "name": "Ignacio Eguinoa" + }, + { + "@id": "https://orcid.org/0000-0002-8122-9522", + "@type": "Person", + "name": "Luiz Gadelha" + }, + { + "@id": "https://orcid.org/0000-0002-8330-4071", + "@type": "Person", + "name": "Mahnoor Zulfiqar" + }, + { + "@id": "https://orcid.org/0000-0002-9464-6640", + "@type": "Person", + "name": "Wolfgang Maier" + }, + { + "@id": "https://orcid.org/0000-0003-0617-9219", + "@type": "Person", + "name": "Jake Emerson" + }, + { + "@id": "https://orcid.org/0000-0003-1361-7301", + "@type": "Person", + "name": "Maciek BΔ…k" + }, + { + "@id": "https://orcid.org/0000-0003-3156-2105", + "@type": "Person", + "name": "Alan R Williams" + }, + { + "@id": "https://orcid.org/0000-0003-3898-9451", + "@type": "Person", + "name": "Stelios Ninidakis" + }, + { + "@id": "https://orcid.org/0000-0003-3986-0510", + "@type": "Person", + "name": "LJ Garcia Castro" + }, + { + "@id": "https://orcid.org/0000-0003-4073-7456", + "@type": "Person", + "name": "Romain David" + }, + { + "@id": "https://orcid.org/0000-0003-4894-4660", + "@type": "Person", + "name": "Kevin Jablonka" + }, + { + "@id": "https://www.researchobject.org/ro-crate/community", + "@type": "Project", + "name": "RO-Crate Community" + }, + { + "@id": "https://w3id.org/sssom/", + "@type": ["WebPage", "Standard"], + "alternateName": "Simple Standard for Sharing Ontological Mappings", + "name": "SSSOM", + "version": "0.15.0" + }, + { + "@id": "https://w3id.org/sssom/schema/MappingSet", + "@type": "DefinedTerm", + "url": "https://mapping-commons.github.io/sssom/MappingSet/" + }, + { + "@id": "https://www.apache.org/licenses/LICENSE-2.0", + "@type": "CreativeWork", + "name": "Apache License, Version 2.0", + "version": "2.0", + "identifier": { + "@id": "http://spdx.org/licenses/Apache-2.0" + } + }, + { + "@id": "https://creativecommons.org/publicdomain/zero/1.0/", + "@type": "CreativeWork", + "identifier": { + "@id": "http://spdx.org/licenses/CC0-1.0" + }, + "name": "Creative Commons Zero v1.0 Universal", + "version": "1.0" + }, + { + "@id": "https://creativecommons.org/licenses/by/4.0/", + "@type": "CreativeWork", + "identifier": { + "@id": "http://spdx.org/licenses/CC-BY-4.0" + }, + "name": "Creative Commons Attribution 4.0 International", + "version": "4.0" + }, + { + "@id": "http://spdx.org/licenses/Apache-2.0", + "@type": "PropertyValue", + "propertyID": "http://spdx.org/rdf/terms#licenseId", + "name": "spdx", + "value": "Apache-2.0" + }, + { + "@id": "http://spdx.org/licenses/CC0-1.0", + "@type": "PropertyValue", + "propertyID": "http://spdx.org/rdf/terms#licenseId", + "name": "spdx", + "value": "CC0-1.0" + }, + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/817", + "@type": "WebSite", + "name": "JSON" + }, + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/x-fmt/13", + "@type": "WebSite", + "name": "Tab-separated Values" + }, + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/874", + "@type": "WebSite", + "name": "Turtle" + }, + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/875", + "@type": "WebSite", + "name": "RDF/XML" + }, + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/818", + "@type": "WebSite", + "name": "YAML" + }, + { + "@id": "mapping/", + "@type": "Dataset", + "name": "PROV mapping to Workflow Run Crate", + "description": "Mapping using SKOS and SSSOM", + "hasPart": [ + { + "@id": "mapping/environment.yml" + }, + { + "@id": "mapping/environment.lock.yml" + }, + { + "@id": "mapping/prov-mapping.tsv" + }, + { + "@id": "mapping/prov-mapping.yml" + }, + { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + { + "@id": "mapping/prov-mapping.ttl" + }, + { + "@id": "mapping/prov-mapping.rdf" + }, + { + "@id": "mapping/prov-mapping.json" + } + ] + } + ] +} diff --git a/tests/data/crates/valid/wrroc-paper/ro-crate-metadata.jsonld b/tests/data/crates/valid/wrroc-paper/ro-crate-metadata.jsonld new file mode 100644 index 00000000..82f7f245 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/ro-crate-metadata.jsonld @@ -0,0 +1,810 @@ +{ + "@context": [ + "https://w3id.org/ro/crate/1.1/context", + { + "Standard": "http://purl.org/dc/terms/Standard", + "Profile": "http://www.w3.org/ns/dx/prof/Profile", + "MappingSet": "https://w3id.org/sssom/schema/MappingSet" + }, + { + "copyrightNotice": "http://schema.org/copyrightNotice", + "interpretedAsClaim": "http://schema.org/interpretedAsClaim", + "archivedAt": "http://schema.org/archivedAt", + "creditText": "http://schema.org/creditText" + } + ], + "@graph": [ + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "conformsTo": { + "@id": "https://w3id.org/ro/crate/1.1" + }, + "about": { + "@id": "./" + }, + "author": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "license": { + "@id": "https://creativecommons.org/publicdomain/zero/1.0/" + } + }, + { + "@id": "./", + "identifier": { + "@id": "https://doi.org/10.5281/zenodo.10368990" + }, + "url": "https://w3id.org/ro/doi/10.5281/zenodo.10368989", + "@type": "Dataset", + "about": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "author": [ + { + "@id": "https://orcid.org/0000-0001-8271-5429" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024" + }, + { + "@id": "https://orcid.org/0000-0002-4663-5613" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140" + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017" + }, + { + "@id": "https://orcid.org/0000-0002-1119-1792" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049" + }, + { + "@id": "https://orcid.org/0000-0002-0309-604X" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + ], + "description": "RO-Crate for the manuscript that describes Workflow Run Crate, includes mapping to PROV using SKOS/SSSOM", + "hasPart": [ + { + "@id": "https://w3id.org/ro/wfrun/process/0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/workflow/0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/provenance/0.3" + }, + { + "@id": "mapping/" + } + ], + "name": "Recording provenance of workflow runs with RO-Crate (RO-Crate and mapping)", + "datePublished": "2023-12-12T21:08", + "license": { + "@id": "https://www.apache.org/licenses/LICENSE-2.0" + }, + "creator": [] + }, + { + "@id": "https://doi.org/10.5281/zenodo.10368990", + "@type": "PropertyValue", + "name": "doi", + "propertyID": "https://registry.identifiers.org/registry/doi", + "value": "doi:10.5281/zenodo.10368990", + "url": "https://doi.org/10.5281/zenodo.10368990" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718", + "@type": "Person", + "affiliation": [ + "Department of Computer Science, The University of Manchester, Manchester, United Kingdom", + "Informatics Institute, University of Amsterdam, Amsterdam, The Netherlands" + ], + "name": "Stian Soiland-Reyes" + }, + { + "@id": "https://researchobject.org/workflow-run-crate/", + "@type": "Project", + "member": [ + { + "@id": "https://orcid.org/0000-0001-8271-5429" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + { + "@id": "https://orcid.org/0000-0002-5432-2748" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140" + }, + { + "@id": "https://orcid.org/0000-0003-3156-2105" + }, + { + "@id": "https://orcid.org/0000-0002-6190-122X" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670" + }, + { + "@id": "https://orcid.org/0000-0003-3986-0510" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024" + }, + { + "@id": "https://orcid.org/0000-0002-9464-6640" + }, + { + "@id": "https://orcid.org/0000-0001-5845-8880" + }, + { + "@id": "https://orcid.org/0000-0003-4894-4660" + }, + { + "@id": "https://orcid.org/0000-0002-4405-6802" + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017" + }, + { + "@id": "https://orcid.org/0000-0003-0617-9219" + }, + { + "@id": "https://orcid.org/0000-0001-9228-2882" + }, + { + "@id": "https://orcid.org/0000-0003-3898-9451" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049" + }, + { + "@id": "https://orcid.org/0000-0001-9818-9320" + }, + { + "@id": "https://orcid.org/0000-0002-8122-9522" + }, + { + "@id": "https://orcid.org/0000-0002-8330-4071" + }, + { + "@id": "https://orcid.org/0000-0003-4073-7456" + }, + { + "@id": "https://orcid.org/0000-0003-1361-7301" + }, + { + "@id": "https://orcid.org/0000-0002-5358-616X" + }, + { + "@id": "https://orcid.org/0000-0002-5477-287X" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086" + }, + { + "@id": "https://orcid.org/0000-0001-8172-8981" + }, + { + "@id": "https://orcid.org/0000-0001-6740-9212" + } + ], + "name": "Workflow Run Crate task force", + "parentOrganization": { + "@id": "https://www.researchobject.org/ro-crate/community" + } + }, + { + "@id": "https://orcid.org/0000-0001-8271-5429", + "@type": "Person", + "affiliation": "Center for Advanced Studies, Research, and Development in Sardinia (CRS4), Pula, Sardinia, Italy", + "name": "Simone Leo" + }, + { + "@id": "https://orcid.org/0000-0002-2961-9670", + "@type": "Person", + "affiliation": [ + "Vrije Universiteit Amsterdam, Amsterdam, The Netherlands", + "DTL Projects, The Netherlands", + "Forschungszentrum JΓΌlich, Germany" + ], + "name": "Michael R Crusoe" + }, + { + "@id": "https://orcid.org/0000-0003-4929-1219", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": "Laura RodrΓ­guez-Navas" + }, + { + "@id": "https://orcid.org/0000-0003-0606-2512", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": "RaΓΌl Sirvent" + }, + { + "@id": "https://orcid.org/0000-0002-3468-0652", + "@type": "Person", + "affiliation": [ + "Biozentrum, University of Basel, Basel, Switzerland", + "Swiss Institute of Bioinformatics, Lausanne, Switzerland" + ], + "name": "Alexander Kanitz" + }, + { + "@id": "https://orcid.org/0000-0002-8940-4946", + "@type": "Person", + "affiliation": "VIB-UGent Center for Plant Systems Biology, Gent, Belgium", + "name": "Paul De Geest" + }, + { + "@id": "https://orcid.org/0000-0002-0003-2024", + "@type": "Person", + "affiliation": [ + "Faculty of Informatics, Masaryk Universit, Brno, Czech Republic", + "Institute of Computer Science, Masaryk University, Brno, Czech Republic", + "BBMRI-ERIC, Graz, Austria" + ], + "name": "Rudolf Wittner" + }, + { + "@id": "https://orcid.org/0000-0002-4663-5613", + "@type": "Person", + "affiliation": "Center for Advanced Studies, Research, and Development in Sardinia (CRS4), Pula, Sardinia, Italy", + "name": "Luca Pireddu" + }, + { + "@id": "https://orcid.org/0000-0003-0454-7145", + "@type": "Person", + "affiliation": "Ontology Engineering Group, Universidad PolitΓ©cnica de Madrid, Madrid, Spain", + "name": "Daniel Garijo" + }, + { + "@id": "https://orcid.org/0000-0002-4806-5140", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": [ + "JosΓ© MarΓ­a FernΓ‘ndez", + "JosΓ© M. FernΓ‘ndez" + ] + }, + { + "@id": "https://orcid.org/0000-0001-9290-2017", + "@type": "Person", + "affiliation": "Computer Science Dept., UniversitΓ  degli Studi di Torino, Torino, Italy", + "name": "Iacopo Colonnelli" + }, + { + "@id": "https://orcid.org/0000-0002-1119-1792", + "@type": "Person", + "affiliation": "Faculty of Informatics, Masaryk University, Brno, Czech Republic", + "name": "Matej Gallo" + }, + { + "@id": "https://orcid.org/0000-0003-3777-5945", + "@type": "Person", + "affiliation": [ + "Database Center for Life Science, Joint Support-Center for Data Science Research, Research Organization of Information and Systems, Shizuoka, Japan", + "Institute for Advanced Academic Research, Chiba University, Chiba, Japan" + ], + "name": "Tazro Ohta" + }, + { + "@id": "https://orcid.org/0000-0003-2765-0049", + "@type": "Person", + "affiliation": "Sator Inc., Tokyo, Japan", + "name": "Hirotaka Suetake" + }, + { + "@id": "https://orcid.org/0000-0002-0309-604X", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": "Salvador Capella-Gutierrez" + }, + { + "@id": "https://orcid.org/0000-0003-0902-0086", + "@type": "Person", + "affiliation": "Vrije Universiteit Amsterdam, Amsterdam, The Netherlands", + "name": "Renske de Wit" + }, + { + "@id": "https://orcid.org/0000-0001-8250-4074", + "@type": "Person", + "affiliation": "Barcelona Supercomputing Center, Barcelona, Spain", + "name": [ + "Bruno P. Kinoshita", + "Bruno de Paula Kinoshita" + ] + }, + { + "@id": "https://w3id.org/ro/wfrun/process/0.3", + "@type": "Profile", + "author": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "name": "Process Run Crate", + "version": "0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/workflow/0.3", + "@type": "Profile", + "author": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "name": "Workflow Run Crate", + "version": "0.3" + }, + { + "@id": "https://w3id.org/ro/wfrun/provenance/0.3", + "@type": "Profile", + "author": { + "@id": "https://researchobject.org/workflow-run-crate/" + }, + "name": "Provenance Run Crate", + "version": "0.3" + }, + { + "@id": "mapping/environment.yml", + "@type": "File", + "conformsTo": { + "@id": "https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually" + }, + "encodingFormat": [ + "application/yaml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/818" + } + ], + "name": "Conda environment for sssom" + }, + { + "@id": "mapping/environment.lock.yml", + "@type": "File", + "conformsTo": { + "@id": "https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually" + }, + "encodingFormat": [ + "application/yaml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/818" + } + ], + "name": "Conda environment for sssom, version-pinned" + }, + { + "@id": "mapping/prov-mapping.tsv", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": [ + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#tsv" + } + ], + "author": [ + { + "@id": "https://orcid.org/0000-0003-0454-7145" + }, + { + "@id": "https://orcid.org/0000-0001-9842-9718" + } + ], + "creator": { + "@id": "https://orcid.org/0000-0001-9842-9718" + }, + "encodingFormat": [ + "text/tab-separated-values", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/x-fmt/13" + } + ], + "name": "PROV mapping to Workflow Run Crate (SSSOM TSV)" + }, + { + "@id": "mapping/prov-mapping.yml", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": { + "@id": "https://w3id.org/sssom/" + }, + "encodingFormat": [ + "text/yaml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/818" + } + ], + "name": "PROV mapping to Workflow Run Crate (SSSOM metadata)" + }, + { + "@id": "mapping/prov-mapping-w-metadata.tsv", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": [ + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#tsv" + } + ], + "encodingFormat": "text/tab-separated-values", + "isBasedOn": [ + { + "@id": "mapping/prov-mapping.tsv" + }, + { + "@id": "mapping/prov-mapping.yml" + } + ], + "name": "PROV mapping to Workflow Run Crate (SSSOM TSV with metadata)" + }, + { + "@id": "mapping/prov-mapping.ttl", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": [ + { + "@id": "http://www.w3.org/TR/owl2-mapping-to-rdf/" + }, + { + "@id": "http://www.w3.org/TR/skos-reference" + } + ], + "encodingFormat": [ + "text/turtle", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/874" + } + ], + "isBasedOn": { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + "name": "PROV to Workflow Run Crate (SKOS and SSSOM OWL axioms)" + }, + { + "@id": "mapping/prov-mapping.rdf", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": [ + { + "@id": "http://www.w3.org/TR/owl2-mapping-to-rdf/" + }, + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#rdfxml-serialised-re-ified-owl-axioms" + } + ], + "encodingFormat": [ + "application/rdf+xml", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/875" + } + ], + "isBasedOn": { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + "name": "PROV mapping to Workflow Run Crate (SSSOM OWL axioms)" + }, + { + "@id": "mapping/prov-mapping.json", + "@type": [ + "File", + "MappingSet" + ], + "conformsTo": [ + { + "@id": "https://w3id.org/sssom/" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#json" + } + ], + "encodingFormat": [ + "application/json", + { + "@id": "https://www.nationalarchives.gov.uk/PRONOM/fmt/817" + } + ], + "isBasedOn": { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + "name": "PROV mapping to Workflow Run Crate (SSSOM JSON)" + }, + { + "@id": "http://www.w3.org/TR/owl2-mapping-to-rdf/", + "@type": "CreativeWork", + "name": "OWL 2 (in RDF)" + }, + { + "@id": "http://www.w3.org/TR/skos-reference", + "@type": "CreativeWork", + "alternateName": "SKOS Simple Knowledge Organization System Reference", + "name": "SKOS" + }, + { + "@id": "http://www.w3.org/ns/dx/prof/Profile", + "@type": "DefinedTerm", + "name": "Profile", + "url": "https://www.w3.org/TR/dx-prof/" + }, + { + "@id": "https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#create-env-file-manually", + "@type": "CreativeWork", + "name": "Conda environment file format" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#json", + "@type": "WebPageElement", + "name": "SSSOM JSON" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#rdfxml-serialised-re-ified-owl-axioms", + "@type": "WebPageElement", + "name": "SSSOM RDF/XML serialised re-ified OWL axioms" + }, + { + "@id": "https://mapping-commons.github.io/sssom/spec/#tsv", + "@type": "WebPageElement", + "name": "SSSOM TSV" + }, + { + "@id": "https://orcid.org/0000-0001-5845-8880", + "@type": "Person", + "name": "Sebastiaan Huber" + }, + { + "@id": "https://orcid.org/0000-0001-6740-9212", + "@type": "Person", + "name": "Samuel Lampa" + }, + { + "@id": "https://orcid.org/0000-0001-8172-8981", + "@type": "Person", + "name": "Jasper Koehorst" + }, + { + "@id": "https://orcid.org/0000-0001-9228-2882", + "@type": "Person", + "name": "Abigail Miller" + }, + { + "@id": "https://orcid.org/0000-0001-9818-9320", + "@type": "Person", + "name": "Johannes KΓΆster" + }, + { + "@id": "https://orcid.org/0000-0002-4405-6802", + "@type": "Person", + "name": "Haris Zafeiropoulos" + }, + { + "@id": "https://orcid.org/0000-0002-5358-616X", + "@type": "Person", + "name": "Petr Holub" + }, + { + "@id": "https://orcid.org/0000-0002-5432-2748", + "@type": "Person", + "name": "Paul Brack" + }, + { + "@id": "https://orcid.org/0000-0002-5477-287X", + "@type": "Person", + "name": "Milan Markovic" + }, + { + "@id": "https://orcid.org/0000-0002-6190-122X", + "@type": "Person", + "name": "Ignacio Eguinoa" + }, + { + "@id": "https://orcid.org/0000-0002-8122-9522", + "@type": "Person", + "name": "Luiz Gadelha" + }, + { + "@id": "https://orcid.org/0000-0002-8330-4071", + "@type": "Person", + "name": "Mahnoor Zulfiqar" + }, + { + "@id": "https://orcid.org/0000-0002-9464-6640", + "@type": "Person", + "name": "Wolfgang Maier" + }, + { + "@id": "https://orcid.org/0000-0003-0617-9219", + "@type": "Person", + "name": "Jake Emerson" + }, + { + "@id": "https://orcid.org/0000-0003-1361-7301", + "@type": "Person", + "name": "Maciek BΔ…k" + }, + { + "@id": "https://orcid.org/0000-0003-3156-2105", + "@type": "Person", + "name": "Alan R Williams" + }, + { + "@id": "https://orcid.org/0000-0003-3898-9451", + "@type": "Person", + "name": "Stelios Ninidakis" + }, + { + "@id": "https://orcid.org/0000-0003-3986-0510", + "@type": "Person", + "name": "LJ Garcia Castro" + }, + { + "@id": "https://orcid.org/0000-0003-4073-7456", + "@type": "Person", + "name": "Romain David" + }, + { + "@id": "https://orcid.org/0000-0003-4894-4660", + "@type": "Person", + "name": "Kevin Jablonka" + }, + { + "@id": "https://www.researchobject.org/ro-crate/community", + "@type": "Project", + "name": "RO-Crate Community" + }, + { + "@id": "https://w3id.org/sssom/", + "@type": [ + "WebPage", + "Standard" + ], + "alternateName": "Simple Standard for Sharing Ontological Mappings", + "name": "SSSOM", + "version": "0.15.0" + }, + { + "@id": "https://w3id.org/sssom/schema/MappingSet", + "@type": "DefinedTerm", + "url": "https://mapping-commons.github.io/sssom/MappingSet/" + }, + { + "@id": "https://www.apache.org/licenses/LICENSE-2.0", + "@type": "CreativeWork", + "name": "Apache License, Version 2.0", + "version": "2.0", + "identifier": { + "@id": "http://spdx.org/licenses/Apache-2.0" + } + }, + { + "@id": "https://creativecommons.org/publicdomain/zero/1.0/", + "@type": "CreativeWork", + "identifier": { "@id": "http://spdx.org/licenses/CC0-1.0"}, + "name": "Creative Commons Zero v1.0 Universal", + "version": "1.0" + }, + { + "@id": "https://creativecommons.org/licenses/by/4.0/", + "@type": "CreativeWork", + "identifier": { "@id": "http://spdx.org/licenses/CC-BY-4.0"}, + "name": "Creative Commons Attribution 4.0 International", + "version": "4.0" + }, + { + "@id": "http://spdx.org/licenses/Apache-2.0", + "@type": "PropertyValue", + "propertyID": "http://spdx.org/rdf/terms#licenseId", + "name": "spdx", + "value": "Apache-2.0" + }, + { + "@id": "http://spdx.org/licenses/CC0-1.0", + "@type": "PropertyValue", + "propertyID": "http://spdx.org/rdf/terms#licenseId", + "name": "spdx", + "value": "CC0-1.0" + }, + { + "@id": "mapping/", + "@type": "Dataset", + "name": "PROV mapping to Workflow Run Crate", + "description": "Mapping using SKOS and SSSOM", + "hasPart": [ + { + "@id": "mapping/environment.yml" + }, + { + "@id": "mapping/environment.lock.yml" + }, + { + "@id": "mapping/prov-mapping.tsv" + }, + { + "@id": "mapping/prov-mapping.yml" + }, + { + "@id": "mapping/prov-mapping-w-metadata.tsv" + }, + { + "@id": "mapping/prov-mapping.ttl" + }, + { + "@id": "mapping/prov-mapping.rdf" + }, + { + "@id": "mapping/prov-mapping.json" + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/data/crates/valid/wrroc-paper/ro-crate-preview.html b/tests/data/crates/valid/wrroc-paper/ro-crate-preview.html new file mode 100644 index 00000000..7a2fc2a8 --- /dev/null +++ b/tests/data/crates/valid/wrroc-paper/ro-crate-preview.html @@ -0,0 +1,2530 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+
+
+

Recording provenance of workflow runs with RO-Crate (RO-Crate and mapping)

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Simone Leo

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Michael R Crusoe

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Laura RodrΓ­guez-Navas

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: RaΓΌl Sirvent

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Alexander Kanitz

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Paul De Geest

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Rudolf Wittner

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Luca Pireddu

+ + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Daniel Garijo

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: JosΓ© MarΓ­a FernΓ‘ndez,JosΓ© M. FernΓ‘ndez

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Iacopo Colonnelli

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Matej Gallo

+ + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Tazro Ohta

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Hirotaka Suetake

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Salvador Capella-Gutierrez

+ + + + +
+ + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Renske de Wit

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Bruno P. Kinoshita,Bruno de Paula Kinoshita

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Stian Soiland-Reyes

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: doi

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Workflow Run Crate task force

+ + + + + + +
+


+
+

Go to: Process Run Crate

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Workflow Run Crate

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+

Go to: Provenance Run Crate

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+ +


+
+

Go to: Apache License, Version 2.0

+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + +
+ +
+


+
+ + + + + + + diff --git a/tests/data/profiles/conflicting_versions/3.2.3/profile.ttl b/tests/data/profiles/conflicting_versions/3.2.3/profile.ttl new file mode 100644 index 00000000..25493919 --- /dev/null +++ b/tests/data/profiles/conflicting_versions/3.2.3/profile.ttl @@ -0,0 +1,31 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . +@prefix schema: . + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile D" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile D."""@en ; + + # URI of the publisher of the profile D + dct:publisher ; + + # This profile is an extension of the profile A + prof:isProfileOf ; + + # Explicit version in conflict with the inferred version + schema:version "3.2.2" ; + + # Explicitly state that this profile is a transitive profile of the profile A + prof:isTransitiveProfileOf ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "c" ; +. diff --git a/tests/data/profiles/conflicting_versions/3.2.3/shape_c.ttl b/tests/data/profiles/conflicting_versions/3.2.3/shape_c.ttl new file mode 100644 index 00000000..cb9a0b59 --- /dev/null +++ b/tests/data/profiles/conflicting_versions/3.2.3/shape_c.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeC + a sh:NodeShape ; + sh:name "The Shape C" ; + sh:description "This is the Shape C" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/fake/a/profile.ttl b/tests/data/profiles/fake/a/profile.ttl new file mode 100644 index 00000000..8f7739db --- /dev/null +++ b/tests/data/profiles/fake/a/profile.ttl @@ -0,0 +1,28 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile A" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile A."""@en ; + + # URI of the publisher of the Workflow RO-Crate Metadata Specification + dct:publisher ; + + # # This profile is an extension of the RO-Crate Metadata Specification 1.1 profile + # prof:isProfileOf ; + + # # Explicitly state that this profile is a transitive profile of the RO-Crate Metadata Specification 1.1 profile + # prof:isTransitiveProfileOf , ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "a" ; +. diff --git a/tests/data/profiles/fake/a/shape_a.ttl b/tests/data/profiles/fake/a/shape_a.ttl new file mode 100644 index 00000000..97f898ad --- /dev/null +++ b/tests/data/profiles/fake/a/shape_a.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeA + a sh:NodeShape ; + sh:name "The Shape A" ; + sh:description "This is the Shape A" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/fake/b/profile.ttl b/tests/data/profiles/fake/b/profile.ttl new file mode 100644 index 00000000..707e55e7 --- /dev/null +++ b/tests/data/profiles/fake/b/profile.ttl @@ -0,0 +1,27 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile B" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile B."""@en ; + + # URI of the publisher of the profile B + dct:publisher ; + + # This profile is an extension of the profile A + prof:isProfileOf ; + + # Explicitly state that this profile is a transitive profile of the profile A + prof:isTransitiveProfileOf ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "b" ; +. diff --git a/tests/data/profiles/fake/b/shape_b.ttl b/tests/data/profiles/fake/b/shape_b.ttl new file mode 100644 index 00000000..328fb881 --- /dev/null +++ b/tests/data/profiles/fake/b/shape_b.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeB + a sh:NodeShape ; + sh:name "The Shape B" ; + sh:description "This is the Shape B" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/fake/c-overridden/profile.ttl b/tests/data/profiles/fake/c-overridden/profile.ttl new file mode 100644 index 00000000..26992dbc --- /dev/null +++ b/tests/data/profiles/fake/c-overridden/profile.ttl @@ -0,0 +1,14 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + a prof:Profile ; + rdfs:label "Profile C2" ; + rdfs:comment """Comment for Profile C2."""@en ; + dct:publisher ; + prof:isProfileOf ; + prof:isTransitiveProfileOf , ; + prof:hasToken "c-overridden" ; +. diff --git a/tests/data/profiles/fake/c-overridden/shape_c.ttl b/tests/data/profiles/fake/c-overridden/shape_c.ttl new file mode 100644 index 00000000..cb9a0b59 --- /dev/null +++ b/tests/data/profiles/fake/c-overridden/shape_c.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeC + a sh:NodeShape ; + sh:name "The Shape C" ; + sh:description "This is the Shape C" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/fake/c/profile.ttl b/tests/data/profiles/fake/c/profile.ttl new file mode 100644 index 00000000..c622c862 --- /dev/null +++ b/tests/data/profiles/fake/c/profile.ttl @@ -0,0 +1,31 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . +@prefix schema: . + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile C" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile C."""@en ; + + # the version of the profile + schema:version "1.0.0" ; + + # URI of the publisher of the profile C + dct:publisher ; + + # This profile is an extension of the profile A + prof:isProfileOf ; + + # Explicitly state that this profile is a transitive profile of the profile A + prof:isTransitiveProfileOf ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "c" ; +. diff --git a/tests/data/profiles/fake/c/shape_c.ttl b/tests/data/profiles/fake/c/shape_c.ttl new file mode 100644 index 00000000..cb9a0b59 --- /dev/null +++ b/tests/data/profiles/fake/c/shape_c.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeC + a sh:NodeShape ; + sh:name "The Shape C" ; + sh:description "This is the Shape C" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/fake/d1/profile.ttl b/tests/data/profiles/fake/d1/profile.ttl new file mode 100644 index 00000000..5b6eec19 --- /dev/null +++ b/tests/data/profiles/fake/d1/profile.ttl @@ -0,0 +1,27 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile D1" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile D1."""@en ; + + # URI of the publisher of the profile D1 + dct:publisher ; + + # This profile is an extension of the profile A + prof:isProfileOf , ; + + # # Explicitly state that this profile is a transitive profile of the profile A + # prof:isTransitiveProfileOf , ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "d1" ; +. diff --git a/tests/data/profiles/fake/d2/profile.ttl b/tests/data/profiles/fake/d2/profile.ttl new file mode 100644 index 00000000..6fee05b6 --- /dev/null +++ b/tests/data/profiles/fake/d2/profile.ttl @@ -0,0 +1,27 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile D2" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile D2."""@en ; + + # URI of the publisher of the profile D2 + dct:publisher ; + + # This profile is an extension of the profile A + prof:isProfileOf , ; + + # Explicitly state that this profile is a transitive profile of the profile A + prof:isTransitiveProfileOf , , ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "d2" ; +. diff --git a/tests/data/profiles/fake/invalid-duplicated-shapes/profile.ttl b/tests/data/profiles/fake/invalid-duplicated-shapes/profile.ttl new file mode 100644 index 00000000..de2ec683 --- /dev/null +++ b/tests/data/profiles/fake/invalid-duplicated-shapes/profile.ttl @@ -0,0 +1,28 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "A fake profile with duplicated shapes" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the fake profile."""@en ; + + # URI of the publisher of the Workflow RO-Crate Metadata Specification + dct:publisher ; + + # # This profile is an extension of the RO-Crate Metadata Specification 1.1 profile + # prof:isProfileOf ; + + # # Explicitly state that this profile is a transitive profile of the RO-Crate Metadata Specification 1.1 profile + # prof:isTransitiveProfileOf , ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "invalid-duplicated-shapes" ; +. diff --git a/tests/data/profiles/fake/invalid-duplicated-shapes/shape_a.ttl b/tests/data/profiles/fake/invalid-duplicated-shapes/shape_a.ttl new file mode 100644 index 00000000..dc86d8c4 --- /dev/null +++ b/tests/data/profiles/fake/invalid-duplicated-shapes/shape_a.ttl @@ -0,0 +1,36 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeA + a sh:NodeShape ; + sh:name "The Shape A" ; + sh:description "This is the Shape A" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . + +ro:ShapeA + a sh:NodeShape ; + sh:name "The Shape A" ; + sh:description "This is the Shape A" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/fake_versioned_profiles/a_explicit_version_property/profile.ttl b/tests/data/profiles/fake_versioned_profiles/a_explicit_version_property/profile.ttl new file mode 100644 index 00000000..78555e3d --- /dev/null +++ b/tests/data/profiles/fake_versioned_profiles/a_explicit_version_property/profile.ttl @@ -0,0 +1,26 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . +@prefix schema: . + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile A" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile A."""@en ; + + # URI of the publisher of the Workflow RO-Crate Metadata Specification + dct:publisher ; + + # the version of the profile + schema:version "1.0.0" ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "a" ; + +. diff --git a/tests/data/profiles/fake_versioned_profiles/a_explicit_version_property/shape_a.ttl b/tests/data/profiles/fake_versioned_profiles/a_explicit_version_property/shape_a.ttl new file mode 100644 index 00000000..97f898ad --- /dev/null +++ b/tests/data/profiles/fake_versioned_profiles/a_explicit_version_property/shape_a.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeA + a sh:NodeShape ; + sh:name "The Shape A" ; + sh:description "This is the Shape A" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/fake_versioned_profiles/inferred_by_folder/2.0/profile.ttl b/tests/data/profiles/fake_versioned_profiles/inferred_by_folder/2.0/profile.ttl new file mode 100644 index 00000000..707e55e7 --- /dev/null +++ b/tests/data/profiles/fake_versioned_profiles/inferred_by_folder/2.0/profile.ttl @@ -0,0 +1,27 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile B" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile B."""@en ; + + # URI of the publisher of the profile B + dct:publisher ; + + # This profile is an extension of the profile A + prof:isProfileOf ; + + # Explicitly state that this profile is a transitive profile of the profile A + prof:isTransitiveProfileOf ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "b" ; +. diff --git a/tests/data/profiles/fake_versioned_profiles/inferred_by_folder/2.0/shape_b.ttl b/tests/data/profiles/fake_versioned_profiles/inferred_by_folder/2.0/shape_b.ttl new file mode 100644 index 00000000..328fb881 --- /dev/null +++ b/tests/data/profiles/fake_versioned_profiles/inferred_by_folder/2.0/shape_b.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeB + a sh:NodeShape ; + sh:name "The Shape B" ; + sh:description "This is the Shape B" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/fake_versioned_profiles/inferred_from_uri/profile.ttl b/tests/data/profiles/fake_versioned_profiles/inferred_from_uri/profile.ttl new file mode 100644 index 00000000..10e790af --- /dev/null +++ b/tests/data/profiles/fake_versioned_profiles/inferred_from_uri/profile.ttl @@ -0,0 +1,28 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . +@prefix schema: . + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile C" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile C."""@en ; + + # URI of the publisher of the profile C + dct:publisher ; + + # This profile is an extension of the profile A + prof:isProfileOf ; + + # Explicitly state that this profile is a transitive profile of the profile A + prof:isTransitiveProfileOf ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "c" ; +. diff --git a/tests/data/profiles/fake_versioned_profiles/inferred_from_uri/shape_c.ttl b/tests/data/profiles/fake_versioned_profiles/inferred_from_uri/shape_c.ttl new file mode 100644 index 00000000..cb9a0b59 --- /dev/null +++ b/tests/data/profiles/fake_versioned_profiles/inferred_from_uri/shape_c.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeC + a sh:NodeShape ; + sh:name "The Shape C" ; + sh:description "This is the Shape C" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/free_folder_structure/a/profile.ttl b/tests/data/profiles/free_folder_structure/a/profile.ttl new file mode 100644 index 00000000..8f7739db --- /dev/null +++ b/tests/data/profiles/free_folder_structure/a/profile.ttl @@ -0,0 +1,28 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile A" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile A."""@en ; + + # URI of the publisher of the Workflow RO-Crate Metadata Specification + dct:publisher ; + + # # This profile is an extension of the RO-Crate Metadata Specification 1.1 profile + # prof:isProfileOf ; + + # # Explicitly state that this profile is a transitive profile of the RO-Crate Metadata Specification 1.1 profile + # prof:isTransitiveProfileOf , ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "a" ; +. diff --git a/tests/data/profiles/free_folder_structure/a/shape_a.ttl b/tests/data/profiles/free_folder_structure/a/shape_a.ttl new file mode 100644 index 00000000..97f898ad --- /dev/null +++ b/tests/data/profiles/free_folder_structure/a/shape_a.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeA + a sh:NodeShape ; + sh:name "The Shape A" ; + sh:description "This is the Shape A" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/free_folder_structure/nested_b/nested_b/b/profile.ttl b/tests/data/profiles/free_folder_structure/nested_b/nested_b/b/profile.ttl new file mode 100644 index 00000000..707e55e7 --- /dev/null +++ b/tests/data/profiles/free_folder_structure/nested_b/nested_b/b/profile.ttl @@ -0,0 +1,27 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile B" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile B."""@en ; + + # URI of the publisher of the profile B + dct:publisher ; + + # This profile is an extension of the profile A + prof:isProfileOf ; + + # Explicitly state that this profile is a transitive profile of the profile A + prof:isTransitiveProfileOf ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "b" ; +. diff --git a/tests/data/profiles/free_folder_structure/nested_b/nested_b/b/shape_b.ttl b/tests/data/profiles/free_folder_structure/nested_b/nested_b/b/shape_b.ttl new file mode 100644 index 00000000..328fb881 --- /dev/null +++ b/tests/data/profiles/free_folder_structure/nested_b/nested_b/b/shape_b.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeB + a sh:NodeShape ; + sh:name "The Shape B" ; + sh:description "This is the Shape B" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/free_folder_structure/nested_c/c/profile.ttl b/tests/data/profiles/free_folder_structure/nested_c/c/profile.ttl new file mode 100644 index 00000000..c622c862 --- /dev/null +++ b/tests/data/profiles/free_folder_structure/nested_c/c/profile.ttl @@ -0,0 +1,31 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . +@prefix schema: . + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile C" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile C."""@en ; + + # the version of the profile + schema:version "1.0.0" ; + + # URI of the publisher of the profile C + dct:publisher ; + + # This profile is an extension of the profile A + prof:isProfileOf ; + + # Explicitly state that this profile is a transitive profile of the profile A + prof:isTransitiveProfileOf ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "c" ; +. diff --git a/tests/data/profiles/free_folder_structure/nested_c/c/shape_c.ttl b/tests/data/profiles/free_folder_structure/nested_c/c/shape_c.ttl new file mode 100644 index 00000000..cb9a0b59 --- /dev/null +++ b/tests/data/profiles/free_folder_structure/nested_c/c/shape_c.ttl @@ -0,0 +1,22 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:ShapeC + a sh:NodeShape ; + sh:name "The Shape C" ; + sh:description "This is the Shape C" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "Check Metadata File Descriptor entity existence" ; + sh:description "Check if the RO-Crate Metadata File Descriptor entity exists" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:message "The root of the document MUST have an entity with @id `ro-crate-metadata.json`" ; + ] . diff --git a/tests/data/profiles/requirement_loading/x/a.ttl b/tests/data/profiles/requirement_loading/x/a.ttl new file mode 100644 index 00000000..d2fee2a1 --- /dev/null +++ b/tests/data/profiles/requirement_loading/x/a.ttl @@ -0,0 +1,46 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:A + a sh:NodeShape ; + sh:name "A" ; + sh:description "This is the requirement A" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "A_0" ; + sh:description "Check A_0: no sh:severity declared" ; + sh:path rdf:type ; + sh:minCount 1 ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "A_1" ; + sh:description "Check A_1: sh:severity set to sh:Violation" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:severity sh:Violation ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "A_2" ; + sh:description "Check A_2: sh:severity set to sh:Warning" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:severity sh:Warning ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "A_3" ; + sh:description "Check A_3: sh:severity set to sh:Info" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:severity sh:Info ; + ] . + diff --git a/tests/data/profiles/requirement_loading/x/b.py b/tests/data/profiles/requirement_loading/x/b.py new file mode 100644 index 00000000..ea870580 --- /dev/null +++ b/tests/data/profiles/requirement_loading/x/b.py @@ -0,0 +1,48 @@ +# Copyright (c) 2024 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 rocrate_validator.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="B") +class B(PyFunctionCheck): + """ + Test requirement outside requirement level folder + """ + + @check(name="B_0") + def check_b0(self, context: ValidationContext) -> bool: + """Check B_0: no requirement level""" + return True + + @check(name="B_1", severity=Severity.REQUIRED) + def check_b1(self, context: ValidationContext) -> bool: + """Check B_1: REQUIRED requirement level""" + return True + + @check(name="B_2", severity=Severity.RECOMMENDED) + def check_b2(self, context: ValidationContext) -> bool: + """Check B_2: RECOMMENDED requirement level""" + return True + + @check(name="B_3", severity=Severity.OPTIONAL) + def check_b3(self, context: ValidationContext) -> bool: + """Check B_3: OPTIONAL requirement level""" + return True diff --git a/tests/data/profiles/requirement_loading/x/must/a_must.ttl b/tests/data/profiles/requirement_loading/x/must/a_must.ttl new file mode 100644 index 00000000..d6e4bd9d --- /dev/null +++ b/tests/data/profiles/requirement_loading/x/must/a_must.ttl @@ -0,0 +1,46 @@ +@prefix ro: <./> . +@prefix dct: . +@prefix rdf: . +@prefix schema_org: . +@prefix sh: . +@prefix xml1: . +@prefix xsd: . + + +ro:A_MUST + a sh:NodeShape ; + sh:name "A_MUST" ; + sh:description "This is the requirement A_MUST" ; + sh:targetNode ro:ro-crate-metadata.json ; + sh:property [ + a sh:PropertyShape ; + sh:name "A_MUST_0" ; + sh:description "Check A_MUST_0: no sh:severity declared" ; + sh:path rdf:type ; + sh:minCount 1 ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "A_MUST_1" ; + sh:description "Check A_MUST_1: sh:severity set to sh:Violation" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:severity sh:Violation ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "A_MUST_2" ; + sh:description "Check A_MUST_2: sh:severity set to sh:Warning" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:severity sh:Warning ; + ] ; + sh:property [ + a sh:PropertyShape ; + sh:name "A_MUST_3" ; + sh:description "Check A_MUST_3: sh:severity set to sh:Info" ; + sh:path rdf:type ; + sh:minCount 1 ; + sh:severity sh:Info ; + ] . + diff --git a/tests/data/profiles/requirement_loading/x/must/b_must.py b/tests/data/profiles/requirement_loading/x/must/b_must.py new file mode 100644 index 00000000..f63b2e33 --- /dev/null +++ b/tests/data/profiles/requirement_loading/x/must/b_must.py @@ -0,0 +1,48 @@ +# Copyright (c) 2024 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 rocrate_validator.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="B_MUST") +class B_MUST(PyFunctionCheck): + """ + Test requirement outside requirement level folder + """ + + @check(name="B_MUST_0") + def check_b0(self, context: ValidationContext) -> bool: + """Check B_MUST_0: no requirement level""" + return True + + @check(name="B_MUST_1", severity=Severity.REQUIRED) + def check_b1(self, context: ValidationContext) -> bool: + """Check B_MUST_1: REQUIRED requirement level""" + return True + + @check(name="B_MUST_2", severity=Severity.RECOMMENDED) + def check_b2(self, context: ValidationContext) -> bool: + """Check B_MUST_2: RECOMMENDED requirement level""" + return True + + @check(name="B_MUST_3", severity=Severity.OPTIONAL) + def check_b3(self, context: ValidationContext) -> bool: + """Check B_MUST_3: OPTIONAL requirement level""" + return True diff --git a/tests/data/profiles/requirement_loading/x/profile.ttl b/tests/data/profiles/requirement_loading/x/profile.ttl new file mode 100644 index 00000000..4a79c64e --- /dev/null +++ b/tests/data/profiles/requirement_loading/x/profile.ttl @@ -0,0 +1,28 @@ +@prefix dct: . +@prefix prof: . +@prefix role: . +@prefix rdfs: . + + + + + a prof:Profile ; + + # the Profile's label + rdfs:label "Profile X" ; + + # regular metadata, a basic description of the Profile + rdfs:comment """Comment for the Profile A."""@en ; + + # URI of the publisher of the Workflow RO-Crate Metadata Specification + dct:publisher ; + + # # This profile is an extension of the RO-Crate Metadata Specification 1.1 profile + # prof:isProfileOf ; + + # # Explicitly state that this profile is a transitive profile of the RO-Crate Metadata Specification 1.1 profile + # prof:isTransitiveProfileOf , ; + + # a short code to refer to the Profile with when a URI can't be used + prof:hasToken "x" ; +. diff --git a/tests/integration/profiles/process-run-crate/test_procrc_action.py b/tests/integration/profiles/process-run-crate/test_procrc_action.py new file mode 100644 index 00000000..bc32c4eb --- /dev/null +++ b/tests/integration/profiles/process-run-crate/test_procrc_action.py @@ -0,0 +1,358 @@ +# Copyright (c) 2024 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 InvalidProcRC +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +def test_procrc_action_no_instrument(): + """\ + Test a Process Run Crate where the action does not have an instrument. + """ + do_entity_test( + InvalidProcRC().action_no_instrument, + Severity.REQUIRED, + False, + ["Process Run Crate Action"], + ["The Action MUST have an instrument property that references the executed tool"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_instrument_bad_type(): + """\ + Test a Process Run Crate where the instrument does not point to a + SoftwareApplication, SoftwareSourceCode or ComputationalWorkflow. + """ + do_entity_test( + InvalidProcRC().action_instrument_bad_type, + Severity.REQUIRED, + False, + ["Process Run Crate Action"], + ["The Action MUST have an instrument property that references the executed tool"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_not_mentioned(): + """\ + Test a Process Run Crate where the action is not listed in the Root Data + Entity's mentions. + """ + do_entity_test( + InvalidProcRC().action_not_mentioned, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["The Action SHOULD be referenced from the Root Data Entity via mentions"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_name(): + """\ + Test a Process Run Crate where the action does not have an name. + """ + do_entity_test( + InvalidProcRC().action_no_name, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["The Action SHOULD have a name"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_description(): + """\ + Test a Process Run Crate where the action does not have a description. + """ + do_entity_test( + InvalidProcRC().action_no_description, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["The Action SHOULD have a description"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_endtime(): + """\ + Test a Process Run Crate where the action does not have an endTime. + """ + do_entity_test( + InvalidProcRC().action_no_endtime, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["The Action SHOULD have an endTime in ISO 8601 format"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_bad_endtime(): + """\ + Test a Process Run Crate where the action does not have an endTime. + """ + do_entity_test( + InvalidProcRC().action_bad_endtime, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["The Action SHOULD have an endTime in ISO 8601 format"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_agent(): + """\ + Test a Process Run Crate where the action does not have an agent. + """ + do_entity_test( + InvalidProcRC().action_no_agent, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["The Action SHOULD have an agent that is a Person or Organization"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_bad_agent(): + """\ + Test a Process Run Crate where the agent is neither a Person nor an + Organization. + """ + do_entity_test( + InvalidProcRC().action_bad_agent, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["The Action SHOULD have an agent that is a Person or Organization"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_result(): + """\ + Test a Process Run Crate where the CreateAction or UpdateAction does not + have a result. + """ + do_entity_test( + InvalidProcRC().action_no_result, + Severity.RECOMMENDED, + False, + ["Process Run Crate CreateAction UpdateAction SHOULD"], + ["The Action SHOULD have a result"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_starttime(): + """\ + Test a Process Run Crate where the action does not have an startTime. + """ + do_entity_test( + InvalidProcRC().action_no_starttime, + Severity.OPTIONAL, + False, + ["Process Run Crate Action MAY"], + ["The Action MAY have a startTime"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_bad_starttime(): + """\ + Test a Process Run Crate where the action does not have an startTime. + """ + do_entity_test( + InvalidProcRC().action_bad_starttime, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["If present, the Action startTime SHOULD be in ISO 8601 format"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_error_not_failed_status(): + """\ + Test a Process Run Crate where the action has an error even though its + actionStatus is not FailedActionStatus. + """ + do_entity_test( + InvalidProcRC().action_error_not_failed_status, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action error"], + ["error SHOULD NOT be specified unless actionStatus is set to FailedActionStatus"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_error_no_status(): + """\ + Test a Process Run Crate where the action has an error even though it has + no actionStatus. + """ + do_entity_test( + InvalidProcRC().action_error_no_status, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action error"], + ["error SHOULD NOT be specified unless actionStatus is set to FailedActionStatus"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_object(): + """\ + Test a Process Run Crate where the Action does not have an object. + """ + do_entity_test( + InvalidProcRC().action_no_object, + Severity.OPTIONAL, + False, + ["Process Run Crate Action MAY"], + ["The Action MAY have an object"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_actionstatus(): + """\ + Test a Process Run Crate where the Action does not have an actionstatus. + """ + do_entity_test( + InvalidProcRC().action_no_actionstatus, + Severity.OPTIONAL, + False, + ["Process Run Crate Action MAY"], + ["The Action MAY have an actionStatus"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_bad_actionstatus(): + """\ + Test a Process Run Crate where the Action has an invalid actionstatus. + """ + do_entity_test( + InvalidProcRC().action_bad_actionstatus, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["If the Action has an actionStatus, it should be " + "http://schema.org/CompletedActionStatus or http://schema.org/FailedActionStatus"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_error(): + """\ + Test a Process Run Crate where the Action does not have an error. + """ + do_entity_test( + InvalidProcRC().action_no_error, + Severity.OPTIONAL, + False, + ["Process Run Crate Action MAY have error"], + ["error MAY be specified if actionStatus is set to FailedActionStatus"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_obj_res_bad_type(): + """\ + Test a Process Run Crate where the Action's object or result does not + point to a MediaObject, Dataset, Collection, CreativeWork or + PropertyValue. + """ + do_entity_test( + InvalidProcRC().action_obj_res_bad_type, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action object and result types"], + ["object and result SHOULD point to entities of type " + "MediaObject, Dataset, Collection, CreativeWork or PropertyValue"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_environment(): + """\ + Test a Process Run Crate where the Action does not have an environment. + """ + do_entity_test( + InvalidProcRC().action_no_environment, + Severity.OPTIONAL, + False, + ["Process Run Crate Action MAY"], + ["The Action MAY have an environment"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_bad_environment(): + """\ + Test a Process Run Crate where the Action has an environment that does not + point to PropertyValues. + """ + do_entity_test( + InvalidProcRC().action_bad_environment, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["If the Action has an environment, it should point to entities of type PropertyValue"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_no_containerimage(): + """\ + Test a Process Run Crate where the Action does not have a containerimage. + """ + do_entity_test( + InvalidProcRC().action_no_containerimage, + Severity.OPTIONAL, + False, + ["Process Run Crate Action MAY"], + ["The Action MAY have a containerImage"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_action_bad_containerimage(): + """\ + Test a Process Run Crate where the Action has a containerImage that does + not point to a URL or to a ContainerImage object. + """ + for crate in (InvalidProcRC().action_bad_containerimage_url, + InvalidProcRC().action_bad_containerimage_type): + do_entity_test( + InvalidProcRC().action_bad_containerimage_url, + Severity.RECOMMENDED, + False, + ["Process Run Crate Action SHOULD"], + ["If the Action has a containerImage, it should point to a ContainerImage or a URL"], + profile_identifier="process-run-crate" + ) diff --git a/tests/integration/profiles/process-run-crate/test_procrc_application.py b/tests/integration/profiles/process-run-crate/test_procrc_application.py new file mode 100644 index 00000000..417f228f --- /dev/null +++ b/tests/integration/profiles/process-run-crate/test_procrc_application.py @@ -0,0 +1,140 @@ +# Copyright (c) 2024 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 InvalidProcRC +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +def test_procrc_application_no_name(): + """\ + Test a Process Run Crate where the application does not have a name. + """ + do_entity_test( + InvalidProcRC().application_no_name, + Severity.RECOMMENDED, + False, + ["ProcRC Application"], + ["The Application SHOULD have a name"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_application_no_url(): + """\ + Test a Process Run Crate where the application does not have a url. + """ + do_entity_test( + InvalidProcRC().application_no_url, + Severity.RECOMMENDED, + False, + ["ProcRC Application"], + ["The Application SHOULD have a url"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_application_no_version(): + """\ + Test a Process Run Crate where the application does not have a version or + SoftwareVersion (SoftwareApplication). + """ + do_entity_test( + InvalidProcRC().application_no_version, + Severity.RECOMMENDED, + False, + ["ProcRC SoftwareApplication"], + ["The SoftwareApplication SHOULD have a version or softwareVersion"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_application_version_softwareversion(): + """\ + Test a Process Run Crate where the application has both a version and a + SoftwareVersion (SoftwareApplication). + """ + do_entity_test( + InvalidProcRC().application_version_softwareVersion, + Severity.RECOMMENDED, + False, + ["ProcRC SoftwareApplication SingleVersion"], + ["Process Run Crate SoftwareApplication should not have both version and softwareVersion"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_softwaresourcecode_no_version(): + """\ + Test a Process Run Crate where the application does not have a version + (SoftwareSourceCode). + """ + do_entity_test( + InvalidProcRC().softwaresourcecode_no_version, + Severity.RECOMMENDED, + False, + ["ProcRC SoftwareSourceCode or ComputationalWorkflow"], + ["The SoftwareSourceCode or ComputationalWorkflow SHOULD have a version"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_application_id_no_absoluteuri(): + """\ + Test a Process Run Crate where the id of the application is not an + absolute URI. + """ + do_entity_test( + InvalidProcRC().application_id_no_absoluteuri, + Severity.RECOMMENDED, + False, + ["ProcRC SoftwareApplication ID"], + ["The SoftwareApplication id SHOULD be an absolute URI"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_softwareapplication_no_softwarerequirements(): + """\ + Test a Process Run Crate where the SoftwareApplication does not have a + SoftwareRequirements. + """ + do_entity_test( + InvalidProcRC().softwareapplication_no_softwarerequirements, + Severity.OPTIONAL, + False, + ["ProcRC SoftwareApplication MAY"], + ["The SoftwareApplication MAY have a softwareRequirements that points to a SoftwareApplication"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_softwareapplication_bad_softwarerequirements(): + """\ + Test a Process Run Crate where the SoftwareApplication has a + SoftwareRequirements that does not point to a SoftwareApplication. + """ + do_entity_test( + InvalidProcRC().softwareapplication_bad_softwarerequirements, + Severity.OPTIONAL, + False, + ["ProcRC SoftwareApplication MAY"], + ["The SoftwareApplication MAY have a softwareRequirements that points to a SoftwareApplication"], + profile_identifier="process-run-crate" + ) diff --git a/tests/integration/profiles/process-run-crate/test_procrc_collection.py b/tests/integration/profiles/process-run-crate/test_procrc_collection.py new file mode 100644 index 00000000..15e0e97b --- /dev/null +++ b/tests/integration/profiles/process-run-crate/test_procrc_collection.py @@ -0,0 +1,65 @@ +# Copyright (c) 2024 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 InvalidProcRC +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +def test_procrc_collection_not_mentioned(): + """\ + Test a Process Run Crate where the collection is not listed in the Root + Data Entity's mentions. + """ + do_entity_test( + InvalidProcRC().collection_not_mentioned, + Severity.RECOMMENDED, + False, + ["Process Run Crate Collection SHOULD"], + ["The Collection SHOULD be referenced from the Root Data Entity via mentions"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_collection_no_haspart(): + """\ + Test a Process Run Crate where the collection does not have a hasPart. + """ + do_entity_test( + InvalidProcRC().collection_no_haspart, + Severity.RECOMMENDED, + False, + ["Process Run Crate Collection SHOULD"], + ["The Collection SHOULD have a hasPart"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_collection_no_mainentity(): + """\ + Test a Process Run Crate where the collection does not have a mainEntity. + """ + do_entity_test( + InvalidProcRC().collection_no_mainentity, + Severity.RECOMMENDED, + False, + ["Process Run Crate Collection SHOULD"], + ["The Collection SHOULD have a mainEntity"], + profile_identifier="process-run-crate" + ) diff --git a/tests/integration/profiles/process-run-crate/test_procrc_containerimage.py b/tests/integration/profiles/process-run-crate/test_procrc_containerimage.py new file mode 100644 index 00000000..e8165733 --- /dev/null +++ b/tests/integration/profiles/process-run-crate/test_procrc_containerimage.py @@ -0,0 +1,111 @@ +# Copyright (c) 2024 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 InvalidProcRC +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +def test_procrc_containerimage_no_additionaltype(): + """\ + Test a Process Run Crate where the ContainerImage has no additionalType. + """ + do_entity_test( + InvalidProcRC().containerimage_no_additionaltype, + Severity.RECOMMENDED, + False, + ["Process Run Crate ContainerImage SHOULD"], + ["The ContainerImage SHOULD have an additionalType pointing " + "to or " + ""], + profile_identifier="process-run-crate" + ) + + +def test_procrc_containerimage_bad_additionaltype(): + """\ + Test a Process Run Crate where the ContainerImage additionalType does not + point to one of the allowed values. + """ + do_entity_test( + InvalidProcRC().containerimage_bad_additionaltype, + Severity.RECOMMENDED, + False, + ["Process Run Crate ContainerImage SHOULD"], + ["The ContainerImage SHOULD have an additionalType pointing " + "to or " + ""], + profile_identifier="process-run-crate" + ) + + +def test_procrc_containerimage_no_registry(): + """\ + Test a Process Run Crate where the ContainerImage has no registry. + """ + do_entity_test( + InvalidProcRC().containerimage_no_registry, + Severity.RECOMMENDED, + False, + ["Process Run Crate ContainerImage SHOULD"], + ["The ContainerImage SHOULD have a registry"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_containerimage_no_name(): + """\ + Test a Process Run Crate where the ContainerImage has no name. + """ + do_entity_test( + InvalidProcRC().containerimage_no_name, + Severity.RECOMMENDED, + False, + ["Process Run Crate ContainerImage SHOULD"], + ["The ContainerImage SHOULD have a name"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_containerimage_no_tag(): + """\ + Test a Process Run Crate where the ContainerImage has no tag. + """ + do_entity_test( + InvalidProcRC().containerimage_no_tag, + Severity.OPTIONAL, + False, + ["Process Run Crate ContainerImage MAY"], + ["The ContainerImage MAY have a tag"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_containerimage_no_sha256(): + """\ + Test a Process Run Crate where the ContainerImage has no sha256. + """ + do_entity_test( + InvalidProcRC().containerimage_no_sha256, + Severity.OPTIONAL, + False, + ["Process Run Crate ContainerImage MAY"], + ["The ContainerImage MAY have a sha256"], + profile_identifier="process-run-crate" + ) diff --git a/tests/integration/profiles/process-run-crate/test_procrc_root_data_entity.py b/tests/integration/profiles/process-run-crate/test_procrc_root_data_entity.py new file mode 100644 index 00000000..255523b0 --- /dev/null +++ b/tests/integration/profiles/process-run-crate/test_procrc_root_data_entity.py @@ -0,0 +1,70 @@ +# Copyright (c) 2024 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, InvalidProcRC +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +def test_procrc_no_conformsto(): + """\ + Test a Process Run Crate where the root data entity does not have a + conformsTo. + """ + do_entity_test( + ValidROC().workflow_roc, + Severity.REQUIRED, + False, + ["Root Data Entity Metadata"], + ["The Root Data Entity MUST reference a CreativeWork entity with an @id URI " + "that is consistent with the versioned permalink of the profile"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_conformsto_bad_type(): + """\ + Test a Process Run Crate where the root data entity does not conformsTo a + CreativeWork. + """ + do_entity_test( + InvalidProcRC().conformsto_bad_type, + Severity.REQUIRED, + False, + ["Root Data Entity Metadata"], + ["The Root Data Entity MUST reference a CreativeWork entity with an @id URI " + "that is consistent with the versioned permalink of the profile"], + profile_identifier="process-run-crate" + ) + + +def test_procrc_conformsto_bad_profile(): + """\ + Test a Process Run Crate where the root data entity does not conformsTo a + Process Run Crate profile. + """ + do_entity_test( + InvalidProcRC().conformsto_bad_profile, + Severity.REQUIRED, + False, + ["Root Data Entity Metadata"], + ["The Root Data Entity MUST reference a CreativeWork entity with an @id URI " + "that is consistent with the versioned permalink of the profile"], + profile_identifier="process-run-crate" + ) diff --git a/tests/integration/profiles/process-run-crate/test_valid_prc.py b/tests/integration/profiles/process-run-crate/test_valid_prc.py new file mode 100644 index 00000000..f32b59f8 --- /dev/null +++ b/tests/integration/profiles/process-run-crate/test_valid_prc.py @@ -0,0 +1,43 @@ +# Copyright (c) 2024 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 + +logger = logging.getLogger(__name__) + + +def test_valid_process_run_crate_required(): + """Test a valid Process Run Crate.""" + do_entity_test( + ValidROC().process_run_crate, + Severity.REQUIRED, + True, + profile_identifier="process-run-crate" + ) + do_entity_test( + ValidROC().process_run_crate_collections, + Severity.REQUIRED, + True, + profile_identifier="process-run-crate" + ) + do_entity_test( + ValidROC().process_run_crate_containerimage, + Severity.REQUIRED, + True, + profile_identifier="process-run-crate" + ) diff --git a/tests/integration/profiles/ro-crate/test_data_entity_metadata.py b/tests/integration/profiles/ro-crate/test_data_entity_metadata.py new file mode 100644 index 00000000..e34500a6 --- /dev/null +++ b/tests/integration/profiles/ro-crate/test_data_entity_metadata.py @@ -0,0 +1,128 @@ +# Copyright (c) 2024 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 import models +from tests.ro_crates import InvalidDataEntity +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +# Β Global set up the paths +paths = InvalidDataEntity() + + +def test_missing_data_entity_reference(): + """Test a RO-Crate without a root data entity.""" + do_entity_test( + paths.missing_hasPart_data_entity_reference, + models.Severity.REQUIRED, + False, + ["Data Entity: REQUIRED properties"], + ["sort-and-change-case.ga", "foo/xxx"] + ) + + +def test_data_entity_must_be_directly_linked(): + """Test a RO-Crate without a root data entity.""" + do_entity_test( + paths.direct_hasPart_data_entity_reference, + models.Severity.REQUIRED, + True + ) + + +def test_data_entity_must_be_indirectly_linked(): + """Test a RO-Crate without a root data entity.""" + do_entity_test( + paths.indirect_hasPart_data_entity_reference, + models.Severity.REQUIRED, + True + ) + + +def test_directory_data_entity_wo_trailing_slash(): + """Test a RO-Crate without a root data entity.""" + do_entity_test( + paths.directory_data_entity_wo_trailing_slash, + models.Severity.REQUIRED, + False, + ["Directory Data Entity: REQUIRED value restriction"], + ["Every Data Entity Directory URI MUST end with `/`"] + ) + + +def test_missing_data_entity_encoding_format(): + """""" + do_entity_test( + paths.missing_data_entity_encoding_format, + models.Severity.RECOMMENDED, + False, + ["File Data Entity: RECOMMENDED properties"], + ["Missing or invalid `encodingFormat` linked to the `File Data Entity`"] + ) + + +def test_invalid_data_entity_encoding_format_pronom(): + """""" + do_entity_test( + paths.invalid_data_entity_encoding_format_pronom, + models.Severity.RECOMMENDED, + False, + ["File Data Entity: RECOMMENDED properties"], + ["Missing or invalid `encodingFormat` linked to the `File Data Entity`"] + ) + + +def test_invalid_data_entity_encoding_format_ctx_website_type(): + """""" + do_entity_test( + paths.invalid_encoding_format_ctx_entity_missing_ws_type, + models.Severity.RECOMMENDED, + False, + ["File Data Entity: RECOMMENDED properties"], + ["Missing or invalid `encodingFormat` linked to the `File Data Entity`"] + ) + + +def test_invalid_data_entity_encoding_format_ctx_website_name(): + """""" + do_entity_test( + paths.invalid_encoding_format_ctx_entity_missing_ws_name, + models.Severity.RECOMMENDED, + False, + ["WebSite RECOMMENDED Properties"], + ["A WebSite MUST have a `name` property"] + ) + + +def test_valid_data_entity_encoding_format_pronom(): + """""" + do_entity_test( + paths.valid_encoding_format_pronom, + models.Severity.RECOMMENDED, + True + ) + + +def test_valid_data_entity_encoding_format_ctx_website(): + """""" + do_entity_test( + paths.valid_encoding_format_ctx_entity, + models.Severity.RECOMMENDED, + True + ) diff --git a/tests/integration/profiles/ro-crate/test_file_descriptor_entity.py b/tests/integration/profiles/ro-crate/test_file_descriptor_entity.py new file mode 100644 index 00000000..96ceba97 --- /dev/null +++ b/tests/integration/profiles/ro-crate/test_file_descriptor_entity.py @@ -0,0 +1,108 @@ +# Copyright (c) 2024 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 + +import pytest + +from rocrate_validator import models +from tests.ro_crates import InvalidFileDescriptorEntity +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + +# Β Global set up the paths +paths = InvalidFileDescriptorEntity() + + +def test_missing_entity(): + """Test a RO-Crate without a file descriptor entity.""" + do_entity_test( + paths.missing_entity, + models.Severity.REQUIRED, + False, + ["RO-Crate Metadata File Descriptor entity existence"], + ["The root of the document MUST have an entity with @id `ro-crate-metadata.json`"] + ) + + +def test_invalid_entity_type(): + """Test a RO-Crate with an invalid file descriptor entity type.""" + do_entity_test( + paths.invalid_entity_type, + models.Severity.REQUIRED, + False, + ["RO-Crate Metadata File Descriptor REQUIRED properties"], + ["The RO-Crate metadata file MUST be a CreativeWork, as per schema.org"] + ) + + +def test_missing_entity_about(): + """Test a RO-Crate with an invalid file descriptor entity type.""" + do_entity_test( + paths.missing_entity_about, + models.Severity.REQUIRED, + False, + ["RO-Crate Metadata File Descriptor REQUIRED properties"], + ["The RO-Crate metadata file MUST be a CreativeWork, as per schema.org", + "The RO-Crate metadata file descriptor MUST have an `about` property referencing the Root Data Entity"] + ) + + +@pytest.mark.skip(reason="This test is not working as expected") +def test_invalid_entity_about(): + """Test a RO-Crate with an invalid about property in the file descriptor.""" + do_entity_test( + paths.invalid_entity_about, + models.Severity.REQUIRED, + False, + ["RO-Crate Metadata File Descriptor REQUIRED properties"], + ["The RO-Crate metadata file descriptor MUST have an `about` property referencing the Root Data Entity"] + ) + + +def test_invalid_entity_about_type(): + """Test a RO-Crate with an invalid file descriptor entity type.""" + do_entity_test( + paths.invalid_entity_about_type, + models.Severity.REQUIRED, + False, + ["RO-Crate Metadata File Descriptor REQUIRED properties"], + ["The RO-Crate metadata file descriptor MUST have an `about` property referencing the Root Data Entity"] + ) + + +def test_missing_conforms_to(): + """Test a RO-Crate with an invalid file descriptor entity type.""" + do_entity_test( + paths.missing_conforms_to, + models.Severity.REQUIRED, + False, + ["RO-Crate Metadata File Descriptor REQUIRED properties"], + ["The RO-Crate metadata file descriptor MUST have a `conformsTo` " + "property with the RO-Crate specification version"] + ) + + +def test_invalid_conforms_to(): + """Test a RO-Crate with an invalid file descriptor entity type.""" + do_entity_test( + paths.invalid_conforms_to, + models.Severity.REQUIRED, + False, + ["RO-Crate Metadata File Descriptor REQUIRED properties"], + ["The RO-Crate metadata file descriptor MUST have a `conformsTo` " + "property with the RO-Crate specification version"] + ) diff --git a/tests/integration/profiles/ro-crate/test_file_descriptor_format.py b/tests/integration/profiles/ro-crate/test_file_descriptor_format.py new file mode 100644 index 00000000..3c4a779f --- /dev/null +++ b/tests/integration/profiles/ro-crate/test_file_descriptor_format.py @@ -0,0 +1,87 @@ +# Copyright (c) 2024 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 import models +from tests.ro_crates import InvalidFileDescriptor +from tests.shared import do_entity_test + +logger = logging.getLogger(__name__) + + +# Β Global set up the paths +paths = InvalidFileDescriptor() + + +def test_missing_file_descriptor(): + """Test a RO-Crate without a file descriptor.""" + with paths.missing_file_descriptor as rocrate_path: + do_entity_test( + rocrate_path, + models.Severity.REQUIRED, + False, + ["File Descriptor existence"], + [] + ) + + +def test_not_valid_json_format(): + """Test a RO-Crate with an invalid JSON file descriptor format.""" + do_entity_test( + paths.invalid_json_format, + models.Severity.REQUIRED, + False, + ["File Descriptor JSON format"], + [] + ) + + +def test_not_valid_jsonld_format_missing_context(): + """Test a RO-Crate with an invalid JSON-LD file descriptor format.""" + do_entity_test( + f"{paths.invalid_jsonld_format}/missing_context", + models.Severity.REQUIRED, + False, + ["File Descriptor JSON-LD format"], + [] + ) + + +def test_not_valid_jsonld_format_missing_ids(): + """ + Test a RO-Crate with an invalid JSON-LD file descriptor format. + One or more entities in the file descriptor do not contain the @id attribute. + """ + do_entity_test( + f"{paths.invalid_jsonld_format}/missing_id", + models.Severity.REQUIRED, + False, + ["File Descriptor JSON-LD format"], + ["file descriptor does not contain the @id attribute"] + ) + + +def test_not_valid_jsonld_format_missing_types(): + """ + Test a RO-Crate with an invalid JSON-LD file descriptor format. + One or more entities in the file descriptor do not contain the @type attribute. + """ + do_entity_test( + f"{paths.invalid_jsonld_format}/missing_type", + models.Severity.REQUIRED, + False, + ["File Descriptor JSON-LD format"], + ["file descriptor does not contain the @type attribute"] + ) diff --git a/tests/integration/profiles/ro-crate/test_root_data_entity.py b/tests/integration/profiles/ro-crate/test_root_data_entity.py new file mode 100644 index 00000000..95a2c1de --- /dev/null +++ b/tests/integration/profiles/ro-crate/test_root_data_entity.py @@ -0,0 +1,135 @@ +# Copyright (c) 2024 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 import models +from tests.ro_crates import InvalidRootDataEntity +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +# Β Global set up the paths +paths = InvalidRootDataEntity() + + +def test_missing_root_data_entity(): + """Test a RO-Crate without a root data entity.""" + do_entity_test( + paths.invalid_root_type, + models.Severity.REQUIRED, + False, + ["RO-Crate Root Data Entity type"], + ["The Root Data Entity MUST be a `Dataset` (as per `schema.org`)"] + ) + + +def test_invalid_root_data_entity_value(): + """Test a RO-Crate with an invalid root data entity value.""" + do_entity_test( + paths.invalid_root_value, + models.Severity.REQUIRED, + False, + ["RO-Crate Root Data Entity value restriction"], + ["The Root Data Entity URI MUST end with `/`"] + ) + + +def test_recommended_root_data_entity_value(): + """Test a RO-Crate with an invalid root data entity value.""" + do_entity_test( + paths.recommended_root_value, + models.Severity.RECOMMENDED, + False, + ["RO-Crate Root Data Entity RECOMMENDED value"], + ["Root Data Entity URI is not denoted by the string `./`"] + ) + + +def test_invalid_root_date(): + """Test a RO-Crate with an invalid root data entity date.""" + do_entity_test( + paths.invalid_root_date, + models.Severity.RECOMMENDED, + False, + ["RO-Crate Root Data Entity RECOMMENDED properties"], + ["The Root Data Entity SHOULD have a `datePublished` property (as specified by schema.org) " + "with a valid ISO 8601 date and the precision of at least the day level"] + ) + + +def test_missing_root_name(): + """Test a RO-Crate without a root data entity name.""" + do_entity_test( + paths.missing_root_name, + models.Severity.RECOMMENDED, + False, + ["RO-Crate Root Data Entity RECOMMENDED properties"], + ["The Root Data Entity SHOULD have a `name` property (as specified by schema.org)"] + ) + + +def test_missing_root_description(): + """Test a RO-Crate without a root data entity description.""" + do_entity_test( + paths.missing_root_description, + models.Severity.RECOMMENDED, + False, + ["RO-Crate Root Data Entity RECOMMENDED properties"], + ["The Root Data Entity SHOULD have a `description` property (as specified by schema.org)"] + ) + + +def test_valid_referenced_generic_data_entities(): + """Test a RO-Crate with invalid referenced data entities.""" + do_entity_test( + paths.valid_referenced_generic_data_entities, + models.Severity.REQUIRED, + True + ) + + +def test_missing_root_license(): + """Test a RO-Crate without a root data entity license.""" + do_entity_test( + paths.missing_root_license, + models.Severity.RECOMMENDED, + False, + ["RO-Crate Root Data Entity RECOMMENDED properties"], + ["The Root Data Entity SHOULD have a link to a Contextual Entity representing the schema_org:license type"] + ) + + +def test_missing_root_license_name(): + """Test a RO-Crate without a root data entity license name.""" + do_entity_test( + paths.missing_root_license_name, + models.Severity.OPTIONAL, + False, + ["License definition"], + ["Missing license name"] + ) + + +def test_missing_root_license_description(): + """Test a RO-Crate without a root data entity license description.""" + do_entity_test( + paths.missing_root_license_description, + models.Severity.OPTIONAL, + False, + ["License definition"], + ["Missing license description"] + ) diff --git a/tests/integration/profiles/ro-crate/test_valid_ro-crate.py b/tests/integration/profiles/ro-crate/test_valid_ro-crate.py new file mode 100644 index 00000000..e44586b3 --- /dev/null +++ b/tests/integration/profiles/ro-crate/test_valid_ro-crate.py @@ -0,0 +1,49 @@ +# Copyright (c) 2024 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 + +# set up logging +logger = logging.getLogger(__name__) + + +def test_valid_roc_required(): + """Test a valid RO-Crate.""" + do_entity_test( + ValidROC().wrroc_paper, + Severity.REQUIRED, + True + ) + + +def test_valid_roc_recommended(): + """Test a valid RO-Crate.""" + do_entity_test( + ValidROC().wrroc_paper, + Severity.RECOMMENDED, + True + ) + + +def test_valid_roc_required_with_long_datetime(): + """Test a valid RO-Crate.""" + do_entity_test( + ValidROC().wrroc_paper_long_date, + Severity.REQUIRED, + True + ) diff --git a/tests/integration/profiles/workflow-ro-crate/test_main_workflow.py b/tests/integration/profiles/workflow-ro-crate/test_main_workflow.py new file mode 100644 index 00000000..e942e67b --- /dev/null +++ b/tests/integration/profiles/workflow-ro-crate/test_main_workflow.py @@ -0,0 +1,170 @@ +# Copyright (c) 2024 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 InvalidMainWorkflow +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +def test_main_workflow_bad_type(): + """\ + Test a Workflow RO-Crate where the main workflow has an incorrect type. + """ + do_entity_test( + InvalidMainWorkflow().main_workflow_bad_type, + Severity.REQUIRED, + False, + ["Main Workflow definition"], + ["The Main Workflow must have types File, SoftwareSourceCode, ComputationalWorfklow"], + profile_identifier="workflow-ro-crate" + ) + + +def test_main_workflow_no_lang(): + """\ + Test a Workflow RO-Crate where the main workflow does not have a + programmingLanguage property. + """ + do_entity_test( + InvalidMainWorkflow().main_workflow_no_lang, + Severity.REQUIRED, + False, + ["Main Workflow definition"], + ["The Main Workflow must refer to its language via programmingLanguage"], + profile_identifier="workflow-ro-crate" + ) + + +def test_main_workflow_no_image(): + """\ + Test a Workflow RO-Crate where the main workflow does not have an + image property. + """ + do_entity_test( + InvalidMainWorkflow().main_workflow_no_image, + Severity.OPTIONAL, + False, + ["Main Workflow optional properties"], + ["The Crate MAY contain a Main Workflow Diagram; if present it MUST be referred to via 'image'"], + profile_identifier="workflow-ro-crate" + ) + + +def test_main_workflow_no_cwl_desc(): + """\ + Test a Workflow RO-Crate where the main workflow does not have an + CWL description. + """ + do_entity_test( + InvalidMainWorkflow().main_workflow_no_cwl_desc, + Severity.OPTIONAL, + False, + ["Main Workflow optional properties"], + ["The Crate MAY contain a Main Workflow CWL Description; if present it MUST be referred to via 'subjectOf'"], + profile_identifier="workflow-ro-crate" + ) + + +def test_main_workflow_cwl_desc_bad_type(): + """\ + Test a Workflow RO-Crate where the main workflow has a CWL description + but of the wrong type. + """ + do_entity_test( + InvalidMainWorkflow().main_workflow_cwl_desc_bad_type, + Severity.OPTIONAL, + False, + ["Main Workflow optional properties"], + ["The CWL Description type must be File, SoftwareSourceCode, HowTo"], + profile_identifier="workflow-ro-crate" + ) + + +def test_main_workflow_cwl_desc_no_lang(): + """\ + Test a Workflow RO-Crate where the main workflow has a CWL description + but the description has no programmingLanguage. + """ + do_entity_test( + InvalidMainWorkflow().main_workflow_cwl_desc_no_lang, + Severity.OPTIONAL, + False, + ["Main Workflow optional properties"], + ["The CWL Description SHOULD have a language of https://w3id.org/workflowhub/workflow-ro-crate#cwl"], + profile_identifier="workflow-ro-crate" + ) + + +def test_main_workflow_file_existence(): + """\ + Test a Workflow RO-Crate where the main workflow file is not in the crate. + """ + do_entity_test( + InvalidMainWorkflow().main_workflow_no_files, + Severity.REQUIRED, + False, + ["Main Workflow file existence"], + ["Main Workflow", "not found in crate"], + profile_identifier="workflow-ro-crate" + ) + + +def test_workflow_diagram_file_existence(): + """\ + Test a Workflow RO-Crate where the workflow diagram file is not in the + crate. + """ + do_entity_test( + InvalidMainWorkflow().main_workflow_no_files, + Severity.OPTIONAL, + False, + ["Workflow-related files existence"], + ["Workflow diagram", "not found in crate"], + profile_identifier="workflow-ro-crate" + ) + + +def test_workflow_description_file_existence(): + """\ + Test a Workflow RO-Crate where the workflow CWL description file is not in + the crate. + """ + do_entity_test( + InvalidMainWorkflow().main_workflow_no_files, + Severity.OPTIONAL, + False, + ["Workflow-related files existence"], + ["Workflow CWL description", "not found in crate"], + profile_identifier="workflow-ro-crate" + ) + + +def test_main_workflow_bad_conformsto(): + """\ + Test a Workflow RO-Crate where the main workflow does not conform to the + bioschemas computational workflow 1.0 or later. + """ + do_entity_test( + InvalidMainWorkflow().main_workflow_bad_conformsto, + Severity.RECOMMENDED, + False, + ["Main Workflow recommended properties"], + ["The Main Workflow SHOULD comply with Bioschemas ComputationalWorkflow profile version 1.0 or later"], + profile_identifier="workflow-ro-crate" + ) diff --git a/tests/integration/profiles/workflow-ro-crate/test_valid_wroc.py b/tests/integration/profiles/workflow-ro-crate/test_valid_wroc.py new file mode 100644 index 00000000..890af63e --- /dev/null +++ b/tests/integration/profiles/workflow-ro-crate/test_valid_wroc.py @@ -0,0 +1,37 @@ +# Copyright (c) 2024 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 + +logger = logging.getLogger(__name__) + + +def test_valid_workflow_roc_required(): + """Test a valid Workflow RO-Crate.""" + do_entity_test( + ValidROC().workflow_roc, + Severity.REQUIRED, + True, + profile_identifier="workflow-ro-crate" + ) + do_entity_test( + ValidROC().workflow_roc_string_license, + Severity.REQUIRED, + True, + profile_identifier="workflow-ro-crate" + ) diff --git a/tests/integration/profiles/workflow-ro-crate/test_wroc_crate.py b/tests/integration/profiles/workflow-ro-crate/test_wroc_crate.py new file mode 100644 index 00000000..3bee8058 --- /dev/null +++ b/tests/integration/profiles/workflow-ro-crate/test_wroc_crate.py @@ -0,0 +1,49 @@ +# Copyright (c) 2024 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 WROCNoLicense +from tests.shared import do_entity_test + +logger = logging.getLogger(__name__) + + +def test_wroc_no_tests(): + """\ + Test a Workflow RO-Crate with no test/ Dataset. + """ + do_entity_test( + WROCNoLicense().wroc_no_license, + Severity.OPTIONAL, + False, + ["test directory"], + ["The test/ dir should be a Dataset"], + profile_identifier="workflow-ro-crate" + ) + + +def test_wroc_no_examples(): + """\ + Test a Workflow RO-Crate with no examples/ Dataset. + """ + do_entity_test( + WROCNoLicense().wroc_no_license, + Severity.OPTIONAL, + False, + ["examples directory"], + ["The examples/ dir should be a Dataset"], + profile_identifier="workflow-ro-crate" + ) diff --git a/tests/integration/profiles/workflow-ro-crate/test_wroc_descriptor.py b/tests/integration/profiles/workflow-ro-crate/test_wroc_descriptor.py new file mode 100644 index 00000000..4c14a564 --- /dev/null +++ b/tests/integration/profiles/workflow-ro-crate/test_wroc_descriptor.py @@ -0,0 +1,38 @@ +# Copyright (c) 2024 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 WROCInvalidConformsTo +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +def test_wroc_descriptor_bad_conforms_to(): + """\ + Test a Workflow RO-Crate where the metadata file descriptor does not + contain the required URIs. + """ + do_entity_test( + WROCInvalidConformsTo().wroc_descriptor_bad_conforms_to, + Severity.RECOMMENDED, + False, + ["WROC Metadata File Descriptor properties"], + ["The Metadata File Descriptor conformsTo SHOULD contain https://w3id.org/ro/crate/1.1 " + "and https://w3id.org/workflowhub/workflow-ro-crate/1.0"], + profile_identifier="workflow-ro-crate" + ) diff --git a/tests/integration/profiles/workflow-ro-crate/test_wroc_readme.py b/tests/integration/profiles/workflow-ro-crate/test_wroc_readme.py new file mode 100644 index 00000000..c4ec2aec --- /dev/null +++ b/tests/integration/profiles/workflow-ro-crate/test_wroc_readme.py @@ -0,0 +1,49 @@ +# Copyright (c) 2024 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 WROCInvalidReadme +from tests.shared import do_entity_test + +logger = logging.getLogger(__name__) + + +def test_wroc_readme_not_about_crate(): + """\ + Test a Workflow RO-Crate where the README.md is not about the crate. + """ + do_entity_test( + WROCInvalidReadme().wroc_readme_not_about_crate, + Severity.RECOMMENDED, + False, + ["README.md properties"], + ["The README.md SHOULD be about the crate"], + profile_identifier="workflow-ro-crate" + ) + + +def test_wroc_readme_wrong_encoding_format(): + """\ + Test a Workflow RO-Crate where the README.md has the wrong encodingFormat.. + """ + do_entity_test( + WROCInvalidReadme().wroc_readme_wrong_encoding_format, + Severity.RECOMMENDED, + False, + ["README.md properties"], + ["The README.md SHOULD have text/markdown as its encodingFormat"], + profile_identifier="workflow-ro-crate" + ) diff --git a/tests/integration/profiles/workflow-ro-crate/test_wroc_root_metadata.py b/tests/integration/profiles/workflow-ro-crate/test_wroc_root_metadata.py new file mode 100644 index 00000000..69965027 --- /dev/null +++ b/tests/integration/profiles/workflow-ro-crate/test_wroc_root_metadata.py @@ -0,0 +1,49 @@ +# Copyright (c) 2024 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 WROCNoLicense, WROCMainEntity +from tests.shared import do_entity_test + +logger = logging.getLogger(__name__) + + +def test_wroc_no_license(): + """\ + Test a Workflow RO-Crate where the root data entity has no license. + """ + do_entity_test( + WROCNoLicense().wroc_no_license, + Severity.REQUIRED, + False, + ["WROC Root Data Entity Required Properties"], + ["The Crate (Root Data Entity) must specify a license, which should be a URL but can also be a string"], + profile_identifier="workflow-ro-crate" + ) + + +def test_wroc_no_mainentity(): + """\ + Test a Workflow RO-Crate where the root data entity has no mainEntity. + """ + do_entity_test( + WROCMainEntity().wroc_no_mainentity, + Severity.REQUIRED, + False, + ["Main Workflow entity existence"], + ["The Main Workflow must be specified through a `mainEntity` property in the root data entity"], + profile_identifier="workflow-ro-crate" + ) diff --git a/tests/integration/profiles/workflow-testing-ro-crate/test_valid_wtroc.py b/tests/integration/profiles/workflow-testing-ro-crate/test_valid_wtroc.py new file mode 100644 index 00000000..b61cbf0c --- /dev/null +++ b/tests/integration/profiles/workflow-testing-ro-crate/test_valid_wtroc.py @@ -0,0 +1,31 @@ +# Copyright (c) 2024 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 + +logger = logging.getLogger(__name__) + + +def test_valid_workflow_roc_required(): + """Test a valid Workflow Testing RO-Crate.""" + do_entity_test( + ValidROC().workflow_testing_ro_crate, + Severity.REQUIRED, + True, + profile_identifier="workflow-testing-ro-crate" + ) diff --git a/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_root_data_entity.py b/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_root_data_entity.py new file mode 100644 index 00000000..517efbf7 --- /dev/null +++ b/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_root_data_entity.py @@ -0,0 +1,37 @@ +# Copyright (c) 2024 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 + +# set up logging +logger = logging.getLogger(__name__) + + +def test_wtroc_no_suites(): + """\ + Test a Workflow Testing RO-Crate where the root data entity does not refer to + any TestSuite via mentions. + """ + do_entity_test( + ValidROC().workflow_roc, # a plain workflow ro-crate, no test suites + Severity.REQUIRED, + False, + ["Root Data Entity Metadata"], + ["The Root Data Entity MUST refer to one or more test suites via mentions"], + profile_identifier="workflow-testing-ro-crate" + ) diff --git a/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_testdefinition.py b/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_testdefinition.py new file mode 100644 index 00000000..f7f35cab --- /dev/null +++ b/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_testdefinition.py @@ -0,0 +1,97 @@ +# Copyright (c) 2024 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 InvalidWTROC +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +def test_wtroc_testdefinition_bad_type(): + """\ + Test a Workflow Testing RO-Crate where a TestDefinition does not have the + File (MediaObject) and TestDefinition types. + """ + do_entity_test( + InvalidWTROC().testdefinition_bad_type, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestDefinition MUST"], + ["The TestDefinition MUST have types TestDefinition and File"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testdefinition_no_engine(): + """\ + Test a Workflow Testing RO-Crate where a TestDefinition does not refer + to the test engine SoftwareApplication via conformsTo. + """ + do_entity_test( + InvalidWTROC().testdefinition_no_engine, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestDefinition MUST"], + ["The TestDefinition MUST refer to the test engine it is written for via conformsTo"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testdefinition_no_engineversion(): + """\ + Test a Workflow Testing RO-Crate where a TestDefinition does not refer + to the test engine's version via engineVersion. + """ + do_entity_test( + InvalidWTROC().testdefinition_no_engineversion, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestDefinition MUST"], + ["The TestDefinition MUST refer to the test engine version via engineVersion"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testdefinition_bad_conformsto(): + """\ + Test a Workflow Testing RO-Crate where a TestDefinition does not refer + to the test engine SoftwareApplication via conformsTo. + """ + do_entity_test( + InvalidWTROC().testdefinition_bad_conformsto, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestDefinition MUST"], + ["The TestDefinition MUST refer to the test engine it is written for via conformsTo"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testdefinition_bad_engineversion(): + """\ + Test a Workflow Testing RO-Crate where a TestDefinition does not refer + to the test engine's version as a string. + """ + do_entity_test( + InvalidWTROC().testdefinition_bad_engineversion, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestDefinition MUST"], + ["The TestDefinition MUST refer to the test engine version via engineVersion"], + profile_identifier="workflow-testing-ro-crate" + ) diff --git a/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_testinstance.py b/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_testinstance.py new file mode 100644 index 00000000..f90aa9a5 --- /dev/null +++ b/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_testinstance.py @@ -0,0 +1,112 @@ +# Copyright (c) 2024 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 InvalidWTROC +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +def test_wtroc_testinstance_no_service(): + """\ + Test a Workflow Testing RO-Crate where a TestInstance does not refer to + a TestService. + """ + do_entity_test( + InvalidWTROC().testinstance_no_service, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestInstance MUST"], + ["The TestInstance MUST refer to a TestService via runsOn"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testinstance_no_url(): + """\ + Test a Workflow Testing RO-Crate where a TestInstance does not refer to + the test service base URL. + """ + do_entity_test( + InvalidWTROC().testinstance_no_url, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestInstance MUST"], + ["The TestInstance MUST refer to the test service base URL via url"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testinstance_no_resource(): + """\ + Test a Workflow Testing RO-Crate where a TestInstance does not refer to + the relative URL of the test project via resource. + """ + do_entity_test( + InvalidWTROC().testinstance_no_resource, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestInstance MUST"], + ["The TestInstance MUST refer to the relative URL of the test project via resource"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testinstance_bad_runson(): + """\ + Test a Workflow Testing RO-Crate where a TestInstance has a runsOn + property that does not refer to a TestService. + """ + do_entity_test( + InvalidWTROC().testinstance_bad_runson, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestInstance MUST"], + ["The TestInstance MUST refer to a TestService via runsOn"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testinstance_bad_url(): + """\ + Test a Workflow Testing RO-Crate where a TestInstance has a url + property that does not refer to a string with a URL pattern. + """ + do_entity_test( + InvalidWTROC().testinstance_bad_url, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestInstance MUST"], + ["The TestInstance MUST refer to the test service base URL via url"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testinstance_bad_resource(): + """\ + Test a Workflow Testing RO-Crate where a TestInstance has a resource + property that does not refer to a string. + """ + do_entity_test( + InvalidWTROC().testinstance_bad_resource, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestInstance MUST"], + ["The TestInstance MUST refer to the relative URL of the test project via resource"], + profile_identifier="workflow-testing-ro-crate" + ) diff --git a/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_testsuite.py b/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_testsuite.py new file mode 100644 index 00000000..bb42070f --- /dev/null +++ b/tests/integration/profiles/workflow-testing-ro-crate/test_wtroc_testsuite.py @@ -0,0 +1,112 @@ +# Copyright (c) 2024 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 InvalidWTROC +from tests.shared import do_entity_test + +# set up logging +logger = logging.getLogger(__name__) + + +def test_wtroc_testsuite_not_mentioned(): + """\ + Test a Workflow Testing RO-Crate where a TestSuite is not listed in the + Root Data Entity's mentions. + """ + do_entity_test( + InvalidWTROC().testsuite_not_mentioned, + Severity.REQUIRED, + False, + ["Workflow Testing RO-Crate TestSuite MUST"], + ["The TestSuite MUST be referenced from the Root Data Entity via mentions"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testsuite_no_instance_no_def(): + """\ + Test a Workflow Testing RO-Crate where a TestSuite does not refer to + either a TestSuite or a TestDefinition. + """ + do_entity_test( + InvalidWTROC().testsuite_no_instance_no_def, + Severity.REQUIRED, + False, + ["TestSuite instance or definition"], + ["The TestSuite MUST refer to a TestInstance or TestDefinition"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testsuite_no_mainentity(): + """\ + Test a Workflow Testing RO-Crate where a TestSuite does not refer to + the tested workflow via mainEntity. + """ + do_entity_test( + InvalidWTROC().testsuite_no_mainentity, + Severity.RECOMMENDED, + False, + ["Workflow Testing RO-Crate TestSuite SHOULD"], + ["The TestSuite SHOULD refer to the tested workflow via mainEntity"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testsuite_bad_instance(): + """\ + Test a Workflow Testing RO-Crate where a TestSuite has an instance + property that does not refer to a TestInstance. + """ + do_entity_test( + InvalidWTROC().testsuite_bad_instance, + Severity.REQUIRED, + False, + ["TestSuite instance or definition"], + ["The TestSuite MUST refer to a TestInstance or TestDefinition"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testsuite_bad_definition(): + """\ + Test a Workflow Testing RO-Crate where a TestSuite has a definition + property that does not refer to a TestDefinition. + """ + do_entity_test( + InvalidWTROC().testsuite_bad_definition, + Severity.REQUIRED, + False, + ["TestSuite instance or definition"], + ["The TestSuite MUST refer to a TestInstance or TestDefinition"], + profile_identifier="workflow-testing-ro-crate" + ) + + +def test_wtroc_testsuite_bad_mainentity(): + """\ + Test a Workflow Testing RO-Crate where a TestSuite has a mainEntity + property that does not refer to a workflow. + """ + do_entity_test( + InvalidWTROC().testsuite_bad_mainentity, + Severity.RECOMMENDED, + False, + ["Workflow Testing RO-Crate TestSuite SHOULD"], + ["The TestSuite SHOULD refer to the tested workflow via mainEntity"], + profile_identifier="workflow-testing-ro-crate" + ) diff --git a/tests/ro-crate-metadata.json b/tests/ro-crate-metadata.json new file mode 100644 index 00000000..8f45fbb5 --- /dev/null +++ b/tests/ro-crate-metadata.json @@ -0,0 +1,312 @@ +{ + "@context": "https://w3id.org/ro/crate/1.1/context", + "@graph": [ + { + "@id": "./", + "@type": "Dataset", + "datePublished": "2022-10-07T10:01:24+00:00", + "hasPart": [ + { + "@id": "packed.cwl" + }, + { + "@id": "327fc7aedf4f6b69a42a7c8b808dc5a7aff61376" + }, + { + "@id": "b9214658cc453331b62c2282b772a5c063dbd284" + }, + { + "@id": "97fe1b50b4582cebc7d853796ebd62e3e163aa3f" + } + ], + "mainEntity": { + "@id": "packed.cwl" + }, + "mentions": [ + { + "@id": "#7aeba0c9-78f6-4fb7-85d9-fcbe18fce057" + } + ] + }, + { + "@id": "ro-crate-metadata.json", + "@type": "CreativeWork", + "about": { + "@id": "./" + }, + "conformsTo": [ + { + "@id": "https://w3id.org/ro/crate/1.1" + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate/1.0" + } + ] + }, + { + "@id": "packed.cwl", + "@type": [ + "File", + "SoftwareSourceCode", + "ComputationalWorkflow", + "HowTo" + ], + "hasPart": [ + { + "@id": "packed.cwl#revtool.cwl" + }, + { + "@id": "packed.cwl#sorttool.cwl" + } + ], + "input": [ + { + "@id": "packed.cwl#main/input" + }, + { + "@id": "packed.cwl#main/reverse_sort" + } + ], + "name": "packed.cwl", + "output": [ + { + "@id": "packed.cwl#main/output" + } + ], + "programmingLanguage": { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl" + } + }, + { + "@id": "https://w3id.org/workflowhub/workflow-ro-crate#cwl", + "@type": "ComputerLanguage", + "alternateName": "CWL", + "identifier": { + "@id": "https://w3id.org/cwl/v1.0/" + }, + "name": "Common Workflow Language", + "url": { + "@id": "https://www.commonwl.org/" + }, + "version": "v1.0" + }, + { + "@id": "packed.cwl#main/input", + "@type": "FormalParameter", + "additionalType": "File", + "connectedTo": { + "@id": "packed.cwl#revtool.cwl/input" + }, + "defaultValue": "file:///home/stain/src/cwltool/tests/wf/hello.txt", + "encodingFormat": "https://www.iana.org/assignments/media-types/text/plain", + "name": "main/input" + }, + { + "@id": "packed.cwl#main/reverse_sort", + "@type": "FormalParameter", + "additionalType": "Boolean", + "connectedTo": { + "@id": "packed.cwl#sorttool.cwl/reverse" + }, + "defaultValue": "True", + "name": "main/reverse_sort" + }, + { + "@id": "packed.cwl#main/output", + "@type": "FormalParameter", + "additionalType": "File", + "name": "main/output" + }, + { + "@id": "packed.cwl#revtool.cwl", + "@type": "SoftwareApplication", + "description": "Reverse each line using the `rev` command", + "input": [ + { + "@id": "packed.cwl#revtool.cwl/input" + } + ], + "name": "revtool.cwl", + "output": [ + { + "@id": "packed.cwl#revtool.cwl/output" + } + ] + }, + { + "@id": "packed.cwl#revtool.cwl/input", + "@type": "FormalParameter", + "additionalType": "File", + "name": "revtool.cwl/input" + }, + { + "@id": "packed.cwl#revtool.cwl/output", + "@type": "FormalParameter", + "additionalType": "File", + "connectedTo": { + "@id": "packed.cwl#sorttool.cwl/input" + }, + "name": "revtool.cwl/output" + }, + { + "@id": "packed.cwl#sorttool.cwl", + "@type": "SoftwareApplication", + "description": "Sort lines using the `sort` command", + "input": [ + { + "@id": "packed.cwl#sorttool.cwl/reverse" + }, + { + "@id": "packed.cwl#sorttool.cwl/input" + } + ], + "name": "sorttool.cwl", + "output": [ + { + "@id": "packed.cwl#sorttool.cwl/output" + } + ] + }, + { + "@id": "packed.cwl#sorttool.cwl/reverse", + "@type": "FormalParameter", + "additionalType": "Boolean", + "name": "sorttool.cwl/reverse" + }, + { + "@id": "packed.cwl#sorttool.cwl/input", + "@type": "FormalParameter", + "additionalType": "File", + "name": "sorttool.cwl/input" + }, + { + "@id": "packed.cwl#sorttool.cwl/output", + "@type": "FormalParameter", + "additionalType": "File", + "connectedTo": { + "@id": "packed.cwl#main/output" + }, + "name": "sorttool.cwl/output" + }, + { + "@id": "#7aeba0c9-78f6-4fb7-85d9-fcbe18fce057", + "@type": "CreateAction", + "endTime": "2018-10-25T15:46:43.020168", + "instrument": { + "@id": "packed.cwl" + }, + "name": "Run of workflow/packed.cwl#main", + "object": [ + { + "@id": "327fc7aedf4f6b69a42a7c8b808dc5a7aff61376" + }, + { + "@id": "#pv-main/reverse_sort" + } + ], + "result": [ + { + "@id": "b9214658cc453331b62c2282b772a5c063dbd284" + } + ], + "startTime": "2018-10-25T15:46:35.211153" + }, + { + "@id": "327fc7aedf4f6b69a42a7c8b808dc5a7aff61376", + "@type": "File", + "exampleOfWork": [ + { + "@id": "packed.cwl#main/input" + }, + { + "@id": "packed.cwl#revtool.cwl/input" + } + ] + }, + { + "@id": "#pv-main/reverse_sort", + "@type": "PropertyValue", + "exampleOfWork": { + "@id": "packed.cwl#main/reverse_sort" + }, + "name": "main/reverse_sort", + "value": "True" + }, + { + "@id": "b9214658cc453331b62c2282b772a5c063dbd284", + "@type": "File", + "exampleOfWork": [ + { + "@id": "packed.cwl#main/output" + }, + { + "@id": "packed.cwl#sorttool.cwl/output" + } + ] + }, + { + "@id": "#a439c61f-2378-49fb-a7e5-7258248daaeb", + "@type": "CreateAction", + "endTime": "2018-10-25T15:46:36.967359", + "instrument": { + "@id": "packed.cwl#revtool.cwl" + }, + "name": "Run of workflow/packed.cwl#main/rev", + "object": [ + { + "@id": "327fc7aedf4f6b69a42a7c8b808dc5a7aff61376" + } + ], + "result": [ + { + "@id": "97fe1b50b4582cebc7d853796ebd62e3e163aa3f" + } + ], + "startTime": "2018-10-25T15:46:35.314101" + }, + { + "@id": "97fe1b50b4582cebc7d853796ebd62e3e163aa3f", + "@type": "File", + "exampleOfWork": [ + { + "@id": "packed.cwl#revtool.cwl/output" + }, + { + "@id": "packed.cwl#sorttool.cwl/input" + } + ] + }, + { + "@id": "#4377b674-1c08-4afe-b3a6-df827c03b1c4", + "@type": "CreateAction", + "endTime": "2018-10-25T15:46:38.069110", + "instrument": { + "@id": "packed.cwl#sorttool.cwl" + }, + "name": "Run of workflow/packed.cwl#main/sorted", + "object": [ + { + "@id": "97fe1b50b4582cebc7d853796ebd62e3e163aa3f" + }, + { + "@id": "#pv-main/sorted/reverse" + } + ], + "result": [ + { + "@id": "b9214658cc453331b62c2282b772a5c063dbd284" + } + ], + "startTime": "2018-10-25T15:46:36.975235" + }, + { + "@id": "#pv-main/sorted/reverse", + "@type": "PropertyValue", + "exampleOfWork": { + "@id": "packed.cwl#sorttool.cwl/reverse" + }, + "name": "main/sorted/reverse", + "value": "True" + } + ] +} \ No newline at end of file diff --git a/tests/ro_crates.py b/tests/ro_crates.py new file mode 100644 index 00000000..66ae548f --- /dev/null +++ b/tests/ro_crates.py @@ -0,0 +1,542 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +from tempfile import TemporaryDirectory + +from pytest import fixture + +CURRENT_PATH = Path(__file__).resolve().parent +TEST_DATA_PATH = (CURRENT_PATH / "data").absolute() +CRATES_DATA_PATH = TEST_DATA_PATH / "crates" +VALID_CRATES_DATA_PATH = CRATES_DATA_PATH / "valid" +INVALID_CRATES_DATA_PATH = CRATES_DATA_PATH / "invalid" + + +@fixture +def ro_crates_path() -> Path: + return CRATES_DATA_PATH + + +class ValidROC: + @property + def wrroc_paper(self) -> Path: + return VALID_CRATES_DATA_PATH / "wrroc-paper" + + @property + def wrroc_paper_long_date(self) -> Path: + return VALID_CRATES_DATA_PATH / "wrroc-paper-long-date" + + @property + def workflow_roc(self) -> Path: + return VALID_CRATES_DATA_PATH / "workflow-roc" + + @property + def workflow_roc_string_license(self) -> Path: + return VALID_CRATES_DATA_PATH / "workflow-roc-string-license" + + @property + def sort_and_change_remote(self) -> Path: + return "https://raw.githubusercontent.com/lifemonitor/validator-test-data/main/sortchangecase.crate.zip" + + @property + def sort_and_change_archive(self) -> Path: + return VALID_CRATES_DATA_PATH / "sortchangecase.crate.zip" + + @property + def process_run_crate(self) -> Path: + return VALID_CRATES_DATA_PATH / "process-run-crate" + + @property + def process_run_crate_collections(self) -> Path: + return VALID_CRATES_DATA_PATH / "process-run-crate-collections" + + @property + def process_run_crate_containerimage(self) -> Path: + return VALID_CRATES_DATA_PATH / "process-run-crate-containerimage" + + @property + def workflow_testing_ro_crate(self) -> Path: + return VALID_CRATES_DATA_PATH / "workflow-testing-ro-crate" + + +class InvalidFileDescriptor: + + base_path = INVALID_CRATES_DATA_PATH / "0_file_descriptor_format" + + @property + def missing_file_descriptor(self) -> Path: + return TemporaryDirectory() + + @property + def invalid_json_format(self) -> Path: + return self.base_path / "invalid_json_format" + + @property + def invalid_jsonld_format(self) -> Path: + return self.base_path / "invalid_jsonld_format" + + +class InvalidRootDataEntity: + + base_path = INVALID_CRATES_DATA_PATH / "2_root_data_entity_metadata" + + @property + def missing_root(self) -> Path: + return self.base_path / "missing_root_entity" + + @property + def invalid_root_type(self) -> Path: + return self.base_path / "invalid_root_type" + + @property + def invalid_root_value(self) -> Path: + return self.base_path / "invalid_root_value" + + @property + def recommended_root_value(self) -> Path: + return self.base_path / "recommended_root_value" + + @property + def invalid_root_date(self) -> Path: + return self.base_path / "invalid_root_date" + + @property + def missing_root_name(self) -> Path: + return self.base_path / "missing_root_name" + + @property + def missing_root_description(self) -> Path: + return self.base_path / "missing_root_description" + + @property + def missing_root_license(self) -> Path: + return self.base_path / "missing_root_license" + + @property + def missing_root_license_name(self) -> Path: + return self.base_path / "missing_root_license_name" + + @property + def missing_root_license_description(self) -> Path: + return self.base_path / "missing_root_license_description" + + @property + def valid_referenced_generic_data_entities(self) -> Path: + return self.base_path / "valid_referenced_generic_data_entities" + + +class InvalidFileDescriptorEntity: + + base_path = INVALID_CRATES_DATA_PATH / "1_file_descriptor_metadata" + + @property + def missing_entity(self) -> Path: + return self.base_path / "missing_entity" + + @property + def invalid_entity_type(self) -> Path: + return self.base_path / "invalid_entity_type" + + @property + def missing_entity_about(self) -> Path: + return self.base_path / "missing_entity_about" + + @property + def invalid_entity_about(self) -> Path: + return self.base_path / "invalid_entity_about" + + @property + def invalid_entity_about_type(self) -> Path: + return self.base_path / "invalid_entity_about_type" + + @property + def missing_conforms_to(self) -> Path: + return self.base_path / "missing_conforms_to" + + @property + def invalid_conforms_to(self) -> Path: + return self.base_path / "invalid_conforms_to" + + +class InvalidDataEntity: + + base_path = INVALID_CRATES_DATA_PATH / "4_data_entity_metadata" + + @property + def missing_hasPart_data_entity_reference(self) -> Path: + return self.base_path / "invalid_missing_hasPart_reference" + + @property + def direct_hasPart_data_entity_reference(self) -> Path: + return self.base_path / "valid_direct_hasPart_reference" + + @property + def indirect_hasPart_data_entity_reference(self) -> Path: + return self.base_path / "valid_indirect_hasPart_reference" + + @property + def directory_data_entity_wo_trailing_slash(self) -> Path: + return self.base_path / "directory_data_entity_wo_trailing_slash" + + @property + def missing_data_entity_encoding_format(self) -> Path: + return self.base_path / "missing_encoding_format" + + @property + def invalid_data_entity_encoding_format_pronom(self) -> Path: + return self.base_path / "invalid_encoding_format_pronom" + + @property + def invalid_encoding_format_ctx_entity_missing_ws_type(self) -> Path: + return self.base_path / "invalid_encoding_format_ctx_entity_missing_ws_type" + + @property + def invalid_encoding_format_ctx_entity_missing_ws_name(self) -> Path: + return self.base_path / "invalid_encoding_format_ctx_entity_missing_ws_name" + + @property + def valid_encoding_format_ctx_entity(self) -> Path: + return self.base_path / "valid_encoding_format_ctx_entity" + + @property + def valid_encoding_format_pronom(self) -> Path: + return self.base_path / "valid_encoding_format_pronom" + + +class InvalidMainWorkflow: + + base_path = INVALID_CRATES_DATA_PATH / "0_main_workflow" + + @property + def main_workflow_bad_type(self) -> Path: + return self.base_path / "main_workflow_bad_type" + + @property + def main_workflow_no_lang(self) -> Path: + return self.base_path / "main_workflow_no_lang" + + @property + def main_workflow_no_image(self) -> Path: + return self.base_path / "main_workflow_no_image" + + @property + def main_workflow_no_cwl_desc(self) -> Path: + return self.base_path / "main_workflow_no_cwl_desc" + + @property + def main_workflow_cwl_desc_bad_type(self) -> Path: + return self.base_path / "main_workflow_cwl_desc_bad_type" + + @property + def main_workflow_cwl_desc_no_lang(self) -> Path: + return self.base_path / "main_workflow_cwl_desc_no_lang" + + @property + def main_workflow_no_files(self) -> Path: + return self.base_path / "no_files" + + @property + def main_workflow_bad_conformsto(self) -> Path: + return self.base_path / "main_workflow_bad_conformsto" + + +class WROCInvalidConformsTo: + + base_path = INVALID_CRATES_DATA_PATH / "2_wroc_descriptor" + + @property + def wroc_descriptor_bad_conforms_to(self) -> Path: + return self.base_path / "wroc_descriptor_bad_conforms_to" + + +class WROCInvalidReadme: + + base_path = INVALID_CRATES_DATA_PATH / "1_wroc_crate/" + + @property + def wroc_readme_not_about_crate(self) -> Path: + return self.base_path / "readme_not_about_crate" + + @property + def wroc_readme_wrong_encoding_format(self) -> Path: + return self.base_path / "readme_wrong_encoding_format" + + +class WROCNoLicense: + + base_path = INVALID_CRATES_DATA_PATH / "1_wroc_crate/" + + @property + def wroc_no_license(self) -> Path: + return self.base_path / "no_license" + + +class WROCMainEntity: + + base_path = INVALID_CRATES_DATA_PATH / "1_wroc_crate/" + + @property + def wroc_no_mainentity(self) -> Path: + return self.base_path / "no_mainentity" + + +class InvalidProcRC: + + base_path = INVALID_CRATES_DATA_PATH / "3_process_run_crate/" + + @property + def conformsto_bad_type(self) -> Path: + return self.base_path / "conformsto_bad_type" + + @property + def conformsto_bad_profile(self) -> Path: + return self.base_path / "conformsto_bad_profile" + + @property + def application_no_name(self) -> Path: + return self.base_path / "application_no_name" + + @property + def application_no_url(self) -> Path: + return self.base_path / "application_no_url" + + @property + def application_no_version(self) -> Path: + return self.base_path / "application_no_version" + + @property + def softwaresourcecode_no_version(self) -> Path: + return self.base_path / "softwaresourcecode_no_version" + + @property + def application_id_no_absoluteuri(self) -> Path: + return self.base_path / "application_id_no_absoluteuri" + + @property + def application_version_softwareVersion(self) -> Path: + return self.base_path / "application_version_softwareVersion" + + @property + def action_no_instrument(self) -> Path: + return self.base_path / "action_no_instrument" + + @property + def action_instrument_bad_type(self) -> Path: + return self.base_path / "action_instrument_bad_type" + + @property + def action_not_mentioned(self) -> Path: + return self.base_path / "action_not_mentioned" + + @property + def action_no_name(self) -> Path: + return self.base_path / "action_no_name" + + @property + def action_no_description(self) -> Path: + return self.base_path / "action_no_description" + + @property + def action_no_endtime(self) -> Path: + return self.base_path / "action_no_endtime" + + @property + def action_bad_endtime(self) -> Path: + return self.base_path / "action_bad_endtime" + + @property + def action_no_agent(self) -> Path: + return self.base_path / "action_no_agent" + + @property + def action_bad_agent(self) -> Path: + return self.base_path / "action_bad_agent" + + @property + def action_no_result(self) -> Path: + return self.base_path / "action_no_result" + + @property + def action_no_starttime(self) -> Path: + return self.base_path / "action_no_starttime" + + @property + def action_bad_starttime(self) -> Path: + return self.base_path / "action_bad_starttime" + + @property + def action_error_not_failed_status(self) -> Path: + return self.base_path / "action_error_not_failed_status" + + @property + def action_error_no_status(self) -> Path: + return self.base_path / "action_error_no_status" + + @property + def action_no_object(self) -> Path: + return self.base_path / "action_no_object" + + @property + def action_no_actionstatus(self) -> Path: + return self.base_path / "action_no_actionstatus" + + @property + def action_bad_actionstatus(self) -> Path: + return self.base_path / "action_bad_actionstatus" + + @property + def action_no_error(self) -> Path: + return self.base_path / "action_no_error" + + @property + def action_obj_res_bad_type(self) -> Path: + return self.base_path / "action_obj_res_bad_type" + + @property + def collection_not_mentioned(self) -> Path: + return self.base_path / "collection_not_mentioned" + + @property + def collection_no_haspart(self) -> Path: + return self.base_path / "collection_no_haspart" + + @property + def collection_no_mainentity(self) -> Path: + return self.base_path / "collection_no_mainentity" + + @property + def action_no_environment(self) -> Path: + return self.base_path / "action_no_environment" + + @property + def action_bad_environment(self) -> Path: + return self.base_path / "action_bad_environment" + + @property + def action_no_containerimage(self) -> Path: + return self.base_path / "action_no_containerimage" + + @property + def action_bad_containerimage_url(self) -> Path: + return self.base_path / "action_bad_containerimage_url" + + @property + def action_bad_containerimage_type(self) -> Path: + return self.base_path / "action_bad_containerimage_type" + + @property + def containerimage_no_additionaltype(self) -> Path: + return self.base_path / "containerimage_no_additionaltype" + + @property + def containerimage_bad_additionaltype(self) -> Path: + return self.base_path / "containerimage_bad_additionaltype" + + @property + def containerimage_no_registry(self) -> Path: + return self.base_path / "containerimage_no_registry" + + @property + def containerimage_no_name(self) -> Path: + return self.base_path / "containerimage_no_name" + + @property + def containerimage_no_tag(self) -> Path: + return self.base_path / "containerimage_no_tag" + + @property + def containerimage_no_sha256(self) -> Path: + return self.base_path / "containerimage_no_sha256" + + @property + def softwareapplication_no_softwarerequirements(self) -> Path: + return self.base_path / "softwareapplication_no_softwarerequirements" + + @property + def softwareapplication_bad_softwarerequirements(self) -> Path: + return self.base_path / "softwareapplication_bad_softwarerequirements" + + +class InvalidWTROC: + + base_path = INVALID_CRATES_DATA_PATH / "5_workflow_testing_ro_crate/" + + @property + def testsuite_not_mentioned(self) -> Path: + return self.base_path / "testsuite_not_mentioned" + + @property + def testsuite_no_instance_no_def(self) -> Path: + return self.base_path / "testsuite_no_instance_no_def" + + @property + def testsuite_no_mainentity(self) -> Path: + return self.base_path / "testsuite_no_mainentity" + + @property + def testinstance_no_service(self) -> Path: + return self.base_path / "testinstance_no_service" + + @property + def testinstance_no_url(self) -> Path: + return self.base_path / "testinstance_no_url" + + @property + def testinstance_no_resource(self) -> Path: + return self.base_path / "testinstance_no_resource" + + @property + def testdefinition_bad_type(self) -> Path: + return self.base_path / "testdefinition_bad_type" + + @property + def testdefinition_no_engine(self) -> Path: + return self.base_path / "testdefinition_no_engine" + + @property + def testdefinition_no_engineversion(self) -> Path: + return self.base_path / "testdefinition_no_engineversion" + + @property + def testsuite_bad_instance(self) -> Path: + return self.base_path / "testsuite_bad_instance" + + @property + def testsuite_bad_definition(self) -> Path: + return self.base_path / "testsuite_bad_definition" + + @property + def testsuite_bad_mainentity(self) -> Path: + return self.base_path / "testsuite_bad_mainentity" + + @property + def testinstance_bad_runson(self) -> Path: + return self.base_path / "testinstance_bad_runson" + + @property + def testinstance_bad_url(self) -> Path: + return self.base_path / "testinstance_bad_url" + + @property + def testinstance_bad_resource(self) -> Path: + return self.base_path / "testinstance_bad_resource" + + @property + def testdefinition_bad_conformsto(self) -> Path: + return self.base_path / "testdefinition_bad_conformsto" + + @property + def testdefinition_bad_engineversion(self) -> Path: + return self.base_path / "testdefinition_bad_engineversion" diff --git a/tests/shared.py b/tests/shared.py new file mode 100644 index 00000000..9d6cd3a0 --- /dev/null +++ b/tests/shared.py @@ -0,0 +1,113 @@ +# Copyright (c) 2024 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. + +""" +Library of shared functions for testing RO-Crate profiles +""" + +import logging +from collections.abc import Collection +from pathlib import Path +from typing import Optional, TypeVar, Union + +from rocrate_validator import models, services +from rocrate_validator.constants import DEFAULT_PROFILE_IDENTIFIER + +logger = logging.getLogger(__name__) + + +T = TypeVar("T") + + +def first(c: Collection[T]) -> T: + return next(iter(c)) + + +def do_entity_test( + rocrate_path: Union[Path, str], + requirement_severity: models.Severity, + expected_validation_result: bool, + expected_triggered_requirements: Optional[list[str]] = None, + expected_triggered_issues: Optional[list[str]] = None, + abort_on_first: bool = True, + profile_identifier: str = DEFAULT_PROFILE_IDENTIFIER +): + """ + Shared function to test a RO-Crate entity + """ + # declare variables + failed_requirements = None + detected_issues = None + + if not isinstance(rocrate_path, Path): + rocrate_path = Path(rocrate_path) + + if expected_triggered_requirements is None: + expected_triggered_requirements = [] + if expected_triggered_issues is None: + expected_triggered_issues = [] + + try: + logger.debug("Testing RO-Crate @ path: %s", rocrate_path) + logger.debug("Requirement severity: %s", requirement_severity) + + # set abort_on_first to False + abort_on_first = False + + # validate RO-Crate + result: models.ValidationResult = \ + services.validate(models.ValidationSettings(**{ + "data_path": rocrate_path, + "requirement_severity": requirement_severity, + "abort_on_first": abort_on_first, + "profile_identifier": profile_identifier + })) + logger.debug("Expected validation result: %s", expected_validation_result) + + assert result.context is not None, "Validation context should not be None" + f"Expected requirement severity to be {requirement_severity}, but got {result.context.requirement_severity}" + assert result.passed() == expected_validation_result, \ + f"RO-Crate should be {'valid' if expected_validation_result else 'invalid'}" + + # check requirement + failed_requirements = [_.name for _ in result.failed_requirements] + # assert len(failed_requirements) == len(expected_triggered_requirements), \ + # f"Expected {len(expected_triggered_requirements)} requirements to be "\ + # f"triggered, but got {len(failed_requirements)}" + + # check that the expected requirements are triggered + for expected_triggered_requirement in expected_triggered_requirements: + if expected_triggered_requirement not in failed_requirements: + assert False, f"The expected requirement " \ + f"\"{expected_triggered_requirement}\" was not found in the failed requirements" + + # check requirement issues + detected_issues = [issue.message for issue in result.get_issues(models.Severity.RECOMMENDED) + if issue.message is not None] + logger.debug("Detected issues: %s", detected_issues) + logger.debug("Expected issues: %s", expected_triggered_issues) + for expected_issue in expected_triggered_issues: + if not any(expected_issue in issue for issue in detected_issues): # support partial match + assert False, f"The expected issue \"{expected_issue}\" was not found in the detected issues" + except Exception as e: + if logger.isEnabledFor(logging.DEBUG): + logger.exception(e) + logger.debug("Failed to validate RO-Crate @ path: %s", rocrate_path) + logger.debug("Requirement severity: %s", requirement_severity) + logger.debug("Expected validation result: %s", expected_validation_result) + logger.debug("Expected triggered requirements: %s", expected_triggered_requirements) + logger.debug("Expected triggered issues: %s", expected_triggered_issues) + logger.debug("Failed requirements: %s", failed_requirements) + logger.debug("Detected issues: %s", detected_issues) + raise e diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..c6e66fb5 --- /dev/null +++ b/tests/test_cli.py @@ -0,0 +1,63 @@ +# Copyright (c) 2024 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 + +from click.testing import CliRunner +from pytest import fixture + +from rocrate_validator import log as logging +from rocrate_validator.cli.main import cli +from rocrate_validator.utils import get_version +from tests.ro_crates import InvalidFileDescriptor, ValidROC + +# set up logging +logger = logging.getLogger(__name__) + + +@fixture +def cli_runner() -> CliRunner: + return CliRunner() + + +def test_version(cli_runner: CliRunner): + result = cli_runner.invoke(cli, ["--version"]) + assert result.exit_code == 0 + assert get_version() in result.output + + +def test_validate_subcmd_invalid_rocrate1(cli_runner: CliRunner): + result = cli_runner.invoke(cli, ['validate', str( + InvalidFileDescriptor().invalid_json_format), '--verbose', '--no-paging', '-p', 'ro-crate']) + logger.error(result.output) + assert result.exit_code == 1 + + +def test_validate_subcmd_valid_local_folder_rocrate(cli_runner: CliRunner): + result = cli_runner.invoke(cli, ['validate', str(ValidROC().wrroc_paper_long_date), '--verbose', '--no-paging']) + assert result.exit_code == 0 + assert re.search(r'RO-Crate.*is a valid', result.output) + + +def test_validate_subcmd_valid_remote_rocrate(cli_runner: CliRunner): + result = cli_runner.invoke( + cli, ['validate', str(ValidROC().sort_and_change_remote), '--verbose', '--no-paging']) + assert result.exit_code == 0 + assert re.search(r'RO-Crate.*is a valid', result.output) + + +def test_validate_subcmd_invalid_local_archive_rocrate(cli_runner: CliRunner): + result = cli_runner.invoke(cli, ['validate', str(ValidROC().sort_and_change_archive), '--verbose', '--no-paging']) + assert result.exit_code == 0 + assert re.search(r'RO-Crate.*is a valid', result.output) diff --git a/tests/test_models.py b/tests/test_models.py new file mode 100644 index 00000000..ed44b254 --- /dev/null +++ b/tests/test_models.py @@ -0,0 +1,120 @@ +# Copyright (c) 2024 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 pytest + +from rocrate_validator import models, services +from rocrate_validator.models import (LevelCollection, RequirementLevel, + Severity, ValidationSettings) +from tests.ro_crates import InvalidRootDataEntity, WROCInvalidReadme + + +def test_severity_ordering(): + assert hash(Severity.OPTIONAL) != 0 # should be ok as long it hash runs + assert Severity.OPTIONAL < Severity.RECOMMENDED + assert Severity.RECOMMENDED > Severity.OPTIONAL + assert Severity.RECOMMENDED < Severity.REQUIRED + assert Severity.OPTIONAL < Severity.REQUIRED + assert Severity.OPTIONAL == Severity.OPTIONAL + assert Severity.RECOMMENDED <= Severity.REQUIRED + assert Severity.RECOMMENDED >= Severity.OPTIONAL + + +def test_level_ordering(): + may = RequirementLevel('MAY', Severity.OPTIONAL) + should = RequirementLevel('SHOULD', Severity.RECOMMENDED) + assert may < should + assert should > may + assert should != may + assert may == may + assert may != 1 + assert may != RequirementLevel('OPTIONAL', Severity.OPTIONAL) + with pytest.raises(TypeError): + _ = may > 1 + + +def test_level_basics(): + may = RequirementLevel('MAY', Severity.OPTIONAL) + assert str(may) == "MAY" + assert int(may) == Severity.OPTIONAL.value + assert hash(may) != 0 # should be find as long as it runs + + +def test_level_collection(): + assert LevelCollection.get('may') == LevelCollection.MAY + + # Test ordering + assert LevelCollection.MAY < LevelCollection.SHOULD + assert LevelCollection.SHOULD > LevelCollection.MAY + assert LevelCollection.SHOULD != LevelCollection.MAY + assert LevelCollection.MAY == LevelCollection.MAY + + all_levels = LevelCollection.all() + assert 10 == len(all_levels) + level_names = [level.name for level in all_levels] + # Test a few of the keys + assert 'MAY' in level_names + assert 'SHOULD_NOT' in level_names + assert 'RECOMMENDED' in level_names + assert 'REQUIRED' in level_names + + +@pytest.fixture +def validation_settings(): + return ValidationSettings( + requirement_severity=Severity.OPTIONAL, + abort_on_first=False + ) + + +# @pytest.mark.skip(reason="Temporarily disabled: we need an RO-Crate with multiple failed requirements to test this.") +def test_sortability_requirements(validation_settings: ValidationSettings): + validation_settings.data_path = InvalidRootDataEntity().invalid_root_type + result: models.ValidationResult = services.validate(validation_settings) + failed_requirements = sorted(result.failed_requirements, reverse=True) + assert len(failed_requirements) > 1 + assert failed_requirements[0] >= failed_requirements[1] + assert failed_requirements[0].order_number >= failed_requirements[1].order_number + + +def test_sortability_checks(validation_settings: ValidationSettings): + validation_settings.data_path = WROCInvalidReadme().wroc_readme_wrong_encoding_format + result: models.ValidationResult = services.validate(validation_settings) + failed_checks = sorted(result.failed_checks, reverse=True) + assert len(failed_checks) > 1 + i_checks = iter(failed_checks) + one, two = next(i_checks), next(i_checks) + assert one >= two + assert one.requirement >= two.requirement + + +def test_sortability_issues(validation_settings: ValidationSettings): + validation_settings.data_path = WROCInvalidReadme().wroc_readme_wrong_encoding_format + result: models.ValidationResult = services.validate(validation_settings) + issues = sorted(result.get_issues(min_severity=Severity.OPTIONAL), reverse=True) + assert len(issues) > 1 + i_issues = iter(issues) + one, two = next(i_issues), next(i_issues) + assert one >= two + assert one.check >= two.check + + +def test_hidden_shape(): + rocrate_profile = services.get_profile(profile_identifier="ro-crate-1.1") + assert rocrate_profile is not None, "Profile should not be None" + # get the hidden requirement + hidden_requirement = rocrate_profile.get_requirement("Identify the Root Data Entity of the RO-Crate") + assert hidden_requirement is not None, "Requirement should not be None" + # check if the requirement is hidden + assert hidden_requirement.hidden is True, "Hidden should be True" diff --git a/tests/unit/requirements/test_load_requirements.py b/tests/unit/requirements/test_load_requirements.py new file mode 100644 index 00000000..770c320d --- /dev/null +++ b/tests/unit/requirements/test_load_requirements.py @@ -0,0 +1,137 @@ +# Copyright (c) 2024 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 +import os + +from rocrate_validator.constants import DEFAULT_PROFILE_IDENTIFIER +from rocrate_validator.models import LevelCollection, Profile, Severity +from tests.ro_crates import InvalidFileDescriptorEntity + +# set up logging +logger = logging.getLogger(__name__) + +# Β Global set up the paths +paths = InvalidFileDescriptorEntity() + + +def test_requirements_loading(profiles_requirement_loading: str): + + # The order of the requirement levels + levels = (LevelCollection.REQUIRED, LevelCollection.REQUIRED, LevelCollection.RECOMMENDED, LevelCollection.OPTIONAL) + + # Define the list of requirements names + requirements_names = ["A", "B", "A_MUST", "B_MUST"] + + # Define the number of checks for each requirement + number_of_checks_per_requirement = 4 + + # Define the settings + settings = { + "profiles_path": profiles_requirement_loading, + "severity": Severity.OPTIONAL + } + + # Load the profiles + profiles = Profile.load_profiles(**settings) + assert len(profiles) == 1 + + # Get the first profile + profile = profiles[0] + assert profile.identifier == "x", "The profile identifier is incorrect" + + # The first profile should have the following requirements + requirements = profile.get_requirements(severity=Severity.OPTIONAL) + assert len(requirements) == len(requirements_names), "The number of requirements is incorrect" + + # Sort requirements by their order + sorted_requirements = sorted( + requirements, key=lambda x: (-x.severity_from_path.value, x.path.name, x.name) + if x.severity_from_path else (0, x.path.name, x.name)) + + # Check the order of the requirements + for i, requirement in enumerate(sorted_requirements): + if i < len(sorted_requirements) - 1: + assert requirement < requirements[i + 1] + + # Check the requirements and their checks + for requirement_name in requirements_names: + logger.debug("The requirement: %r", requirement_name) + requirement = profile.get_requirement(requirement_name) + assert requirement.name == requirement_name, "The name of the requirement is incorrect" + if requirement_name in ["A", "B"]: + assert requirement.severity_from_path is None, "The severity of the requirement should be None" + elif requirement_name in ["A_MUST", "B_MUST"]: + assert requirement.severity_from_path == Severity.REQUIRED, \ + "The severity of the requirement should be REQUIRED" + + assert len(requirement.get_checks()) == number_of_checks_per_requirement, \ + "The number of requirement checks is incorrect" + + for i in range(number_of_checks_per_requirement): + logger.debug("The requirement check: %r", f"{requirement_name}_{i}") + check = requirement.get_checks()[i] + assert check.name == f"{requirement_name}_{i}", "The name of the requirement check is incorrect" + assert check.level.severity == levels[i].severity, "The level of the requirement check is incorrect" + + +def test_order_of_loaded_profile_requirements(profiles_path: str): + """Test the order of the loaded profiles.""" + logger.debug("The profiles path: %r", profiles_path) + assert os.path.exists(profiles_path) + profiles = Profile.load_profiles(profiles_path=profiles_path, severity=Severity.RECOMMENDED) + # The number of profiles should be greater than 0 + assert len(profiles) > 0 + + # The first profile should be the default profile + assert profiles[0].identifier == DEFAULT_PROFILE_IDENTIFIER + + # Get the first profile + profile = profiles[0] + + # The first profile should have the following requirements + requirements = profile.get_requirements() + assert len(requirements) > 0 + for requirement in requirements: + logger.debug("%r The requirement: %r -> severity: %r (path: %s)", requirement.order_number, + requirement.name, requirement.severity_from_path, requirement.path) + + # Sort requirements by their order + requirements = sorted(requirements, key=lambda x: (-x.severity_from_path.value, x.path.name, x.name) + if x.severity_from_path else (0, x.path.name, x.name)) + + # Check the order of the requirements + for i, requirement in enumerate(requirements): + if i < len(requirements) - 1: + assert requirement < requirements[i + 1] + + # Check severity of some RequirementChecks + for r in profile.get_requirements(severity=Severity.OPTIONAL): + logger.debug("The requirement: %r -> severity: %r", r.name, r.severity_from_path) + + r = profile.get_requirement("RO-Crate Root Data Entity RECOMMENDED value") + assert r.severity_from_path == Severity.RECOMMENDED, "The severity of the requirement should be RECOMMENDED" + + # Check the number of requirement checks + r_checks = r.get_checks() + assert len(r_checks) == 1, "The number of requirement checks should be 1" + + # Inspect the first requirement check + requirement_check = r_checks[0] + assert requirement_check.name == "Root Data Entity: RECOMMENDED value", \ + "The name of the requirement check is incorrect" + assert requirement_check.description == \ + "Check if the Root Data Entity is denoted by the string `./` in the file descriptor JSON-LD", \ + "The description of the requirement check is incorrect" + assert requirement_check.severity == Severity.RECOMMENDED, "The severity of the requirement check is incorrect" diff --git a/tests/unit/requirements/test_profiles.py b/tests/unit/requirements/test_profiles.py new file mode 100644 index 00000000..f80c39aa --- /dev/null +++ b/tests/unit/requirements/test_profiles.py @@ -0,0 +1,297 @@ +# Copyright (c) 2024 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 +import os + +import pytest + +from rocrate_validator.constants import DEFAULT_PROFILE_IDENTIFIER +from rocrate_validator.errors import (DuplicateRequirementCheck, + InvalidProfilePath, + ProfileSpecificationError) +from rocrate_validator.models import (Profile, ValidationContext, + ValidationSettings, Validator) +from tests.ro_crates import InvalidFileDescriptorEntity, ValidROC + +# set up logging +logger = logging.getLogger(__name__) + +# Β Global set up the paths +paths = InvalidFileDescriptorEntity() + + +def test_order_of_loaded_profiles(profiles_path: str): + """Test the order of the loaded profiles.""" + logger.debug("The profiles path: %r", profiles_path) + assert os.path.exists(profiles_path) + profiles = Profile.load_profiles(profiles_path=profiles_path) + # The number of profiles should be greater than 0 + assert len(profiles) > 0 + + # Extract the profile names + profile_names = sorted([profile.token for profile in profiles]) + logger.debug("The profile names: %r", profile_names) + + # The order of the profiles should be the same as the order of the directories + # in the profiles directory + profile_directories = sorted(os.listdir(profiles_path)) + logger.debug("The profile directories: %r", profile_directories) + assert profile_names == profile_directories + + +def test_load_invalid_profile_from_validation_context(fake_profiles_path: str): + """Test the loaded profiles from the validator context.""" + settings = { + "profiles_path": "/tmp/random_path_xxx", + "profile_identifier": DEFAULT_PROFILE_IDENTIFIER, + "data_path": ValidROC().wrroc_paper, + "inherit_profiles": False + } + + settings = ValidationSettings(**settings) + assert not settings.inherit_profiles, "The inheritance mode should be set to False" + + validator = Validator(settings) + # initialize the validation context + context = ValidationContext(validator, validator.validation_settings.to_dict()) + + # Check if the InvalidProfilePath exception is raised + with pytest.raises(InvalidProfilePath): + profiles = context.profiles + logger.debug("The profiles: %r", profiles) + + +def test_load_valid_profile_without_inheritance_from_validation_context(fake_profiles_path: str): + """Test the loaded profiles from the validator context.""" + settings = { + "profiles_path": fake_profiles_path, + "profile_identifier": "c", + "data_path": ValidROC().wrroc_paper, + "inherit_profiles": False + } + + settings = ValidationSettings(**settings) + assert not settings.inherit_profiles, "The inheritance mode should be set to False" + + validator = Validator(settings) + # initialize the validation context + context = ValidationContext(validator, validator.validation_settings.to_dict()) + + # Load the profiles + profiles = context.profiles + logger.debug("The profiles: %r", profiles) + + # The number of profiles should be 1 + assert len(profiles) == 1, "The number of profiles should be 1" + + +def test_profile_spec_properties(fake_profiles_path: str): + """Test the loaded profiles from the validator context.""" + settings = { + "profiles_path": fake_profiles_path, + "profile_identifier": "c", + "data_path": ValidROC().wrroc_paper, + "inherit_profiles": True, + "disable_check_for_duplicates": True, + } + + settings = ValidationSettings(**settings) + assert settings.inherit_profiles, "The inheritance mode should be set to True" + + validator = Validator(settings) + # initialize the validation context + context = ValidationContext(validator, validator.validation_settings.to_dict()) + + # Load the profiles + profiles = context.profiles + logger.debug("The profiles: %r", profiles) + + # The number of profiles should be 1 + assert len(profiles) == 2, "The number of profiles should be 2" + + # Get the profile + profile = context.get_profile_by_token("c")[0] + assert profile.token == "c", "The profile name should be c" + assert profile.comment == "Comment for the Profile C.", "The profile comment should be 'Comment for the Profile C.'" + assert profile.version == "1.0.0", "The profile version should be 1.0.0" + assert profile.is_profile_of == ["https://w3id.org/a"], "The profileOf property should be ['a']" + assert profile.is_transitive_profile_of == [ + "https://w3id.org/a"], "The transitiveProfileOf property should be ['a']" + + +def test_profiles_loading_free_folder_structure(profiles_with_free_folder_structure_path): + """Test the loaded profiles from the validator context.""" + profiles = Profile.load_profiles(profiles_path=profiles_with_free_folder_structure_path) + logger.debug("The profiles: %r", profiles) + for p in profiles: + logger.warning("The profile '%s' has %d requirements", p, len(p.requirements)) + + # The number of profiles should be 3 + assert len(profiles) == 3, "The number of profiles should be 3" + + # The profile names should be a, b, and c + assert profiles[0].token == "a", "The profile name should be 'a'" + assert profiles[1].token == "b", "The profile name should be 'b'" + assert profiles[2].token == "c", "The profile name should be 'c'" + + +def test_versioned_profiles_loading(fake_versioned_profiles_path): + """Test the loaded profiles from the validator context.""" + profiles = Profile.load_profiles(profiles_path=fake_versioned_profiles_path) + logger.debug("The profiles: %r", profiles) + for p in profiles: + logger.warning("The profile '%s' has %d requirements", p, len(p.requirements)) + # The number of profiles should be 3 + assert len(profiles) == 3, "The number of profiles should be 3" + + # The profile a should have an explicit version 1.0.0 + assert profiles[0].token == "a", "The profile name should be 'a'" + assert profiles[0].version == "1.0.0", "The profile version should be 1.0.0" + + # The profile b should have a version inferred by the 2.0 + assert profiles[1].token == "b", "The profile name should be 'b'" + assert profiles[1].version == "2.0", "The profile version should be 2.0" + + # The profile c should have a version inferred by the 3.2.1 + assert profiles[2].token == "c", "The profile name should be 'c'" + assert profiles[2].version == "3.2.1", "The profile version should be 3.2.1" + + +def test_conflicting_versioned_profiles_loading(fake_conflicting_versioned_profiles_path): + """Test the loaded profiles from the validator context.""" + with pytest.raises(ProfileSpecificationError) as excinfo: + logger.debug("result: %r", excinfo) + # Load the profiles + Profile.load_profiles(profiles_path=fake_conflicting_versioned_profiles_path) + # Check that the conflicting versions are found + assert "Inconsistent versions found: {'3.2.2', '3.2.1', '2.3'}" + + +def test_loaded_valid_profile_with_inheritance_from_validator_context(fake_profiles_path: str): + """Test the loaded profiles from the validator context.""" + + def __perform_test__(profile_identifier: str, expected_inherited_profiles: list[str]): + settings = { + "profiles_path": fake_profiles_path, + "profile_identifier": profile_identifier, + "data_path": ValidROC().wrroc_paper, + "disable_check_for_duplicates": True, + } + + validator = Validator(settings) + # initialize the validation context + context = ValidationContext(validator, validator.validation_settings.to_dict()) + + # Check if the inheritance mode is set to True + assert context.inheritance_enabled + + profiles = context.profiles + logger.debug("The profiles: %r", profiles) + + # get and check the profile + profile = context.get_profile_by_token(profile_identifier)[0] + assert profile.token == profile_identifier, f"The profile name should be {profile_identifier}" + + # The number of profiles should be 1 + profiles_names = [_.token for _ in profile.inherited_profiles] + assert profiles_names == expected_inherited_profiles, \ + f"The number of profiles should be {expected_inherited_profiles}" + + # Test the inheritance mode with 1 profile + __perform_test__("a", []) + # Test the inheritance mode with 2 profiles + __perform_test__("b", ["a"]) + # Test the inheritance mode with 2 profiles + __perform_test__("c", ["a"]) + # Test the inheritance mode with 4 profiles: using the profileOf property + __perform_test__("d1", ["a", "b", "c"]) + # Test the inheritance mode with 4 profiles: using the transitiveProfileOf property + __perform_test__("d2", ["a", "b", "c"]) + + +def test_load_invalid_profile_no_override_enabled(fake_profiles_path: str): + """Test the loaded profiles from the validator context.""" + settings = { + "profiles_path": fake_profiles_path, + "profile_identifier": "invalid-duplicated-shapes", + "data_path": ValidROC().wrroc_paper, + "inherit_profiles": True, + "allow_requirement_check_override": False, + } + + settings = ValidationSettings(**settings) + assert settings.inherit_profiles, "The inheritance mode should be set to True" + assert not settings.allow_requirement_check_override, "The override mode should be set to False" + + validator = Validator(settings) + # initialize the validation context + context = ValidationContext(validator, validator.validation_settings.to_dict()) + + with pytest.raises(DuplicateRequirementCheck): + # Load the profiles + profiles = context.profiles + logger.debug("The profiles: %r", profiles) + + +def test_load_invalid_profile_with_override_on_same_profile(fake_profiles_path: str): + """Test the loaded profiles from the validator context.""" + settings = { + "profiles_path": fake_profiles_path, + "profile_identifier": "invalid-duplicated-shapes", + "data_path": ValidROC().wrroc_paper, + "inherit_profiles": True, + "allow_requirement_check_override": False + } + + settings = ValidationSettings(**settings) + assert settings.inherit_profiles, "The inheritance mode should be set to True" + assert not settings.allow_requirement_check_override, "The override mode should be set to `True`" + validator = Validator(settings) + # initialize the validation context + context = ValidationContext(validator, validator.validation_settings.to_dict()) + + with pytest.raises(DuplicateRequirementCheck): + # Load the profiles + profiles = context.profiles + logger.debug("The profiles: %r", profiles) + + +def test_load_valid_profile_with_override_on_inherited_profile(fake_profiles_path: str): + """Test the loaded profiles from the validator context.""" + settings = { + "profiles_path": fake_profiles_path, + "profile_identifier": "c-overridden", + "data_path": ValidROC().wrroc_paper, + "inherit_profiles": True, + "allow_requirement_check_override": True + } + + settings = ValidationSettings(**settings) + assert settings.inherit_profiles, "The inheritance mode should be set to True" + assert settings.allow_requirement_check_override, "The override mode should be set to `True`" + validator = Validator(settings) + # initialize the validation context + context = ValidationContext(validator, validator.validation_settings.to_dict()) + + # Load the profiles + profiles = context.profiles + logger.debug("The profiles: %r", profiles) + + # The number of profiles should be 2 + assert len(profiles) == 3, "The number of profiles should be 3" + + # the number of checks should be 2 + requirements_checks = [requirement for profile in profiles for requirement in profile.requirements] + assert len(requirements_checks) == 3, "The number of requirements should be 2" diff --git a/tests/unit/test_cli_internals.py b/tests/unit/test_cli_internals.py new file mode 100644 index 00000000..7685ea33 --- /dev/null +++ b/tests/unit/test_cli_internals.py @@ -0,0 +1,67 @@ +# Copyright (c) 2024 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 os + + +from rocrate_validator import log as logging +from rocrate_validator.cli.commands.validate import __compute_profile_stats__ +from rocrate_validator.models import DEFAULT_PROFILES_PATH, Profile + +# set up logging +logger = logging.getLogger(__name__) + + +def test_compute_stats(): + + settings = { + "fail_fast": False, + "profiles_path": DEFAULT_PROFILES_PATH, + "profile_identifier": "ro-crate", + "inherit_profiles": True, + "allow_requirement_check_override": True, + "requirement_severity": "REQUIRED", + } + + profiles_path = DEFAULT_PROFILES_PATH + logger.debug("The profiles path: %r", DEFAULT_PROFILES_PATH) + assert os.path.exists(profiles_path) + profiles = Profile.load_profiles(profiles_path) + # The number of profiles should be greater than 0 + assert len(profiles) > 0 + + # Get the profile ro-crate + profile = profiles[0] + logger.debug("The profile: %r", profile) + assert profile is not None + assert profile.identifier == "ro-crate-1.1" + + # extract the list of not hidden requirements + logger.error("The number of requirements: %r", len(profile.get_requirements())) + requirements = [r for r in profile.get_requirements() if not r.hidden] + logger.debug("The requirements: %r", requirements) + assert len(requirements) > 0 + + stats = __compute_profile_stats__(settings) + + # Check severity + assert stats["severity"].name == "REQUIRED" + + # Check the number of profiles + assert len(stats["profiles"]) == 1 + + # check the number of requirements in stats and the number of requirements in the profile + assert stats["total_requirements"] == len(requirements) + + logger.error(stats) diff --git a/tests/unit/test_rocrate.py b/tests/unit/test_rocrate.py new file mode 100644 index 00000000..7350b253 --- /dev/null +++ b/tests/unit/test_rocrate.py @@ -0,0 +1,239 @@ +# Copyright (c) 2024 CRS4 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from pathlib import Path +import pytest + +from rocrate_validator import log as logging +from rocrate_validator.errors import ROCrateInvalidURIError +from rocrate_validator.rocrate import ( + ROCrate, + ROCrateEntity, + ROCrateLocalFolder, + ROCrateLocalZip, + ROCrateMetadata, + ROCrateRemoteZip, +) +from tests.ro_crates import ValidROC + +# set up logging +logger = logging.getLogger(__name__) + + +metadata_file_descriptor = Path(ROCrateMetadata.METADATA_FILE_DESCRIPTOR) + + +################################ +# ROCrateLocalFolder +################################ + + +def test_invalid_local_ro_crate(): + with pytest.raises(ROCrateInvalidURIError): + ROCrateLocalFolder("/tmp/does_not_exist") + + +def test_valid_local_rocrate(): + roc = ROCrateLocalFolder(ValidROC().wrroc_paper) + assert isinstance(roc, ROCrateLocalFolder) + + # raise Exception("Test not implemented: %s", str(roc.uri)) + + # test list files + files = roc.list_files() + logger.debug(f"Files: {files}") + assert len(files) == 14, "Should have 14 files" + + # test is_file + assert roc.has_file(metadata_file_descriptor), "Should be a file" + + # test file size + size = roc.get_file_size(metadata_file_descriptor) + assert size == 26788, "Size should be 26788" + + # test crate size + assert roc.size == 309521, "Size should be 309521" + + # test get_file_content binary mode + content = roc.get_file_content(metadata_file_descriptor) + assert isinstance(content, bytes), "Content should be bytes" + + # test get_file_content text mode + content = roc.get_file_content(metadata_file_descriptor, binary_mode=False) + assert isinstance(content, str), "Content should be str" + + # test metadata + metadata = roc.metadata + assert isinstance(metadata, ROCrateMetadata), "Metadata should be ROCrateMetadata" + + # test metadata id + file_descriptor_entity = metadata.get_entity("ro-crate-metadata.json") + logger.debug(f"File descriptor entity: {file_descriptor_entity}") + assert isinstance(file_descriptor_entity, ROCrateEntity), "Entity should be ROCrateEntity" + assert file_descriptor_entity.id == "ro-crate-metadata.json", "Id should be ro-crate-metadata.json" + assert file_descriptor_entity.type == "CreativeWork", "Type should be File" + + # test root data entity + root_data_entity = metadata.get_entity("./") + logger.debug(f"Root data entity: {root_data_entity}") + assert isinstance(root_data_entity, ROCrateEntity), "Entity should be ROCrateEntity" + assert root_data_entity.id == "./", "Id should be ./" + assert root_data_entity.type == "Dataset", "Type should be Dataset" + assert root_data_entity.name == "Recording provenance of workflow runs with RO-Crate (RO-Crate and mapping)", \ + "Name should be wrroc-paper" + + # check metadata consistency + assert root_data_entity.metadata == metadata, "Metadata should be the same" + assert root_data_entity.metadata == roc.metadata, "Metadata should be the same" + + # check availability + assert root_data_entity.is_available(), "Main entity should be available" + + +################################ +# ROCrateLocalZip +################################ +def test_valid_zip_rocrate(): + roc = ROCrateLocalZip(ValidROC().sort_and_change_archive) + assert isinstance(roc, ROCrateLocalZip) + + # test list files + files = roc.list_files() + logger.debug(f"Files: {files}") + assert len(files) == 9, "Should have 5 files" + + # test is_file + assert roc.has_file(metadata_file_descriptor), "Should be a file" + + # test file size + size = roc.get_file_size(metadata_file_descriptor) + assert size == 3882, "Size should be 26788" + + # test crate size + assert roc.size == 136267, "Size should be 136267" + + # test get_file_content binary mode + content = roc.get_file_content(metadata_file_descriptor) + assert isinstance(content, bytes), "Content should be bytes" + + # test get_file_content text mode + content = roc.get_file_content(metadata_file_descriptor, binary_mode=False) + assert isinstance(content, str), "Content should be str" + + # test metadata + metadata = roc.metadata + assert isinstance(metadata, ROCrateMetadata), "Metadata should be ROCrateMetadata" + + # test metadata id + file_descriptor_entity = metadata.get_entity("ro-crate-metadata.json") + logger.debug(f"File descriptor entity: {file_descriptor_entity}") + assert isinstance(file_descriptor_entity, ROCrateEntity), "Entity should be ROCrateEntity" + assert file_descriptor_entity.id == "ro-crate-metadata.json", "Id should be ro-crate-metadata.json" + assert file_descriptor_entity.type == "CreativeWork", "Type should be File" + + # test root data entity + root_data_entity = metadata.get_entity("./") + logger.debug(f"Root data entity: {root_data_entity}") + assert isinstance(root_data_entity, ROCrateEntity), "Entity should be ROCrateEntity" + assert root_data_entity.id == "./", "Id should be ./" + assert root_data_entity.type == "Dataset", "Type should be Dataset" + assert root_data_entity.name == "sort-and-change-case", "Name should be sort_and_change" + + # test subEntity mainEntity + main_entity = root_data_entity.get_property("mainEntity") + logger.debug(f"Main entity: {main_entity}") + assert isinstance(main_entity, ROCrateEntity), "Entity should be ROCrateEntity" + assert main_entity.id == "sort-and-change-case.ga", "Id should be main-entity" + assert "ComputationalWorkflow" in main_entity.type, "Type should be ComputationalWorkflow" + + # check metadata consistency + assert main_entity.metadata == metadata, "Metadata should be the same" + assert main_entity.metadata == roc.metadata, "Metadata should be the same" + + # check availability + assert main_entity.is_available(), "Main entity should be available" + + +################################ +# ROCrateRemoteZip +################################ + + +def test_valid_remote_zip_rocrate(): + roc = ROCrateRemoteZip(ValidROC().sort_and_change_remote) + # assert isinstance(roc, ROCrateRemoteZip) + # return + # # test list files + files = roc.list_files() + logger.debug(f"Files: {files}") + assert len(files) == 9, "Should have 5 files" + + # test crate size + assert roc.size == 136267, "Size should be 136267" + + # test is_file + assert roc.has_file(metadata_file_descriptor), "Should be a file" + + # test file size + size = roc.get_file_size(metadata_file_descriptor) + assert size == 3882, "Size should be 1097" + + # test get_file_content binary mode + content = roc.get_file_content(metadata_file_descriptor) + assert isinstance(content, bytes), "Content should be bytes" + + # test get_file_content text mode + content = roc.get_file_content(metadata_file_descriptor, binary_mode=False) + assert isinstance(content, str), "Content should be str" + + # test metadata + metadata = roc.metadata + assert isinstance(metadata, ROCrateMetadata), "Metadata should be ROCrateMetadata" + + # test metadata id + file_descriptor_entity = metadata.get_entity("ro-crate-metadata.json") + logger.debug(f"File descriptor entity: {file_descriptor_entity}") + assert isinstance(file_descriptor_entity, ROCrateEntity), "Entity should be ROCrateEntity" + assert file_descriptor_entity.id == "ro-crate-metadata.json", "Id should be ro-crate-metadata.json" + assert file_descriptor_entity.type == "CreativeWork", "Type should be File" + + # test root data entity + root_data_entity = metadata.get_entity("./") + logger.debug(f"Root data entity: {root_data_entity}") + assert isinstance(root_data_entity, ROCrateEntity), "Entity should be ROCrateEntity" + assert root_data_entity.id == "./", "Id should be ./" + assert root_data_entity.type == "Dataset", "Type should be Dataset" + assert root_data_entity.name == "sort-and-change-case", "Name should be sort_and_change" + + # test subEntity mainEntity + main_entity = root_data_entity.get_property("mainEntity") + logger.debug(f"Main entity: {main_entity}") + assert isinstance(main_entity, ROCrateEntity), "Entity should be ROCrateEntity" + assert main_entity.id == "sort-and-change-case.ga", "Id should be main-entity" + assert "ComputationalWorkflow" in main_entity.type, "Type should be ComputationalWorkflow" + + # check metadata consistency + assert main_entity.metadata == metadata, "Metadata should be the same" + assert main_entity.metadata == roc.metadata, "Metadata should be the same" + + # check availability + assert main_entity.is_available(), "Main entity should be available" + + +def test_external_file(): + content = ROCrate.get_external_file_content(ValidROC().sort_and_change_remote) + assert isinstance(content, bytes), "Content should be bytes" + + size = ROCrate.get_external_file_size(ValidROC().sort_and_change_remote) + assert size == 136267, "Size should be 136267" diff --git a/tests/unit/test_uri.py b/tests/unit/test_uri.py new file mode 100644 index 00000000..adbe6daa --- /dev/null +++ b/tests/unit/test_uri.py @@ -0,0 +1,86 @@ +# Copyright (c) 2024 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 unittest + +import pytest + +from rocrate_validator.utils import URI + + +def test_valid_url(): + uri = URI("http://example.com") + assert uri.is_remote_resource() + + +def test_invalid_url(): + with pytest.raises(ValueError): + URI("httpx:///example.com") + + +def test_url_with_query_params(): + uri = URI("http://example.com?param1=value1¶m2=value2") + assert uri.get_query_param("param1") == "value1" + assert uri.get_query_param("param2") == "value2" + + +def test_url_without_query_params(): + uri = URI("http://example.com") + assert uri.get_query_param("param1") is None + + +def test_url_with_fragment(): + uri = URI("http://example.com#fragment") + assert uri.fragment == "fragment" + + +def test_url_without_fragment(): + uri = URI("http://example.com") + assert uri.fragment is None + + +def test_valid_path(): + uri = URI("README.md") + assert uri.is_local_resource() + assert uri.is_available() + + +def test_invalid_path(): + uri = URI("path/to/file.txt") + assert not uri.is_available() + + +def test_path_with_query_params(): + uri = URI("/path/to/file.txt?param1=value1¶m2=value2") + assert uri.get_query_param("param1") == "value1" + assert uri.get_query_param("param2") == "value2" + + +def test_path_without_query_params(): + uri = URI("/path/to/file.txt") + assert uri.get_query_param("param1") is None + + +def test_path_with_fragment(): + uri = URI("/path/to/file.txt#fragment") + assert uri.fragment == "fragment" + + +def test_path_without_fragment(): + uri = URI("/path/to/file.txt") + assert uri.fragment is None + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/test_validation_settings.py b/tests/unit/test_validation_settings.py new file mode 100644 index 00000000..1160a4e0 --- /dev/null +++ b/tests/unit/test_validation_settings.py @@ -0,0 +1,103 @@ +# Copyright (c) 2024 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 pytest + +from rocrate_validator.models import Severity, ValidationSettings + + +def test_validation_settings_parse_dict(): + settings_dict = { + "data_path": "/path/to/data", + "profiles_path": "/path/to/profiles", + "requirement_severity": "RECOMMENDED", + "allow_infos": True, + "inherit_profiles": False, + } + settings = ValidationSettings.parse(settings_dict) + assert settings.data_path == "/path/to/data" + assert settings.profiles_path == "/path/to/profiles" + assert settings.requirement_severity == Severity.RECOMMENDED + assert settings.allow_infos is True + assert settings.inherit_profiles is False + + +def test_validation_settings_parse_object(): + existing_settings = ValidationSettings( + data_path="/path/to/data", + profiles_path="/path/to/profiles", + requirement_severity=Severity.RECOMMENDED, + allow_infos=True, + inherit_profiles=False + ) + settings = ValidationSettings.parse(existing_settings) + assert settings.data_path == "/path/to/data" + assert settings.profiles_path == "/path/to/profiles" + assert settings.requirement_severity == Severity.RECOMMENDED + assert settings.allow_infos is True + assert settings.inherit_profiles is False + + +def test_validation_settings_parse_invalid_type(): + with pytest.raises(ValueError): + ValidationSettings.parse("invalid_settings") + + +def test_validation_settings_to_dict(): + settings = ValidationSettings( + data_path="/path/to/data", + profiles_path="/path/to/profiles", + requirement_severity=Severity.RECOMMENDED, + allow_infos=True, + inherit_profiles=False + ) + settings_dict = settings.to_dict() + assert settings_dict["data_path"] == "/path/to/data" + assert settings_dict["profiles_path"] == "/path/to/profiles" + assert settings_dict["requirement_severity"] == Severity.RECOMMENDED + assert settings_dict["allow_infos"] is True + assert settings_dict["inherit_profiles"] is False + + +def test_validation_settings_inherit_profiles(): + settings = ValidationSettings(inherit_profiles=True) + assert settings.inherit_profiles is True + + settings = ValidationSettings(inherit_profiles=False) + assert settings.inherit_profiles is False + + +def test_validation_settings_data_path(): + settings = ValidationSettings(data_path="/path/to/data") + assert settings.data_path == "/path/to/data" + + +def test_validation_settings_profiles_path(): + settings = ValidationSettings(profiles_path="/path/to/profiles") + assert settings.profiles_path == "/path/to/profiles" + + +def test_validation_settings_requirement_severity(): + settings = ValidationSettings(requirement_severity=Severity.RECOMMENDED) + assert settings.requirement_severity == Severity.RECOMMENDED + + +def test_validation_settings_allow_infos(): + settings = ValidationSettings(allow_infos=True) + assert settings.allow_infos is True + + +def test_validation_settings_abort_on_first(): + settings = ValidationSettings(abort_on_first=True) + assert settings.abort_on_first is True