From 45db1734d49285ca6c7aa8a9dbb911652d86c9f2 Mon Sep 17 00:00:00 2001 From: jasquat Date: Wed, 24 Jun 2026 14:55:25 -0400 Subject: [PATCH 1/2] do not use command http_status as top level status w/ burnettk --- src/spiffworkflow_proxy/blueprint.py | 3 +- .../commands/async_body_status.py | 29 +++++++++++++++++++ .../integration/blueprint_test.py | 21 ++++++++++++-- .../unit/plugin_service_test.py | 2 +- 4 files changed, 49 insertions(+), 6 deletions(-) create mode 100644 tests/mock_connectors/connector-example/src/connector_example/commands/async_body_status.py diff --git a/src/spiffworkflow_proxy/blueprint.py b/src/spiffworkflow_proxy/blueprint.py index 1c463d7..742f9b4 100644 --- a/src/spiffworkflow_proxy/blueprint.py +++ b/src/spiffworkflow_proxy/blueprint.py @@ -71,8 +71,7 @@ def do_command(plugin_display_name: str, command_name: str) -> Response: if is_async_call: response = json.dumps(result) - status_code = result.get("command_response", {}).get("http_status", 200) if isinstance(result, dict) else 200 - return Response(response, mimetype='application/json', status=status_code) + return Response(response, mimetype='application/json', status=202) elif "command_response_version" in result and result["command_response_version"] > 1: # type: ignore response = json.dumps(result) return Response(response, mimetype='application/json', status=200) diff --git a/tests/mock_connectors/connector-example/src/connector_example/commands/async_body_status.py b/tests/mock_connectors/connector-example/src/connector_example/commands/async_body_status.py new file mode 100644 index 0000000..20b4699 --- /dev/null +++ b/tests/mock_connectors/connector-example/src/connector_example/commands/async_body_status.py @@ -0,0 +1,29 @@ +"""Async command used by connector proxy tests.""" +from typing import Any + +from spiffworkflow_connector_command.command_interface import ConnectorProxyResponseDict + + +class AsyncBodyStatus: + """Returns a body-level http_status from execute_async.""" + + def __init__(self, upstream_status: int = 500): + self.upstream_status = upstream_status + + def execute(self, _config: Any, _task_data: Any) -> ConnectorProxyResponseDict: + return { + "command_response_version": 2, + "command_response": { + "body": {"started": False}, + "mimetype": "application/json", + "http_status": self.upstream_status, + }, + "error": { + "error_code": "AsyncStartRejected", + "message": "The async command rejected the start request.", + }, + "spiff__logs": [], + } + + def execute_async(self, _config: Any, _task_data: Any, callback_url: str) -> ConnectorProxyResponseDict: + return self.execute(_config, {"callback_url": callback_url}) diff --git a/tests/spiffworkflow_proxy/integration/blueprint_test.py b/tests/spiffworkflow_proxy/integration/blueprint_test.py index 445d517..9fce10f 100644 --- a/tests/spiffworkflow_proxy/integration/blueprint_test.py +++ b/tests/spiffworkflow_proxy/integration/blueprint_test.py @@ -28,9 +28,9 @@ def test_list_commands() -> None: assert rv.status_code == 200 # using rv.get_json() was giving me a "weakly-referenced object no longer exists". :/ json_resp = json.loads(rv.get_data(as_text=True)) - assert(len(json_resp) == 1) - assert(json_resp[0]["id"] == "example/CombineStrings") - assert(len(json_resp[0]["parameters"]) == 2) + commands_by_id = {command["id"]: command for command in json_resp} + assert(set(commands_by_id.keys()) == {"example/AsyncBodyStatus", "example/CombineStrings"}) + assert(len(commands_by_id["example/CombineStrings"]["parameters"]) == 2) def test_do_command_ignores_spiff_params() -> None: @@ -49,3 +49,18 @@ def test_do_command_ignores_spiff_params() -> None: json_resp = json.loads(rv.get_data(as_text=True)) assert json_resp["command_response"]["body"]["command_response"]["arg1"] == "hello" assert json_resp["command_response"]["body"]["command_response"]["arg2"] == "world" + + +def test_async_command_always_returns_accepted_status() -> None: + payload = { + "upstream_status": 500, + "spiff__callback_url": "http://localhost/callback", + "spiff__task_data": {"example": True}, + } + + rv = web_client().post("/v1/do/example/AsyncBodyStatus", json=payload) + + assert rv.status_code == 202 + json_resp = json.loads(rv.get_data(as_text=True)) + assert json_resp["command_response"]["http_status"] == 500 + assert json_resp["error"]["error_code"] == "AsyncStartRejected" diff --git a/tests/spiffworkflow_proxy/unit/plugin_service_test.py b/tests/spiffworkflow_proxy/unit/plugin_service_test.py index bc7af4f..91d0f70 100644 --- a/tests/spiffworkflow_proxy/unit/plugin_service_test.py +++ b/tests/spiffworkflow_proxy/unit/plugin_service_test.py @@ -19,4 +19,4 @@ def test_plugin_for_display_name() -> None: def test_available_commands_by_plugin() -> None: commands = PluginService.available_commands_by_plugin() - assert(list(commands['connector_example'].keys()) == ['CombineStrings']) + assert(set(commands['connector_example'].keys()) == {'AsyncBodyStatus', 'CombineStrings'}) From c127c3d2b7da7ff6740ea27a70f08b60708c11ac Mon Sep 17 00:00:00 2001 From: burnettk Date: Wed, 24 Jun 2026 15:10:09 -0400 Subject: [PATCH 2/2] actions warnings w/ jasquat --- .github/workflows/tests.yml | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 39d48c1..e729a4f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,9 +47,11 @@ jobs: linting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - - uses: actions/cache@v3 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 + with: + python-version: "3.13" + - uses: actions/cache@v5 with: path: ~/.cache/pip key: ${{ runner.os }}-pip @@ -66,10 +68,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out repository - uses: actions/checkout@v3 + uses: actions/checkout@v5 - name: Set up python ${{ matrix.python-version }} id: setup-python - uses: actions/setup-python@v4 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} - name: Install Poetry @@ -80,7 +82,7 @@ jobs: virtualenvs-in-project: true - name: Load cached venv id: cached-poetry-dependencies - uses: actions/cache@v3 + uses: actions/cache@v5 with: path: .venv key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}