Skip to content

Commit 8df4bf1

Browse files
committed
Add more tests for OAuth modules
1 parent e78578a commit 8df4bf1

7 files changed

Lines changed: 303 additions & 5 deletions

File tree

slack_bolt/oauth/async_oauth_flow.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def sqlite3(
9595
# state parameter related configurations
9696
state_cookie_name: str = OAuthStateUtils.default_cookie_name,
9797
state_expiration_seconds: int = OAuthStateUtils.default_expiration_seconds,
98+
client: Optional[AsyncWebClient] = None,
9899
logger: Optional[Logger] = None,
99100
) -> "AsyncOAuthFlow":
100101

@@ -104,7 +105,7 @@ def sqlite3(
104105
user_scopes = user_scopes or os.environ.get("SLACK_USER_SCOPES", "").split(",")
105106
redirect_uri = redirect_uri or os.environ.get("SLACK_REDIRECT_URI", None)
106107
return AsyncOAuthFlow(
107-
client=AsyncWebClient(),
108+
client=client or AsyncWebClient(),
108109
logger=logger,
109110
settings=AsyncOAuthSettings(
110111
# OAuth flow parameters/credentials

slack_bolt/oauth/oauth_flow.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def sqlite3(
9595
# state parameter related configurations
9696
state_cookie_name: str = OAuthStateUtils.default_cookie_name,
9797
state_expiration_seconds: int = OAuthStateUtils.default_expiration_seconds,
98+
client: Optional[WebClient] = None,
9899
logger: Optional[Logger] = None,
99100
) -> "OAuthFlow":
100101

@@ -104,7 +105,7 @@ def sqlite3(
104105
user_scopes = user_scopes or os.environ.get("SLACK_USER_SCOPES", "").split(",")
105106
redirect_uri = redirect_uri or os.environ.get("SLACK_REDIRECT_URI", None)
106107
return OAuthFlow(
107-
client=WebClient(),
108+
client=client or WebClient(),
108109
logger=logger,
109110
settings=OAuthSettings(
110111
# OAuth flow parameters/credentials

tests/mock_web_api_server.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ def set_common_headers(self):
5353
}
5454
}
5555
"""
56+
auth_test_response = """
57+
{
58+
"ok": true,
59+
"url": "https://subarachnoid.slack.com/",
60+
"team": "Subarachnoid Workspace",
61+
"user": "bot",
62+
"team_id": "T0G9PQBBK",
63+
"user_id": "W23456789",
64+
"bot_id": "BZYBOTHED"
65+
}
66+
"""
5667

5768
def _handle(self):
5869
self.received_requests[self.path] = self.received_requests.get(self.path, 0) + 1
@@ -67,6 +78,12 @@ def _handle(self):
6778
if self.is_valid_token():
6879
parsed_path = urlparse(self.path)
6980

81+
if self.path == "/auth.test":
82+
self.send_response(200)
83+
self.set_common_headers()
84+
self.wfile.write(self.auth_test_response.encode("utf-8"))
85+
return
86+
7087
len_header = self.headers.get("Content-Length") or 0
7188
content_len = int(len_header)
7289
post_body = self.rfile.read(content_len)

tests/slack_bolt/oauth/test_oauth_flow.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ def setup_method(self):
2323
def teardown_method(self):
2424
cleanup_mock_web_api_server(self)
2525

26-
def next(self):
27-
pass
28-
2926
def test_instantiation(self):
3027
oauth_flow = OAuthFlow(
3128
settings=OAuthSettings(
@@ -37,6 +34,8 @@ def test_instantiation(self):
3734
)
3835
)
3936
assert oauth_flow is not None
37+
assert oauth_flow.logger is not None
38+
assert oauth_flow.client is not None
4039

4140
def test_handle_installation(self):
4241
oauth_flow = OAuthFlow(
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import re
2+
3+
from slack_sdk import WebClient
4+
from slack_sdk.oauth.installation_store import FileInstallationStore
5+
from slack_sdk.oauth.state_store import FileOAuthStateStore
6+
7+
from slack_bolt import BoltRequest, BoltResponse
8+
from slack_bolt.oauth import OAuthFlow
9+
from slack_bolt.oauth.callback_options import CallbackOptions, SuccessArgs, FailureArgs
10+
from slack_bolt.oauth.oauth_settings import OAuthSettings
11+
from tests.mock_web_api_server import (
12+
cleanup_mock_web_api_server,
13+
setup_mock_web_api_server,
14+
)
15+
16+
17+
class TestOAuthFlowSQLite3:
18+
mock_api_server_base_url = "http://localhost:8888"
19+
20+
def setup_method(self):
21+
setup_mock_web_api_server(self)
22+
23+
def teardown_method(self):
24+
cleanup_mock_web_api_server(self)
25+
26+
def test_instantiation(self):
27+
oauth_flow = OAuthFlow.sqlite3(
28+
database="./logs/test_db",
29+
client_id="111.222",
30+
client_secret="xxx",
31+
scopes=["chat:write", "commands"],
32+
)
33+
assert oauth_flow is not None
34+
assert oauth_flow.logger is not None
35+
assert oauth_flow.client is not None
36+
37+
def test_handle_installation(self):
38+
oauth_flow = OAuthFlow.sqlite3(
39+
client=WebClient(base_url=self.mock_api_server_base_url),
40+
database="./logs/test_db",
41+
client_id="111.222",
42+
client_secret="xxx",
43+
scopes=["chat:write", "commands"],
44+
)
45+
req = BoltRequest(body="")
46+
resp = oauth_flow.handle_installation(req)
47+
assert resp.status == 302
48+
url = resp.headers["location"][0]
49+
assert (
50+
re.compile(
51+
"https://slack.com/oauth/v2/authorize\\?state=[-0-9a-z]+."
52+
"&client_id=111\\.222"
53+
"&scope=chat:write,commands"
54+
"&user_scope="
55+
).match(url)
56+
is not None
57+
)
58+
59+
def test_handle_callback(self):
60+
oauth_flow = OAuthFlow.sqlite3(
61+
client=WebClient(base_url=self.mock_api_server_base_url),
62+
database="./logs/test_db",
63+
client_id="111.222",
64+
client_secret="xxx",
65+
scopes=["chat:write", "commands"],
66+
success_url="https://www.example.com/completion",
67+
failure_url="https://www.example.com/failure",
68+
)
69+
state = oauth_flow.issue_new_state(None)
70+
req = BoltRequest(
71+
body="",
72+
query=f"code=foo&state={state}",
73+
headers={"cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"]},
74+
)
75+
resp = oauth_flow.handle_callback(req)
76+
assert resp.status == 200
77+
assert "https://www.example.com/completion" in resp.body
78+
79+
def test_handle_callback_invalid_state(self):
80+
oauth_flow = OAuthFlow.sqlite3(
81+
client=WebClient(base_url=self.mock_api_server_base_url),
82+
database="./logs/test_db",
83+
client_id="111.222",
84+
client_secret="xxx",
85+
scopes=["chat:write", "commands"],
86+
)
87+
state = oauth_flow.issue_new_state(None)
88+
req = BoltRequest(
89+
body="",
90+
query=f"code=foo&state=invalid",
91+
headers={"cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"]},
92+
)
93+
resp = oauth_flow.handle_callback(req)
94+
assert resp.status == 400
95+
96+
def test_handle_callback_using_options(self):
97+
def success(args: SuccessArgs) -> BoltResponse:
98+
assert args.request is not None
99+
return BoltResponse(status=200, body="customized")
100+
101+
def failure(args: FailureArgs) -> BoltResponse:
102+
assert args.request is not None
103+
assert args.reason is not None
104+
return BoltResponse(status=502, body="customized")
105+
106+
oauth_flow = OAuthFlow.sqlite3(
107+
client=WebClient(base_url=self.mock_api_server_base_url),
108+
database="./logs/test_db",
109+
client_id="111.222",
110+
client_secret="xxx",
111+
scopes=["chat:write", "commands"],
112+
callback_options=CallbackOptions(success=success, failure=failure),
113+
)
114+
state = oauth_flow.issue_new_state(None)
115+
req = BoltRequest(
116+
body="",
117+
query=f"code=foo&state={state}",
118+
headers={"cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"]},
119+
)
120+
resp = oauth_flow.handle_callback(req)
121+
assert resp.status == 200
122+
assert resp.body == "customized"
123+
124+
state = oauth_flow.issue_new_state(None)
125+
req = BoltRequest(
126+
body="",
127+
query=f"code=foo&state=invalid",
128+
headers={"cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"]},
129+
)
130+
resp = oauth_flow.handle_callback(req)
131+
assert resp.status == 502
132+
assert resp.body == "customized"

tests/slack_bolt_async/oauth/test_async_oauth_flow.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ async def test_instantiation(self):
4747
)
4848
)
4949
assert oauth_flow is not None
50+
assert oauth_flow.logger is not None
51+
assert oauth_flow.client is not None
5052

5153
@pytest.mark.asyncio
5254
async def test_handle_installation(self):
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
import asyncio
2+
import re
3+
4+
import pytest
5+
from slack_sdk.oauth.installation_store import FileInstallationStore
6+
from slack_sdk.oauth.state_store import FileOAuthStateStore
7+
from slack_sdk.web.async_client import AsyncWebClient
8+
9+
from slack_bolt import BoltResponse
10+
from slack_bolt.oauth.async_callback_options import (
11+
AsyncFailureArgs,
12+
AsyncSuccessArgs,
13+
AsyncCallbackOptions,
14+
)
15+
from slack_bolt.oauth.async_oauth_flow import AsyncOAuthFlow
16+
from slack_bolt.oauth.async_oauth_settings import AsyncOAuthSettings
17+
from slack_bolt.request.async_request import AsyncBoltRequest
18+
from tests.mock_web_api_server import (
19+
cleanup_mock_web_api_server,
20+
setup_mock_web_api_server,
21+
)
22+
23+
24+
class TestAsyncOAuthFlowSQLite3:
25+
mock_api_server_base_url = "http://localhost:8888"
26+
27+
@pytest.fixture
28+
def event_loop(self):
29+
setup_mock_web_api_server(self)
30+
loop = asyncio.get_event_loop()
31+
yield loop
32+
loop.close()
33+
cleanup_mock_web_api_server(self)
34+
35+
def next(self):
36+
pass
37+
38+
@pytest.mark.asyncio
39+
async def test_instantiation(self):
40+
oauth_flow = AsyncOAuthFlow.sqlite3(
41+
database="./logs/test_db",
42+
client_id="111.222",
43+
client_secret="xxx",
44+
scopes=["chat:write", "commands"],
45+
)
46+
assert oauth_flow is not None
47+
assert oauth_flow.logger is not None
48+
assert oauth_flow.client is not None
49+
50+
@pytest.mark.asyncio
51+
async def test_handle_installation(self):
52+
oauth_flow = AsyncOAuthFlow.sqlite3(
53+
database="./logs/test_db",
54+
client_id="111.222",
55+
client_secret="xxx",
56+
scopes=["chat:write", "commands"],
57+
)
58+
req = AsyncBoltRequest(body="")
59+
resp = await oauth_flow.handle_installation(req)
60+
assert resp.status == 302
61+
url = resp.headers["location"][0]
62+
assert (
63+
re.compile(
64+
"https://slack.com/oauth/v2/authorize\\?state=[-0-9a-z]+."
65+
"&client_id=111\\.222"
66+
"&scope=chat:write,commands"
67+
"&user_scope="
68+
).match(url)
69+
is not None
70+
)
71+
72+
@pytest.mark.asyncio
73+
async def test_handle_callback(self):
74+
oauth_flow = AsyncOAuthFlow.sqlite3(
75+
database="./logs/test_db",
76+
client=AsyncWebClient(base_url=self.mock_api_server_base_url),
77+
client_id="111.222",
78+
client_secret="xxx",
79+
scopes=["chat:write", "commands"],
80+
success_url="https://www.example.com/completion",
81+
failure_url="https://www.example.com/failure",
82+
)
83+
state = await oauth_flow.issue_new_state(None)
84+
req = AsyncBoltRequest(
85+
body="",
86+
query=f"code=foo&state={state}",
87+
headers={"cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"]},
88+
)
89+
resp = await oauth_flow.handle_callback(req)
90+
assert resp.status == 200
91+
assert "https://www.example.com/completion" in resp.body
92+
93+
@pytest.mark.asyncio
94+
async def test_handle_callback_invalid_state(self):
95+
oauth_flow = AsyncOAuthFlow.sqlite3(
96+
database="./logs/test_db",
97+
client_id="111.222",
98+
client_secret="xxx",
99+
scopes=["chat:write", "commands"],
100+
)
101+
state = await oauth_flow.issue_new_state(None)
102+
req = AsyncBoltRequest(
103+
body="",
104+
query=f"code=foo&state=invalid",
105+
headers={"cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"]},
106+
)
107+
resp = await oauth_flow.handle_callback(req)
108+
assert resp.status == 400
109+
110+
@pytest.mark.asyncio
111+
async def test_handle_callback_using_options(self):
112+
async def success(args: AsyncSuccessArgs) -> BoltResponse:
113+
assert args.request is not None
114+
return BoltResponse(status=200, body="customized")
115+
116+
async def failure(args: AsyncFailureArgs) -> BoltResponse:
117+
assert args.request is not None
118+
assert args.reason is not None
119+
return BoltResponse(status=502, body="customized")
120+
121+
oauth_flow = AsyncOAuthFlow.sqlite3(
122+
client=AsyncWebClient(base_url=self.mock_api_server_base_url),
123+
database="./logs/test_db",
124+
client_id="111.222",
125+
client_secret="xxx",
126+
scopes=["chat:write", "commands"],
127+
callback_options=AsyncCallbackOptions(success=success, failure=failure,),
128+
)
129+
state = await oauth_flow.issue_new_state(None)
130+
req = AsyncBoltRequest(
131+
body="",
132+
query=f"code=foo&state={state}",
133+
headers={"cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"]},
134+
)
135+
resp = await oauth_flow.handle_callback(req)
136+
assert resp.status == 200
137+
assert resp.body == "customized"
138+
139+
req = AsyncBoltRequest(
140+
body="",
141+
query=f"code=foo&state=invalid",
142+
headers={"cookie": [f"{oauth_flow.settings.state_cookie_name}={state}"]},
143+
)
144+
resp = await oauth_flow.handle_callback(req)
145+
assert resp.status == 502
146+
assert resp.body == "customized"

0 commit comments

Comments
 (0)