From c5da98df9355d1e1006dcb871535f9207b5aff13 Mon Sep 17 00:00:00 2001 From: Jeremy Lujan Date: Thu, 25 Mar 2021 09:51:43 -0500 Subject: [PATCH 1/4] Add support for lazy listeners when running with chalice local --- .../adapter/aws_lambda/chalice_handler.py | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/slack_bolt/adapter/aws_lambda/chalice_handler.py b/slack_bolt/adapter/aws_lambda/chalice_handler.py index 8d5fc9b21..a67abb117 100644 --- a/slack_bolt/adapter/aws_lambda/chalice_handler.py +++ b/slack_bolt/adapter/aws_lambda/chalice_handler.py @@ -1,6 +1,12 @@ import logging +import json +from os import getenv from chalice.app import Request, Response, Chalice +from chalice.config import Config +from chalice.test import ( + BaseClient, LambdaContext, InvokeResponse +) from slack_bolt.adapter.aws_lambda.chalice_lazy_listener_runner import ( ChaliceLazyListenerRunner, @@ -13,14 +19,41 @@ from slack_bolt.response import BoltResponse +class LocalLambdaClient(BaseClient): + """Lambda client implementing `invoke` for use when running with Chalice CLI""" + def __init__(self, app, config): + # type: (Chalice, Config) -> None + self._app = app + self._config = config + + def invoke(self, FunctionName: str = None, InvocationType: str = "Event", Payload: str = None): + # type: (str, Any) -> InvokeResponse + if Payload is None: + Payload = '{}' + scoped = self._config.scope(self._config.chalice_stage, FunctionName) + lambda_context = LambdaContext( + FunctionName, memory_size=scoped.lambda_memory_size) + + with self._patched_env_vars(scoped.environment_variables): + response = self._app(json.loads(Payload), lambda_context) + return InvokeResponse(payload=response) + + class ChaliceSlackRequestHandler: def __init__(self, app: App, chalice: Chalice): # type: ignore self.app = app self.chalice = chalice self.logger = get_bolt_app_logger(app.name, ChaliceSlackRequestHandler) + + lambda_client = None + if getenv('AWS_CHALICE_CLI_MODE') == 'true': + lambda_client = LocalLambdaClient(self.chalice, Config()) + self.app.listener_runner.lazy_listener_runner = ChaliceLazyListenerRunner( - logger=self.logger + logger=self.logger, + lambda_client=lambda_client ) + if self.app.oauth_flow is not None: self.app.oauth_flow.settings.redirect_uri_page_renderer.install_path = "?" From 19d814e2ad08c95c404dec9560a0bbe38c1adcdd Mon Sep 17 00:00:00 2001 From: Jeremy Lujan Date: Thu, 25 Mar 2021 14:44:37 -0500 Subject: [PATCH 2/4] Fix types in LocalLambdaClient --- .../adapter/aws_lambda/chalice_handler.py | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/slack_bolt/adapter/aws_lambda/chalice_handler.py b/slack_bolt/adapter/aws_lambda/chalice_handler.py index a67abb117..12a4f3ed4 100644 --- a/slack_bolt/adapter/aws_lambda/chalice_handler.py +++ b/slack_bolt/adapter/aws_lambda/chalice_handler.py @@ -4,9 +4,7 @@ from chalice.app import Request, Response, Chalice from chalice.config import Config -from chalice.test import ( - BaseClient, LambdaContext, InvokeResponse -) +from chalice.test import BaseClient, LambdaContext, InvokeResponse from slack_bolt.adapter.aws_lambda.chalice_lazy_listener_runner import ( ChaliceLazyListenerRunner, @@ -21,18 +19,23 @@ class LocalLambdaClient(BaseClient): """Lambda client implementing `invoke` for use when running with Chalice CLI""" - def __init__(self, app, config): - # type: (Chalice, Config) -> None + + def __init__(self, app: Chalice, config: Config) -> None: self._app = app self._config = config - def invoke(self, FunctionName: str = None, InvocationType: str = "Event", Payload: str = None): - # type: (str, Any) -> InvokeResponse + def invoke( + self, + FunctionName: str = None, + InvocationType: str = "Event", + Payload: str = None, + ) -> InvokeResponse: if Payload is None: - Payload = '{}' + Payload = "{}" scoped = self._config.scope(self._config.chalice_stage, FunctionName) lambda_context = LambdaContext( - FunctionName, memory_size=scoped.lambda_memory_size) + FunctionName, memory_size=scoped.lambda_memory_size + ) with self._patched_env_vars(scoped.environment_variables): response = self._app(json.loads(Payload), lambda_context) @@ -46,12 +49,11 @@ def __init__(self, app: App, chalice: Chalice): # type: ignore self.logger = get_bolt_app_logger(app.name, ChaliceSlackRequestHandler) lambda_client = None - if getenv('AWS_CHALICE_CLI_MODE') == 'true': + if getenv("AWS_CHALICE_CLI_MODE") == "true": lambda_client = LocalLambdaClient(self.chalice, Config()) self.app.listener_runner.lazy_listener_runner = ChaliceLazyListenerRunner( - logger=self.logger, - lambda_client=lambda_client + logger=self.logger, lambda_client=lambda_client ) if self.app.oauth_flow is not None: From 76c50835141cd5aee53fd91afd20bdd0f05f010a Mon Sep 17 00:00:00 2001 From: Jeremy Lujan Date: Tue, 30 Mar 2021 14:45:58 -0500 Subject: [PATCH 3/4] Add tests for chalice local lazy through LocalLambdaClient --- .../adapter/aws_lambda/chalice_handler.py | 4 +- tests/adapter_tests/aws/test_aws_chalice.py | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/slack_bolt/adapter/aws_lambda/chalice_handler.py b/slack_bolt/adapter/aws_lambda/chalice_handler.py index 12a4f3ed4..f4c7220ba 100644 --- a/slack_bolt/adapter/aws_lambda/chalice_handler.py +++ b/slack_bolt/adapter/aws_lambda/chalice_handler.py @@ -28,10 +28,8 @@ def invoke( self, FunctionName: str = None, InvocationType: str = "Event", - Payload: str = None, + Payload: str = "{}", ) -> InvokeResponse: - if Payload is None: - Payload = "{}" scoped = self._config.scope(self._config.chalice_stage, FunctionName) lambda_context = LambdaContext( FunctionName, memory_size=scoped.lambda_memory_size diff --git a/tests/adapter_tests/aws/test_aws_chalice.py b/tests/adapter_tests/aws/test_aws_chalice.py index ad9263550..83ec0a3fe 100644 --- a/tests/adapter_tests/aws/test_aws_chalice.py +++ b/tests/adapter_tests/aws/test_aws_chalice.py @@ -1,12 +1,16 @@ import json +import os from time import time from typing import Dict, Any from urllib.parse import quote +from unittest import mock +import logging from chalice import Chalice, Response from chalice.app import Request from chalice.config import Config from chalice.local import LocalGateway +from chalice.test import Client from slack_sdk.signature import SignatureVerifier from slack_sdk.web import WebClient @@ -265,6 +269,67 @@ def say_it(say): assert_auth_test_count(self, 1) assert self.mock_received_requests["/chat.postMessage"] == 1 + def test_lazy_listeners_cli(self): + with mock.patch.dict(os.environ, {"AWS_CHALICE_CLI_MODE": "true"}): + assert os.environ.get("AWS_CHALICE_CLI_MODE") == "true" + app = App( + client=self.web_client, + signing_secret=self.signing_secret, + process_before_response=True + ) + + def command_handler(ack): + ack() + + def say_it(say): + say("Done!") + + app.command("/hello-world")(ack=command_handler, lazy=[say_it]) + + input = ( + "token=verification_token" + "&team_id=T111" + "&team_domain=test-domain" + "&channel_id=C111" + "&channel_name=random" + "&user_id=W111" + "&user_name=primary-owner" + "&command=%2Fhello-world" + "&text=Hi" + "&enterprise_id=E111" + "&enterprise_name=Org+Name" + "&response_url=https%3A%2F%2Fhooks.slack.com%2Fcommands%2FT111%2F111%2Fxxxxx" + "&trigger_id=111.111.xxx" + ) + timestamp, body = str(int(time())), input + + chalice_app = Chalice(app_name="bolt-python-chalice") + slack_handler = ChaliceSlackRequestHandler(app=app, chalice=chalice_app) + + @chalice_app.route( + "/slack/events", + methods=["POST"], + content_types=["application/x-www-form-urlencoded", "application/json"], + ) + def events() -> Response: + return slack_handler.handle(chalice_app.current_request) + + headers = self.build_headers(timestamp, body) + client = Client(chalice_app, Config()) + response = client.http.post("/slack/events", headers=headers, body=body) + # + # response: Dict[str, Any] = LocalGateway(chalice_app, Config()).handle_request( + # method="POST", + # path="/slack/events", + # body=body, + # headers=self.build_headers(timestamp, body), + # ) + + # assert response["statusCode"] == 200, f"error: {response['body']}" + assert response.status_code == 200, f"Failed request: {response.body}" + assert_auth_test_count(self, 1) + assert self.mock_received_requests["/chat.postMessage"] == 1 + def test_oauth(self): app = App( client=self.web_client, From a497fac84eea0ef4baba039d43514b1c894c9c9c Mon Sep 17 00:00:00 2001 From: Jeremy Lujan Date: Tue, 30 Mar 2021 15:41:21 -0500 Subject: [PATCH 4/4] Remove commented code in test_aws_chalice --- tests/adapter_tests/aws/test_aws_chalice.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/tests/adapter_tests/aws/test_aws_chalice.py b/tests/adapter_tests/aws/test_aws_chalice.py index 83ec0a3fe..7b9daad7b 100644 --- a/tests/adapter_tests/aws/test_aws_chalice.py +++ b/tests/adapter_tests/aws/test_aws_chalice.py @@ -317,15 +317,7 @@ def events() -> Response: headers = self.build_headers(timestamp, body) client = Client(chalice_app, Config()) response = client.http.post("/slack/events", headers=headers, body=body) - # - # response: Dict[str, Any] = LocalGateway(chalice_app, Config()).handle_request( - # method="POST", - # path="/slack/events", - # body=body, - # headers=self.build_headers(timestamp, body), - # ) - - # assert response["statusCode"] == 200, f"error: {response['body']}" + assert response.status_code == 200, f"Failed request: {response.body}" assert_auth_test_count(self, 1) assert self.mock_received_requests["/chat.postMessage"] == 1