X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions slack_bolt/app/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -590,13 +590,13 @@ def __call__(*args, **kwargs):

def block_action(
self,
action_id: Union[str, Pattern],
constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
matchers: Optional[List[Callable[..., bool]]] = None,
middleware: Optional[List[Union[Callable, Middleware]]] = None,
):
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
primary_matcher = builtin_matchers.block_action(action_id)
primary_matcher = builtin_matchers.block_action(constraints)
return self._register_listener(
list(functions), primary_matcher, matchers, middleware
)
Expand Down
4 changes: 2 additions & 2 deletions slack_bolt/app/async_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,13 +622,13 @@ def __call__(*args, **kwargs):

def block_action(
self,
action_id: Union[str, Pattern],
constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
matchers: Optional[List[Callable[..., Awaitable[bool]]]] = None,
middleware: Optional[List[Union[Callable, AsyncMiddleware]]] = None,
):
def __call__(*args, **kwargs):
functions = self._to_listener_functions(kwargs) if kwargs else list(args)
primary_matcher = builtin_matchers.block_action(action_id, True)
primary_matcher = builtin_matchers.block_action(constraints, True)
return self._register_listener(
list(functions), primary_matcher, matchers, middleware
)
Expand Down
28 changes: 22 additions & 6 deletions slack_bolt/listener_matcher/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ def action(
elif "type" in constraints:
action_type = constraints["type"]
if action_type == "block_actions":
return block_action(constraints["action_id"], asyncio)
return block_action(constraints, asyncio)
if action_type == "interactive_message":
return attachment_action(constraints["callback_id"], asyncio)
if action_type == "dialog_submission":
Expand All @@ -197,19 +197,35 @@ def action(
return workflow_step_edit(constraints["callback_id"], asyncio)

raise BoltError(f"type: {action_type} is unsupported")
elif "action_id" in constraints:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the key change.

# The default value is "block_actions"
return block_action(constraints, asyncio)

raise BoltError(
f"action ({constraints}: {type(constraints)}) must be any of str, Pattern, and dict"
)


def block_action(
action_id: Union[str, Pattern], asyncio: bool = False,
constraints: Union[str, Pattern, Dict[str, Union[str, Pattern]]],
asyncio: bool = False,
) -> Union[ListenerMatcher, "AsyncListenerMatcher"]:
def func(body: Dict[str, Any]) -> bool:
return is_block_actions(body) and _matches(
action_id, to_action(body)["action_id"]
)
if is_block_actions(body) is False:
return False

action = to_action(body)
if isinstance(constraints, (str, Pattern)):
action_id = constraints
return _matches(action_id, action["action_id"])
elif isinstance(constraints, dict):
# block_id matching is optional
block_id: Optional[Union[str, Pattern]] = constraints.get("block_id")
block_id_matched = block_id is None or _matches(
block_id, action.get("block_id")
)
action_id_matched = _matches(constraints["action_id"], action["action_id"])
return block_id_matched and action_id_matched

return build_listener_matcher(func, asyncio)

Expand Down Expand Up @@ -347,7 +363,7 @@ def _matches(str_or_pattern: Union[str, Pattern], input: Optional[str]) -> bool:
return input == exact_match_str
elif isinstance(str_or_pattern, Pattern):
pattern: Pattern = str_or_pattern
return pattern.search(input)
return pattern.search(input) is not None
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to make the type consistent - bool

else:
raise BoltError(
f"{str_or_pattern} ({type(str_or_pattern)}) must be either str or Pattern"
Expand Down
30 changes: 30 additions & 0 deletions tests/async_scenario_tests/test_block_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,36 @@ async def test_success_2(self):
assert response.status == 200
assert self.mock_received_requests["/auth.test"] == 1

@pytest.mark.asyncio
async def test_default_type(self):
app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret,)
app.action({"action_id": "a", "block_id": "b"})(simple_listener)

request = self.build_valid_request()
response = await app.async_dispatch(request)
assert response.status == 200
assert self.mock_received_requests["/auth.test"] == 1

@pytest.mark.asyncio
async def test_default_type_no_block_id(self):
app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret,)
app.action({"action_id": "a"})(simple_listener)

request = self.build_valid_request()
response = await app.async_dispatch(request)
assert response.status == 200
assert self.mock_received_requests["/auth.test"] == 1

@pytest.mark.asyncio
async def test_default_type_unmatched_block_id(self):
app = AsyncApp(client=self.web_client, signing_secret=self.signing_secret,)
app.action({"action_id": "a", "block_id": "bbb"})(simple_listener)

request = self.build_valid_request()
response = await app.async_dispatch(request)
assert response.status == 404
assert self.mock_received_requests["/auth.test"] == 1

@pytest.mark.asyncio
async def test_process_before_response(self):
app = AsyncApp(
Expand Down
27 changes: 27 additions & 0 deletions tests/scenario_tests/test_block_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,33 @@ def test_process_before_response(self):
assert response.status == 200
assert self.mock_received_requests["/auth.test"] == 1

def test_default_type(self):
app = App(client=self.web_client, signing_secret=self.signing_secret)
app.action({"action_id": "a", "block_id": "b"})(simple_listener)

request = self.build_valid_request()
response = app.dispatch(request)
assert response.status == 200
assert self.mock_received_requests["/auth.test"] == 1

def test_default_type_no_block_id(self):
app = App(client=self.web_client, signing_secret=self.signing_secret)
app.action({"action_id": "a"})(simple_listener)

request = self.build_valid_request()
response = app.dispatch(request)
assert response.status == 200
assert self.mock_received_requests["/auth.test"] == 1

def test_default_type_and_unmatched_block_id(self):
app = App(client=self.web_client, signing_secret=self.signing_secret)
app.action({"action_id": "a", "block_id": "bbb"})(simple_listener)

request = self.build_valid_request()
response = app.dispatch(request)
assert response.status == 404
assert self.mock_received_requests["/auth.test"] == 1

def test_failure(self):
app = App(client=self.web_client, signing_secret=self.signing_secret,)
request = self.build_valid_request()
Expand Down
Empty file.
97 changes: 97 additions & 0 deletions tests/slack_bolt/listener_matcher/test_builtins.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import json
import re
from urllib.parse import quote

from slack_bolt import BoltRequest, BoltResponse
from slack_bolt.listener_matcher.builtins import block_action, action


class TestBuiltins:
def setup_method(self):
pass

def teardown_method(self):
pass

def test_block_action(self):
body = {
"type": "block_actions",
"actions": [
{
"type": "button",
"action_id": "valid_action_id",
"block_id": "b",
"action_ts": "111.222",
"value": "v",
}
],
}
raw_body = f"payload={quote(json.dumps(body))}"
headers = {"Content-Type": "application/x-www-form-urlencoded"}
req = BoltRequest(body=raw_body, headers=headers)
resp = BoltResponse(status=404)

assert block_action("valid_action_id").matches(req, resp) is True
assert block_action("invalid_action_id").matches(req, resp) is False
assert block_action(re.compile("valid_.+")).matches(req, resp) is True
assert block_action(re.compile("invalid_.+")).matches(req, resp) is False

assert action("valid_action_id").matches(req, resp) is True
assert action("invalid_action_id").matches(req, resp) is False
assert action(re.compile("valid_.+")).matches(req, resp) is True
assert action(re.compile("invalid_.+")).matches(req, resp) is False

assert action({"action_id": "valid_action_id"}).matches(req, resp) is True
assert action({"action_id": "invalid_action_id"}).matches(req, resp) is False
assert action({"action_id": re.compile("valid_.+")}).matches(req, resp) is True
assert (
action({"action_id": re.compile("invalid_.+")}).matches(req, resp) is False
)

assert (
action({"action_id": "valid_action_id", "block_id": "b"}).matches(req, resp)
is True
)
assert (
action({"action_id": "invalid_action_id", "block_id": "b"}).matches(
req, resp
)
is False
)
assert (
action({"action_id": re.compile("valid_.+"), "block_id": "b"}).matches(
req, resp
)
is True
)
assert (
action({"action_id": re.compile("invalid_.+"), "block_id": "b"}).matches(
req, resp
)
is False
)

assert (
action({"action_id": "valid_action_id", "block_id": "bbb"}).matches(
req, resp
)
is False
)
assert (
action({"action_id": "invalid_action_id", "block_id": "bbb"}).matches(
req, resp
)
is False
)
assert (
action({"action_id": re.compile("valid_.+"), "block_id": "bbb"}).matches(
req, resp
)
is False
)
assert (
action({"action_id": re.compile("invalid_.+"), "block_id": "bbb"}).matches(
req, resp
)
is False
)
X Tutup