Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions slack_bolt/adapter/socket_mode/async_internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import json
import logging
from time import time
from typing import Dict

from slack_sdk.socket_mode.async_client import AsyncBaseSocketModeClient
from slack_sdk.socket_mode.request import SocketModeRequest
Expand All @@ -20,6 +21,19 @@ async def run_async_bolt_app(app: AsyncApp, req: SocketModeRequest):
return bolt_resp


def _to_payload(content_type: str, body: str) -> Dict:
# A text-only ack is labeled as JSON when it starts with "{".
# Only treat it as JSON when it really parses to an object; otherwise send it as text.
if content_type.startswith("application/json"):
try:
parsed = json.loads(body)
if isinstance(parsed, dict):
return parsed
except ValueError:
pass
return {"text": body}


async def send_async_response(
client: AsyncBaseSocketModeClient,
req: SocketModeRequest,
Expand All @@ -30,15 +44,9 @@ async def send_async_response(
content_type = bolt_resp.headers.get("content-type", [""])[0]
if bolt_resp.body is None or len(bolt_resp.body) == 0:
await client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id))
elif content_type.startswith("application/json"):
dict_body = json.loads(bolt_resp.body)
await client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id, payload=dict_body))
else:
await client.send_socket_mode_response(
SocketModeResponse(
envelope_id=req.envelope_id,
payload={"text": bolt_resp.body},
)
SocketModeResponse(envelope_id=req.envelope_id, payload=_to_payload(content_type, bolt_resp.body))
)
if client.logger.level <= logging.DEBUG:
spent_time = int((time() - start_time) * 1000)
Expand Down
18 changes: 14 additions & 4 deletions slack_bolt/adapter/socket_mode/internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,19 @@ def run_bolt_app(app: App, req: SocketModeRequest):
return bolt_resp


def _to_payload(content_type: str, body: str) -> Dict:
# A text-only ack is labeled as JSON when it starts with "{".
# Only treat it as JSON when it really parses to an object; otherwise send it as text.
if content_type.startswith("application/json"):
try:
parsed = json.loads(body)
if isinstance(parsed, dict):
return parsed
except ValueError:
pass
return {"text": body}


def send_response(
client: BaseSocketModeClient,
req: SocketModeRequest,
Expand All @@ -40,12 +53,9 @@ def send_response(
content_type = bolt_resp.headers.get("content-type", [""])[0]
if bolt_resp.body is None or len(bolt_resp.body) == 0:
client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id))
elif content_type.startswith("application/json"):
dict_body = json.loads(bolt_resp.body)
client.send_socket_mode_response(SocketModeResponse(envelope_id=req.envelope_id, payload=dict_body))
else:
client.send_socket_mode_response(
SocketModeResponse(envelope_id=req.envelope_id, payload={"text": bolt_resp.body})
SocketModeResponse(envelope_id=req.envelope_id, payload=_to_payload(content_type, bolt_resp.body))
)

if client.logger.level <= logging.DEBUG:
Expand Down
23 changes: 23 additions & 0 deletions tests/adapter_tests/socket_mode/test_internals.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,26 @@ def test_build_retry_headers_with_retry(self):
)
headers = build_headers(req)
assert headers == {"x-slack-retry-num": "2", "x-slack-retry-reason": "http_timeout"}

def test_send_response_text_starting_with_brace(self):
# A text ack that happens to start with "{" is not JSON and must be sent as text
from slack_bolt.adapter.socket_mode.internals import send_response
from slack_bolt.response import BoltResponse
import logging

sent = []

class FakeClient:
logger = logging.getLogger("test")

def send_socket_mode_response(self, response):
sent.append(response)

req = SocketModeRequest(type="slash_commands", envelope_id="e1", payload={"command": "/echo"})
send_response(FakeClient(), req, BoltResponse(status=200, body="{foo"), 0.0)
assert len(sent) == 1
assert sent[0].payload == {"text": "{foo"}

sent.clear()
send_response(FakeClient(), req, BoltResponse(status=200, body='{"text": "hi"}'), 0.0)
assert sent[0].payload == {"text": "hi"}
29 changes: 29 additions & 0 deletions tests/adapter_tests_async/socket_mode/test_async_internals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import logging

import pytest
from slack_sdk.socket_mode.request import SocketModeRequest

from slack_bolt.adapter.socket_mode.async_internals import send_async_response
from slack_bolt.response import BoltResponse


class TestAsyncSocketModeInternals:
@pytest.mark.asyncio
async def test_send_response_text_starting_with_brace(self):
# A text ack that happens to start with "{" is not JSON and must be sent as text
sent = []

class FakeClient:
logger = logging.getLogger("test")

async def send_socket_mode_response(self, response):
sent.append(response)

req = SocketModeRequest(type="slash_commands", envelope_id="e1", payload={"command": "/echo"})
await send_async_response(FakeClient(), req, BoltResponse(status=200, body="{foo"), 0.0)
assert len(sent) == 1
assert sent[0].payload == {"text": "{foo"}

sent.clear()
await send_async_response(FakeClient(), req, BoltResponse(status=200, body='{"text": "hi"}'), 0.0)
assert sent[0].payload == {"text": "hi"}