Skip to content
2 changes: 2 additions & 0 deletions slack_sdk/web/async_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ def __init__(
):
self.token = None if token is None else token.strip()
"""A string specifying an `xoxp-*` or `xoxb-*` token."""
if not base_url.endswith("/"):
base_url += "/"
self.base_url = base_url
"""A string representing the Slack API base URL.
Default is `'https://slack.com/api/'`."""
Expand Down
2 changes: 2 additions & 0 deletions slack_sdk/web/base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ def __init__(
):
self.token = None if token is None else token.strip()
"""A string specifying an `xoxp-*` or `xoxb-*` token."""
if not base_url.endswith("/"):
base_url += "/"
self.base_url = base_url
"""A string representing the Slack API base URL.
Default is `'https://slack.com/api/'`."""
Expand Down
2 changes: 2 additions & 0 deletions slack_sdk/web/internal_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ def _get_url(base_url: str, api_method: str) -> str:
The absolute API URL.
e.g. 'https://slack.com/api/chat.postMessage'
"""
# Ensure no leading slash in api_method to prevent double slashes
api_method = api_method.lstrip("/")
return urljoin(base_url, api_method)


Expand Down
2 changes: 2 additions & 0 deletions slack_sdk/web/legacy_base_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ def __init__(
):
self.token = None if token is None else token.strip()
"""A string specifying an `xoxp-*` or `xoxb-*` token."""
if not base_url.endswith("/"):
base_url += "/"
self.base_url = base_url
"""A string representing the Slack API base URL.
Default is `'https://slack.com/api/'`."""
Expand Down
26 changes: 26 additions & 0 deletions tests/slack_sdk/web/test_internal_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
_parse_web_class_objects,
_to_v2_file_upload_item,
_next_cursor_is_present,
_get_url,
)


Expand Down Expand Up @@ -108,3 +109,28 @@ def test_next_cursor_is_present(self):
assert _next_cursor_is_present({"response_metadata": {"next_cursor": ""}}) is False
assert _next_cursor_is_present({"response_metadata": {"next_cursor": None}}) is False
assert _next_cursor_is_present({"something_else": {"next_cursor": "next-page"}}) is False

def test_get_url_prevent_double_slash(self):
# Test case: Prevent double slash when both base_url and api_method include slashes
api_url = _get_url("https://slack.com/api/", "/chat.postMessage")
self.assertEqual(
api_url,
"https://slack.com/api/chat.postMessage",
"Should correctly handle and remove double slashes between base_url and api_method",
)

# Test case: Handle api_method without leading slash
api_url = _get_url("https://slack.com/api/", "chat.postMessage")
self.assertEqual(
api_url,
"https://slack.com/api/chat.postMessage",
"Should correctly handle api_method without a leading slash",
)

# Test case: Both inputs are clean
api_url = _get_url("https://slack.com/api/", "chat.postMessage")
self.assertEqual(
api_url,
"https://slack.com/api/chat.postMessage",
"Should correctly combine base_url and api_method with clean inputs",
)
8 changes: 8 additions & 0 deletions tests/slack_sdk/web/test_web_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,11 @@ def test_user_auth_blocks(self):
user_auth_blocks=[DividerBlock(), DividerBlock()],
)
self.assertIsNone(new_message.get("error"))

def test_base_url_appends_trailing_slash_issue_15141(self):
client = self.client
self.assertEqual(client.base_url, "http://localhost:8888/")

def test_base_url_preserves_trailing_slash_issue_15141(self):
client = WebClient(base_url="http://localhost:8888/")
self.assertEqual(client.base_url, "http://localhost:8888/")
Loading