From 9b44b53f4b2948f898b22b3738f7933e2b47dc1e Mon Sep 17 00:00:00 2001 From: Michael Fritzsche Date: Mon, 19 Jan 2026 09:50:50 +0100 Subject: [PATCH] added tests and support for curate step --- src/hermes/commands/__init__.py | 2 +- src/hermes/commands/cli.py | 4 +- src/hermes/commands/curate/base.py | 38 +++++---- test/hermes_test/model/test_api_e2e.py | 111 +++++++++++++++++++++++-- 4 files changed, 131 insertions(+), 24 deletions(-) diff --git a/src/hermes/commands/__init__.py b/src/hermes/commands/__init__.py index 14f77741..d2116ef2 100644 --- a/src/hermes/commands/__init__.py +++ b/src/hermes/commands/__init__.py @@ -12,7 +12,7 @@ # from hermes.commands.base import HermesVersionCommand # from hermes.commands.clean.base import HermesCleanCommand # from hermes.commands.init.base import HermesInitCommand -# from hermes.commands.curate.base import HermesCurateCommand +from hermes.commands.curate.base import HermesCurateCommand from hermes.commands.harvest.base import HermesHarvestCommand # from hermes.commands.process.base import HermesProcessCommand # from hermes.commands.deposit.base import HermesDepositCommand diff --git a/src/hermes/commands/cli.py b/src/hermes/commands/cli.py index db109a5e..565381fc 100644 --- a/src/hermes/commands/cli.py +++ b/src/hermes/commands/cli.py @@ -16,7 +16,7 @@ # from hermes.commands import (HermesHelpCommand, HermesVersionCommand, HermesCleanCommand, # HermesHarvestCommand, HermesProcessCommand, HermesCurateCommand, # HermesDepositCommand, HermesPostprocessCommand, HermesInitCommand) -from hermes.commands import HermesHarvestCommand +from hermes.commands import HermesCurateCommand, HermesHarvestCommand from hermes.commands.base import HermesCommand @@ -44,7 +44,7 @@ def main() -> None: # HermesCleanCommand(parser), HermesHarvestCommand(parser), # HermesProcessCommand(parser), - # HermesCurateCommand(parser), + HermesCurateCommand(parser), # HermesDepositCommand(parser), # HermesPostprocessCommand(parser), ): diff --git a/src/hermes/commands/curate/base.py b/src/hermes/commands/curate/base.py index 4c990bc7..15d7c8db 100644 --- a/src/hermes/commands/curate/base.py +++ b/src/hermes/commands/curate/base.py @@ -5,17 +5,16 @@ # SPDX-FileContributor: Michael Meinel import argparse -import os -import shutil -import sys from pydantic import BaseModel from hermes.commands.base import HermesCommand -from hermes.model.context import CodeMetaContext +from hermes.model import SoftwareMetadata +from hermes.model.context_manager import HermesContext +from hermes.model.error import HermesValidationError -class _CurateSettings(BaseModel): +class CurateSettings(BaseModel): """Generic deposition settings.""" pass @@ -25,23 +24,30 @@ class HermesCurateCommand(HermesCommand): """ Curate the unified metadata before deposition. """ command_name = "curate" - settings_class = _CurateSettings + settings_class = CurateSettings def init_command_parser(self, command_parser: argparse.ArgumentParser) -> None: pass def __call__(self, args: argparse.Namespace) -> None: - self.log.info("# Metadata curation") - ctx = CodeMetaContext() - process_output = ctx.hermes_dir / 'process' / (ctx.hermes_name + ".json") + ctx = HermesContext() + ctx.prepare_step("curate") + + ctx.prepare_step("process") + with ctx["result"] as process_ctx: + expanded_data = process_ctx["expanded"] + context_data = process_ctx["context"] + ctx.finalize_step("process") + + try: + data = SoftwareMetadata(expanded_data[0], context_data["@context"][1]) + except Exception as e: + raise HermesValidationError("The results of the process step are invalid.") from e - if not process_output.is_file(): - self.log.error( - "No processed metadata found. Please run `hermes process` before curation." - ) - sys.exit(1) + with ctx["result"] as curate_ctx: + curate_ctx["expanded"] = data.ld_value + curate_ctx["context"] = {"@context": data.full_context} - os.makedirs(ctx.hermes_dir / 'curate', exist_ok=True) - shutil.copy(process_output, ctx.hermes_dir / 'curate' / (ctx.hermes_name + '.json')) + ctx.finalize_step("curate") diff --git a/test/hermes_test/model/test_api_e2e.py b/test/hermes_test/model/test_api_e2e.py index f4ec7fd6..3e43073d 100644 --- a/test/hermes_test/model/test_api_e2e.py +++ b/test/hermes_test/model/test_api_e2e.py @@ -6,7 +6,8 @@ import pytest import sys -from hermes.model import context_manager, SoftwareMetadata +from hermes.model import SoftwareMetadata +from hermes.model.context_manager import HermesContext from hermes.commands import cli @@ -181,10 +182,10 @@ def test_cff_harvest(tmp_path, monkeypatch, cff, res): sys.argv = ["hermes", "harvest", "--path", str(tmp_path), "--config", str(config_file)] result = {} try: - monkeypatch.setattr(context_manager.HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) + monkeypatch.setattr(HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) cli.main() except SystemExit: - manager = context_manager.HermesContext() + manager = HermesContext() manager.prepare_step("harvest") with manager["cff"] as cache: result = SoftwareMetadata(cache["codemeta"]) @@ -341,10 +342,10 @@ def test_codemeta_harvest(tmp_path, monkeypatch, codemeta, res): sys.argv = ["hermes", "harvest", "--path", str(tmp_path), "--config", str(config_file)] result = {} try: - monkeypatch.setattr(context_manager.HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) + monkeypatch.setattr(HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) cli.main() except SystemExit: - manager = context_manager.HermesContext() + manager = HermesContext() manager.prepare_step("harvest") with manager["codemeta"] as cache: result = SoftwareMetadata(cache["codemeta"]) @@ -353,3 +354,103 @@ def test_codemeta_harvest(tmp_path, monkeypatch, codemeta, res): sys.argv = orig_argv assert result.data_dict == res.data_dict + + +@pytest.mark.parametrize( + "process_result, res", + [ + 2 * ( + SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/description": [{"@value": "for testing"}], + "http://schema.org/name": [{"@value": "Test"}] + }), + ), + 2 * ( + SoftwareMetadata({ + "@type": ["http://schema.org/SoftwareSourceCode"], + "http://schema.org/applicationCategory": [{"@id": "Testing"}], + "http://schema.org/author": [ + { + "@list": [ + { + "@id": "_:author_1", + "@type": ["http://schema.org/Person"], + "http://schema.org/email": [{"@value": "test.testi@test.testi"}], + "http://schema.org/familyName": [{"@value": "Testi"}], + "http://schema.org/givenName": [{"@value": "Test"}] + } + ] + } + ], + "http://schema.org/codeRepository": [{"@id": "https://github.com/softwarepub/hermes"}], + "http://schema.org/contributor": [ + { + "@id": "_:contributor_1", + "@type": ["http://schema.org/Person"], + "http://schema.org/email": [{"@value": "test.testi@test.testi"}], + "http://schema.org/familyName": [{"@value": "Testi"}], + "http://schema.org/givenName": [{"@value": "Test"}] + } + ], + "http://schema.org/dateCreated": [{"@type": "http://schema.org/Date", "@value": "2026-01-16"}], + "http://schema.org/dateModified": [{"@type": "http://schema.org/Date", "@value": "2026-01-16"}], + "http://schema.org/datePublished": [{"@type": "http://schema.org/Date", "@value": "2026-01-16"}], + "http://schema.org/description": [{"@value": "for testing"}], + "http://schema.org/funder": [ + { + "@type": ["http://schema.org/Organization"], + "http://schema.org/name": [{"@value": "TestsTests"}] + } + ], + "http://schema.org/keywords": [{"@value": "testing"}, {"@value": "more testing"}], + "http://schema.org/license": [ + {"@id": "https://spdx.org/licenses/Adobe-2006"}, + {"@id": "https://spdx.org/licenses/Abstyles"}, + {"@id": "https://spdx.org/licenses/AGPL-1.0-only"} + ], + "http://schema.org/name": [{"@value": "Test"}], + "http://schema.org/operatingSystem": [{"@value": "Windows"}], + "http://schema.org/programmingLanguage": [{"@value": "Python"}, {"@value": "Python 3"}], + "http://schema.org/relatedLink": [{"@id": "https://docs.software-metadata.pub/en/latest"}], + "http://schema.org/releaseNotes": [{"@value": "get it now"}], + "http://schema.org/version": [{"@value": "1.1.1"}], + "https://codemeta.github.io/terms/developmentStatus": [{"@id": "abandoned"}], + "https://codemeta.github.io/terms/funding": [{"@value": "none :("}], + "https://codemeta.github.io/terms/isSourceCodeOf": [{"@id": "HERMES"}], + "https://codemeta.github.io/terms/issueTracker": [ + {"@id": "https://github.com/softwarepub/hermes/issues"} + ], + "https://codemeta.github.io/terms/referencePublication": [{"@id": "https://arxiv.org/abs/2201.09015"}] + }), + ), + ] +) +def test_do_nothing_curate(tmp_path, monkeypatch, process_result, res): + monkeypatch.chdir(tmp_path) + + manager = HermesContext(tmp_path) + manager.prepare_step("process") + with manager["result"] as cache: + cache["expanded"] = process_result.ld_value + cache["context"] = {"@context": process_result.full_context} + manager.finalize_step("process") + + config_file = tmp_path / "hermes.toml" + config_file.write_text("") + + orig_argv = sys.argv[:] + sys.argv = ["hermes", "curate", "--path", str(tmp_path), "--config", str(config_file)] + result = {} + try: + monkeypatch.setattr(HermesContext.__init__, "__defaults__", (tmp_path.cwd(),)) + cli.main() + except SystemExit: + manager.prepare_step("curate") + with manager["result"] as cache: + result = SoftwareMetadata(cache["expanded"][0], cache["context"]["@context"][1]) + manager.finalize_step("curate") + finally: + sys.argv = orig_argv + + assert result.data_dict == res.data_dict