diff --git a/tests/processes/test_convert.py b/tests/processes/test_convert.py index eef1cdfca..837d5dc88 100644 --- a/tests/processes/test_convert.py +++ b/tests/processes/test_convert.py @@ -25,6 +25,8 @@ from weaver.formats import IANA_NAMESPACE_DEFINITION, OGC_MAPPING, OGC_NAMESPACE_DEFINITION, ContentType from weaver.processes.constants import ( CWL_REQUIREMENT_APP_OGC_API, + CWL_REQUIREMENT_APP_WPS1, + CWL_REQUIREMENT_INLINE_JAVASCRIPT, IO_INPUT, IO_OUTPUT, WPS_BOUNDINGBOX, @@ -58,7 +60,8 @@ parse_cwl_enum_type, repr2json_input_values, set_field, - wps2json_io + wps2json_io, + xml_wps2cwl ) from weaver.utils import null @@ -1796,3 +1799,99 @@ def test_ogcapi2cwl_process_without_extra(): body["executionUnit"] = [{"unit": cwl}] body["deploymentProfile"] = "http://www.opengis.net/profiles/eoc/ogcapiApplication" assert info == body, "Process information should be updated with minimal details since no CWL detected in input." + + + +@pytest.mark.parametrize( + ["input_str", "input_int", "input_float"], + [ + # OpenAPI schema references + ( + {"schema": {"type": "string", "enum": ["a", "b", "c"]}}, + {"schema": {"type": "integer", "enum": [1, 2, 3]}}, + {"schema": {"type": "number", "format": "float", "enum": [1.2, 3.4]}}, + ), + # OGC-API input definitions + ( + {"data_type": "string", "allowed_values": ["a", "b", "c"]}, + {"data_type": "integer", "allowed_values": [1, 2, 3]}, + {"data_type": "float", "allowed_values": [1.2, 3.4]}, + ), + ] +) +def test_ogcapi2cwl_process_cwl_enum_updated(input_str, input_int, input_float): + """ + Test that a :term:`CWL` with pseudo-``Enum`` type has the necessary :term:`CWL` requirements to perform validation. + + .. seealso:: + - :func:`test_any2cwl_io_enum_convert` + - :func:`test_any2cwl_io_enum_validate` + """ + href = "https://remote-server.com/processes/test-process" + body = { + "inputs": { + "enum-str": input_str, + "enum-int": input_int, + "enum-float": input_float, + }, + "outputs": { + "output": {"schema": {"type": "string", "contentMediaType": ContentType.TEXT_PLAIN}}, + } + } + cwl, info = ogcapi2cwl_process(body, href) + assert info is not body, "copy should be created, not inplace modifications" + + assert cwl["inputs"]["enum-str"]["type"] == {"type": "enum", "symbols": ["a", "b", "c"]} + assert cwl["inputs"]["enum-int"]["type"] == "int" + assert "symbols" not in cwl["inputs"]["enum-int"] + cwl_value_from = cwl["inputs"]["enum-int"]["inputBinding"]["valueFrom"].strip() + assert cwl_value_from.startswith("${") and cwl_value_from.endswith("}") + assert "[1, 2, 3]" in cwl_value_from + assert cwl["inputs"]["enum-float"]["type"] == "float" + assert "symbols" not in cwl["inputs"]["enum-float"] + cwl_value_from = cwl["inputs"]["enum-float"]["inputBinding"]["valueFrom"].strip() + assert cwl_value_from.startswith("${") and cwl_value_from.endswith("}") + assert "[1.2, 3.4]" in cwl_value_from + assert cwl["requirements"] == {CWL_REQUIREMENT_INLINE_JAVASCRIPT: {}} + assert cwl["hints"] == {CWL_REQUIREMENT_APP_OGC_API: {"process": href}} + + +def test_xml_wps2cwl_enum_updated(): + """ + Test that a :term:`CWL` with pseudo-``Enum`` type has the necessary :term:`CWL` requirements to perform validation. + + .. seealso:: + - :func:`test_any2cwl_io_enum_convert` + - :func:`test_any2cwl_io_enum_validate` + """ + raise NotImplementedError # FIXME + + proc = "test-process" + prov = "https://remote-server.com" + href = f"{prov}?service=WPS&version=1.0.0&request=DescribeProcess&identifier={proc}" + body = { + "inputs": { + "enum-str": input_str, + "enum-int": input_int, + "enum-float": input_float, + }, + "outputs": { + "output": {"schema": {"type": "string", "contentMediaType": ContentType.TEXT_PLAIN}}, + } + } + cwl, info = ogcapi2cwl_process(body, href) + assert info is not body, "copy should be created, not inplace modifications" + + assert cwl["inputs"]["enum-str"]["type"] == {"type": "enum", "symbols": ["a", "b", "c"]} + assert cwl["inputs"]["enum-int"]["type"] == "int" + assert "symbols" not in cwl["inputs"]["enum-int"] + cwl_value_from = cwl["inputs"]["enum-int"]["inputBinding"]["valueFrom"].strip() + assert cwl_value_from.startswith("${") and cwl_value_from.endswith("}") + assert "[1, 2, 3]" in cwl_value_from + assert cwl["inputs"]["enum-float"]["type"] == "float" + assert "symbols" not in cwl["inputs"]["enum-float"] + cwl_value_from = cwl["inputs"]["enum-float"]["inputBinding"]["valueFrom"].strip() + assert cwl_value_from.startswith("${") and cwl_value_from.endswith("}") + assert "[1.2, 3.4]" in cwl_value_from + assert cwl["requirements"] == {CWL_REQUIREMENT_INLINE_JAVASCRIPT: {}} + assert cwl["hints"] == {CWL_REQUIREMENT_APP_WPS1: {"provider": prov, "process": proc}} diff --git a/weaver/processes/convert.py b/weaver/processes/convert.py index 20f462507..d79af1e5f 100644 --- a/weaver/processes/convert.py +++ b/weaver/processes/convert.py @@ -39,6 +39,7 @@ from weaver.processes.constants import ( CWL_REQUIREMENT_APP_OGC_API, CWL_REQUIREMENT_APP_WPS1, + CWL_REQUIREMENT_INLINE_JAVASCRIPT, IO_INPUT, IO_OUTPUT, OAS_ARRAY_TYPES, @@ -773,6 +774,29 @@ def any2cwl_io(wps_io, io_select): return cwl_io, cwl_ns +def _patch_cwl_enum_js_requirement(cwl_package): + # type: (CWL) -> None + """ + Applies the JavaScript requirement to validate a pseudo-``Enum`` applied to a :term:`CWL` input definition. + + .. seealso:: + - :func:`any2cwl_io` + - :func:`_convert_cwl_io_enum` + - :func:`_get_cwl_js_value_from` + """ + cwl_items = cwl_package.get("inputs", []) + if isinstance(cwl_items, dict): + cwl_items = list(cwl_items.values()) + for cwl_input in cwl_items: + cwl_value_from = cwl_input.get("inputBinding", {}).get("valueFrom", {}) + if isinstance(cwl_value_from, str): + cwl_value_from = cwl_value_from.strip() + if cwl_value_from.startswith("${") and cwl_value_from.endswith("}"): + cwl_package.setdefault("requirements", {}) + cwl_package["requirements"].setdefault(CWL_REQUIREMENT_INLINE_JAVASCRIPT, {}) + return # early exit, no need to check more + + def wps2cwl_requirement(wps_service_url, wps_process_id): # type: (Union[str, ParseResult], str) -> JSON """ @@ -829,6 +853,7 @@ def ows2json(wps_process, wps_service_name, wps_service_url, wps_provider_name=N if "$namespaces" not in cwl_package: cwl_package["$namespaces"] = {} cwl_package["$namespaces"].update(cwl_ns) + _patch_cwl_enum_js_requirement(cwl_package) return cwl_package, process_info @@ -952,6 +977,7 @@ def ogcapi2cwl_process(payload, reference): } } cwl_package.update(cwl_pkg) # type: ignore + _patch_cwl_enum_js_requirement(cwl_package) payload_copy["executionUnit"] = [{"unit": cwl_package}] payload_copy["deploymentProfile"] = "http://www.opengis.net/profiles/eoc/ogcapiApplication" return cwl_package, payload_copy @@ -2279,6 +2305,7 @@ def oas2json_io_literal(io_info): io_allow.update(io_info) # noqa io_allow["data_type"] = data_type domains = any2json_literal_data_domains(io_allow) + io_json["allowed_values"] = io_allow["allowed_values"] # propagate to help CWL resolution of enum later on io_json["literalDataDomains"] = domains return io_json