Skip to content

Commit ace3cb8

Browse files
authored
Fix #330 Potentially request.body can be None when using a custom adapter (#331)
* Fix #330 Potentially request.body can be None when using a custom adapter * Add scenario tests
1 parent 31436f7 commit ace3cb8

File tree

8 files changed

+159
-11
lines changed

8 files changed

+159
-11
lines changed

slack_bolt/request/async_request.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
build_normalized_headers,
1010
extract_content_type,
1111
error_message_raw_body_required_in_http_mode,
12-
error_message_unknown_request_body_type,
1312
)
1413

1514

@@ -45,7 +44,7 @@ def __init__(
4544

4645
if mode == "http":
4746
# HTTP Mode
48-
if not isinstance(body, str):
47+
if body is not None and not isinstance(body, str):
4948
raise BoltError(error_message_raw_body_required_in_http_mode())
5049
self.raw_body = body if body is not None else ""
5150
else:
@@ -60,12 +59,14 @@ def __init__(
6059
self.query = parse_query(query)
6160
self.headers = build_normalized_headers(headers)
6261
self.content_type = extract_content_type(self.headers)
62+
6363
if isinstance(body, str):
6464
self.body = parse_body(self.raw_body, self.content_type)
6565
elif isinstance(body, dict):
6666
self.body = body
6767
else:
68-
raise BoltError(error_message_unknown_request_body_type())
68+
self.body = {}
69+
6970
self.context = build_async_context(
7071
AsyncBoltContext(context if context else {}), self.body
7172
)

slack_bolt/request/internals.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,6 @@ def error_message_raw_body_required_in_http_mode() -> str:
183183
return "`body` must be a raw string data when running in the HTTP server mode"
184184

185185

186-
def error_message_unknown_request_body_type() -> str:
187-
return "`body` must be either str or dict"
188-
189-
190186
def debug_multiple_response_urls_detected() -> str:
191187
return (
192188
"`response_urls` in the body has multiple URLs in it. "

slack_bolt/request/request.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
build_context,
1010
extract_content_type,
1111
error_message_raw_body_required_in_http_mode,
12-
error_message_unknown_request_body_type,
1312
)
1413

1514

@@ -44,7 +43,7 @@ def __init__(
4443
"""
4544
if mode == "http":
4645
# HTTP Mode
47-
if not isinstance(body, str):
46+
if body is not None and not isinstance(body, str):
4847
raise BoltError(error_message_raw_body_required_in_http_mode())
4948
self.raw_body = body if body is not None else ""
5049
else:
@@ -59,12 +58,13 @@ def __init__(
5958
self.query = parse_query(query)
6059
self.headers = build_normalized_headers(headers)
6160
self.content_type = extract_content_type(self.headers)
61+
6262
if isinstance(body, str):
6363
self.body = parse_body(self.raw_body, self.content_type)
6464
elif isinstance(body, dict):
6565
self.body = body
6666
else:
67-
raise BoltError(error_message_unknown_request_body_type())
67+
self.body = {}
6868

6969
self.context = build_context(BoltContext(context if context else {}), self.body)
7070
self.lazy_only = bool(self.headers.get("x-slack-bolt-lazy-only", [False])[0])

tests/scenario_tests/test_app.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from slack_sdk.oauth.installation_store import FileInstallationStore
44
from slack_sdk.oauth.state_store import FileOAuthStateStore
55

6-
from slack_bolt import App, Say
6+
from slack_bolt import App, Say, BoltRequest
77
from slack_bolt.authorization import AuthorizeResult
88
from slack_bolt.error import BoltError
99
from slack_bolt.oauth import OAuthFlow
@@ -187,3 +187,38 @@ def test_installation_store_conflicts(self):
187187
installation_store=store1,
188188
)
189189
assert app.installation_store is store1
190+
191+
def test_none_body(self):
192+
app = App(signing_secret="valid", client=self.web_client)
193+
194+
req = BoltRequest(body=None, headers={}, mode="http")
195+
response = app.dispatch(req)
196+
# request verification failure
197+
assert response.status == 401
198+
assert response.body == '{"error": "invalid request"}'
199+
200+
req = BoltRequest(body=None, headers={}, mode="socket_mode")
201+
response = app.dispatch(req)
202+
# request verification is skipped for Socket Mode
203+
assert response.status == 404
204+
assert response.body == '{"error": "unhandled request"}'
205+
206+
def test_none_body_no_middleware(self):
207+
app = App(
208+
signing_secret="valid",
209+
client=self.web_client,
210+
ssl_check_enabled=False,
211+
ignoring_self_events_enabled=False,
212+
request_verification_enabled=False,
213+
token_verification_enabled=False,
214+
url_verification_enabled=False,
215+
)
216+
req = BoltRequest(body=None, headers={}, mode="http")
217+
response = app.dispatch(req)
218+
assert response.status == 404
219+
assert response.body == '{"error": "unhandled request"}'
220+
221+
req = BoltRequest(body=None, headers={}, mode="socket_mode")
222+
response = app.dispatch(req)
223+
assert response.status == 404
224+
assert response.body == '{"error": "unhandled request"}'
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import asyncio
2+
3+
import pytest
4+
from slack_sdk.web.async_client import AsyncWebClient
5+
6+
from slack_bolt.app.async_app import AsyncApp
7+
from slack_bolt.request.async_request import AsyncBoltRequest
8+
from tests.mock_web_api_server import (
9+
setup_mock_web_api_server,
10+
cleanup_mock_web_api_server,
11+
)
12+
from tests.utils import remove_os_env_temporarily, restore_os_env
13+
14+
15+
class TestAsyncAppDispatch:
16+
signing_secret = "secret"
17+
valid_token = "xoxb-valid"
18+
mock_api_server_base_url = "http://localhost:8888"
19+
web_client = AsyncWebClient(token=valid_token, base_url=mock_api_server_base_url)
20+
21+
@pytest.fixture
22+
def event_loop(self):
23+
old_os_env = remove_os_env_temporarily()
24+
try:
25+
setup_mock_web_api_server(self)
26+
loop = asyncio.get_event_loop()
27+
yield loop
28+
loop.close()
29+
cleanup_mock_web_api_server(self)
30+
finally:
31+
restore_os_env(old_os_env)
32+
33+
@pytest.mark.asyncio
34+
async def test_none_body(self):
35+
app = AsyncApp(
36+
client=self.web_client,
37+
signing_secret=self.signing_secret,
38+
)
39+
40+
req = AsyncBoltRequest(body=None, headers={}, mode="http")
41+
response = await app.async_dispatch(req)
42+
# request verification failure
43+
assert response.status == 401
44+
assert response.body == '{"error": "invalid request"}'
45+
46+
req = AsyncBoltRequest(body=None, headers={}, mode="socket_mode")
47+
response = await app.async_dispatch(req)
48+
# request verification is skipped for Socket Mode
49+
assert response.status == 404
50+
assert response.body == '{"error": "unhandled request"}'
51+
52+
@pytest.mark.asyncio
53+
async def test_none_body_no_middleware(self):
54+
app = AsyncApp(
55+
client=self.web_client,
56+
signing_secret=self.signing_secret,
57+
ssl_check_enabled=False,
58+
ignoring_self_events_enabled=False,
59+
request_verification_enabled=False,
60+
# token_verification_enabled=False,
61+
url_verification_enabled=False,
62+
)
63+
64+
req = AsyncBoltRequest(body=None, headers={}, mode="http")
65+
response = await app.async_dispatch(req)
66+
assert response.status == 404
67+
assert response.body == '{"error": "unhandled request"}'
68+
69+
req = AsyncBoltRequest(body=None, headers={}, mode="socket_mode")
70+
response = await app.async_dispatch(req)
71+
assert response.status == 404
72+
assert response.body == '{"error": "unhandled request"}'
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from slack_bolt.request.request import BoltRequest
2+
3+
4+
class TestRequest:
5+
def setup_method(self):
6+
pass
7+
8+
def teardown_method(self):
9+
pass
10+
11+
def test_all_none_inputs_http(self):
12+
req = BoltRequest(body=None, headers=None, query=None, context=None)
13+
assert req is not None
14+
assert req.raw_body == ""
15+
assert req.body == {}
16+
17+
def test_all_none_inputs_socket_mode(self):
18+
req = BoltRequest(
19+
body=None, headers=None, query=None, context=None, mode="socket_mode"
20+
)
21+
assert req is not None
22+
assert req.raw_body == ""
23+
assert req.body == {}

tests/slack_bolt_async/request/__init__.py

Whitespace-only changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import pytest
2+
3+
from slack_bolt.request.async_request import AsyncBoltRequest
4+
5+
6+
class TestAsyncRequest:
7+
@pytest.mark.asyncio
8+
async def test_all_none_values_http(self):
9+
req = AsyncBoltRequest(body=None, headers=None, query=None, context=None)
10+
assert req is not None
11+
assert req.raw_body == ""
12+
assert req.body == {}
13+
14+
@pytest.mark.asyncio
15+
async def test_all_none_values_socket_mode(self):
16+
req = AsyncBoltRequest(
17+
body=None, headers=None, query=None, context=None, mode="socket_mode"
18+
)
19+
assert req is not None
20+
assert req.raw_body == ""
21+
assert req.body == {}

0 commit comments

Comments
 (0)