Skip to content

Commit 78bd61e

Browse files
committed
Add unit tests for OAuthFlow
1 parent 8bf4aad commit 78bd61e

5 files changed

Lines changed: 229 additions & 0 deletions

File tree

tests/mock_web_api_server.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,41 @@ def set_common_headers(self):
2929
"error": "invalid_auth",
3030
}
3131

32+
oauth_v2_access_response = """
33+
{
34+
"ok": true,
35+
"access_token": "xoxb-17653672481-19874698323-pdFZKVeTuE8sk7oOcBrzbqgy",
36+
"token_type": "bot",
37+
"scope": "chat:write,commands",
38+
"bot_user_id": "U0KRQLJ9H",
39+
"app_id": "A0KRD7HC3",
40+
"team": {
41+
"name": "Slack Softball Team",
42+
"id": "T9TK3CUKW"
43+
},
44+
"enterprise": {
45+
"name": "slack-sports",
46+
"id": "E12345678"
47+
},
48+
"authed_user": {
49+
"id": "U1234",
50+
"scope": "chat:write",
51+
"access_token": "xoxp-1234",
52+
"token_type": "user"
53+
}
54+
}
55+
"""
56+
3257
def _handle(self):
3358
self.received_requests[self.path] = self.received_requests.get(self.path, 0) + 1
3459
try:
3560
body = {"ok": True}
61+
if self.path == "/oauth.v2.access":
62+
self.send_response(200)
63+
self.set_common_headers()
64+
self.wfile.write(self.oauth_v2_access_response.encode("utf-8"))
65+
return
66+
3667
if self.is_valid_token():
3768
parsed_path = urlparse(self.path)
3869

tests/slack_bolt/oauth/__init__.py

Whitespace-only changes.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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
8+
from slack_bolt.oauth import OAuthFlow
9+
from tests.mock_web_api_server import (
10+
cleanup_mock_web_api_server,
11+
setup_mock_web_api_server,
12+
)
13+
14+
15+
class TestOAuthFlow:
16+
mock_api_server_base_url = "http://localhost:8888"
17+
18+
def setup_method(self):
19+
setup_mock_web_api_server(self)
20+
21+
def teardown_method(self):
22+
cleanup_mock_web_api_server(self)
23+
24+
def next(self):
25+
pass
26+
27+
def test_instantiation(self):
28+
oauth_flow = OAuthFlow(
29+
client_id="111.222",
30+
client_secret="xxx",
31+
scopes=["chat:write", "commands"],
32+
installation_store=FileInstallationStore(),
33+
oauth_state_store=FileOAuthStateStore(expiration_seconds=120),
34+
)
35+
assert oauth_flow is not None
36+
37+
def test_handle_installation(self):
38+
oauth_flow = OAuthFlow(
39+
client_id="111.222",
40+
client_secret="xxx",
41+
scopes=["chat:write", "commands"],
42+
installation_store=FileInstallationStore(),
43+
oauth_state_store=FileOAuthStateStore(expiration_seconds=120),
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(
61+
client=WebClient(base_url=self.mock_api_server_base_url),
62+
client_id="111.222",
63+
client_secret="xxx",
64+
scopes=["chat:write", "commands"],
65+
installation_store=FileInstallationStore(),
66+
oauth_state_store=FileOAuthStateStore(expiration_seconds=120),
67+
success_url="https://www.example.com/completion",
68+
failure_url="https://www.example.com/failure",
69+
)
70+
state = oauth_flow.issue_new_state(None)
71+
req = BoltRequest(
72+
body="",
73+
query=f"code=foo&state={state}",
74+
headers={"cookie": [f"{oauth_flow.oauth_state_cookie_name}={state}"]},
75+
)
76+
resp = oauth_flow.handle_callback(req)
77+
assert resp.status == 200
78+
assert "https://www.example.com/completion" in resp.body
79+
80+
def test_handle_callback_invalid_state(self):
81+
oauth_flow = OAuthFlow(
82+
client_id="111.222",
83+
client_secret="xxx",
84+
scopes=["chat:write", "commands"],
85+
installation_store=FileInstallationStore(),
86+
oauth_state_store=FileOAuthStateStore(expiration_seconds=120),
87+
)
88+
state = oauth_flow.issue_new_state(None)
89+
req = BoltRequest(
90+
body="",
91+
query=f"code=foo&state=invalid",
92+
headers={"cookie": [f"{oauth_flow.oauth_state_cookie_name}={state}"]},
93+
)
94+
resp = oauth_flow.handle_callback(req)
95+
assert resp.status == 400

tests/slack_bolt_async/oauth/__init__.py

Whitespace-only changes.
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
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.oauth.async_oauth_flow import AsyncOAuthFlow
10+
from slack_bolt.request.async_request import AsyncBoltRequest
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 TestAsyncOAuthFlow:
18+
mock_api_server_base_url = "http://localhost:8888"
19+
20+
@pytest.fixture
21+
def event_loop(self):
22+
setup_mock_web_api_server(self)
23+
loop = asyncio.get_event_loop()
24+
yield loop
25+
loop.close()
26+
cleanup_mock_web_api_server(self)
27+
28+
def next(self):
29+
pass
30+
31+
@pytest.mark.asyncio
32+
async def test_instantiation(self):
33+
oauth_flow = AsyncOAuthFlow(
34+
client_id="111.222",
35+
client_secret="xxx",
36+
scopes=["chat:write", "commands"],
37+
installation_store=FileInstallationStore(),
38+
oauth_state_store=FileOAuthStateStore(expiration_seconds=120),
39+
)
40+
assert oauth_flow is not None
41+
42+
@pytest.mark.asyncio
43+
async def test_handle_installation(self):
44+
oauth_flow = AsyncOAuthFlow(
45+
client_id="111.222",
46+
client_secret="xxx",
47+
scopes=["chat:write", "commands"],
48+
installation_store=FileInstallationStore(),
49+
oauth_state_store=FileOAuthStateStore(expiration_seconds=120),
50+
)
51+
req = AsyncBoltRequest(body="")
52+
resp = await oauth_flow.handle_installation(req)
53+
assert resp.status == 302
54+
url = resp.headers["location"][0]
55+
assert (
56+
re.compile(
57+
"https://slack.com/oauth/v2/authorize\\?state=[-0-9a-z]+."
58+
"&client_id=111\\.222"
59+
"&scope=chat:write,commands"
60+
"&user_scope="
61+
).match(url)
62+
is not None
63+
)
64+
65+
@pytest.mark.asyncio
66+
async def test_handle_callback(self):
67+
oauth_flow = AsyncOAuthFlow(
68+
client=AsyncWebClient(base_url=self.mock_api_server_base_url),
69+
client_id="111.222",
70+
client_secret="xxx",
71+
scopes=["chat:write", "commands"],
72+
installation_store=FileInstallationStore(),
73+
oauth_state_store=FileOAuthStateStore(expiration_seconds=120),
74+
success_url="https://www.example.com/completion",
75+
failure_url="https://www.example.com/failure",
76+
)
77+
state = await oauth_flow.issue_new_state(None)
78+
req = AsyncBoltRequest(
79+
body="",
80+
query=f"code=foo&state={state}",
81+
headers={"cookie": [f"{oauth_flow.oauth_state_cookie_name}={state}"]},
82+
)
83+
resp = await oauth_flow.handle_callback(req)
84+
assert resp.status == 200
85+
assert "https://www.example.com/completion" in resp.body
86+
87+
@pytest.mark.asyncio
88+
async def test_handle_callback_invalid_state(self):
89+
oauth_flow = AsyncOAuthFlow(
90+
client_id="111.222",
91+
client_secret="xxx",
92+
scopes=["chat:write", "commands"],
93+
installation_store=FileInstallationStore(),
94+
oauth_state_store=FileOAuthStateStore(expiration_seconds=120),
95+
)
96+
state = await oauth_flow.issue_new_state(None)
97+
req = AsyncBoltRequest(
98+
body="",
99+
query=f"code=foo&state=invalid",
100+
headers={"cookie": [f"{oauth_flow.oauth_state_cookie_name}={state}"]},
101+
)
102+
resp = await oauth_flow.handle_callback(req)
103+
assert resp.status == 400

0 commit comments

Comments
 (0)