Skip to content

Commit 6fa3fe8

Browse files
alexsohn1126claude
andcommitted
ref(slack): Extract assert_setup_flow into shared test helper
Move the Slack OAuth setup flow assertion logic from SlackIntegrationTest into a standalone assert_slack_setup_flow() function in tests/sentry/integrations/slack/test_helpers.py. Both SlackIntegrationTest and SlackStagingSidegradeFlowTest now delegate to it, fixing mypy errors from cross-class method reuse. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent be5e1ba commit 6fa3fe8

File tree

3 files changed

+115
-104
lines changed

3 files changed

+115
-104
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
from __future__ import annotations
2+
3+
from collections.abc import Mapping
4+
from typing import Any
5+
from unittest.mock import patch
6+
from urllib.parse import parse_qs, urlencode, urlparse
7+
8+
import responses
9+
from slack_sdk.web import SlackResponse
10+
11+
from sentry.testutils.cases import IntegrationTestCase
12+
13+
14+
def assert_slack_setup_flow(
15+
test_case: IntegrationTestCase,
16+
team_id: str = "TXXXXXXX1",
17+
authorizing_user_id: str = "UXXXXXXX1",
18+
expected_client_id: str = "slack-client-id",
19+
expected_client_secret: str = "slack-client-secret",
20+
customer_domain: str | None = None,
21+
init_params: Mapping[str, str] | None = None,
22+
) -> None:
23+
responses.reset()
24+
25+
extra_kwargs: dict[str, Any] = {}
26+
if customer_domain:
27+
extra_kwargs["HTTP_HOST"] = customer_domain
28+
29+
init_path = test_case.init_path
30+
if init_params:
31+
init_path = f"{init_path}?{urlencode(init_params)}"
32+
33+
resp = test_case.client.get(init_path, **extra_kwargs)
34+
assert resp.status_code == 302
35+
redirect = urlparse(resp["Location"])
36+
assert redirect.scheme == "https"
37+
assert redirect.netloc == "slack.com"
38+
assert redirect.path == "/oauth/v2/authorize"
39+
params = parse_qs(redirect.query)
40+
scopes = test_case.provider.identity_oauth_scopes
41+
assert params["scope"] == [" ".join(scopes)]
42+
assert params["state"]
43+
assert params["redirect_uri"] == ["http://testserver/extensions/slack/setup/"]
44+
assert params["response_type"] == ["code"]
45+
assert params["client_id"] == [expected_client_id]
46+
47+
assert params.get("user_scope") == [" ".join(test_case.provider.user_scopes)]
48+
# once we've asserted on it, switch to singular values to make life easier
49+
authorize_params = {k: v[0] for k, v in params.items()}
50+
51+
access_json = {
52+
"ok": True,
53+
"access_token": "xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx",
54+
"scope": ",".join(sorted(test_case.provider.identity_oauth_scopes)),
55+
"team": {"id": team_id, "name": "Example"},
56+
"authed_user": {"id": authorizing_user_id},
57+
}
58+
responses.add(responses.POST, "https://slack.com/api/oauth.v2.access", json=access_json)
59+
60+
response_json = {
61+
"ok": True,
62+
"members": [
63+
{
64+
"id": authorizing_user_id,
65+
"team_id": team_id,
66+
"deleted": False,
67+
"profile": {
68+
"email": test_case.user.email,
69+
"team": team_id,
70+
},
71+
},
72+
],
73+
"response_metadata": {"next_cursor": ""},
74+
}
75+
with patch(
76+
"slack_sdk.web.client.WebClient.users_list",
77+
return_value=SlackResponse(
78+
client=None,
79+
http_verb="GET",
80+
api_url="https://slack.com/api/users.list",
81+
req_args={},
82+
data=response_json,
83+
headers={},
84+
status_code=200,
85+
),
86+
) as mock_post:
87+
test_case.mock_post = mock_post # type: ignore[attr-defined]
88+
resp = test_case.client.get(
89+
"{}?{}".format(
90+
test_case.setup_path,
91+
urlencode({"code": "oauth-code", "state": authorize_params["state"]}),
92+
)
93+
)
94+
95+
if customer_domain:
96+
assert resp.status_code == 302
97+
assert resp["Location"].startswith(f"http://{customer_domain}/extensions/slack/setup/")
98+
resp = test_case.client.get(resp["Location"], **extra_kwargs)
99+
100+
mock_request = responses.calls[0].request
101+
req_params = parse_qs(mock_request.body)
102+
assert req_params["grant_type"] == ["authorization_code"]
103+
assert req_params["code"] == ["oauth-code"]
104+
assert req_params["redirect_uri"] == ["http://testserver/extensions/slack/setup/"]
105+
assert req_params["client_id"] == [expected_client_id]
106+
assert req_params["client_secret"] == [expected_client_secret]
107+
108+
assert resp.status_code == 200
109+
test_case.assertDialogSuccess(resp)

tests/sentry/integrations/slack/test_integration.py

Lines changed: 3 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
from unittest.mock import MagicMock, patch
2-
from urllib.parse import parse_qs, urlencode, urlparse
32

43
import orjson
54
import pytest
65
import responses
76
from responses.matchers import query_string_matcher
87
from slack_sdk.errors import SlackApiError
9-
from slack_sdk.web import SlackResponse
108

119
from sentry import audit_log
1210
from sentry.integrations.models.integration import Integration
@@ -27,6 +25,7 @@
2725
from sentry.testutils.notifications.platform import MockNotification, MockNotificationTemplate
2826
from sentry.testutils.silo import control_silo_test
2927
from sentry.users.models.identity import Identity, IdentityProvider, IdentityStatus
28+
from tests.sentry.integrations.slack.test_helpers import assert_slack_setup_flow
3029

3130

3231
@control_silo_test
@@ -52,104 +51,8 @@ class SlackIntegrationTest(IntegrationTestCase):
5251
def setUp(self) -> None:
5352
super().setUp()
5453

55-
def assert_setup_flow(
56-
self,
57-
team_id="TXXXXXXX1",
58-
authorizing_user_id="UXXXXXXX1",
59-
expected_client_id="slack-client-id",
60-
expected_client_secret="slack-client-secret",
61-
customer_domain=None,
62-
init_params=None,
63-
):
64-
responses.reset()
65-
66-
kwargs = {}
67-
if customer_domain:
68-
kwargs["HTTP_HOST"] = customer_domain
69-
70-
init_path = self.init_path
71-
if init_params:
72-
init_path = f"{init_path}?{urlencode(init_params)}"
73-
74-
resp = self.client.get(init_path, **kwargs)
75-
assert resp.status_code == 302
76-
redirect = urlparse(resp["Location"])
77-
assert redirect.scheme == "https"
78-
assert redirect.netloc == "slack.com"
79-
assert redirect.path == "/oauth/v2/authorize"
80-
params = parse_qs(redirect.query)
81-
scopes = self.provider.identity_oauth_scopes
82-
assert params["scope"] == [" ".join(scopes)]
83-
assert params["state"]
84-
assert params["redirect_uri"] == ["http://testserver/extensions/slack/setup/"]
85-
assert params["response_type"] == ["code"]
86-
assert params["client_id"] == [expected_client_id]
87-
88-
assert params.get("user_scope") == [" ".join(self.provider.user_scopes)]
89-
# once we've asserted on it, switch to a singular values to make life
90-
# easier
91-
authorize_params = {k: v[0] for k, v in params.items()}
92-
93-
access_json = {
94-
"ok": True,
95-
"access_token": "xoxb-xxxxxxxxx-xxxxxxxxxx-xxxxxxxxxxxx",
96-
"scope": ",".join(sorted(self.provider.identity_oauth_scopes)),
97-
"team": {"id": team_id, "name": "Example"},
98-
"authed_user": {"id": authorizing_user_id},
99-
}
100-
responses.add(responses.POST, "https://slack.com/api/oauth.v2.access", json=access_json)
101-
102-
response_json = {
103-
"ok": True,
104-
"members": [
105-
{
106-
"id": authorizing_user_id,
107-
"team_id": team_id,
108-
"deleted": False,
109-
"profile": {
110-
"email": self.user.email,
111-
"team": team_id,
112-
},
113-
},
114-
],
115-
"response_metadata": {"next_cursor": ""},
116-
}
117-
with patch(
118-
"slack_sdk.web.client.WebClient.users_list",
119-
return_value=SlackResponse(
120-
client=None,
121-
http_verb="GET",
122-
api_url="https://slack.com/api/users.list",
123-
req_args={},
124-
data=response_json,
125-
headers={},
126-
status_code=200,
127-
),
128-
) as self.mock_post:
129-
resp = self.client.get(
130-
"{}?{}".format(
131-
self.setup_path,
132-
urlencode({"code": "oauth-code", "state": authorize_params["state"]}),
133-
)
134-
)
135-
136-
if customer_domain:
137-
assert resp.status_code == 302
138-
assert resp["Location"].startswith(
139-
f"http://{customer_domain}/extensions/slack/setup/"
140-
)
141-
resp = self.client.get(resp["Location"], **kwargs)
142-
143-
mock_request = responses.calls[0].request
144-
req_params = parse_qs(mock_request.body)
145-
assert req_params["grant_type"] == ["authorization_code"]
146-
assert req_params["code"] == ["oauth-code"]
147-
assert req_params["redirect_uri"] == ["http://testserver/extensions/slack/setup/"]
148-
assert req_params["client_id"] == [expected_client_id]
149-
assert req_params["client_secret"] == [expected_client_secret]
150-
151-
assert resp.status_code == 200
152-
self.assertDialogSuccess(resp)
54+
def assert_setup_flow(self, **kwargs):
55+
assert_slack_setup_flow(self, **kwargs)
15356

15457
@responses.activate
15558
def test_bot_flow(self, mock_api_call: MagicMock) -> None:

tests/sentry/integrations/slack/test_slack_staging_sidegrade.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
Integration row.
66
"""
77

8-
from typing import Any
98
from unittest import mock
109
from unittest.mock import MagicMock, patch
1110
from urllib.parse import parse_qs, urlencode, urlparse
@@ -23,7 +22,7 @@
2322
from sentry.testutils.helpers import override_options
2423
from sentry.testutils.helpers.features import with_feature
2524
from sentry.testutils.silo import control_silo_test
26-
from tests.sentry.integrations.slack.test_integration import SlackIntegrationTest
25+
from tests.sentry.integrations.slack.test_helpers import assert_slack_setup_flow
2726

2827
STAGING_CLIENT_ID = "staging-client-id"
2928
STAGING_CLIENT_SECRET = "staging-client-secret"
@@ -205,8 +204,8 @@ class SlackStagingSidegradeFlowTest(IntegrationTestCase):
205204

206205
provider = SlackIntegrationProvider
207206

208-
def assert_setup_flow(self, **kwargs: Any) -> None:
209-
SlackIntegrationTest.assert_setup_flow(self, **kwargs)
207+
def assert_setup_flow(self, **kwargs):
208+
assert_slack_setup_flow(self, **kwargs)
210209

211210
@responses.activate
212211
@with_feature({"organizations:slack-staging-app": True})

0 commit comments

Comments
 (0)