Skip to content

Commit 4720705

Browse files
committed
Tighten Quart response handling
1 parent 3c4800f commit 4720705

5 files changed

Lines changed: 68 additions & 5 deletions

File tree

examples/quart/async_app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ async def endpoint():
2626
api.run(host="0.0.0.0", port=int(os.environ.get("PORT", 3000)))
2727

2828

29+
# Requires Python 3.9+
2930
# pip install -r requirements.txt
3031
# export SLACK_SIGNING_SECRET=***
3132
# export SLACK_BOT_TOKEN=xoxb-***

examples/quart/async_oauth_app.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ async def oauth_redirect():
3636
api.run(host="0.0.0.0", port=int(os.environ.get("PORT", 3000)))
3737

3838

39+
# Requires Python 3.9+
3940
# pip install -r requirements.txt
4041

4142
# # -- OAuth flow -- #

examples/quart/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
# Requires Python 3.9+
12
quart>=0.20,<1

slack_bolt/adapter/quart/async_handler.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,14 @@ async def to_quart_response(bolt_resp: BoltResponse) -> Response:
3636

3737
for cookie in bolt_resp.cookies():
3838
for name, c in cookie.items():
39+
max_age = int(c["max-age"]) if c.get("max-age") else None
3940
resp.set_cookie(
4041
key=name,
4142
value=c.value,
42-
max_age=c.get("max-age"),
43-
expires=c.get("expires"),
44-
path=c.get("path"),
45-
domain=c.get("domain"),
43+
max_age=max_age,
44+
expires=c.get("expires") or None,
45+
path=c.get("path") or None,
46+
domain=c.get("domain") or None,
4647
secure=True,
4748
httponly=True,
4849
)

tests/adapter_tests_async/test_async_quart.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from slack_sdk.signature import SignatureVerifier
99
from slack_sdk.web.async_client import AsyncWebClient
1010

11+
from slack_bolt import BoltResponse
1112
from slack_bolt.app.async_app import AsyncApp
1213
from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings
1314
from tests.mock_web_api_server import (
@@ -20,7 +21,7 @@
2021
pytestmark = pytest.mark.skipif(sys.version_info < (3, 9), reason="Quart requires Python 3.9+")
2122

2223
if sys.version_info >= (3, 9):
23-
from slack_bolt.adapter.quart.async_handler import AsyncSlackRequestHandler
24+
from slack_bolt.adapter.quart.async_handler import AsyncSlackRequestHandler, to_quart_response
2425
from quart import Quart, request
2526

2627

@@ -275,6 +276,64 @@ async def test_url_verification(self):
275276
assert json.loads(await response.get_data(as_text=True)) == {"challenge": input["challenge"]}
276277
assert_auth_test_count(self, 0)
277278

279+
@pytest.mark.asyncio
280+
async def test_to_quart_response_preserves_multi_value_headers_and_content_type(self):
281+
api = Quart(__name__)
282+
async with api.app_context():
283+
response = await to_quart_response(
284+
BoltResponse(
285+
status=201,
286+
body="created",
287+
headers={
288+
"content-type": "application/custom",
289+
"x-bolt-test": ["one", "two"],
290+
},
291+
)
292+
)
293+
294+
assert response.status_code == 201
295+
assert await response.get_data(as_text=True) == "created"
296+
assert response.headers.get("content-type") == "application/custom"
297+
assert response.headers.getlist("x-bolt-test") == ["one", "two"]
298+
299+
@pytest.mark.asyncio
300+
async def test_to_quart_response_preserves_cookie_attributes(self):
301+
api = Quart(__name__)
302+
async with api.app_context():
303+
response = await to_quart_response(
304+
BoltResponse(
305+
status=200,
306+
body="",
307+
headers={
308+
"set-cookie": [
309+
"session=abc; Max-Age=60; Path=/install; Domain=example.com",
310+
"bare=xyz",
311+
],
312+
},
313+
)
314+
)
315+
316+
set_cookie_headers = response.headers.getlist("set-cookie")
317+
assert len(set_cookie_headers) == 2
318+
319+
session_cookie = set_cookie_headers[0]
320+
assert "session=abc" in session_cookie
321+
assert "Domain=example.com" in session_cookie
322+
assert "Max-Age=60" in session_cookie
323+
assert "Path=/install" in session_cookie
324+
assert "Secure" in session_cookie
325+
assert "HttpOnly" in session_cookie
326+
assert "Expires=;" not in session_cookie
327+
328+
bare_cookie = set_cookie_headers[1]
329+
assert "bare=xyz" in bare_cookie
330+
assert "Secure" in bare_cookie
331+
assert "HttpOnly" in bare_cookie
332+
assert "Domain=" not in bare_cookie
333+
assert "Expires=" not in bare_cookie
334+
assert "Max-Age=" not in bare_cookie
335+
assert "Path=" not in bare_cookie
336+
278337
@pytest.mark.asyncio
279338
async def test_not_found(self):
280339
app = self.build_app()

0 commit comments

Comments
 (0)