From fe598cda0ec833dabfea4a267d40c2f604509b72 Mon Sep 17 00:00:00 2001 From: William Bergamin Date: Wed, 27 Nov 2024 00:41:08 +0000 Subject: [PATCH] test: improve the mock server experience (#1602) --- .github/workflows/ci-build.yml | 2 +- tests/helpers.py | 15 - tests/mock_web_api_server/__init__.py | 87 ++++++ .../mock_web_api_server/mock_server_thread.py | 41 +++ .../mock_web_api_server/received_requests.py | 21 ++ ..._api_server.py => mock_web_api_handler.py} | 132 +-------- tests/slack_sdk/audit_logs/test_client.py | 8 +- .../audit_logs/test_client_http_retry.py | 8 +- .../token_rotation/test_token_rotator.py | 8 +- tests/slack_sdk/scim/mock_web_api_handler.py | 64 ++++ tests/slack_sdk/scim/mock_web_api_server.py | 198 ------------- tests/slack_sdk/scim/test_client.py | 8 +- .../slack_sdk/scim/test_client_http_retry.py | 8 +- .../socket_mode/mock_web_api_handler.py | 111 +++++++ .../socket_mode/mock_web_api_server.py | 275 ------------------ tests/slack_sdk/socket_mode/test_builtin.py | 8 +- .../socket_mode/test_interactions_builtin.py | 8 +- .../test_interactions_websocket_client.py | 8 +- ..._api_server.py => mock_web_api_handler.py} | 163 +---------- .../web/mock_web_api_http_retry_handler.py | 67 +++++ .../web/mock_web_api_server_http_retry.py | 154 ---------- tests/slack_sdk/web/test_web_client.py | 8 +- .../web/test_web_client_http_retry.py | 8 +- .../test_web_client_http_retry_connection.py | 10 +- ...test_web_client_http_retry_server_error.py | 8 +- .../web/test_web_client_issue_1049.py | 9 +- .../web/test_web_client_issue_829.py | 8 +- .../web/test_web_client_issue_891.py | 8 +- .../web/test_web_client_issue_900.py | 8 +- ...test_web_client_issue_921_custom_logger.py | 8 +- .../web/test_web_client_issue_971.py | 8 +- .../web/test_web_client_stars_deprecation.py | 8 +- ...st_web_client_workflow_step_deprecation.py | 8 +- .../slack_sdk/webhook/mock_web_api_server.py | 171 +---------- tests/slack_sdk/webhook/test_webhook.py | 8 +- .../webhook/test_webhook_http_retry.py | 8 +- .../audit_logs/test_async_client.py | 10 +- .../test_async_client_http_retry.py | 10 +- .../token_rotation/test_token_rotator.py | 10 +- .../slack_sdk_async/scim/test_async_client.py | 10 +- .../scim/test_async_client_http_retry.py | 10 +- .../socket_mode/test_aiohttp.py | 11 +- .../socket_mode/test_interactions_aiohttp.py | 10 +- .../test_interactions_websockets.py | 10 +- .../socket_mode/test_websockets.py | 10 +- .../web/test_async_web_client.py | 10 +- .../web/test_async_web_client_http_retry.py | 10 +- .../web/test_web_client_coverage.py | 11 +- .../web/test_web_client_issue_829.py | 10 +- .../web/test_web_client_issue_891.py | 10 +- ...test_web_client_issue_921_custom_logger.py | 10 +- .../webhook/test_async_webhook.py | 10 +- .../webhook/test_async_webhook_http_retry.py | 10 +- ..._api_server.py => mock_web_api_handler.py} | 176 +---------- tests/web/test_async_web_client.py | 10 +- tests/web/test_web_client.py | 11 +- tests/web/test_web_client_functional.py | 8 +- tests/web/test_web_client_issue_829.py | 10 +- tests/web/test_web_client_issue_891.py | 8 +- ...test_web_client_issue_921_custom_logger.py | 8 +- tests/webhook/mock_web_api_handler.py | 79 +++++ tests/webhook/mock_web_api_server.py | 267 ----------------- tests/webhook/test_async_webhook.py | 10 +- tests/webhook/test_webhook.py | 8 +- 64 files changed, 663 insertions(+), 1784 deletions(-) create mode 100644 tests/mock_web_api_server/__init__.py create mode 100644 tests/mock_web_api_server/mock_server_thread.py create mode 100644 tests/mock_web_api_server/received_requests.py rename tests/slack_sdk/audit_logs/{mock_web_api_server.py => mock_web_api_handler.py} (71%) create mode 100644 tests/slack_sdk/scim/mock_web_api_handler.py delete mode 100644 tests/slack_sdk/scim/mock_web_api_server.py create mode 100644 tests/slack_sdk/socket_mode/mock_web_api_handler.py delete mode 100644 tests/slack_sdk/socket_mode/mock_web_api_server.py rename tests/slack_sdk/web/{mock_web_api_server.py => mock_web_api_handler.py} (72%) create mode 100644 tests/slack_sdk/web/mock_web_api_http_retry_handler.py delete mode 100644 tests/slack_sdk/web/mock_web_api_server_http_retry.py rename tests/web/{mock_web_api_server.py => mock_web_api_handler.py} (64%) create mode 100644 tests/webhook/mock_web_api_handler.py delete mode 100644 tests/webhook/mock_web_api_server.py diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index 3c03239d8..a275e8e59 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -24,7 +24,6 @@ jobs: - '3.6' - 'pypy3.10' env: - PYTHON_SLACK_SDK_MOCK_SERVER_MODE: 'threading' CI_LARGE_SOCKET_MODE_PAYLOAD_TESTING_DISABLED: '1' CI_UNSTABLE_TESTS_SKIP_ENABLED: '1' FORCE_COLOR: '1' @@ -59,3 +58,4 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} # python setup.py validate generates the coverage file files: ./coverage.xml + diff --git a/tests/helpers.py b/tests/helpers.py index 598d74f1c..8b08b7d36 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -26,20 +26,5 @@ def restore_os_env(old_env: dict) -> None: os.environ.update(old_env) -def get_mock_server_mode() -> str: - """Returns a str representing the mode. - - :return: threading/multiprocessing - """ - mode = os.environ.get("PYTHON_SLACK_SDK_MOCK_SERVER_MODE") - if mode is None: - # We used to use "multiprocessing"" for macOS until Big Sur 11.1 - # Since 11.1, the "multiprocessing" mode started failing a lot... - # Therefore, we switched the default mode back to "threading". - return "threading" - else: - return mode - - def is_ci_unstable_test_skip_enabled() -> bool: return os.environ.get("CI_UNSTABLE_TESTS_SKIP_ENABLED") == "1" diff --git a/tests/mock_web_api_server/__init__.py b/tests/mock_web_api_server/__init__.py new file mode 100644 index 000000000..afb5761c4 --- /dev/null +++ b/tests/mock_web_api_server/__init__.py @@ -0,0 +1,87 @@ +import asyncio +from http.server import SimpleHTTPRequestHandler +from queue import Queue +import threading +import time +from typing import Type +from unittest import TestCase + +from tests.mock_web_api_server.received_requests import ReceivedRequests +from tests.mock_web_api_server.mock_server_thread import MockServerThread + + +def setup_mock_web_api_server(test: TestCase, handler: Type[SimpleHTTPRequestHandler], port: int = 8888): + test.server_started = threading.Event() + test.received_requests = ReceivedRequests(Queue()) + test.thread = MockServerThread(queue=test.received_requests.queue, test=test, handler=handler, port=port) + test.thread.start() + test.server_started.wait() + + +def cleanup_mock_web_api_server(test: TestCase): + test.thread.stop() + test.thread = None + + +def assert_received_request_count(test: TestCase, path: str, min_count: int, timeout: float = 1): + start_time = time.time() + error = None + while time.time() - start_time < timeout: + try: + received_count = test.received_requests.get(path, 0) + assert ( + received_count == min_count + ), f"Expected {min_count} '{path}' {'requests' if min_count > 1 else 'request'}, but got {received_count}!" + return + except Exception as e: + error = e + # waiting for some requests to be received + time.sleep(0.05) + + if error is not None: + raise error + + +def assert_auth_test_count(test: TestCase, expected_count: int): + assert_received_request_count(test, "/auth.test", expected_count, 0.5) + + +######### +# async # +######### + + +def setup_mock_web_api_server_async(test: TestCase, handler: Type[SimpleHTTPRequestHandler], port: int = 8888): + test.server_started = threading.Event() + test.received_requests = ReceivedRequests(asyncio.Queue()) + test.thread = MockServerThread(queue=test.received_requests.queue, test=test, handler=handler, port=port) + test.thread.start() + test.server_started.wait() + + +def cleanup_mock_web_api_server_async(test: TestCase): + test.thread.stop_unsafe() + test.thread = None + + +async def assert_received_request_count_async(test: TestCase, path: str, min_count: int, timeout: float = 1): + start_time = time.time() + error = None + while time.time() - start_time < timeout: + try: + received_count = await test.received_requests.get_async(path, 0) + assert ( + received_count == min_count + ), f"Expected {min_count} '{path}' {'requests' if min_count > 1 else 'request'}, but got {received_count}!" + return + except Exception as e: + error = e + # waiting for mock_received_requests updates + await asyncio.sleep(0.05) + + if error is not None: + raise error + + +async def assert_auth_test_count_async(test: TestCase, expected_count: int): + await assert_received_request_count_async(test, "/auth.test", expected_count, 0.5) diff --git a/tests/mock_web_api_server/mock_server_thread.py b/tests/mock_web_api_server/mock_server_thread.py new file mode 100644 index 000000000..0888cc4ea --- /dev/null +++ b/tests/mock_web_api_server/mock_server_thread.py @@ -0,0 +1,41 @@ +from asyncio import Queue +import asyncio +from http.server import HTTPServer, SimpleHTTPRequestHandler +import threading +from typing import Type, Union +from unittest import TestCase + + +class MockServerThread(threading.Thread): + def __init__( + self, queue: Union[Queue, asyncio.Queue], test: TestCase, handler: Type[SimpleHTTPRequestHandler], port: int = 8888 + ): + threading.Thread.__init__(self) + self.handler = handler + self.test = test + self.queue = queue + self.port = port + + def run(self): + self.server = HTTPServer(("localhost", self.port), self.handler) + self.server.queue = self.queue + self.test.server_url = f"http://localhost:{str(self.port)}" + self.test.host, self.test.port = self.server.socket.getsockname() + self.test.server_started.set() # threading.Event() + + self.test = None + try: + self.server.serve_forever(0.05) + finally: + self.server.server_close() + + def stop(self): + with self.server.queue.mutex: + del self.server.queue + self.server.shutdown() + self.join() + + def stop_unsafe(self): + del self.server.queue + self.server.shutdown() + self.join() diff --git a/tests/mock_web_api_server/received_requests.py b/tests/mock_web_api_server/received_requests.py new file mode 100644 index 000000000..a146f5e1f --- /dev/null +++ b/tests/mock_web_api_server/received_requests.py @@ -0,0 +1,21 @@ +import asyncio +from queue import Queue +from typing import Optional, Union + + +class ReceivedRequests: + def __init__(self, queue: Union[Queue, asyncio.Queue]): + self.queue = queue + self.received_requests: dict = {} + + def get(self, key: str, default: Optional[int] = None) -> Optional[int]: + while not self.queue.empty(): + path = self.queue.get() + self.received_requests[path] = self.received_requests.get(path, 0) + 1 + return self.received_requests.get(key, default) + + async def get_async(self, key: str, default: Optional[int] = None) -> Optional[int]: + while not self.queue.empty(): + path = await self.queue.get() + self.received_requests[path] = self.received_requests.get(path, 0) + 1 + return self.received_requests.get(key, default) diff --git a/tests/slack_sdk/audit_logs/mock_web_api_server.py b/tests/slack_sdk/audit_logs/mock_web_api_handler.py similarity index 71% rename from tests/slack_sdk/audit_logs/mock_web_api_server.py rename to tests/slack_sdk/audit_logs/mock_web_api_handler.py index 4857207f6..7feae4f3a 100644 --- a/tests/slack_sdk/audit_logs/mock_web_api_server.py +++ b/tests/slack_sdk/audit_logs/mock_web_api_handler.py @@ -1,17 +1,9 @@ import json import logging import re -import sys -import threading import time from http import HTTPStatus -from http.server import HTTPServer, SimpleHTTPRequestHandler -from multiprocessing.context import Process -from typing import Type -from unittest import TestCase -from urllib.request import Request, urlopen - -from tests.helpers import get_mock_server_mode +from http.server import SimpleHTTPRequestHandler class MockHandler(SimpleHTTPRequestHandler): @@ -32,6 +24,8 @@ def set_common_headers(self): self.end_headers() def do_GET(self): + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) if self.path == "/received_requests.json": self.send_response(200) self.set_common_headers() @@ -97,123 +91,3 @@ def do_GET(self): except Exception as e: self.logger.error(str(e), exc_info=True) raise - - -class MockServerProcessTarget: - def __init__(self, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - self.handler = handler - - def run(self): - self.handler.received_requests = {} - self.server = HTTPServer(("localhost", 8888), self.handler) - try: - self.server.serve_forever(0.05) - finally: - self.server.server_close() - - def stop(self): - self.handler.received_requests = {} - self.server.shutdown() - self.join() - - -class MonitorThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self, daemon=True) - self.handler = handler - self.test = test - self.test.mock_received_requests = None - self.is_running = True - - def run(self) -> None: - while self.is_running: - try: - req = Request(f"{self.test.server_url}/received_requests.json") - resp = urlopen(req, timeout=1) - self.test.mock_received_requests = json.loads(resp.read().decode("utf-8")) - except Exception as e: - # skip logging for the initial request - if self.test.mock_received_requests is not None: - logging.getLogger(__name__).exception(e) - time.sleep(0.01) - - def stop(self): - self.is_running = False - self.join() - - -class MockServerThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self) - self.handler = handler - self.test = test - - def run(self): - self.server = HTTPServer(("localhost", 8888), self.handler) - self.test.server_url = "http://localhost:8888" - self.test.host, self.test.port = self.server.socket.getsockname() - self.test.server_started.set() # threading.Event() - - self.test = None - try: - self.server.serve_forever() - finally: - self.server.server_close() - - def stop(self): - self.server.shutdown() - self.join() - - -def setup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.server_started = threading.Event() - test.thread = MockServerThread(test) - test.thread.start() - test.server_started.wait() - else: - # start a mock server as another process - target = MockServerProcessTarget() - test.server_url = "http://localhost:8888" - test.host, test.port = "localhost", 8888 - test.process = Process(target=target.run, daemon=True) - test.process.start() - - time.sleep(0.1) - - # start a thread in the current process - # this thread fetches mock_received_requests from the remote process - test.monitor_thread = MonitorThread(test) - test.monitor_thread.start() - count = 0 - # wait until the first successful data retrieval - while test.mock_received_requests is None: - time.sleep(0.01) - count += 1 - if count >= 100: - raise Exception("The mock server is not yet running!") - - -def cleanup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.thread.stop() - test.thread = None - else: - # stop the thread to fetch mock_received_requests from the remote process - test.monitor_thread.stop() - - retry_count = 0 - # terminate the process - while test.process.is_alive(): - test.process.terminate() - time.sleep(0.01) - retry_count += 1 - if retry_count >= 100: - raise Exception("Failed to stop the mock server!") - - # Python 3.6 does not have this method - if sys.version_info.major == 3 and sys.version_info.minor > 6: - # cleanup the process's resources - test.process.close() - - test.process = None diff --git a/tests/slack_sdk/audit_logs/test_client.py b/tests/slack_sdk/audit_logs/test_client.py index 47766c4f8..f682888a8 100644 --- a/tests/slack_sdk/audit_logs/test_client.py +++ b/tests/slack_sdk/audit_logs/test_client.py @@ -2,16 +2,14 @@ from urllib.error import URLError from slack_sdk.audit_logs import AuditLogsClient, AuditLogsResponse -from tests.slack_sdk.audit_logs.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.slack_sdk.audit_logs.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestAuditLogsClient(unittest.TestCase): def setUp(self): self.client = AuditLogsClient(token="xoxp-", base_url="http://localhost:8888/") - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/audit_logs/test_client_http_retry.py b/tests/slack_sdk/audit_logs/test_client_http_retry.py index 258d3dd98..7c76d3819 100644 --- a/tests/slack_sdk/audit_logs/test_client_http_retry.py +++ b/tests/slack_sdk/audit_logs/test_client_http_retry.py @@ -2,16 +2,14 @@ from slack_sdk.audit_logs import AuditLogsClient from slack_sdk.http_retry import RateLimitErrorRetryHandler -from tests.slack_sdk.audit_logs.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.slack_sdk.audit_logs.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server from ..my_retry_handler import MyRetryHandler class TestAuditLogsClient_HttpRetries(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/oauth/token_rotation/test_token_rotator.py b/tests/slack_sdk/oauth/token_rotation/test_token_rotator.py index 212cffc06..9926932c2 100644 --- a/tests/slack_sdk/oauth/token_rotation/test_token_rotator.py +++ b/tests/slack_sdk/oauth/token_rotation/test_token_rotator.py @@ -4,15 +4,13 @@ from slack_sdk.oauth.installation_store import Installation from slack_sdk.oauth.token_rotation import TokenRotator from slack_sdk.web import WebClient -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestTokenRotator(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) self.token_rotator = TokenRotator( client=WebClient(base_url="http://localhost:8888", token=None), client_id="111.222", diff --git a/tests/slack_sdk/scim/mock_web_api_handler.py b/tests/slack_sdk/scim/mock_web_api_handler.py new file mode 100644 index 000000000..6b31f4b1e --- /dev/null +++ b/tests/slack_sdk/scim/mock_web_api_handler.py @@ -0,0 +1,64 @@ +import logging +import re +from http import HTTPStatus +from http.server import SimpleHTTPRequestHandler + + +class MockHandler(SimpleHTTPRequestHandler): + protocol_version = "HTTP/1.1" + default_request_version = "HTTP/1.1" + logger = logging.getLogger(__name__) + + pattern_for_language = re.compile("python/(\\S+)", re.IGNORECASE) + pattern_for_package_identifier = re.compile("slackclient/(\\S+)") + + def is_valid_user_agent(self): + user_agent = self.headers["User-Agent"] + return self.pattern_for_language.search(user_agent) and self.pattern_for_package_identifier.search(user_agent) + + def is_valid_token(self): + if self.path.startswith("oauth"): + return True + return "Authorization" in self.headers and str(self.headers["Authorization"]).startswith("Bearer xoxp-") + + def set_common_headers(self): + self.send_header("content-type", "application/json;charset=utf-8") + self.send_header("connection", "close") + self.end_headers() + + def _handle(self): + try: + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) + header = self.headers["Authorization"] + if header is not None and "xoxp-" in header: + pattern = str(header).split("xoxp-", 1)[1] + if "remote_disconnected" in pattern: + # http.client.RemoteDisconnected + self.finish() + return + if "ratelimited" in pattern: + self.send_response(429) + self.send_header("retry-after", 1) + self.set_common_headers() + self.wfile.write("""{"ok": false, "error": "ratelimited"}""".encode("utf-8")) + return + + if self.is_valid_token() and self.is_valid_user_agent(): + self.send_response(HTTPStatus.OK) + self.set_common_headers() + self.wfile.close() + else: + self.send_response(HTTPStatus.BAD_REQUEST) + self.set_common_headers() + self.wfile.close() + + except Exception as e: + self.logger.error(str(e), exc_info=True) + raise + + def do_GET(self): + self._handle() + + def do_POST(self): + self._handle() diff --git a/tests/slack_sdk/scim/mock_web_api_server.py b/tests/slack_sdk/scim/mock_web_api_server.py deleted file mode 100644 index aa50eb7d0..000000000 --- a/tests/slack_sdk/scim/mock_web_api_server.py +++ /dev/null @@ -1,198 +0,0 @@ -import json -import logging -import re -import sys -import threading -import time -from http import HTTPStatus -from http.server import HTTPServer, SimpleHTTPRequestHandler -from multiprocessing.context import Process -from typing import Type -from unittest import TestCase -from urllib.request import Request, urlopen - -from tests.helpers import get_mock_server_mode - - -class MockHandler(SimpleHTTPRequestHandler): - protocol_version = "HTTP/1.1" - default_request_version = "HTTP/1.1" - logger = logging.getLogger(__name__) - - pattern_for_language = re.compile("python/(\\S+)", re.IGNORECASE) - pattern_for_package_identifier = re.compile("slackclient/(\\S+)") - - def is_valid_user_agent(self): - user_agent = self.headers["User-Agent"] - return self.pattern_for_language.search(user_agent) and self.pattern_for_package_identifier.search(user_agent) - - def is_valid_token(self): - if self.path.startswith("oauth"): - return True - return "Authorization" in self.headers and str(self.headers["Authorization"]).startswith("Bearer xoxp-") - - def set_common_headers(self): - self.send_header("content-type", "application/json;charset=utf-8") - self.send_header("connection", "close") - self.end_headers() - - def _handle(self): - try: - if self.path == "/received_requests.json": - self.send_response(200) - self.set_common_headers() - self.wfile.write(json.dumps(self.received_requests).encode("utf-8")) - return - - header = self.headers["Authorization"] - if header is not None and "xoxp-" in header: - pattern = str(header).split("xoxp-", 1)[1] - if "remote_disconnected" in pattern: - # http.client.RemoteDisconnected - self.finish() - return - if "ratelimited" in pattern: - self.send_response(429) - self.send_header("retry-after", 1) - self.set_common_headers() - self.wfile.write("""{"ok": false, "error": "ratelimited"}""".encode("utf-8")) - return - - if self.is_valid_token() and self.is_valid_user_agent(): - self.send_response(HTTPStatus.OK) - self.set_common_headers() - self.wfile.close() - else: - self.send_response(HTTPStatus.BAD_REQUEST) - self.set_common_headers() - self.wfile.close() - - except Exception as e: - self.logger.error(str(e), exc_info=True) - raise - - def do_GET(self): - self._handle() - - def do_POST(self): - self._handle() - - -class MockServerProcessTarget: - def __init__(self, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - self.handler = handler - - def run(self): - self.handler.received_requests = {} - self.server = HTTPServer(("localhost", 8888), self.handler) - try: - self.server.serve_forever(0.05) - finally: - self.server.server_close() - - def stop(self): - self.handler.received_requests = {} - self.server.shutdown() - self.join() - - -class MonitorThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self, daemon=True) - self.handler = handler - self.test = test - self.test.mock_received_requests = None - self.is_running = True - - def run(self) -> None: - while self.is_running: - try: - req = Request(f"{self.test.server_url}/received_requests.json") - resp = urlopen(req, timeout=1) - self.test.mock_received_requests = json.loads(resp.read().decode("utf-8")) - except Exception as e: - # skip logging for the initial request - if self.test.mock_received_requests is not None: - logging.getLogger(__name__).exception(e) - time.sleep(0.01) - - def stop(self): - self.is_running = False - self.join() - - -class MockServerThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self) - self.handler = handler - self.test = test - - def run(self): - self.server = HTTPServer(("localhost", 8888), self.handler) - self.test.server_url = "http://localhost:8888" - self.test.host, self.test.port = self.server.socket.getsockname() - self.test.server_started.set() # threading.Event() - - self.test = None - try: - self.server.serve_forever() - finally: - self.server.server_close() - - def stop(self): - self.server.shutdown() - self.join() - - -def setup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.server_started = threading.Event() - test.thread = MockServerThread(test) - test.thread.start() - test.server_started.wait() - else: - # start a mock server as another process - target = MockServerProcessTarget() - test.server_url = "http://localhost:8888" - test.host, test.port = "localhost", 8888 - test.process = Process(target=target.run, daemon=True) - test.process.start() - - time.sleep(0.1) - - # start a thread in the current process - # this thread fetches mock_received_requests from the remote process - test.monitor_thread = MonitorThread(test) - test.monitor_thread.start() - count = 0 - # wait until the first successful data retrieval - while test.mock_received_requests is None: - time.sleep(0.01) - count += 1 - if count >= 100: - raise Exception("The mock server is not yet running!") - - -def cleanup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.thread.stop() - test.thread = None - else: - # stop the thread to fetch mock_received_requests from the remote process - test.monitor_thread.stop() - - retry_count = 0 - # terminate the process - while test.process.is_alive(): - test.process.terminate() - time.sleep(0.01) - retry_count += 1 - if retry_count >= 100: - raise Exception("Failed to stop the mock server!") - - # Python 3.6 does not have this method - if sys.version_info.major == 3 and sys.version_info.minor > 6: - # cleanup the process's resources - test.process.close() - - test.process = None diff --git a/tests/slack_sdk/scim/test_client.py b/tests/slack_sdk/scim/test_client.py index 79a000666..2ba68b1d5 100644 --- a/tests/slack_sdk/scim/test_client.py +++ b/tests/slack_sdk/scim/test_client.py @@ -4,15 +4,13 @@ from slack_sdk.scim import SCIMClient, User, Group from slack_sdk.scim.v1.group import GroupMember from slack_sdk.scim.v1.user import UserName, UserEmail -from tests.slack_sdk.scim.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.scim.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestSCIMClient(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/scim/test_client_http_retry.py b/tests/slack_sdk/scim/test_client_http_retry.py index 1a5451750..47d7adfc3 100644 --- a/tests/slack_sdk/scim/test_client_http_retry.py +++ b/tests/slack_sdk/scim/test_client_http_retry.py @@ -2,16 +2,14 @@ from slack_sdk.http_retry import RateLimitErrorRetryHandler from slack_sdk.scim import SCIMClient -from tests.slack_sdk.scim.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.scim.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server from ..my_retry_handler import MyRetryHandler class TestSCIMClient(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/socket_mode/mock_web_api_handler.py b/tests/slack_sdk/socket_mode/mock_web_api_handler.py new file mode 100644 index 000000000..387778ecb --- /dev/null +++ b/tests/slack_sdk/socket_mode/mock_web_api_handler.py @@ -0,0 +1,111 @@ +import json +import logging +import re +from http import HTTPStatus +from http.server import SimpleHTTPRequestHandler +from urllib.parse import urlparse, parse_qs + + +class MockHandler(SimpleHTTPRequestHandler): + protocol_version = "HTTP/1.1" + default_request_version = "HTTP/1.1" + logger = logging.getLogger(__name__) + + pattern_for_language = re.compile("python/(\\S+)", re.IGNORECASE) + pattern_for_package_identifier = re.compile("slackclient/(\\S+)") + + def is_valid_user_agent(self): + user_agent = self.headers["User-Agent"] + return self.pattern_for_language.search(user_agent) and self.pattern_for_package_identifier.search(user_agent) + + def is_valid_token(self): + if self.path.startswith("oauth"): + return True + return "Authorization" in self.headers and ( + str(self.headers["Authorization"]).startswith("Bearer xoxb-") + or str(self.headers["Authorization"]).startswith("Bearer xapp-") + ) + + def set_common_headers(self): + self.send_header("content-type", "application/json;charset=utf-8") + self.send_header("connection", "close") + self.end_headers() + + invalid_auth = { + "ok": False, + "error": "invalid_auth", + } + + not_found = { + "ok": False, + "error": "test_data_not_found", + } + + def _handle(self): + try: + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) + if self.is_valid_token() and self.is_valid_user_agent(): + parsed_path = urlparse(self.path) + + len_header = self.headers.get("Content-Length") or 0 + content_len = int(len_header) + post_body = self.rfile.read(content_len) + request_body = None + if post_body: + try: + post_body = post_body.decode("utf-8") + if post_body.startswith("{"): + request_body = json.loads(post_body) + else: + request_body = {k: v[0] for k, v in parse_qs(post_body).items()} + except UnicodeDecodeError: + pass + else: + if parsed_path and parsed_path.query: + request_body = {k: v[0] for k, v in parse_qs(parsed_path.query).items()} + + body = {"ok": False, "error": "internal_error"} + if self.path == "/auth.test": + body = { + "ok": True, + "url": "https://xyz.slack.com/", + "team": "Testing Workspace", + "user": "bot-user", + "team_id": "T111", + "user_id": "W11", + "bot_id": "B111", + "enterprise_id": "E111", + "is_enterprise_install": False, + } + if self.path == "/apps.connections.open": + body = { + "ok": True, + "url": "ws://0.0.0.0:3001/link", + } + if self.path == "/api.test" and request_body: + body = {"ok": True, "args": request_body} + else: + body = self.invalid_auth + + if not body: + body = self.not_found + + self.send_response(HTTPStatus.OK) + self.set_common_headers() + self.wfile.write(json.dumps(body).encode("utf-8")) + self.wfile.close() + + except Exception as e: + self.logger.error(str(e), exc_info=True) + raise + + def do_GET(self): + self._handle() + + def do_POST(self): + self._handle() + + def do_CONNECT(self): + self.wfile.write("HTTP/1.1 200 Connection established\r\n\r\n".encode("utf-8")) + self.wfile.close() diff --git a/tests/slack_sdk/socket_mode/mock_web_api_server.py b/tests/slack_sdk/socket_mode/mock_web_api_server.py deleted file mode 100644 index f37529ecc..000000000 --- a/tests/slack_sdk/socket_mode/mock_web_api_server.py +++ /dev/null @@ -1,275 +0,0 @@ -import asyncio -import json -import logging -import re -import sys -import threading -import time -from http import HTTPStatus -from http.server import HTTPServer, SimpleHTTPRequestHandler -from multiprocessing.context import Process -from typing import Type -from unittest import TestCase -from urllib.parse import urlparse, parse_qs -from urllib.request import Request, urlopen - -from tests.helpers import get_mock_server_mode - - -class MockHandler(SimpleHTTPRequestHandler): - protocol_version = "HTTP/1.1" - default_request_version = "HTTP/1.1" - logger = logging.getLogger(__name__) - - pattern_for_language = re.compile("python/(\\S+)", re.IGNORECASE) - pattern_for_package_identifier = re.compile("slackclient/(\\S+)") - - def is_valid_user_agent(self): - user_agent = self.headers["User-Agent"] - return self.pattern_for_language.search(user_agent) and self.pattern_for_package_identifier.search(user_agent) - - def is_valid_token(self): - if self.path.startswith("oauth"): - return True - return "Authorization" in self.headers and ( - str(self.headers["Authorization"]).startswith("Bearer xoxb-") - or str(self.headers["Authorization"]).startswith("Bearer xapp-") - ) - - def set_common_headers(self): - self.send_header("content-type", "application/json;charset=utf-8") - self.send_header("connection", "close") - self.end_headers() - - invalid_auth = { - "ok": False, - "error": "invalid_auth", - } - - not_found = { - "ok": False, - "error": "test_data_not_found", - } - - def _handle(self): - try: - if self.is_valid_token() and self.is_valid_user_agent(): - parsed_path = urlparse(self.path) - - len_header = self.headers.get("Content-Length") or 0 - content_len = int(len_header) - post_body = self.rfile.read(content_len) - request_body = None - if post_body: - try: - post_body = post_body.decode("utf-8") - if post_body.startswith("{"): - request_body = json.loads(post_body) - else: - request_body = {k: v[0] for k, v in parse_qs(post_body).items()} - except UnicodeDecodeError: - pass - else: - if parsed_path and parsed_path.query: - request_body = {k: v[0] for k, v in parse_qs(parsed_path.query).items()} - - body = {"ok": False, "error": "internal_error"} - if self.path == "/auth.test": - body = { - "ok": True, - "url": "https://xyz.slack.com/", - "team": "Testing Workspace", - "user": "bot-user", - "team_id": "T111", - "user_id": "W11", - "bot_id": "B111", - "enterprise_id": "E111", - "is_enterprise_install": False, - } - if self.path == "/apps.connections.open": - body = { - "ok": True, - "url": "ws://0.0.0.0:3001/link", - } - if self.path == "/api.test" and request_body: - body = {"ok": True, "args": request_body} - else: - body = self.invalid_auth - - if not body: - body = self.not_found - - self.send_response(HTTPStatus.OK) - self.set_common_headers() - self.wfile.write(json.dumps(body).encode("utf-8")) - self.wfile.close() - - except Exception as e: - self.logger.error(str(e), exc_info=True) - raise - - def do_GET(self): - self._handle() - - def do_POST(self): - self._handle() - - def do_CONNECT(self): - self.wfile.write("HTTP/1.1 200 Connection established\r\n\r\n".encode("utf-8")) - self.wfile.close() - - -class MockServerProcessTarget: - def __init__(self, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - self.handler = handler - - def run(self): - self.handler.received_requests = {} - self.server = HTTPServer(("localhost", 8888), self.handler) - try: - self.server.serve_forever(0.05) - finally: - self.server.server_close() - - def stop(self): - self.handler.received_requests = {} - self.server.shutdown() - self.join() - - -class MonitorThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self, daemon=True) - self.handler = handler - self.test = test - self.test.mock_received_requests = None - self.is_running = True - - def run(self) -> None: - while self.is_running: - try: - req = Request(f"{self.test.server_url}/received_requests.json") - resp = urlopen(req, timeout=1) - self.test.mock_received_requests = json.loads(resp.read().decode("utf-8")) - except Exception as e: - # skip logging for the initial request - if self.test.mock_received_requests is not None: - logging.getLogger(__name__).exception(e) - time.sleep(0.01) - - def stop(self): - self.is_running = False - self.join() - - -class MockServerThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self) - self.handler = handler - self.test = test - - def run(self): - self.server = HTTPServer(("localhost", 8888), self.handler) - self.test.server_url = "http://localhost:8888" - self.test.host, self.test.port = self.server.socket.getsockname() - self.test.server_started.set() # threading.Event() - - self.test = None - try: - self.server.serve_forever() - finally: - self.server.server_close() - - def stop(self): - self.server.shutdown() - self.join() - - -def setup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.server_started = threading.Event() - test.thread = MockServerThread(test) - test.thread.start() - test.server_started.wait() - else: - # start a mock server as another process - target = MockServerProcessTarget() - test.server_url = "http://localhost:8888" - test.host, test.port = "localhost", 8888 - test.process = Process(target=target.run, daemon=True) - test.process.start() - - time.sleep(0.1) - - # start a thread in the current process - # this thread fetches mock_received_requests from the remote process - test.monitor_thread = MonitorThread(test) - test.monitor_thread.start() - count = 0 - # wait until the first successful data retrieval - while test.mock_received_requests is None: - time.sleep(0.01) - count += 1 - if count >= 100: - raise Exception("The mock server is not yet running!") - - -def cleanup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.thread.stop() - test.thread = None - else: - # stop the thread to fetch mock_received_requests from the remote process - test.monitor_thread.stop() - - retry_count = 0 - # terminate the process - while test.process.is_alive(): - test.process.terminate() - time.sleep(0.01) - retry_count += 1 - if retry_count >= 100: - raise Exception("Failed to stop the mock server!") - - # Python 3.6 does not have this method - if sys.version_info.major == 3 and sys.version_info.minor > 6: - # cleanup the process's resources - test.process.close() - - test.process = None - - -def assert_auth_test_count(test: TestCase, expected_count: int): - time.sleep(0.1) - retry_count = 0 - error = None - while retry_count < 3: - try: - test.mock_received_requests["/auth.test"] == expected_count - break - except Exception as e: - error = e - retry_count += 1 - # waiting for mock_received_requests updates - time.sleep(0.1) - - if error is not None: - raise error - - -async def assert_auth_test_count_async(test: TestCase, expected_count: int): - await asyncio.sleep(0.1) - retry_count = 0 - error = None - while retry_count < 3: - try: - test.mock_received_requests["/auth.test"] == expected_count - break - except Exception as e: - error = e - retry_count += 1 - # waiting for mock_received_requests updates - await asyncio.sleep(0.1) - - if error is not None: - raise error diff --git a/tests/slack_sdk/socket_mode/test_builtin.py b/tests/slack_sdk/socket_mode/test_builtin.py index db4ef9bda..a1780a7e0 100644 --- a/tests/slack_sdk/socket_mode/test_builtin.py +++ b/tests/slack_sdk/socket_mode/test_builtin.py @@ -18,17 +18,15 @@ _use_or_create_ssl_context, ) from slack_sdk.web.legacy_client import LegacyWebClient -from .mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from .mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestBuiltin(unittest.TestCase): logger = logging.getLogger(__name__) def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) self.web_client = WebClient( token="xoxb-api_test", base_url="http://localhost:8888", diff --git a/tests/slack_sdk/socket_mode/test_interactions_builtin.py b/tests/slack_sdk/socket_mode/test_interactions_builtin.py index ada3e050a..43577200e 100644 --- a/tests/slack_sdk/socket_mode/test_interactions_builtin.py +++ b/tests/slack_sdk/socket_mode/test_interactions_builtin.py @@ -18,10 +18,8 @@ socket_mode_envelopes, socket_mode_hello_message, ) -from tests.slack_sdk.socket_mode.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.socket_mode.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server import sys @@ -30,7 +28,7 @@ class TestInteractionsBuiltin(unittest.TestCase): logger = logging.getLogger(__name__) def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) self.web_client = WebClient( token="xoxb-api_test", base_url="http://localhost:8888", diff --git a/tests/slack_sdk/socket_mode/test_interactions_websocket_client.py b/tests/slack_sdk/socket_mode/test_interactions_websocket_client.py index a1d27ad3c..cfe861567 100644 --- a/tests/slack_sdk/socket_mode/test_interactions_websocket_client.py +++ b/tests/slack_sdk/socket_mode/test_interactions_websocket_client.py @@ -18,17 +18,15 @@ socket_mode_envelopes, socket_mode_hello_message, ) -from tests.slack_sdk.socket_mode.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.socket_mode.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestInteractionsWebSocketClient(unittest.TestCase): logger = logging.getLogger(__name__) def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) self.web_client = WebClient( token="xoxb-api_test", base_url="http://localhost:8888", diff --git a/tests/slack_sdk/web/mock_web_api_server.py b/tests/slack_sdk/web/mock_web_api_handler.py similarity index 72% rename from tests/slack_sdk/web/mock_web_api_server.py rename to tests/slack_sdk/web/mock_web_api_handler.py index c7dbf6a54..12a487e05 100644 --- a/tests/slack_sdk/web/mock_web_api_server.py +++ b/tests/slack_sdk/web/mock_web_api_handler.py @@ -1,19 +1,15 @@ import asyncio import json import logging +from queue import Queue import re -import sys import threading import time from http import HTTPStatus from http.server import HTTPServer, SimpleHTTPRequestHandler -from multiprocessing.context import Process -from typing import Type +from typing import Type, Union from unittest import TestCase from urllib.parse import urlparse, parse_qs -from urllib.request import Request, urlopen - -from tests.helpers import get_mock_server_mode class MockHandler(SimpleHTTPRequestHandler): @@ -78,11 +74,8 @@ def set_common_headers(self): def _handle(self): try: - if self.path == "/received_requests.json": - self.send_response(200) - self.set_common_headers() - self.wfile.write(json.dumps(self.received_requests).encode("utf-8")) - return + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) if self.path in {"/oauth.access", "/oauth.v2.access"}: self.send_response(200) @@ -267,159 +260,35 @@ def do_POST(self): self._handle() -class MockServerProcessTarget: - def __init__(self, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - self.handler = handler - - def run(self): - self.handler.received_requests = {} - self.handler.state = {"ratelimited_count": 0} - self.server = HTTPServer(("localhost", 8888), self.handler) - try: - self.server.serve_forever(0.05) - finally: - self.server.server_close() - - def stop(self): - self.handler.received_requests = {} - self.handler.state = {"ratelimited_count": 0} - self.server.shutdown() - self.join() - - -class MonitorThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self, daemon=True) - self.handler = handler - self.test = test - self.test.mock_received_requests = None - self.is_running = True - - def run(self) -> None: - while self.is_running: - try: - req = Request(f"{self.test.server_url}/received_requests.json") - resp = urlopen(req, timeout=1) - self.test.mock_received_requests = json.loads(resp.read().decode("utf-8")) - except Exception as e: - # skip logging for the initial request - if self.test.mock_received_requests is not None: - logging.getLogger(__name__).exception(e) - time.sleep(0.01) - - def stop(self): - self.is_running = False - self.join() - - class MockServerThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): + def __init__( + self, queue: Union[Queue, asyncio.Queue], test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler + ): threading.Thread.__init__(self) self.handler = handler self.test = test + self.queue = queue def run(self): self.server = HTTPServer(("localhost", 8888), self.handler) + self.server.queue = self.queue self.test.server_url = "http://localhost:8888" self.test.host, self.test.port = self.server.socket.getsockname() self.test.server_started.set() # threading.Event() self.test = None try: - self.server.serve_forever() + self.server.serve_forever(0.05) finally: self.server.server_close() def stop(self): + with self.server.queue.mutex: + del self.server.queue self.server.shutdown() self.join() - -def setup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.server_started = threading.Event() - test.thread = MockServerThread(test) - test.thread.start() - test.server_started.wait() - else: - # start a mock server as another process - target = MockServerProcessTarget() - test.server_url = "http://localhost:8888" - test.host, test.port = "localhost", 8888 - test.process = Process(target=target.run, daemon=True) - test.process.start() - - time.sleep(0.1) - - # start a thread in the current process - # this thread fetches mock_received_requests from the remote process - test.monitor_thread = MonitorThread(test) - test.monitor_thread.start() - count = 0 - # wait until the first successful data retrieval - while test.mock_received_requests is None: - time.sleep(0.01) - count += 1 - if count >= 100: - raise Exception("The mock server is not yet running!") - - -def cleanup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.thread.stop() - test.thread = None - else: - # stop the thread to fetch mock_received_requests from the remote process - test.monitor_thread.stop() - - retry_count = 0 - # terminate the process - while test.process.is_alive(): - test.process.terminate() - time.sleep(0.01) - retry_count += 1 - if retry_count >= 100: - raise Exception("Failed to stop the mock server!") - - # Python 3.6 does not have this method - if sys.version_info.major == 3 and sys.version_info.minor > 6: - # cleanup the process's resources - test.process.close() - - test.process = None - - -def assert_auth_test_count(test: TestCase, expected_count: int): - time.sleep(0.1) - retry_count = 0 - error = None - while retry_count < 3: - try: - test.mock_received_requests["/auth.test"] == expected_count - break - except Exception as e: - error = e - retry_count += 1 - # waiting for mock_received_requests updates - time.sleep(0.1) - - if error is not None: - raise error - - -async def assert_auth_test_count_async(test: TestCase, expected_count: int): - await asyncio.sleep(0.1) - retry_count = 0 - error = None - while retry_count < 3: - try: - test.mock_received_requests["/auth.test"] == expected_count - break - except Exception as e: - error = e - retry_count += 1 - # waiting for mock_received_requests updates - await asyncio.sleep(0.1) - - if error is not None: - raise error + def stop_unsafe(self): + del self.server.queue + self.server.shutdown() + self.join() diff --git a/tests/slack_sdk/web/mock_web_api_http_retry_handler.py b/tests/slack_sdk/web/mock_web_api_http_retry_handler.py new file mode 100644 index 000000000..d22eaa38b --- /dev/null +++ b/tests/slack_sdk/web/mock_web_api_http_retry_handler.py @@ -0,0 +1,67 @@ +import logging +import time +from http import HTTPStatus +from http.server import SimpleHTTPRequestHandler + + +class MockHandler(SimpleHTTPRequestHandler): + protocol_version = "HTTP/1.1" + default_request_version = "HTTP/1.1" + logger = logging.getLogger(__name__) + state = {"request_count": 0} + + def set_common_headers(self): + self.send_header("content-type", "application/json;charset=utf-8") + self.send_header("connection", "close") + self.end_headers() + + success_response = {"ok": True} + + def _handle(self): + self.state["request_count"] += 1 + try: + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) + header = self.headers["authorization"] + pattern = str(header).split("xoxb-", 1)[1] + + if self.state["request_count"] % 2 == 1: + if "remote_disconnected" in pattern: + # http.client.RemoteDisconnected + self.finish() + return + + if pattern.isnumeric(): + self.send_response(int(pattern)) + self.set_common_headers() + self.wfile.write("""{"ok":false}""".encode("utf-8")) + return + if pattern == "ratelimited": + self.send_response(429) + self.send_header("retry-after", 1) + self.set_common_headers() + self.wfile.write("""{"ok":false,"error":"ratelimited"}""".encode("utf-8")) + self.wfile.close() + return + + if pattern == "timeout": + time.sleep(2) + self.send_response(200) + self.wfile.write("""{"ok":true}""".encode("utf-8")) + self.wfile.close() + return + + self.send_response(HTTPStatus.OK) + self.set_common_headers() + self.wfile.write("""{"ok":true}""".encode("utf-8")) + self.wfile.close() + + except Exception as e: + self.logger.error(str(e), exc_info=True) + raise + + def do_GET(self): + self._handle() + + def do_POST(self): + self._handle() diff --git a/tests/slack_sdk/web/mock_web_api_server_http_retry.py b/tests/slack_sdk/web/mock_web_api_server_http_retry.py deleted file mode 100644 index 18e405709..000000000 --- a/tests/slack_sdk/web/mock_web_api_server_http_retry.py +++ /dev/null @@ -1,154 +0,0 @@ -import logging -import sys -import threading -import time -from http import HTTPStatus -from http.server import HTTPServer, SimpleHTTPRequestHandler -from multiprocessing.context import Process -from typing import Type -from unittest import TestCase - -from tests.helpers import get_mock_server_mode - - -class MockHandler(SimpleHTTPRequestHandler): - protocol_version = "HTTP/1.1" - default_request_version = "HTTP/1.1" - logger = logging.getLogger(__name__) - state = {"request_count": 0} - - def set_common_headers(self): - self.send_header("content-type", "application/json;charset=utf-8") - self.send_header("connection", "close") - self.end_headers() - - success_response = {"ok": True} - - def _handle(self): - self.state["request_count"] += 1 - try: - header = self.headers["authorization"] - pattern = str(header).split("xoxb-", 1)[1] - - if self.state["request_count"] % 2 == 1: - if "remote_disconnected" in pattern: - # http.client.RemoteDisconnected - self.finish() - return - - if pattern.isnumeric(): - self.send_response(int(pattern)) - self.set_common_headers() - self.wfile.write("""{"ok":false}""".encode("utf-8")) - return - if pattern == "ratelimited": - self.send_response(429) - self.send_header("retry-after", 1) - self.set_common_headers() - self.wfile.write("""{"ok":false,"error":"ratelimited"}""".encode("utf-8")) - self.wfile.close() - return - - if pattern == "timeout": - time.sleep(2) - self.send_response(200) - self.wfile.write("""{"ok":true}""".encode("utf-8")) - self.wfile.close() - return - - self.send_response(HTTPStatus.OK) - self.set_common_headers() - self.wfile.write("""{"ok":true}""".encode("utf-8")) - self.wfile.close() - - except Exception as e: - self.logger.error(str(e), exc_info=True) - raise - - def do_GET(self): - self._handle() - - def do_POST(self): - self._handle() - - -class MockServerProcessTarget: - def __init__(self, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - self.handler = handler - - def run(self): - self.handler.state = {"request_count": 0} - self.server = HTTPServer(("localhost", 8889), self.handler) - try: - self.server.serve_forever(0.05) - finally: - self.server.server_close() - - def stop(self): - self.handler.state = {"request_count": 0} - self.server.shutdown() - self.join() - - -class MockServerThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self) - self.handler = handler - self.test = test - - def run(self): - self.server = HTTPServer(("localhost", 8889), self.handler) - self.test.server_url = "http://localhost:8889" - self.test.host, self.test.port = self.server.socket.getsockname() - self.test.server_started.set() # threading.Event() - - self.test = None - try: - self.server.serve_forever() - finally: - self.server.server_close() - - def stop(self): - self.server.shutdown() - self.join() - - -def setup_mock_retry_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.server_started = threading.Event() - test.thread = MockServerThread(test) - test.thread.start() - test.server_started.wait() - else: - # start a mock server as another process - target = MockServerProcessTarget() - test.server_url = "http://localhost:8889" - test.host, test.port = "localhost", 8889 - test.process = Process(target=target.run, daemon=True) - test.process.start() - time.sleep(0.1) - - -def cleanup_mock_retry_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.thread.stop() - test.thread = None - else: - # stop the thread to fetch mock_received_requests from the remote process - test.monitor_thread.stop() - - retry_count = 0 - # terminate the process - while test.process.is_alive(): - test.process.terminate() - time.sleep(0.01) - retry_count += 1 - if retry_count >= 100: - raise Exception("Failed to stop the mock server!") - - # Python 3.6 does not have this method - if sys.version_info.major == 3 and sys.version_info.minor > 6: - # cleanup the process's resources - test.process.close() - - test.process = None diff --git a/tests/slack_sdk/web/test_web_client.py b/tests/slack_sdk/web/test_web_client.py index 024915a20..8423dee72 100644 --- a/tests/slack_sdk/web/test_web_client.py +++ b/tests/slack_sdk/web/test_web_client.py @@ -7,15 +7,13 @@ from slack_sdk import WebClient from slack_sdk.models.blocks import DividerBlock from slack_sdk.models.metadata import Metadata -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) self.client = WebClient( token="xoxb-api_test", base_url="http://localhost:8888", diff --git a/tests/slack_sdk/web/test_web_client_http_retry.py b/tests/slack_sdk/web/test_web_client_http_retry.py index 7036187f3..85d1c0875 100644 --- a/tests/slack_sdk/web/test_web_client_http_retry.py +++ b/tests/slack_sdk/web/test_web_client_http_retry.py @@ -3,17 +3,15 @@ from slack_sdk.errors import SlackApiError from slack_sdk.http_retry import RateLimitErrorRetryHandler from slack_sdk.web import WebClient -from tests.slack_sdk.web.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server from ..fatal_error_retry_handler import FatalErrorRetryHandler from ..my_retry_handler import MyRetryHandler class TestWebClient_HttpRetry(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/web/test_web_client_http_retry_connection.py b/tests/slack_sdk/web/test_web_client_http_retry_connection.py index 31e4ba15e..b77a30b7e 100644 --- a/tests/slack_sdk/web/test_web_client_http_retry_connection.py +++ b/tests/slack_sdk/web/test_web_client_http_retry_connection.py @@ -1,18 +1,16 @@ import unittest from slack_sdk.web import WebClient -from tests.slack_sdk.web.mock_web_api_server_http_retry import ( - setup_mock_retry_web_api_server, - cleanup_mock_retry_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_http_retry_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient_HttpRetry(unittest.TestCase): def setUp(self): - setup_mock_retry_web_api_server(self) + setup_mock_web_api_server(self, MockHandler, port=8889) def tearDown(self): - cleanup_mock_retry_web_api_server(self) + cleanup_mock_web_api_server(self) def test_remote_disconnected(self): client = WebClient( diff --git a/tests/slack_sdk/web/test_web_client_http_retry_server_error.py b/tests/slack_sdk/web/test_web_client_http_retry_server_error.py index ab7408f3c..b0d607683 100644 --- a/tests/slack_sdk/web/test_web_client_http_retry_server_error.py +++ b/tests/slack_sdk/web/test_web_client_http_retry_server_error.py @@ -5,10 +5,8 @@ from slack_sdk.http_retry.builtin_handlers import ServerErrorRetryHandler from slack_sdk.http_retry.handler import default_interval_calculator from slack_sdk.web import WebClient -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class MyServerErrorRetryHandler(RetryHandler): @@ -36,7 +34,7 @@ def _can_retry( class TestWebClient_HttpRetry_ServerError(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/web/test_web_client_issue_1049.py b/tests/slack_sdk/web/test_web_client_issue_1049.py index ebe364266..5281514fc 100644 --- a/tests/slack_sdk/web/test_web_client_issue_1049.py +++ b/tests/slack_sdk/web/test_web_client_issue_1049.py @@ -1,16 +1,13 @@ -import json import unittest from slack_sdk.web import WebClient -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient_Issue_1049(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/web/test_web_client_issue_829.py b/tests/slack_sdk/web/test_web_client_issue_829.py index 346d6a3c8..a8b0b898b 100644 --- a/tests/slack_sdk/web/test_web_client_issue_829.py +++ b/tests/slack_sdk/web/test_web_client_issue_829.py @@ -2,15 +2,13 @@ import slack_sdk.errors as err from slack_sdk.web import WebClient -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient_Issue_829(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/web/test_web_client_issue_891.py b/tests/slack_sdk/web/test_web_client_issue_891.py index 4de79bcca..ed47b0e4d 100644 --- a/tests/slack_sdk/web/test_web_client_issue_891.py +++ b/tests/slack_sdk/web/test_web_client_issue_891.py @@ -1,15 +1,13 @@ import unittest from slack_sdk import WebClient -from tests.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient_Issue_891(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/web/test_web_client_issue_900.py b/tests/slack_sdk/web/test_web_client_issue_900.py index 007258421..08969eab2 100644 --- a/tests/slack_sdk/web/test_web_client_issue_900.py +++ b/tests/slack_sdk/web/test_web_client_issue_900.py @@ -2,15 +2,13 @@ from os.path import dirname from slack_sdk.web import WebClient -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient_Issue_900(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/web/test_web_client_issue_921_custom_logger.py b/tests/slack_sdk/web/test_web_client_issue_921_custom_logger.py index 1049eec6b..bcaf13316 100644 --- a/tests/slack_sdk/web/test_web_client_issue_921_custom_logger.py +++ b/tests/slack_sdk/web/test_web_client_issue_921_custom_logger.py @@ -2,15 +2,13 @@ from logging import Logger from slack_sdk.web import WebClient -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient_Issue_921_CustomLogger(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/web/test_web_client_issue_971.py b/tests/slack_sdk/web/test_web_client_issue_971.py index bc171d18b..d086d0fde 100644 --- a/tests/slack_sdk/web/test_web_client_issue_971.py +++ b/tests/slack_sdk/web/test_web_client_issue_971.py @@ -2,15 +2,13 @@ import unittest from slack_sdk.web import WebClient -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient_Issue_971(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/web/test_web_client_stars_deprecation.py b/tests/slack_sdk/web/test_web_client_stars_deprecation.py index e2c0846b1..c9c970405 100644 --- a/tests/slack_sdk/web/test_web_client_stars_deprecation.py +++ b/tests/slack_sdk/web/test_web_client_stars_deprecation.py @@ -3,15 +3,13 @@ import pytest from slack_sdk.web import WebClient -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/web/test_web_client_workflow_step_deprecation.py b/tests/slack_sdk/web/test_web_client_workflow_step_deprecation.py index 397f600e3..e5619a6b4 100644 --- a/tests/slack_sdk/web/test_web_client_workflow_step_deprecation.py +++ b/tests/slack_sdk/web/test_web_client_workflow_step_deprecation.py @@ -3,15 +3,13 @@ from slack_sdk.web import WebClient from tests.helpers import remove_os_env_temporarily, restore_os_env -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) self.env_values = remove_os_env_temporarily() def tearDown(self): diff --git a/tests/slack_sdk/webhook/mock_web_api_server.py b/tests/slack_sdk/webhook/mock_web_api_server.py index e9b1e432c..f42c9bf27 100644 --- a/tests/slack_sdk/webhook/mock_web_api_server.py +++ b/tests/slack_sdk/webhook/mock_web_api_server.py @@ -1,18 +1,9 @@ -import asyncio import json import logging import re -import sys -import threading import time from http import HTTPStatus -from http.server import HTTPServer, SimpleHTTPRequestHandler -from multiprocessing.context import Process -from typing import Type -from unittest import TestCase -from urllib.request import Request, urlopen - -from tests.helpers import get_mock_server_mode +from http.server import SimpleHTTPRequestHandler class MockHandler(SimpleHTTPRequestHandler): @@ -35,6 +26,8 @@ def set_common_headers(self): self.end_headers() def do_GET(self): + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) if self.path == "/received_requests.json": self.send_response(200) self.set_common_headers() @@ -43,6 +36,8 @@ def do_GET(self): def do_POST(self): try: + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) if self.path == "/remote_disconnected": # http.client.RemoteDisconnected self.finish() @@ -96,159 +91,3 @@ def do_POST(self): except Exception as e: self.logger.error(str(e), exc_info=True) raise - - -class MockServerProcessTarget: - def __init__(self, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - self.handler = handler - - def run(self): - self.handler.received_requests = {} - self.server = HTTPServer(("localhost", 8888), self.handler) - try: - self.server.serve_forever(0.05) - finally: - self.server.server_close() - - def stop(self): - self.handler.received_requests = {} - self.server.shutdown() - self.join() - - -class MonitorThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self, daemon=True) - self.handler = handler - self.test = test - self.test.mock_received_requests = None - self.is_running = True - - def run(self) -> None: - while self.is_running: - try: - req = Request(f"{self.test.server_url}/received_requests.json") - resp = urlopen(req, timeout=1) - self.test.mock_received_requests = json.loads(resp.read().decode("utf-8")) - except Exception as e: - # skip logging for the initial request - if self.test.mock_received_requests is not None: - logging.getLogger(__name__).exception(e) - time.sleep(0.01) - - def stop(self): - self.is_running = False - self.join() - - -class MockServerThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self) - self.handler = handler - self.test = test - - def run(self): - self.server = HTTPServer(("localhost", 8888), self.handler) - self.test.server_url = "http://localhost:8888" - self.test.host, self.test.port = self.server.socket.getsockname() - self.test.server_started.set() # threading.Event() - - self.test = None - try: - self.server.serve_forever() - finally: - self.server.server_close() - - def stop(self): - self.server.shutdown() - self.join() - - -def setup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.server_started = threading.Event() - test.thread = MockServerThread(test) - test.thread.start() - test.server_started.wait() - else: - # start a mock server as another process - target = MockServerProcessTarget() - test.server_url = "http://localhost:8888" - test.host, test.port = "localhost", 8888 - test.process = Process(target=target.run, daemon=True) - test.process.start() - - time.sleep(0.1) - - # start a thread in the current process - # this thread fetches mock_received_requests from the remote process - test.monitor_thread = MonitorThread(test) - test.monitor_thread.start() - count = 0 - # wait until the first successful data retrieval - while test.mock_received_requests is None: - time.sleep(0.01) - count += 1 - if count >= 100: - raise Exception("The mock server is not yet running!") - - -def cleanup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.thread.stop() - test.thread = None - else: - # stop the thread to fetch mock_received_requests from the remote process - test.monitor_thread.stop() - - retry_count = 0 - # terminate the process - while test.process.is_alive(): - test.process.terminate() - time.sleep(0.01) - retry_count += 1 - if retry_count >= 100: - raise Exception("Failed to stop the mock server!") - - # Python 3.6 does not have this method - if sys.version_info.major == 3 and sys.version_info.minor > 6: - # cleanup the process's resources - test.process.close() - - test.process = None - - -def assert_auth_test_count(test: TestCase, expected_count: int): - time.sleep(0.1) - retry_count = 0 - error = None - while retry_count < 3: - try: - test.mock_received_requests["/auth.test"] == expected_count - break - except Exception as e: - error = e - retry_count += 1 - # waiting for mock_received_requests updates - time.sleep(0.1) - - if error is not None: - raise error - - -async def assert_auth_test_count_async(test: TestCase, expected_count: int): - await asyncio.sleep(0.1) - retry_count = 0 - error = None - while retry_count < 3: - try: - test.mock_received_requests["/auth.test"] == expected_count - break - except Exception as e: - error = e - retry_count += 1 - # waiting for mock_received_requests updates - await asyncio.sleep(0.1) - - if error is not None: - raise error diff --git a/tests/slack_sdk/webhook/test_webhook.py b/tests/slack_sdk/webhook/test_webhook.py index 42a7ac585..19ac9a805 100644 --- a/tests/slack_sdk/webhook/test_webhook.py +++ b/tests/slack_sdk/webhook/test_webhook.py @@ -6,15 +6,13 @@ from slack_sdk.models.attachments import Attachment, AttachmentField from slack_sdk.models.blocks import SectionBlock, ImageBlock from slack_sdk.webhook import WebhookClient, WebhookResponse -from tests.slack_sdk.webhook.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.slack_sdk.webhook.mock_web_api_server import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebhook(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk/webhook/test_webhook_http_retry.py b/tests/slack_sdk/webhook/test_webhook_http_retry.py index 1ae3b2622..c35441500 100644 --- a/tests/slack_sdk/webhook/test_webhook_http_retry.py +++ b/tests/slack_sdk/webhook/test_webhook_http_retry.py @@ -2,16 +2,14 @@ from slack_sdk.http_retry import RateLimitErrorRetryHandler from slack_sdk.webhook import WebhookClient -from tests.slack_sdk.webhook.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.slack_sdk.webhook.mock_web_api_server import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server from ..my_retry_handler import MyRetryHandler class TestWebhook_HttpRetries(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/slack_sdk_async/audit_logs/test_async_client.py b/tests/slack_sdk_async/audit_logs/test_async_client.py index ab9eea734..92f9fa149 100644 --- a/tests/slack_sdk_async/audit_logs/test_async_client.py +++ b/tests/slack_sdk_async/audit_logs/test_async_client.py @@ -3,19 +3,17 @@ from slack_sdk.audit_logs.async_client import AsyncAuditLogsClient from slack_sdk.audit_logs import AuditLogsResponse from tests.helpers import async_test -from tests.slack_sdk.audit_logs.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.slack_sdk.audit_logs.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestAsyncAuditLogsClient(unittest.TestCase): def setUp(self): self.client = AsyncAuditLogsClient(token="xoxp-", base_url="http://localhost:8888/") - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_logs(self): diff --git a/tests/slack_sdk_async/audit_logs/test_async_client_http_retry.py b/tests/slack_sdk_async/audit_logs/test_async_client_http_retry.py index 9e6e59ef0..b25200072 100644 --- a/tests/slack_sdk_async/audit_logs/test_async_client_http_retry.py +++ b/tests/slack_sdk_async/audit_logs/test_async_client_http_retry.py @@ -3,19 +3,17 @@ from slack_sdk.audit_logs.async_client import AsyncAuditLogsClient from slack_sdk.http_retry.builtin_async_handlers import AsyncRateLimitErrorRetryHandler from tests.helpers import async_test -from tests.slack_sdk.audit_logs.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.slack_sdk.audit_logs.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async from ..my_retry_handler import MyRetryHandler class TestAsyncAuditLogsClient_HttpRetries(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_http_retries(self): diff --git a/tests/slack_sdk_async/oauth/token_rotation/test_token_rotator.py b/tests/slack_sdk_async/oauth/token_rotation/test_token_rotator.py index f379de925..edc957647 100644 --- a/tests/slack_sdk_async/oauth/token_rotation/test_token_rotator.py +++ b/tests/slack_sdk_async/oauth/token_rotation/test_token_rotator.py @@ -5,15 +5,13 @@ from slack_sdk.oauth.token_rotation.async_rotator import AsyncTokenRotator from slack_sdk.web.async_client import AsyncWebClient from tests.helpers import async_test -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestTokenRotator(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) self.token_rotator = AsyncTokenRotator( client=AsyncWebClient(base_url="http://localhost:8888", token=None), client_id="111.222", @@ -21,7 +19,7 @@ def setUp(self): ) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_refresh(self): diff --git a/tests/slack_sdk_async/scim/test_async_client.py b/tests/slack_sdk_async/scim/test_async_client.py index 1d38a673a..c4faa6a73 100644 --- a/tests/slack_sdk_async/scim/test_async_client.py +++ b/tests/slack_sdk_async/scim/test_async_client.py @@ -6,18 +6,16 @@ from slack_sdk.scim.v1.group import GroupMember from slack_sdk.scim.v1.user import UserName, UserEmail from tests.helpers import async_test -from tests.slack_sdk.scim.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.scim.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestSCIMClient(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_users(self): diff --git a/tests/slack_sdk_async/scim/test_async_client_http_retry.py b/tests/slack_sdk_async/scim/test_async_client_http_retry.py index fa60a32e0..92fe595dd 100644 --- a/tests/slack_sdk_async/scim/test_async_client_http_retry.py +++ b/tests/slack_sdk_async/scim/test_async_client_http_retry.py @@ -3,19 +3,17 @@ from slack_sdk.http_retry.builtin_async_handlers import AsyncRateLimitErrorRetryHandler from slack_sdk.scim.v1.async_client import AsyncSCIMClient from tests.helpers import async_test -from tests.slack_sdk.scim.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.scim.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async from ..my_retry_handler import MyRetryHandler class TestSCIMClient_HttpRetries(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_retries(self): diff --git a/tests/slack_sdk_async/socket_mode/test_aiohttp.py b/tests/slack_sdk_async/socket_mode/test_aiohttp.py index 1ede5da29..797c51c37 100644 --- a/tests/slack_sdk_async/socket_mode/test_aiohttp.py +++ b/tests/slack_sdk_async/socket_mode/test_aiohttp.py @@ -1,25 +1,22 @@ -import asyncio import unittest from slack_sdk.socket_mode.aiohttp import SocketModeClient from slack_sdk.web.async_client import AsyncWebClient -from tests.slack_sdk.socket_mode.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.socket_mode.mock_web_api_handler import MockHandler from tests.slack_sdk_async.helpers import async_test +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestAiohttp(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) self.web_client = AsyncWebClient( token="xoxb-api_test", base_url="http://localhost:8888", ) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_init_close(self): diff --git a/tests/slack_sdk_async/socket_mode/test_interactions_aiohttp.py b/tests/slack_sdk_async/socket_mode/test_interactions_aiohttp.py index 57ea6ca4c..cfe71a83b 100644 --- a/tests/slack_sdk_async/socket_mode/test_interactions_aiohttp.py +++ b/tests/slack_sdk_async/socket_mode/test_interactions_aiohttp.py @@ -20,10 +20,8 @@ socket_mode_envelopes, socket_mode_hello_message, ) -from tests.slack_sdk.socket_mode.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.socket_mode.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async from tests.slack_sdk_async.helpers import async_test @@ -31,14 +29,14 @@ class TestInteractionsAiohttp(unittest.TestCase): logger = logging.getLogger(__name__) def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) self.web_client = AsyncWebClient( token="xoxb-api_test", base_url="http://localhost:8888", ) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_interactions(self): diff --git a/tests/slack_sdk_async/socket_mode/test_interactions_websockets.py b/tests/slack_sdk_async/socket_mode/test_interactions_websockets.py index 6cb354fb5..99652b688 100644 --- a/tests/slack_sdk_async/socket_mode/test_interactions_websockets.py +++ b/tests/slack_sdk_async/socket_mode/test_interactions_websockets.py @@ -20,10 +20,8 @@ socket_mode_envelopes, socket_mode_hello_message, ) -from tests.slack_sdk.socket_mode.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.socket_mode.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async from tests.slack_sdk_async.helpers import async_test @@ -31,14 +29,14 @@ class TestInteractionsWebsockets(unittest.TestCase): logger = logging.getLogger(__name__) def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) self.web_client = AsyncWebClient( token="xoxb-api_test", base_url="http://localhost:8888", ) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_interactions(self): diff --git a/tests/slack_sdk_async/socket_mode/test_websockets.py b/tests/slack_sdk_async/socket_mode/test_websockets.py index 4fc50b345..322bd8c73 100644 --- a/tests/slack_sdk_async/socket_mode/test_websockets.py +++ b/tests/slack_sdk_async/socket_mode/test_websockets.py @@ -2,23 +2,21 @@ from slack_sdk.socket_mode.websockets import SocketModeClient from slack_sdk.web.async_client import AsyncWebClient -from tests.slack_sdk.socket_mode.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.socket_mode.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async from tests.slack_sdk_async.helpers import async_test class TestAiohttp(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) self.web_client = AsyncWebClient( token="xoxb-api_test", base_url="http://localhost:8888", ) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_init_close(self): diff --git a/tests/slack_sdk_async/web/test_async_web_client.py b/tests/slack_sdk_async/web/test_async_web_client.py index b3a5287a0..e48ad0941 100644 --- a/tests/slack_sdk_async/web/test_async_web_client.py +++ b/tests/slack_sdk_async/web/test_async_web_client.py @@ -5,22 +5,20 @@ from slack_sdk.models.blocks import DividerBlock from slack_sdk.web.async_client import AsyncWebClient from tests.slack_sdk_async.helpers import async_test -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestAsyncWebClient(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) self.client = AsyncWebClient( token="xoxp-1234", base_url="http://localhost:8888", ) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) pattern_for_language = re.compile("python/(\\S+)", re.IGNORECASE) pattern_for_package_identifier = re.compile("slackclient/(\\S+)") diff --git a/tests/slack_sdk_async/web/test_async_web_client_http_retry.py b/tests/slack_sdk_async/web/test_async_web_client_http_retry.py index 8a352e0d5..dac633aeb 100644 --- a/tests/slack_sdk_async/web/test_async_web_client_http_retry.py +++ b/tests/slack_sdk_async/web/test_async_web_client_http_retry.py @@ -4,20 +4,18 @@ from slack_sdk.http_retry.builtin_async_handlers import AsyncRateLimitErrorRetryHandler, AsyncServerErrorRetryHandler from slack_sdk.web.async_client import AsyncWebClient from tests.slack_sdk_async.helpers import async_test -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async from ..fatal_error_retry_handler import FatalErrorRetryHandler from ..my_retry_handler import MyRetryHandler class TestAsyncWebClient_HttpRetries(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_remote_disconnected(self): diff --git a/tests/slack_sdk_async/web/test_web_client_coverage.py b/tests/slack_sdk_async/web/test_web_client_coverage.py index 885acdb0d..e09a0c0ec 100644 --- a/tests/slack_sdk_async/web/test_web_client_coverage.py +++ b/tests/slack_sdk_async/web/test_web_client_coverage.py @@ -7,10 +7,9 @@ from slack_sdk.web import WebClient from slack_sdk.web.async_client import AsyncWebClient from slack_sdk.web.legacy_client import LegacyWebClient -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async + from tests.slack_sdk_async.helpers import async_test @@ -25,7 +24,7 @@ class TestWebClientCoverage(unittest.TestCase): os.environ.setdefault("SLACKCLIENT_SKIP_DEPRECATION", "1") def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) self.client = WebClient(token="xoxb-coverage", base_url="http://localhost:8888") self.legacy_client = LegacyWebClient(token="xoxb-coverage", base_url="http://localhost:8888") self.async_client = AsyncWebClient(token="xoxb-coverage", base_url="http://localhost:8888") @@ -74,7 +73,7 @@ def setUp(self): self.api_methods_to_call.append(api_method) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) async def run_method(self, method_name, method, async_method): # Run the api calls with required arguments diff --git a/tests/slack_sdk_async/web/test_web_client_issue_829.py b/tests/slack_sdk_async/web/test_web_client_issue_829.py index 3fe077723..a7f438e18 100644 --- a/tests/slack_sdk_async/web/test_web_client_issue_829.py +++ b/tests/slack_sdk_async/web/test_web_client_issue_829.py @@ -3,18 +3,16 @@ import slack_sdk.errors as err from slack_sdk.web.async_client import AsyncWebClient from tests.helpers import async_test -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestWebClient_Issue_829(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_html_response_body_issue_829_async(self): diff --git a/tests/slack_sdk_async/web/test_web_client_issue_891.py b/tests/slack_sdk_async/web/test_web_client_issue_891.py index 55e0cdb12..1e81f775d 100644 --- a/tests/slack_sdk_async/web/test_web_client_issue_891.py +++ b/tests/slack_sdk_async/web/test_web_client_issue_891.py @@ -2,18 +2,16 @@ from slack_sdk.web.async_client import AsyncWebClient from tests.helpers import async_test -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestWebClient_Issue_829(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_missing_text_warning_chat_postMessage(self): diff --git a/tests/slack_sdk_async/web/test_web_client_issue_921_custom_logger.py b/tests/slack_sdk_async/web/test_web_client_issue_921_custom_logger.py index 4b69f6bfc..9239206cf 100644 --- a/tests/slack_sdk_async/web/test_web_client_issue_921_custom_logger.py +++ b/tests/slack_sdk_async/web/test_web_client_issue_921_custom_logger.py @@ -3,18 +3,16 @@ from slack_sdk.web.async_client import AsyncWebClient from tests.helpers import async_test -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestWebClient_Issue_921_CustomLogger(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_if_it_uses_custom_logger(self): diff --git a/tests/slack_sdk_async/webhook/test_async_webhook.py b/tests/slack_sdk_async/webhook/test_async_webhook.py index b2e0a64bd..c84a0fc93 100644 --- a/tests/slack_sdk_async/webhook/test_async_webhook.py +++ b/tests/slack_sdk_async/webhook/test_async_webhook.py @@ -5,18 +5,16 @@ from slack_sdk.models.blocks import SectionBlock, ImageBlock from slack_sdk.webhook.async_client import AsyncWebhookClient, WebhookResponse from tests.slack_sdk_async.helpers import async_test -from tests.slack_sdk.webhook.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.slack_sdk.webhook.mock_web_api_server import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestAsyncWebhook(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_send(self): diff --git a/tests/slack_sdk_async/webhook/test_async_webhook_http_retry.py b/tests/slack_sdk_async/webhook/test_async_webhook_http_retry.py index e44d2f5cb..9f85b61be 100644 --- a/tests/slack_sdk_async/webhook/test_async_webhook_http_retry.py +++ b/tests/slack_sdk_async/webhook/test_async_webhook_http_retry.py @@ -3,19 +3,17 @@ from slack_sdk.http_retry.builtin_async_handlers import AsyncRateLimitErrorRetryHandler from slack_sdk.webhook.async_client import AsyncWebhookClient from tests.slack_sdk_async.helpers import async_test -from tests.slack_sdk.webhook.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.slack_sdk.webhook.mock_web_api_server import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async from ..my_retry_handler import MyRetryHandler class TestAsyncWebhook_HttpRetries(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_send(self): diff --git a/tests/web/mock_web_api_server.py b/tests/web/mock_web_api_handler.py similarity index 64% rename from tests/web/mock_web_api_server.py rename to tests/web/mock_web_api_handler.py index a189a3da2..f58aa36ec 100644 --- a/tests/web/mock_web_api_server.py +++ b/tests/web/mock_web_api_handler.py @@ -1,19 +1,10 @@ -import asyncio import json import logging import re -import sys -import threading import time from http import HTTPStatus -from http.server import HTTPServer, SimpleHTTPRequestHandler -from multiprocessing.context import Process -from typing import Type -from unittest import TestCase -from urllib.parse import urlparse, parse_qs -from urllib.request import Request, urlopen - -from tests.helpers import get_mock_server_mode +from http.server import SimpleHTTPRequestHandler +from urllib.parse import parse_qs, urlparse class MockHandler(SimpleHTTPRequestHandler): @@ -56,11 +47,8 @@ def set_common_headers(self): def _handle(self): try: - if self.path == "/received_requests.json": - self.send_response(200) - self.set_common_headers() - self.wfile.write(json.dumps(self.received_requests).encode("utf-8")) - return + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) if self.path in {"/oauth.access", "/oauth.v2.access"}: self.send_response(200) @@ -196,159 +184,3 @@ def do_GET(self): def do_POST(self): self._handle() - - -class MockServerProcessTarget: - def __init__(self, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - self.handler = handler - - def run(self): - self.handler.received_requests = {} - self.server = HTTPServer(("localhost", 8888), self.handler) - try: - self.server.serve_forever(0.05) - finally: - self.server.server_close() - - def stop(self): - self.handler.received_requests = {} - self.server.shutdown() - self.join() - - -class MonitorThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self, daemon=True) - self.handler = handler - self.test = test - self.test.mock_received_requests = None - self.is_running = True - - def run(self) -> None: - while self.is_running: - try: - req = Request(f"{self.test.server_url}/received_requests.json") - resp = urlopen(req, timeout=1) - self.test.mock_received_requests = json.loads(resp.read().decode("utf-8")) - except Exception as e: - # skip logging for the initial request - if self.test.mock_received_requests is not None: - logging.getLogger(__name__).exception(e) - time.sleep(0.01) - - def stop(self): - self.is_running = False - self.join() - - -class MockServerThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self) - self.handler = handler - self.test = test - - def run(self): - self.server = HTTPServer(("localhost", 8888), self.handler) - self.test.server_url = "http://localhost:8888" - self.test.host, self.test.port = self.server.socket.getsockname() - self.test.server_started.set() # threading.Event() - - self.test = None - try: - self.server.serve_forever() - finally: - self.server.server_close() - - def stop(self): - self.server.shutdown() - self.join() - - -def setup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.server_started = threading.Event() - test.thread = MockServerThread(test) - test.thread.start() - test.server_started.wait() - else: - # start a mock server as another process - target = MockServerProcessTarget() - test.server_url = "http://localhost:8888" - test.host, test.port = "localhost", 8888 - test.process = Process(target=target.run, daemon=True) - test.process.start() - - time.sleep(0.1) - - # start a thread in the current process - # this thread fetches mock_received_requests from the remote process - test.monitor_thread = MonitorThread(test) - test.monitor_thread.start() - count = 0 - # wait until the first successful data retrieval - while test.mock_received_requests is None: - time.sleep(0.01) - count += 1 - if count >= 100: - raise Exception("The mock server is not yet running!") - - -def cleanup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.thread.stop() - test.thread = None - else: - # stop the thread to fetch mock_received_requests from the remote process - test.monitor_thread.stop() - - retry_count = 0 - # terminate the process - while test.process.is_alive(): - test.process.terminate() - time.sleep(0.01) - retry_count += 1 - if retry_count >= 100: - raise Exception("Failed to stop the mock server!") - - # Python 3.6 does not have this method - if sys.version_info.major == 3 and sys.version_info.minor > 6: - # cleanup the process's resources - test.process.close() - - test.process = None - - -def assert_auth_test_count(test: TestCase, expected_count: int): - time.sleep(0.1) - retry_count = 0 - error = None - while retry_count < 3: - try: - test.mock_received_requests["/auth.test"] == expected_count - break - except Exception as e: - error = e - retry_count += 1 - # waiting for mock_received_requests updates - time.sleep(0.1) - - if error is not None: - raise error - - -async def assert_auth_test_count_async(test: TestCase, expected_count: int): - await asyncio.sleep(0.1) - retry_count = 0 - error = None - while retry_count < 3: - try: - test.mock_received_requests["/auth.test"] == expected_count - break - except Exception as e: - error = e - retry_count += 1 - # waiting for mock_received_requests updates - await asyncio.sleep(0.1) - - if error is not None: - raise error diff --git a/tests/web/test_async_web_client.py b/tests/web/test_async_web_client.py index 786503b72..151a6ae55 100644 --- a/tests/web/test_async_web_client.py +++ b/tests/web/test_async_web_client.py @@ -5,22 +5,20 @@ import slack.errors as err from slack import AsyncWebClient from tests.helpers import async_test -from tests.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestAsyncWebClient(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) self.client = AsyncWebClient( token="xoxp-1234", base_url="http://localhost:8888", ) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) pattern_for_language = re.compile("python/(\\S+)", re.IGNORECASE) pattern_for_package_identifier = re.compile("slackclient/(\\S+)") diff --git a/tests/web/test_web_client.py b/tests/web/test_web_client.py index d13487c04..71c8b6287 100644 --- a/tests/web/test_web_client.py +++ b/tests/web/test_web_client.py @@ -1,5 +1,4 @@ import asyncio -import gc import io import re import socket @@ -8,15 +7,13 @@ import slack.errors as err from slack import WebClient from tests.helpers import async_test -from tests.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestWebClient(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) self.client = WebClient( token="xoxp-1234", base_url="http://localhost:8888", @@ -28,7 +25,7 @@ def setUp(self): ) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) pattern_for_language = re.compile("python/(\\S+)", re.IGNORECASE) pattern_for_package_identifier = re.compile("slackclient/(\\S+)") diff --git a/tests/web/test_web_client_functional.py b/tests/web/test_web_client_functional.py index 6bf3cd4bd..c2ece8de7 100644 --- a/tests/web/test_web_client_functional.py +++ b/tests/web/test_web_client_functional.py @@ -1,15 +1,13 @@ import unittest import slack -from tests.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClientFunctional(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) self.client = slack.WebClient(token="xoxb-api_test", base_url="http://localhost:8888") def tearDown(self): diff --git a/tests/web/test_web_client_issue_829.py b/tests/web/test_web_client_issue_829.py index 3e4b9c504..d728b8404 100644 --- a/tests/web/test_web_client_issue_829.py +++ b/tests/web/test_web_client_issue_829.py @@ -3,15 +3,13 @@ import slack.errors as err from slack import WebClient from tests.helpers import async_test -from tests.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestWebClient_Issue_829(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) self.client = WebClient( token="xoxp-1234", base_url="http://localhost:8888", @@ -23,7 +21,7 @@ def setUp(self): ) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) def test_html_response_body_issue_829(self): client = WebClient(base_url="http://localhost:8888") diff --git a/tests/web/test_web_client_issue_891.py b/tests/web/test_web_client_issue_891.py index 6aca56049..f2d341f49 100644 --- a/tests/web/test_web_client_issue_891.py +++ b/tests/web/test_web_client_issue_891.py @@ -1,15 +1,13 @@ import unittest from slack import WebClient -from tests.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient_Issue_891(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/web/test_web_client_issue_921_custom_logger.py b/tests/web/test_web_client_issue_921_custom_logger.py index 16e147e75..1580b9b5c 100644 --- a/tests/web/test_web_client_issue_921_custom_logger.py +++ b/tests/web/test_web_client_issue_921_custom_logger.py @@ -2,15 +2,13 @@ from logging import Logger from slack.web import WebClient -from tests.slack_sdk.web.mock_web_api_server import ( - setup_mock_web_api_server, - cleanup_mock_web_api_server, -) +from tests.slack_sdk.web.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebClient_Issue_921_CustomLogger(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self) diff --git a/tests/webhook/mock_web_api_handler.py b/tests/webhook/mock_web_api_handler.py new file mode 100644 index 000000000..9de171fee --- /dev/null +++ b/tests/webhook/mock_web_api_handler.py @@ -0,0 +1,79 @@ +import json +import logging +import re +import time +from http import HTTPStatus +from http.server import SimpleHTTPRequestHandler + + +class MockHandler(SimpleHTTPRequestHandler): + protocol_version = "HTTP/1.1" + default_request_version = "HTTP/1.1" + logger = logging.getLogger(__name__) + + pattern_for_language = re.compile("python/(\\S+)", re.IGNORECASE) + pattern_for_package_identifier = re.compile("slackclient/(\\S+)") + + error_html_response_body = '\n\n\n\t\n\tServer Error | Slack\n\t\n\t\n\n\n\t\n\t
\n\t\t
\n\t\t\t

\n\t\t\t\t\n\t\t\t\tServer Error\n\t\t\t

\n\t\t\t
\n\t\t\t\t

It seems like there’s a problem connecting to our servers, and we’re investigating the issue.

\n\t\t\t\t

Please check our Status page for updates.

\n\t\t\t
\n\t\t
\n\t
\n\t\n\n' + + def is_valid_user_agent(self): + user_agent = self.headers["User-Agent"] + return self.pattern_for_language.search(user_agent) and self.pattern_for_package_identifier.search(user_agent) + + def set_common_headers(self): + self.send_header("content-type", "text/plain;charset=utf-8") + self.send_header("connection", "close") + self.end_headers() + + def do_GET(self): + if self.path == "/received_requests.json": + self.send_response(200) + self.set_common_headers() + self.wfile.write(json.dumps(self.received_requests).encode("utf-8")) + return + + def do_POST(self): + try: + # put_nowait is common between Queue & asyncio.Queue, it does not need to be awaited + self.server.queue.put_nowait(self.path) + if self.path == "/timeout": + time.sleep(2) + + # user-agent-this_is-test + if self.path.startswith("/user-agent-"): + elements = self.path.split("-") + prefix, suffix = elements[2], elements[-1] + ua: str = self.headers["User-Agent"] + if ua.startswith(prefix) and ua.endswith(suffix): + self.send_response(HTTPStatus.OK) + self.set_common_headers() + self.wfile.write("ok".encode("utf-8")) + self.wfile.close() + return + else: + self.send_response(HTTPStatus.BAD_REQUEST) + self.set_common_headers() + self.wfile.write("invalid user agent".encode("utf-8")) + self.wfile.close() + return + + if self.path == "/error": + self.send_response(HTTPStatus.INTERNAL_SERVER_ERROR) + # no charset here is intentional for testing + self.send_header("content-type", "text/html") + self.send_header("connection", "close") + self.end_headers() + self.wfile.write(self.error_html_response_body.encode("utf-8")) + self.wfile.close() + return + + body = "ok" + + self.send_response(HTTPStatus.OK) + self.set_common_headers() + self.wfile.write(body.encode("utf-8")) + self.wfile.close() + + except Exception as e: + self.logger.error(str(e), exc_info=True) + raise diff --git a/tests/webhook/mock_web_api_server.py b/tests/webhook/mock_web_api_server.py deleted file mode 100644 index cd0f33b07..000000000 --- a/tests/webhook/mock_web_api_server.py +++ /dev/null @@ -1,267 +0,0 @@ -import asyncio -import json -import logging -import re -import sys -import threading -import time -from http import HTTPStatus -from http.server import HTTPServer, SimpleHTTPRequestHandler -from multiprocessing.context import Process -from typing import Type -from unittest import TestCase -from urllib.request import Request, urlopen - -from tests.helpers import get_mock_server_mode - - -class MockHandler(SimpleHTTPRequestHandler): - protocol_version = "HTTP/1.1" - default_request_version = "HTTP/1.1" - logger = logging.getLogger(__name__) - - pattern_for_language = re.compile("python/(\\S+)", re.IGNORECASE) - pattern_for_package_identifier = re.compile("slackclient/(\\S+)") - - error_html_response_body = '\n\n\n\t\n\tServer Error | Slack\n\t\n\t\n\n\n\t\n\t
\n\t\t
\n\t\t\t

\n\t\t\t\t\n\t\t\t\tServer Error\n\t\t\t

\n\t\t\t
\n\t\t\t\t

It seems like there’s a problem connecting to our servers, and we’re investigating the issue.

\n\t\t\t\t

Please check our Status page for updates.

\n\t\t\t
\n\t\t
\n\t
\n\t\n\n' - - def is_valid_user_agent(self): - user_agent = self.headers["User-Agent"] - return self.pattern_for_language.search(user_agent) and self.pattern_for_package_identifier.search(user_agent) - - def set_common_headers(self): - self.send_header("content-type", "text/plain;charset=utf-8") - self.send_header("connection", "close") - self.end_headers() - - def do_GET(self): - if self.path == "/received_requests.json": - self.send_response(200) - self.set_common_headers() - self.wfile.write(json.dumps(self.received_requests).encode("utf-8")) - return - - def do_POST(self): - try: - if self.path == "/timeout": - time.sleep(2) - - # user-agent-this_is-test - if self.path.startswith("/user-agent-"): - elements = self.path.split("-") - prefix, suffix = elements[2], elements[-1] - ua: str = self.headers["User-Agent"] - if ua.startswith(prefix) and ua.endswith(suffix): - self.send_response(HTTPStatus.OK) - self.set_common_headers() - self.wfile.write("ok".encode("utf-8")) - self.wfile.close() - return - else: - self.send_response(HTTPStatus.BAD_REQUEST) - self.set_common_headers() - self.wfile.write("invalid user agent".encode("utf-8")) - self.wfile.close() - return - - if self.path == "/error": - self.send_response(HTTPStatus.INTERNAL_SERVER_ERROR) - # no charset here is intentional for testing - self.send_header("content-type", "text/html") - self.send_header("connection", "close") - self.end_headers() - self.wfile.write(self.error_html_response_body.encode("utf-8")) - self.wfile.close() - return - - body = "ok" - - self.send_response(HTTPStatus.OK) - self.set_common_headers() - self.wfile.write(body.encode("utf-8")) - self.wfile.close() - - except Exception as e: - self.logger.error(str(e), exc_info=True) - raise - - -class MockServerProcessTarget: - def __init__(self, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - self.handler = handler - - def run(self): - self.handler.received_requests = {} - self.server = HTTPServer(("localhost", 8888), self.handler) - try: - self.server.serve_forever(0.05) - finally: - self.server.server_close() - - def stop(self): - self.handler.received_requests = {} - self.server.shutdown() - self.join() - - -class MonitorThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self, daemon=True) - self.handler = handler - self.test = test - self.test.mock_received_requests = None - self.is_running = True - - def run(self) -> None: - while self.is_running: - try: - req = Request(f"{self.test.server_url}/received_requests.json") - resp = urlopen(req, timeout=1) - self.test.mock_received_requests = json.loads(resp.read().decode("utf-8")) - except Exception as e: - # skip logging for the initial request - if self.test.mock_received_requests is not None: - logging.getLogger(__name__).exception(e) - time.sleep(0.01) - - def stop(self): - self.is_running = False - self.join() - - -class MonitorThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self, daemon=True) - self.handler = handler - self.test = test - self.test.mock_received_requests = None - self.is_running = True - - def run(self) -> None: - while self.is_running: - try: - req = Request(f"{self.test.server_url}/received_requests.json") - resp = urlopen(req, timeout=1) - self.test.mock_received_requests = json.loads(resp.read().decode("utf-8")) - except Exception as e: - # skip logging for the initial request - if self.test.mock_received_requests is not None: - logging.getLogger(__name__).exception(e) - time.sleep(0.01) - - def stop(self): - self.is_running = False - self.join() - - -class MockServerThread(threading.Thread): - def __init__(self, test: TestCase, handler: Type[SimpleHTTPRequestHandler] = MockHandler): - threading.Thread.__init__(self) - self.handler = handler - self.test = test - - def run(self): - self.server = HTTPServer(("localhost", 8888), self.handler) - self.test.server_url = "http://localhost:8888" - self.test.host, self.test.port = self.server.socket.getsockname() - self.test.server_started.set() # threading.Event() - - self.test = None - try: - self.server.serve_forever() - finally: - self.server.server_close() - - def stop(self): - self.server.shutdown() - self.join() - - -def setup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.server_started = threading.Event() - test.thread = MockServerThread(test) - test.thread.start() - test.server_started.wait() - else: - # start a mock server as another process - target = MockServerProcessTarget() - test.server_url = "http://localhost:8888" - test.host, test.port = "localhost", 8888 - test.process = Process(target=target.run, daemon=True) - test.process.start() - - time.sleep(0.1) - - # start a thread in the current process - # this thread fetches mock_received_requests from the remote process - test.monitor_thread = MonitorThread(test) - test.monitor_thread.start() - count = 0 - # wait until the first successful data retrieval - while test.mock_received_requests is None: - time.sleep(0.01) - count += 1 - if count >= 100: - raise Exception("The mock server is not yet running!") - - -def cleanup_mock_web_api_server(test: TestCase): - if get_mock_server_mode() == "threading": - test.thread.stop() - test.thread = None - else: - # stop the thread to fetch mock_received_requests from the remote process - test.monitor_thread.stop() - - retry_count = 0 - # terminate the process - while test.process.is_alive(): - test.process.terminate() - time.sleep(0.01) - retry_count += 1 - if retry_count >= 100: - raise Exception("Failed to stop the mock server!") - - # Python 3.6 does not have this method - if sys.version_info.major == 3 and sys.version_info.minor > 6: - # cleanup the process's resources - test.process.close() - - test.process = None - - -def assert_auth_test_count(test: TestCase, expected_count: int): - time.sleep(0.1) - retry_count = 0 - error = None - while retry_count < 3: - try: - test.mock_received_requests["/auth.test"] == expected_count - break - except Exception as e: - error = e - retry_count += 1 - # waiting for mock_received_requests updates - time.sleep(0.1) - - if error is not None: - raise error - - -async def assert_auth_test_count_async(test: TestCase, expected_count: int): - await asyncio.sleep(0.1) - retry_count = 0 - error = None - while retry_count < 3: - try: - test.mock_received_requests["/auth.test"] == expected_count - break - except Exception as e: - error = e - retry_count += 1 - # waiting for mock_received_requests updates - await asyncio.sleep(0.1) - - if error is not None: - raise error diff --git a/tests/webhook/test_async_webhook.py b/tests/webhook/test_async_webhook.py index e96244d0d..829ec0565 100644 --- a/tests/webhook/test_async_webhook.py +++ b/tests/webhook/test_async_webhook.py @@ -4,18 +4,16 @@ from slack.web.classes.attachments import Attachment, AttachmentField from slack.web.classes.blocks import SectionBlock, ImageBlock from slack.webhook import AsyncWebhookClient, WebhookResponse -from tests.webhook.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.webhook.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server_async, cleanup_mock_web_api_server_async class TestAsyncWebhook(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server_async(self, MockHandler) def tearDown(self): - cleanup_mock_web_api_server(self) + cleanup_mock_web_api_server_async(self) @async_test async def test_send(self): diff --git a/tests/webhook/test_webhook.py b/tests/webhook/test_webhook.py index 3e2cf1a06..abd7f79fa 100644 --- a/tests/webhook/test_webhook.py +++ b/tests/webhook/test_webhook.py @@ -5,15 +5,13 @@ from slack.web.classes.attachments import Attachment, AttachmentField from slack.web.classes.blocks import SectionBlock, ImageBlock from slack.webhook import WebhookClient, WebhookResponse -from tests.webhook.mock_web_api_server import ( - cleanup_mock_web_api_server, - setup_mock_web_api_server, -) +from tests.webhook.mock_web_api_handler import MockHandler +from tests.mock_web_api_server import setup_mock_web_api_server, cleanup_mock_web_api_server class TestWebhook(unittest.TestCase): def setUp(self): - setup_mock_web_api_server(self) + setup_mock_web_api_server(self, MockHandler) def tearDown(self): cleanup_mock_web_api_server(self)