Assure that when something goes wrong calling a service task that we get as much good information about the problem as possible.

This commit is contained in:
Dan 2023-02-08 11:53:20 -05:00
parent 9b3a2e2fb2
commit 443a855782
2 changed files with 57 additions and 4 deletions

View File

@ -7,6 +7,7 @@ import sentry_sdk
from flask import current_app from flask import current_app
from flask import g from flask import g
from spiffworkflow_backend.exceptions.api_error import ApiError
from spiffworkflow_backend.services.file_system_service import FileSystemService from spiffworkflow_backend.services.file_system_service import FileSystemService
from spiffworkflow_backend.services.secret_service import SecretService from spiffworkflow_backend.services.secret_service import SecretService
from spiffworkflow_backend.services.user_service import UserService from spiffworkflow_backend.services.user_service import UserService
@ -43,6 +44,28 @@ class ServiceTaskDelegate:
return value return value
@staticmethod
def get_message_for_status(code):
"""Given a code like 404, return a string like 'The requested resource was not found.'"""
msg = f'HTTP Status Code {code}.'
if code == 301:
msg = '301 (Permanent Redirect) - you may need to use a different URL in this service task.'
if code == 302:
msg = '302 (Temporary Redirect) - you may need to use a different URL in this service task.'
if code == 400:
msg = '400 (Bad Request) - The request was received by the service, but it was not understood.'
if code == 401:
msg = '401 (Unauthorized Error) - this end point requires some form of authentication.'
if code == 403:
msg = '403 (Forbidden) - The service you called refused to accept the request.'
if code == 404:
msg = '404 (Not Found) - The service did not find the requested resource.'
if code == 500:
msg = '500 (Internal Server Error) - The service you called is experiencing technical difficulties.'
if code == 501:
msg = '501 (Not Implemented) - This service needs to be called with the different method (like POST not GET).'
return msg
@staticmethod @staticmethod
def call_connector(name: str, bpmn_params: Any, task_data: Any) -> str: def call_connector(name: str, bpmn_params: Any, task_data: Any) -> str:
"""Calls a connector via the configured proxy.""" """Calls a connector via the configured proxy."""
@ -55,20 +78,40 @@ class ServiceTaskDelegate:
params["spiff__task_data"] = task_data params["spiff__task_data"] = task_data
proxied_response = requests.post(call_url, json=params) proxied_response = requests.post(call_url, json=params)
response_text = proxied_response.text
json_parse_error = None
parsed_response = json.loads(proxied_response.text) if response_text == "":
response_text = "{}"
try:
parsed_response = json.loads(response_text)
except Exception as e:
json_parse_error = e
parsed_response = {}
if proxied_response.status_code >= 300:
error = f"Received an unexpected response from the service : "
error += ServiceTaskDelegate.get_message_for_status(proxied_response.status_code)
if "error" in parsed_response:
error += parsed_response["error"]
if json_parse_error:
error += "A critical component (The connector proxy) is not responding correctly."
raise ConnectorProxyError(error)
elif json_parse_error:
raise ConnectorProxyError( f"There is a problem with this connector: '{name}'. "
f"Responses for connectors must be in JSON format. ")
if "refreshed_token_set" not in parsed_response: if "refreshed_token_set" not in parsed_response:
return proxied_response.text return response_text
secret_key = parsed_response["auth"] secret_key = parsed_response["auth"]
refreshed_token_set = json.dumps(parsed_response["refreshed_token_set"]) refreshed_token_set = json.dumps(parsed_response["refreshed_token_set"])
user_id = g.user.id if UserService.has_user() else None user_id = g.user.id if UserService.has_user() else None
SecretService().update_secret(secret_key, refreshed_token_set, user_id) SecretService().update_secret(secret_key, refreshed_token_set, user_id)
return json.dumps(parsed_response["api_response"]) return json.dumps(parsed_response["api_response"])
class ServiceTaskService: class ServiceTaskService:
"""ServiceTaskService.""" """ServiceTaskService."""

View File

@ -1,9 +1,10 @@
"""Test_various_bpmn_constructs.""" """Test_various_bpmn_constructs."""
import pytest
from flask.app import Flask from flask.app import Flask
from tests.spiffworkflow_backend.helpers.base_test import BaseTest from tests.spiffworkflow_backend.helpers.base_test import BaseTest
from spiffworkflow_backend.services.secret_service import SecretService from spiffworkflow_backend.services.secret_service import SecretService
from spiffworkflow_backend.services.service_task_service import ServiceTaskDelegate from spiffworkflow_backend.services.service_task_service import ServiceTaskDelegate, ConnectorProxyError
class TestServiceTaskDelegate(BaseTest): class TestServiceTaskDelegate(BaseTest):
@ -31,3 +32,12 @@ class TestServiceTaskDelegate(BaseTest):
SecretService().add_secret("hot_secret", "my_secret_value", user.id) SecretService().add_secret("hot_secret", "my_secret_value", user.id)
result = ServiceTaskDelegate.check_prefixes("secret:hot_secret") result = ServiceTaskDelegate.check_prefixes("secret:hot_secret")
assert result == "my_secret_value" assert result == "my_secret_value"
def test_invalid_call_returns_good_error_message(
self, app: Flask, with_db_and_bpmn_file_cleanup: None
) -> None:
with pytest.raises(ConnectorProxyError) as ae:
ServiceTaskDelegate.call_connector('my_invalid_operation', {}, {})
assert "404" in str(ae)
assert "The service did not find the requested resource." in str(ae)
assert "A critical component (The connector proxy) is not responding correctly." in str(ae)