Exemplo n.º 1
0
    def test_jsonarray2netcdf_execute_async_output_by_reference_dontcare_response_document(
            self):
        """
        Jobs submitted with ``response=document`` are not impacted by ``transmissionMode``.

        The results schema should always be returned when document is requested.

        .. seealso::
            https://docs.ogc.org/is/18-062r2/18-062r2.html#req_core_process-execute-sync-document
        """
        with contextlib.ExitStack() as stack_exec:
            body, nc_data = self.setup_inputs(stack_exec)
            body.update({
                "response":
                ExecuteResponse.
                DOCUMENT,  # by value/reference don't care because of this
                "outputs": [{
                    "id":
                    "output",
                    "transmissionMode":
                    ExecuteTransmissionMode.REFERENCE
                }],
            })
            for mock_exec in mocked_execute_celery():
                stack_exec.enter_context(mock_exec)
            path = "/processes/jsonarray2netcdf/jobs"
            resp = mocked_sub_requests(self.app,
                                       "post_json",
                                       path,
                                       data=body,
                                       headers=self.json_headers,
                                       only_local=True)

        assert resp.content_type in ContentType.APP_JSON
        assert resp.status_code == 201, f"Error: {resp.json}"
        job_url = resp.json["location"]
        self.monitor_job(
            job_url, return_status=True)  # don't fetch results automatically

        resp = self.app.get(f"{job_url}/results", headers=self.json_headers)
        assert resp.status_code == 200, f"Error: {resp.text}"
        assert resp.content_type == ContentType.APP_JSON
        result_links = [
            hdr for hdr in resp.headers if hdr[0].lower() == "link"
        ]
        assert len(result_links) == 0
        results = resp.json

        # even though results are requested by Link reference,
        # Weaver still offers them with document on outputs endpoint
        output_url = job_url + "/outputs"
        resp = self.app.get(output_url, headers=self.json_headers)
        assert resp.status_code == 200, f"Error job outputs:\n{resp.text}"
        outputs = resp.json

        self.validate_results(results, outputs, nc_data, result_links)
Exemplo n.º 2
0
    def test_jsonarray2netcdf_execute_async_output_by_value_response_raw(self):
        """
        Jobs submitted with ``response=raw`` and single output as ``transmissionMode=value`` must return its raw data.

        .. seealso::
            https://docs.ogc.org/is/18-062r2/18-062r2.html#req_core_process-execute-sync-raw-value-one
        """
        with contextlib.ExitStack() as stack_exec:
            body, nc_data = self.setup_inputs(stack_exec)
            body.update({
                "response":
                ExecuteResponse.RAW,  # by value/reference important here
                # NOTE: quantity of outputs important as well
                #       since single output, content-type is directly that output (otherwise should be multipart)
                "outputs": [{
                    "id": "output",
                    "transmissionMode": ExecuteTransmissionMode.VALUE
                }],  # data dump
            })
            for mock_exec in mocked_execute_celery():
                stack_exec.enter_context(mock_exec)
            path = "/processes/jsonarray2netcdf/jobs"
            resp = mocked_sub_requests(self.app,
                                       "post_json",
                                       path,
                                       data=body,
                                       headers=self.json_headers,
                                       only_local=True)

        assert resp.content_type in ContentType.APP_JSON
        assert resp.status_code == 201, f"Error: {resp.json}"
        job_url = resp.json["location"]
        self.monitor_job(
            job_url, return_status=True)  # don't fetch results automatically

        resp = self.app.get(f"{job_url}/results", headers=self.json_headers)
        assert resp.status_code < 400, f"Error: {resp.text}"
        assert resp.status_code == 200, "Body should contain literal raw data dump"
        assert resp.content_type in ContentType.APP_NETCDF, "raw result by value should be directly the content-type"
        assert resp.text == nc_data, "raw result by value should be directly the data content"
        assert resp.headers
        result_links = [
            hdr for hdr in resp.headers if hdr[0].lower() == "link"
        ]
        assert len(result_links) == 0

        # even though results are requested by raw data,
        # Weaver still offers them with document on outputs endpoint
        output_url = job_url + "/outputs"
        resp = self.app.get(output_url, headers=self.json_headers)
        assert resp.status_code == 200, f"Error job outputs:\n{resp.text}"
        outputs = resp.json

        self.validate_results(None, outputs, nc_data, result_links)
Exemplo n.º 3
0
    def test_jsonarray2netcdf_execute_async_output_by_reference_response_raw(
            self):
        """
        Jobs submitted with ``response=raw`` and single output as ``transmissionMode=reference`` must a link.

        Contents should be empty, and the reference should be provided with HTTP ``Link`` header.

        .. seealso::
            https://docs.ogc.org/is/18-062r2/18-062r2.html#req_core_process-execute-sync-raw-ref
        """
        with contextlib.ExitStack() as stack_exec:
            body, nc_data = self.setup_inputs(stack_exec)
            body.update({
                "response":
                ExecuteResponse.RAW,  # by value/reference important here
                "outputs": [{
                    "id":
                    "output",
                    "transmissionMode":
                    ExecuteTransmissionMode.REFERENCE
                }],  # Link header
            })
            for mock_exec in mocked_execute_celery():
                stack_exec.enter_context(mock_exec)
            path = "/processes/jsonarray2netcdf/jobs"
            resp = mocked_sub_requests(self.app,
                                       "post_json",
                                       path,
                                       data=body,
                                       headers=self.json_headers,
                                       only_local=True)

        assert resp.content_type in ContentType.APP_JSON
        assert resp.status_code == 201, f"Error: {resp.json}"
        job_url = resp.json["location"]
        self.monitor_job(
            job_url, return_status=True)  # don't fetch results automatically

        resp = self.app.get(f"{job_url}/results", headers=self.json_headers)
        assert resp.status_code < 400, f"Error: {resp.text}"
        assert resp.status_code == 204, "Body should be empty since all outputs requested by reference (Link header)"
        assert resp.content_type is None
        assert resp.headers
        result_links = [hdr for hdr in resp.headers if hdr[0] == "Link"]

        # even though results are requested by Link reference,
        # Weaver still offers them with document on outputs endpoint
        resp = self.app.get(f"{job_url}/outputs", headers=self.json_headers)
        assert resp.status_code == 200, f"Error job outputs:\n{resp.json}"
        outputs = resp.json

        self.validate_results(None, outputs, nc_data, result_links)
Exemplo n.º 4
0
 def test_execute_allowed_demo(self):
     template = "service=wps&request=execute&version=1.0.0&identifier={}&datainputs=name=tux"
     params = template.format(HelloWPS.identifier)
     url = self.make_url(params)
     with contextlib.ExitStack() as stack_exec:
         for mock_exec in mocked_execute_celery():
             stack_exec.enter_context(mock_exec)
         resp = self.app.get(url)
     assert resp.status_code == 200  # FIXME: replace by 202 Accepted (?) https://github.com/crim-ca/weaver/issues/14
     assert resp.content_type in ContentType.ANY_XML
     resp.mustcontain("<wps:ExecuteResponse")
     resp.mustcontain("<wps:ProcessAccepted")
     resp.mustcontain(f"PyWPS Process {HelloWPS.identifier}")
Exemplo n.º 5
0
 def test_execute_deployed_with_visibility_allowed(self):
     headers = {"Accept": ContentType.APP_XML}
     params_template = "service=wps&request=execute&version=1.0.0&identifier={}&datainputs=test_input=test"
     url = self.make_url(
         params_template.format(self.process_public.identifier))
     with contextlib.ExitStack() as stack_exec:
         for mock_exec in mocked_execute_celery():
             stack_exec.enter_context(mock_exec)
         resp = self.app.get(url, headers=headers)
     assert resp.status_code == 200  # FIXME: replace by 202 Accepted (?) https://github.com/crim-ca/weaver/issues/14
     assert resp.content_type in ContentType.ANY_XML
     resp.mustcontain("<wps:ExecuteResponse")
     resp.mustcontain("<wps:ProcessAccepted")
     resp.mustcontain(f"PyWPS Process {self.process_public.identifier}")
Exemplo n.º 6
0
 def test_execute_deployed_with_visibility_denied(self):
     headers = {"Accept": ContentType.APP_XML}
     params_template = "service=wps&request=execute&version=1.0.0&identifier={}&datainputs=test_input=test"
     url = self.make_url(
         params_template.format(self.process_private.identifier))
     with contextlib.ExitStack() as stack_exec:
         for mock_exec in mocked_execute_celery():
             stack_exec.enter_context(mock_exec)
         resp = self.app.get(url, headers=headers, expect_errors=True)
     assert resp.status_code == 403
     assert resp.content_type in ContentType.ANY_XML, f"Error Response: {resp.text}"
     resp.mustcontain(
         "<Exception exceptionCode=\"AccessForbidden\" locator=\"service\">"
     )
     err_desc = f"Process with ID '{self.process_private.identifier}' is not accessible."
     resp.mustcontain(f"<ExceptionText>{err_desc}</ExceptionText>")
Exemplo n.º 7
0
    def test_quote_atomic_process(self, mocked_estimate):
        with contextlib.ExitStack() as stack_quote:
            for mock_quote in mocked_execute_celery(
                    celery_task=
                    "weaver.quotation.estimation.process_quote_estimator"):
                stack_quote.enter_context(mock_quote)

            data = {
                "inputs": {
                    "message": "test quote"
                },
                "outputs": {
                    "output": {
                        "transmissionMode": ExecuteTransmissionMode.VALUE
                    }
                }
            }
            path = sd.process_quotes_service.path.format(process_id="Echo")
            resp = mocked_sub_requests(self.app,
                                       "POST",
                                       path,
                                       json=data,
                                       headers=self.json_headers,
                                       only_local=True)
            assert resp.status_code == 202  # 'Accepted' async task, but already finished by skipping celery
            assert mocked_estimate.called
            body = resp.json
            assert body["status"] == QuoteStatus.SUBMITTED

            path = sd.process_quote_service.path.format(process_id="Echo",
                                                        quote_id=body["id"])
            resp = mocked_sub_requests(self.app,
                                       "GET",
                                       path,
                                       headers=self.json_headers,
                                       only_local=True)
            assert resp.status_code == 200
            body = resp.json
            assert body["status"] == QuoteStatus.COMPLETED
            assert isinstance(body["price"], float) and body["price"] > 0
            assert isinstance(body["currency"],
                              str) and body["currency"] == "CAD"
            assert isinstance(body["estimatedSeconds"],
                              int) and body["estimatedSeconds"] > 0
Exemplo n.º 8
0
    def test_jsonarray2netcdf_execute_async(self):
        with contextlib.ExitStack() as stack_exec:
            body, nc_data = self.setup_inputs(stack_exec)
            body.update({
                "mode":
                ExecuteMode.ASYNC,
                "response":
                ExecuteResponse.DOCUMENT,
                "outputs": [{
                    "id": "output",
                    "transmissionMode": ExecuteTransmissionMode.VALUE
                }],
            })
            for mock_exec in mocked_execute_celery():
                stack_exec.enter_context(mock_exec)
            path = "/processes/jsonarray2netcdf/jobs"
            resp = mocked_sub_requests(self.app,
                                       "post_json",
                                       path,
                                       data=body,
                                       headers=self.json_headers,
                                       only_local=True)

        assert resp.status_code == 201, f"Error: {resp.json}"
        assert resp.content_type in ContentType.APP_JSON
        # following details not available yet in async, but are in sync
        assert "created" not in resp.json
        assert "finished" not in resp.json
        assert "duration" not in resp.json
        assert "progress" not in resp.json

        job_url = resp.json["location"]
        results = self.monitor_job(job_url)

        output_url = job_url + "/outputs"
        resp = self.app.get(output_url, headers=self.json_headers)
        assert resp.status_code == 200, f"Error job outputs:\n{resp.json}"
        outputs = resp.json

        self.validate_results(results, outputs, nc_data, None)
Exemplo n.º 9
0
    def test_quote_workflow_process(self, mocked_estimate):
        with contextlib.ExitStack() as stack_quote:
            for mock_quote in mocked_execute_celery(
                    celery_task=
                    "weaver.quotation.estimation.process_quote_estimator"):
                stack_quote.enter_context(mock_quote)

            path = os.path.join(APP_PKG_ROOT, "WorkflowChainStrings",
                                "execute.json")
            with open(path, mode="r", encoding="utf-8") as exec_file:
                data = yaml.safe_load(exec_file)
            path = sd.process_quotes_service.path.format(
                process_id="WorkflowChainStrings")
            resp = mocked_sub_requests(self.app,
                                       "POST",
                                       path,
                                       json=data,
                                       headers=self.json_headers,
                                       only_local=True)
            assert resp.status_code == 202  # 'Accepted' async task, but already finished by skipping celery
            assert mocked_estimate.called
            body = resp.json
            assert body["status"] == QuoteStatus.SUBMITTED

            path = sd.process_quote_service.path.format(process_id="Echo",
                                                        quote_id=body["id"])
            resp = mocked_sub_requests(self.app,
                                       "GET",
                                       path,
                                       headers=self.json_headers,
                                       only_local=True)
            assert resp.status_code == 200
            body = resp.json
            assert body["status"] == QuoteStatus.COMPLETED
            assert isinstance(body["price"], float) and body["price"] > 0
            assert isinstance(body["currency"],
                              str) and body["currency"] == "CAD"
            assert isinstance(body["estimatedSeconds"],
                              int) and body["estimatedSeconds"] > 0
Exemplo n.º 10
0
    def test_jsonarray2netcdf_execute_sync(self):
        """
        Job submitted with ``mode=sync`` or ``Prefer`` header for sync should respond directly with the results schema.

        .. seealso::
            https://docs.ogc.org/is/18-062r2/18-062r2.html#sc_execute_response
        """
        with contextlib.ExitStack() as stack_exec:
            body, nc_data = self.setup_inputs(stack_exec)
            body.update({
                "response":
                ExecuteResponse.DOCUMENT,
                "outputs": [{
                    "id": "output",
                    "transmissionMode": ExecuteTransmissionMode.VALUE
                }]
            })
            for mock_exec in mocked_execute_celery():
                stack_exec.enter_context(mock_exec)
            headers = {"Prefer": "wait=10"}
            headers.update(self.json_headers)
            path = "/processes/jsonarray2netcdf/jobs"
            resp = mocked_sub_requests(self.app,
                                       "post_json",
                                       path,
                                       data=body,
                                       headers=headers,
                                       only_local=True)

        assert resp.status_code == 200, f"Error: {resp.text}"
        assert resp.content_type in ContentType.APP_JSON

        # since sync, results are directly available instead of job status
        # even if results are returned directly (instead of status),
        # status location link is available for reference as needed
        assert "Location" in resp.headers
        # validate sync was indeed applied (in normal situation, not considering mock test that runs in sync)
        assert resp.headers["Preference-Applied"] == headers["Prefer"]
        # following details should not be available since results are returned in sync instead of async job status
        for field in ["status", "created", "finished", "duration", "progress"]:
            assert field not in resp.json

        # validate that job can still be found and its metadata are defined although executed in sync
        job_url = resp.headers["Location"]
        resp = self.app.get(job_url, headers=self.json_headers)
        assert resp.status_code == 200
        assert resp.content_type == ContentType.APP_JSON
        for field in ["status", "created", "finished", "duration", "progress"]:
            assert field in resp.json
        assert resp.json["status"] == Status.SUCCEEDED
        assert resp.json["progress"] == 100

        out_url = f"{job_url}/results"
        resp = self.app.get(out_url, headers=self.json_headers)
        assert resp.status_code == 200
        assert resp.content_type == ContentType.APP_JSON
        results = resp.json

        output_url = job_url + "/outputs"
        resp = self.app.get(output_url, headers=self.json_headers)
        assert resp.status_code == 200, f"Error job outputs:\n{resp.json}"
        outputs = resp.json

        self.validate_results(results, outputs, nc_data, None)
Exemplo n.º 11
0
    def test_execute_docker_embedded_python_script(self):
        test_proc = "test-docker-python-script"
        cwl = load_file(
            os.path.join(WEAVER_ROOT_DIR,
                         "docs/examples/docker-python-script-report.cwl"))
        body = {
            "processDescription": {
                "process": {
                    "id": test_proc
                }
            },
            "executionUnit": [{
                "unit": cwl
            }],
            "deploymentProfileName":
            "http://www.opengis.net/profiles/eoc/dockerizedApplication"
        }
        self.deploy_process(body)

        with contextlib.ExitStack() as stack:
            for mock_exec in mocked_execute_celery():
                stack.enter_context(mock_exec)

            path = f"/processes/{test_proc}/execution"
            cost = 2.45
            amount = 3
            body = {
                "mode":
                ExecuteMode.ASYNC,
                "response":
                ExecuteResponse.DOCUMENT,
                "inputs": [{
                    "id": "amount",
                    "value": amount
                }, {
                    "id": "cost",
                    "value": cost
                }],
                "outputs": [
                    {
                        "id": "quote",
                        "transmissionMode": ExecuteTransmissionMode.VALUE
                    },
                ]
            }
            resp = mocked_sub_requests(self.app,
                                       "POST",
                                       path,
                                       json=body,
                                       headers=self.json_headers,
                                       only_local=True)
            status_url = resp.headers["Location"]
            results = self.monitor_job(status_url)

            assert results["quote"]["href"].startswith("http")
            stack.enter_context(mocked_wps_output(self.settings))
            tmpdir = stack.enter_context(tempfile.TemporaryDirectory())
            report_file = fetch_file(results["quote"]["href"], tmpdir,
                                     self.settings)
            report_data = load_file(report_file, text=True)
            assert report_data == f"Order Total: {amount * cost:0.2f}$\n"
Exemplo n.º 12
0
    def wps_execute(self, version, accept, url=None):
        if url:
            wps_url = url
        else:
            wps_url = get_wps_url(self.settings)
        if version == "1.0.0":
            test_content = "Test file in Docker - WPS KVP"
            wps_method = "GET"
        elif version == "2.0.0":
            test_content = "Test file in Docker - WPS XML"
            wps_method = "POST"
        else:
            raise ValueError(f"Invalid WPS version: {version}")
        accept_type = accept.split("/")[-1].upper()
        test_content += f" {wps_method} request - Accept {accept_type}"

        with contextlib.ExitStack() as stack_exec:
            # setup
            dir_name = tempfile.gettempdir()
            tmp_file = stack_exec.enter_context(
                tempfile.NamedTemporaryFile(dir=dir_name,
                                            mode="w",
                                            suffix=".txt"))
            tmp_file.write(test_content)
            tmp_file.seek(0)
            for mock_exec in mocked_execute_celery():
                stack_exec.enter_context(mock_exec)

            # execute
            if version == "1.0.0":
                wps_inputs = [
                    f"file={tmp_file.name}@mimeType={ContentType.TEXT_PLAIN}"
                ]
                wps_params = {
                    "service": "WPS",
                    "request": "Execute",
                    "version": version,
                    "identifier": self.process_id,
                    "DataInputs": wps_inputs,
                }
                wps_headers = {"Accept": accept}
                wps_data = None
            else:
                wps_inputs = [
                    ("file",
                     ComplexDataInput(tmp_file.name,
                                      mimeType=ContentType.TEXT_PLAIN))
                ]
                wps_outputs = [(self.out_key, True)]  # as reference
                wps_exec = WPSExecution(version=version, url=wps_url)
                wps_req = wps_exec.buildRequest(self.process_id, wps_inputs,
                                                wps_outputs)
                wps_data = xml_util.tostring(wps_req)
                wps_headers = {
                    "Accept": accept,
                    "Content-Type": ContentType.APP_XML
                }
                wps_params = None
            resp = mocked_sub_requests(self.app,
                                       wps_method,
                                       wps_url,
                                       params=wps_params,
                                       data=wps_data,
                                       headers=wps_headers,
                                       only_local=True)
            assert resp.status_code in [
                200, 201
            ], (f"Failed with: [{resp.status_code}]\nTest: [{test_content}]\nReason:\n{resp.text}"
                )

            # parse response status
            if accept == ContentType.APP_XML:
                assert resp.content_type in ContentType.ANY_XML, test_content
                xml_body = xml_util.fromstring(str2bytes(resp.text))
                status_url = xml_body.get("statusLocation")
                job_id = status_url.split("/")[-1].split(".")[0]
            elif accept == ContentType.APP_JSON:
                assert resp.content_type == ContentType.APP_JSON, test_content
                status_url = resp.json["location"]
                job_id = resp.json["jobID"]
            assert status_url
            assert job_id

            if accept == ContentType.APP_XML:
                wps_out_url = self.settings["weaver.wps_output_url"]
                weaver_url = self.settings["weaver.url"]
                assert status_url == f"{wps_out_url}/{job_id}.xml", "Status URL should be XML file for WPS-1 request"
                # remap to employ JSON monitor method (could be done with XML parsing otherwise)
                status_url = f"{weaver_url}/jobs/{job_id}"

            # job monitoring
            results = self.monitor_job(status_url)
            outputs = self.get_outputs(status_url)

            # validate XML status is updated accordingly
            wps_xml_status = os.path.join(
                self.settings["weaver.wps_output_dir"], job_id + ".xml")
            assert os.path.isfile(wps_xml_status)
            with open(wps_xml_status, mode="r",
                      encoding="utf-8") as status_file:
                assert "ProcessSucceeded" in status_file.read()

        self.validate_outputs(job_id, results, outputs, test_content)
Exemplo n.º 13
0
    def test_execute_wps_rest_resp_json(self):
        """
        Test validates that basic Docker application runs successfully, fetching the reference as needed.

        The job execution is launched using the WPS-REST endpoint for this test.
        Both the request body and response content are JSON.

        .. seealso::
            - :meth:`test_execute_wps_kvp_get_resp_xml`
            - :meth:`test_execute_wps_kvp_get_resp_json`
            - :meth:`test_execute_wps_xml_post_resp_xml`
            - :meth:`test_execute_wps_xml_post_resp_json`
        """

        test_content = "Test file in Docker - WPS-REST job endpoint"
        with contextlib.ExitStack() as stack_exec:
            # setup
            dir_name = tempfile.gettempdir()
            tmp_file = stack_exec.enter_context(
                tempfile.NamedTemporaryFile(dir=dir_name,
                                            mode="w",
                                            suffix=".txt"))
            tmp_file.write(test_content)
            tmp_file.seek(0)
            exec_body = {
                "mode":
                ExecuteMode.ASYNC,
                "response":
                ExecuteResponse.DOCUMENT,
                "inputs": [
                    {
                        "id": "file",
                        "href": tmp_file.name
                    },
                ],
                "outputs": [
                    {
                        "id": self.out_key,
                        "transmissionMode": ExecuteTransmissionMode.VALUE
                    },
                ]
            }
            for mock_exec in mocked_execute_celery():
                stack_exec.enter_context(mock_exec)

            # execute
            proc_url = f"/processes/{self.process_id}/jobs"
            resp = mocked_sub_requests(self.app,
                                       "post_json",
                                       proc_url,
                                       data=exec_body,
                                       headers=self.json_headers,
                                       only_local=True)
            assert resp.status_code in [
                200, 201
            ], f"Failed with: [{resp.status_code}]\nReason:\n{resp.json}"
            status_url = resp.json["location"]
            job_id = resp.json["jobID"]

            # job monitoring
            results = self.monitor_job(status_url)
            outputs = self.get_outputs(status_url)

        self.validate_outputs(job_id, results, outputs, test_content)
Exemplo n.º 14
0
    def test_register_describe_execute_ncdump(self, mock_responses):
        # type: (RequestsMock) -> None
        """
        Test the full workflow from remote WPS-1 provider registration, process description, execution and fetch result.

        The complete execution and definitions (XML responses) of the "remote" WPS are mocked.
        Requests and response negotiation between Weaver and that "remote" WPS are effectively executed and validated.

        Validation is accomplished against the same process and mocked server from corresponding test deployment
        server in order to detect early any breaking feature. Responses XML bodies employed to simulate the mocked
        server are pre-generated from real request calls to the actual service that was running on a live platform.

        .. seealso::
            - Reference notebook testing the same process on a live server:
              https://github.com/Ouranosinc/pavics-sdi/blob/master/docs/source/notebook-components/weaver_example.ipynb
            - Evaluate format of submitted Execute body (see `#340 <https://github.com/crim-ca/weaver/issues/340>`_).
        """
        self.service_store.clear_services()

        # register the provider
        remote_provider_name = "test-wps-remote-provider-hummingbird"
        path = "/providers"
        data = {"id": remote_provider_name, "url": resources.TEST_REMOTE_SERVER_URL}
        resp = self.app.post_json(path, params=data, headers=self.json_headers)
        assert resp.status_code == 201

        # validate service capabilities
        path = f"/providers/{remote_provider_name}"
        resp = self.app.get(path, headers=self.json_headers)
        assert resp.status_code == 200
        body = resp.json
        assert "id" in body and body["id"] == remote_provider_name
        assert "hummingbird" in body["title"].lower()
        assert body["type"] == ProcessType.WPS_REMOTE

        # validate processes capabilities
        path = f"/providers/{remote_provider_name}/processes"
        resp = self.app.get(path, headers=self.json_headers)
        body = resp.json
        assert resp.status_code == 200
        assert "processes" in body and len(body["processes"]) == len(resources.TEST_HUMMINGBIRD_WPS1_PROCESSES)
        processes = {process["id"]: process for process in body["processes"]}
        assert "ncdump" in processes
        assert processes["ncdump"]["version"] == "4.4.1.1"
        assert processes["ncdump"]["metadata"][0]["rel"] == "birdhouse"
        assert processes["ncdump"]["metadata"][1]["rel"] == "user-guide"
        # keyword 'Hummingbird' in this case is from GetCapabilities ProviderName
        # keyword of the service name within Weaver is also provided, which can be different than provider
        expect_keywords = [ProcessType.WPS_REMOTE, "Hummingbird", remote_provider_name]
        assert all(key in processes["ncdump"]["keywords"] for key in expect_keywords)
        proc_desc_url = processes["ncdump"]["processDescriptionURL"]
        proc_wps1_url = processes["ncdump"]["processEndpointWPS1"]
        proc_exec_url = processes["ncdump"]["executeEndpoint"]
        assert proc_wps1_url.startswith(resources.TEST_REMOTE_SERVER_URL)
        assert proc_desc_url == self.app_url + path + "/ncdump"
        assert proc_exec_url == self.app_url + path + "/ncdump/jobs"

        # validate process description
        resp = self.app.get(proc_desc_url, headers=self.json_headers)
        assert resp.status_code == 200
        body = resp.json

        assert "inputs" in body and len(body["inputs"]) == 2
        assert all(iid in body["inputs"] for iid in ["dataset", "dataset_opendap"])
        assert body["inputs"]["dataset"]["minOccurs"] == 0
        assert body["inputs"]["dataset"]["maxOccurs"] == 100
        assert "formats" in body["inputs"]["dataset"]
        assert len(body["inputs"]["dataset"]["formats"]) == 1
        assert body["inputs"]["dataset"]["formats"][0]["default"] is True
        assert "literalDataDomains" not in body["inputs"]["dataset"]
        assert body["inputs"]["dataset"]["formats"][0]["mediaType"] == ContentType.APP_NETCDF
        assert body["inputs"]["dataset_opendap"]["minOccurs"] == 0
        assert body["inputs"]["dataset_opendap"]["maxOccurs"] == 100
        assert "formats" not in body["inputs"]["dataset_opendap"]
        assert "literalDataDomains" in body["inputs"]["dataset_opendap"]
        assert len(body["inputs"]["dataset_opendap"]["literalDataDomains"]) == 1
        assert body["inputs"]["dataset_opendap"]["literalDataDomains"][0]["dataType"]["name"] == "string"
        assert body["inputs"]["dataset_opendap"]["literalDataDomains"][0]["valueDefinition"]["anyValue"] is True
        assert body["inputs"]["dataset_opendap"]["literalDataDomains"][0]["default"] is True

        assert "outputs" in body and len(body["outputs"]) == 1
        assert "output" in body["outputs"]
        assert "formats" in body["outputs"]["output"]
        assert len(body["outputs"]["output"]["formats"]) == 1
        assert body["outputs"]["output"]["formats"][0]["default"] is True
        assert body["outputs"]["output"]["formats"][0]["mediaType"] == ContentType.TEXT_PLAIN
        assert "literalDataDomains" not in body["outputs"]["output"]

        assert body["processDescriptionURL"] == proc_desc_url
        assert body["processEndpointWPS1"] == proc_wps1_url
        assert body["executeEndpoint"] == proc_exec_url
        job_exec_url = proc_exec_url.replace("/execution", "/jobs")  # both are aliases, any could be returned
        ogc_exec_url = proc_exec_url.replace("/jobs", "/execution")
        links = {link["rel"].rsplit("/")[-1]: link["href"] for link in body["links"]}
        assert links["execute"] in [job_exec_url, ogc_exec_url]
        assert links["process-meta"] == proc_desc_url
        # WPS-1 URL also includes relevant query parameters to obtain a valid response directly from remote service
        assert links["process-desc"] == proc_wps1_url
        assert links["service-desc"].startswith(resources.TEST_REMOTE_SERVER_URL)
        assert "DescribeProcess" in links["process-desc"]
        assert "GetCapabilities" in links["service-desc"]

        assert ExecuteControlOption.ASYNC in body["jobControlOptions"]
        assert ExecuteTransmissionMode.VALUE in body["outputTransmission"]

        # validate execution submission
        # (don't actually execute because server is mocked, only validate parsing of I/O and job creation)

        # first setup all expected contents and files
        exec_file = "http://localhost.com/dont/care.nc"
        exec_body = {
            "mode": ExecuteMode.ASYNC,
            "response": ExecuteResponse.DOCUMENT,
            "inputs": [{"id": "dataset", "href": exec_file}],
            "outputs": [{"id": "output", "transmissionMode": ExecuteTransmissionMode.VALUE}]
        }
        status_url = resources.TEST_REMOTE_SERVER_URL + "/status.xml"
        output_url = resources.TEST_REMOTE_SERVER_URL + "/output.txt"
        with open(resources.TEST_HUMMINGBIRD_STATUS_WPS1_XML, mode="r", encoding="utf-8") as status_file:
            status = status_file.read().format(
                TEST_SERVER_URL=resources.TEST_REMOTE_SERVER_URL,
                LOCATION_XML=status_url,
                OUTPUT_FILE=output_url,
            )

        xml_headers = {"Content-Type": ContentType.TEXT_XML}
        ncdump_data = "Fake NetCDF Data"
        with contextlib.ExitStack() as stack_exec:
            # mock direct execution bypassing celery
            for mock_exec in mocked_execute_celery():
                stack_exec.enter_context(mock_exec)
            # mock responses expected by "remote" WPS-1 Execute request and relevant documents
            mock_responses.add("GET", exec_file, body=ncdump_data, headers={"Content-Type": ContentType.APP_NETCDF})
            mock_responses.add("POST", resources.TEST_REMOTE_SERVER_URL, body=status, headers=xml_headers)
            mock_responses.add("GET", status_url, body=status, headers=xml_headers)
            mock_responses.add("GET", output_url, body=ncdump_data, headers={"Content-Type": ContentType.TEXT_PLAIN})

            # add reference to specific provider execute class to validate it was called
            # (whole procedure must run even though a lot of parts are mocked)
            real_wps1_process_execute = Wps1Process.execute
            handle_wps1_process_execute = stack_exec.enter_context(
                mock.patch.object(Wps1Process, "execute", side_effect=real_wps1_process_execute, autospec=True)
            )  # type: MockPatch

            # launch job execution and validate
            resp = mocked_sub_requests(self.app, "post_json", proc_exec_url, timeout=5,
                                       data=exec_body, headers=self.json_headers, only_local=True)
            assert resp.status_code in [200, 201], f"Failed with: [{resp.status_code}]\nReason:\n{resp.json}"
            assert handle_wps1_process_execute.called, "WPS-1 handler should have been called by CWL runner context"

            status_url = resp.json["location"]
            job_id = resp.json["jobID"]
            assert status_url == proc_exec_url + "/" + job_id
            results = self.monitor_job(status_url)
            wps_dir = self.settings["weaver.wps_output_dir"]
            wps_url = self.settings["weaver.wps_output_url"]
            output_url = f"{wps_url}/{job_id}/output.txt"
            output_path = f"{wps_dir}/{job_id}/output.txt"
            assert results["output"]["format"]["mediaType"] == ContentType.TEXT_PLAIN
            assert results["output"]["href"] == output_url
            with open(output_path, mode="r", encoding="utf-8") as out_file:
                data = out_file.read()
            assert data == ncdump_data