This is an automated email from the ASF dual-hosted git repository.

eladkal pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/main by this push:
     new 9758acf6c2 Optionally use `client.files_upload_v2` in Slack Provider 
(#36757)
9758acf6c2 is described below

commit 9758acf6c2ca100fa7fc2e4f324a95d2a7189e97
Author: Andrey Anshin <[email protected]>
AuthorDate: Sat Jan 20 19:41:50 2024 +0300

    Optionally use `client.files_upload_v2` in Slack Provider (#36757)
    
    * Optionally use `client.files_upload_v2` in Slack Provider
    
    * Revert  default value for method
    
    * Remove redundand assigment
---
 airflow/providers/slack/hooks/slack.py             | 148 ++++++++-
 airflow/providers/slack/operators/slack.py         |  40 ++-
 airflow/providers/slack/provider.yaml              |   2 +-
 airflow/providers/slack/transfers/sql_to_slack.py  |  32 +-
 .../operators/slack_api.rst                        |  18 +-
 .../operators/sql_to_slack.rst                     |  18 ++
 generated/provider_dependencies.json               |   2 +-
 pyproject.toml                                     |   2 +-
 tests/providers/slack/hooks/test_slack.py          | 332 +++++++++++++++------
 tests/providers/slack/operators/test_slack.py      |  82 +++--
 .../providers/slack/transfers/test_sql_to_slack.py |  14 +-
 tests/system/providers/slack/example_slack.py      |   1 +
 12 files changed, 547 insertions(+), 144 deletions(-)

diff --git a/airflow/providers/slack/hooks/slack.py 
b/airflow/providers/slack/hooks/slack.py
index 50abcb63f5..b25282f370 100644
--- a/airflow/providers/slack/hooks/slack.py
+++ b/airflow/providers/slack/hooks/slack.py
@@ -21,10 +21,11 @@ import json
 import warnings
 from functools import cached_property
 from pathlib import Path
-from typing import TYPE_CHECKING, Any, Sequence
+from typing import TYPE_CHECKING, Any, Sequence, TypedDict
 
 from slack_sdk import WebClient
 from slack_sdk.errors import SlackApiError
+from typing_extensions import NotRequired
 
 from airflow.exceptions import AirflowNotFoundException
 from airflow.hooks.base import BaseHook
@@ -36,6 +37,26 @@ if TYPE_CHECKING:
     from slack_sdk.web.slack_response import SlackResponse
 
 
+class FileUploadTypeDef(TypedDict):
+    """
+    Represents the structure of the file upload data.
+
+    :ivar file: Optional. Path to file which need to be sent.
+    :ivar content: Optional. File contents. If omitting this parameter, you 
must provide a file.
+    :ivar filename: Optional. Displayed filename.
+    :ivar title: Optional. The title of the uploaded file.
+    :ivar alt_txt: Optional. Description of image for screen-reader.
+    :ivar snippet_type: Optional. Syntax type of the snippet being uploaded.
+    """
+
+    file: NotRequired[str | None]
+    content: NotRequired[str | None]
+    filename: NotRequired[str | None]
+    title: NotRequired[str | None]
+    alt_txt: NotRequired[str | None]
+    snippet_type: NotRequired[str | None]
+
+
 class SlackHook(BaseHook):
     """
     Creates a Slack API Connection to be used for calls.
@@ -111,6 +132,9 @@ class SlackHook(BaseHook):
             extra_client_args["logger"] = self.log
         self.extra_client_args = extra_client_args
 
+        # Use for caching channels result
+        self._channels_mapping: dict[str, str] = {}
+
     @cached_property
     def client(self) -> WebClient:
         """Get the underlying slack_sdk.WebClient (cached)."""
@@ -212,6 +236,128 @@ class SlackHook(BaseHook):
             channels=channels,
         )
 
+    def send_file_v2(
+        self,
+        *,
+        channel_id: str | None = None,
+        file_uploads: FileUploadTypeDef | list[FileUploadTypeDef],
+        initial_comment: str | None = None,
+    ) -> SlackResponse:
+        """
+        Sends one or more files to a Slack channel using the Slack SDK Client 
method `files_upload_v2`.
+
+        :param channel_id: The ID of the channel to send the file to.
+            If omitting this parameter, then file will send to workspace.
+        :param file_uploads: The file(s) specification to upload.
+        :param initial_comment: The message text introducing the file in 
specified ``channel``.
+        """
+        if channel_id and channel_id.startswith("#"):
+            retried_channel_id = self.get_channel_id(channel_id[1:])
+            warnings.warn(
+                "The method `files_upload_v2` in the Slack SDK Client expects 
a Slack Channel ID, "
+                f"but received a Slack Channel Name. To resolve this, consider 
replacing {channel_id!r} "
+                f"with the corresponding Channel ID {retried_channel_id!r}.",
+                UserWarning,
+                stacklevel=2,
+            )
+            channel_id = retried_channel_id
+
+        if not isinstance(file_uploads, list):
+            file_uploads = [file_uploads]
+        for file_upload in file_uploads:
+            if not file_upload.get("filename"):
+                # Some of early version of Slack SDK (such as 3.19.0) raise an 
error if ``filename`` not set.
+                file_upload["filename"] = "Uploaded file"
+
+        return self.client.files_upload_v2(
+            channel=channel_id,
+            # mypy doesn't happy even if TypedDict used instead of dict[str, 
Any]
+            # see: https://github.com/python/mypy/issues/4976
+            file_uploads=file_uploads,  # type: ignore[arg-type]
+            initial_comment=initial_comment,
+        )
+
+    def send_file_v1_to_v2(
+        self,
+        *,
+        channels: str | Sequence[str] | None = None,
+        file: str | Path | None = None,
+        content: str | None = None,
+        filename: str | None = None,
+        initial_comment: str | None = None,
+        title: str | None = None,
+        filetype: str | None = None,
+    ) -> list[SlackResponse]:
+        """
+        Smooth transition between ``send_file`` and ``send_file_v2`` methods.
+
+        :param channels: Comma-separated list of channel names or IDs where 
the file will be shared.
+            If omitting this parameter, then file will send to workspace.
+            File would be uploaded for each channel individually.
+        :param file: Path to file which need to be sent.
+        :param content: File contents. If omitting this parameter, you must 
provide a file.
+        :param filename: Displayed filename.
+        :param initial_comment: The message text introducing the file in 
specified ``channels``.
+        :param title: Title of the file.
+        :param filetype: A file type identifier.
+        """
+        if not exactly_one(file, content):
+            raise ValueError("Either `file` or `content` must be provided, not 
both.")
+        if file:
+            file = Path(file)
+            file_uploads: FileUploadTypeDef = {"file": file.__fspath__(), 
"filename": filename or file.name}
+        else:
+            file_uploads = {"content": content, "filename": filename}
+
+        file_uploads.update({"title": title, "snippet_type": filetype})
+
+        if channels:
+            if isinstance(channels, str):
+                channels = channels.split(",")
+            channels_to_share: list[str | None] = list(map(str.strip, 
channels))
+        else:
+            channels_to_share = [None]
+
+        responses = []
+        for channel in channels_to_share:
+            responses.append(
+                self.send_file_v2(
+                    channel_id=channel, file_uploads=file_uploads, 
initial_comment=initial_comment
+                )
+            )
+        return responses
+
+    def get_channel_id(self, channel_name: str) -> str:
+        """
+        Retrieves a Slack channel id by a channel name.
+
+        It continuously iterates over all Slack channels (public and private)
+        until it finds the desired channel name in addition cache results for 
further usage.
+
+        .. seealso::
+            https://api.slack.com/methods/conversations.list
+
+        :param channel_name: The name of the Slack channel for which ID has to 
be found.
+        """
+        next_cursor = None
+        while not (channel_id := self._channels_mapping.get(channel_name)):
+            res = self.client.conversations_list(cursor=next_cursor, 
types="public_channel,private_channel")
+            if TYPE_CHECKING:
+                # Slack SDK response type too broad, this should make mypy 
happy
+                assert isinstance(res.data, dict)
+
+            for channel_data in res.data.get("channels", []):
+                self._channels_mapping[channel_data["name"]] = 
channel_data["id"]
+
+            if not (next_cursor := res.data.get("response_metadata", 
{}).get("next_cursor")):
+                channel_id = self._channels_mapping.get(channel_name)
+                break
+
+        if not channel_id:
+            msg = f"Unable to find slack channel with name: {channel_name!r}"
+            raise LookupError(msg)
+        return channel_id
+
     def test_connection(self):
         """Tests the Slack API connection.
 
diff --git a/airflow/providers/slack/operators/slack.py 
b/airflow/providers/slack/operators/slack.py
index 4062eb6d55..838786f332 100644
--- a/airflow/providers/slack/operators/slack.py
+++ b/airflow/providers/slack/operators/slack.py
@@ -22,6 +22,8 @@ import warnings
 from functools import cached_property
 from typing import TYPE_CHECKING, Any, Sequence
 
+from typing_extensions import Literal
+
 from airflow.exceptions import AirflowProviderDeprecationWarning
 from airflow.models import BaseOperator
 from airflow.providers.slack.hooks.slack import SlackHook
@@ -29,13 +31,15 @@ from airflow.providers.slack.hooks.slack import SlackHook
 if TYPE_CHECKING:
     from slack_sdk.http_retry import RetryHandler
 
+    from airflow.utils.context import Context
+
 
 class SlackAPIOperator(BaseOperator):
     """Base Slack Operator class.
 
     :param slack_conn_id: :ref:`Slack API Connection <howto/connection:slack>`
         which its password is Slack API token.
-    :param method: The Slack API Method to Call 
(https://api.slack.com/methods). Optional
+    :param method: The Slack API Method to Call 
(https://api.slack.com/methods).
     :param api_params: API Method call parameters 
(https://api.slack.com/methods). Optional
     :param timeout: The maximum number of seconds the client will wait to 
connect
         and receive a response from Slack. Optional
@@ -56,6 +60,13 @@ class SlackAPIOperator(BaseOperator):
         retry_handlers: list[RetryHandler] | None = None,
         **kwargs,
     ) -> None:
+        if not method:
+            warnings.warn(
+                "Define `method` parameter as empty string or None is 
deprecated. "
+                "In the future it will raise an error on task initialisation.",
+                AirflowProviderDeprecationWarning,
+                stacklevel=2,
+            )
         super().__init__(**kwargs)
         self.slack_conn_id = slack_conn_id
         self.method = method
@@ -90,7 +101,10 @@ class SlackAPIOperator(BaseOperator):
             "SlackAPIOperator should not be used directly. Chose one of the 
subclasses instead"
         )
 
-    def execute(self, **kwargs):
+    def execute(self, context: Context):
+        if not self.method:
+            msg = f"Expected non empty `method` attribute in 
{type(self).__name__!r}, but got {self.method!r}"
+            raise ValueError(msg)
         if not self.api_params:
             self.construct_api_call_params()
         self.hook.call(self.method, json=self.api_params)
@@ -138,14 +152,13 @@ class SlackAPIPostOperator(SlackAPIOperator):
         attachments: list | None = None,
         **kwargs,
     ) -> None:
-        self.method = "chat.postMessage"
+        super().__init__(method="chat.postMessage", **kwargs)
         self.channel = channel
         self.username = username
         self.text = text
         self.icon_url = icon_url
         self.attachments = attachments or []
         self.blocks = blocks or []
-        super().__init__(method=self.method, **kwargs)
 
     def construct_api_call_params(self) -> Any:
         self.api_params = {
@@ -191,7 +204,7 @@ class SlackAPIFileOperator(SlackAPIOperator):
     :param filetype: slack filetype. (templated) See: 
https://api.slack.com/types/file#file_types
     :param content: file content. (templated)
     :param title: title of file. (templated)
-    :param channel: (deprecated) channel in which to sent file on slack name
+    :param method_version: The version of the method of Slack SDK Client to be 
used, either "v1" or "v2".
     """
 
     template_fields: Sequence[str] = (
@@ -212,10 +225,10 @@ class SlackAPIFileOperator(SlackAPIOperator):
         filetype: str | None = None,
         content: str | None = None,
         title: str | None = None,
-        channel: str | None = None,
+        method_version: Literal["v1", "v2"] = "v1",
         **kwargs,
     ) -> None:
-        if channel:
+        if channel := kwargs.pop("channel", None):
             warnings.warn(
                 "Argument `channel` is deprecated and will removed in a future 
releases. "
                 "Please use `channels` instead.",
@@ -226,16 +239,23 @@ class SlackAPIFileOperator(SlackAPIOperator):
                 raise ValueError(f"Cannot set both arguments: 
channel={channel!r} and channels={channels!r}.")
             channels = channel
 
+        super().__init__(method="files.upload", **kwargs)
         self.channels = channels
         self.initial_comment = initial_comment
         self.filename = filename
         self.filetype = filetype
         self.content = content
         self.title = title
-        super().__init__(method="files.upload", **kwargs)
+        self.method_version = method_version
+
+    @property
+    def _method_resolver(self):
+        if self.method_version == "v1":
+            return self.hook.send_file
+        return self.hook.send_file_v1_to_v2
 
-    def execute(self, **kwargs):
-        self.hook.send_file(
+    def execute(self, context: Context):
+        self._method_resolver(
             channels=self.channels,
             # For historical reason SlackAPIFileOperator use filename as 
reference to file
             file=self.filename,
diff --git a/airflow/providers/slack/provider.yaml 
b/airflow/providers/slack/provider.yaml
index ede4202c6b..d8c95c4ab4 100644
--- a/airflow/providers/slack/provider.yaml
+++ b/airflow/providers/slack/provider.yaml
@@ -58,7 +58,7 @@ versions:
 dependencies:
   - apache-airflow>=2.6.0
   - apache-airflow-providers-common-sql>=1.3.1
-  - slack_sdk>=3.0.0
+  - slack_sdk>=3.19.0
 
 integrations:
   - integration-name: Slack
diff --git a/airflow/providers/slack/transfers/sql_to_slack.py 
b/airflow/providers/slack/transfers/sql_to_slack.py
index 6f2ab24f85..1639798c24 100644
--- a/airflow/providers/slack/transfers/sql_to_slack.py
+++ b/airflow/providers/slack/transfers/sql_to_slack.py
@@ -17,9 +17,12 @@
 from __future__ import annotations
 
 import warnings
+from functools import cached_property
 from tempfile import NamedTemporaryFile
 from typing import TYPE_CHECKING, Any, Mapping, Sequence
 
+from typing_extensions import Literal
+
 from airflow.exceptions import AirflowException, 
AirflowProviderDeprecationWarning
 from airflow.providers.slack.hooks.slack import SlackHook
 from airflow.providers.slack.transfers.base_sql_to_slack import 
BaseSqlToSlackOperator
@@ -53,6 +56,7 @@ class SqlToSlackApiFileOperator(BaseSqlToSlackOperator):
     :param slack_initial_comment: The message text introducing the file in 
specified ``slack_channels``.
     :param slack_title: Title of file.
     :param slack_base_url: A string representing the Slack API base URL. 
Optional
+    :param slack_method_version: The version of the Slack SDK Client method to 
be used, either "v1" or "v2".
     :param df_kwargs: Keyword arguments forwarded to 
``pandas.DataFrame.to_{format}()`` method.
     """
 
@@ -81,6 +85,7 @@ class SqlToSlackApiFileOperator(BaseSqlToSlackOperator):
         slack_initial_comment: str | None = None,
         slack_title: str | None = None,
         slack_base_url: str | None = None,
+        slack_method_version: Literal["v1", "v2"] = "v1",
         df_kwargs: dict | None = None,
         **kwargs,
     ):
@@ -93,8 +98,26 @@ class SqlToSlackApiFileOperator(BaseSqlToSlackOperator):
         self.slack_initial_comment = slack_initial_comment
         self.slack_title = slack_title
         self.slack_base_url = slack_base_url
+        self.slack_method_version = slack_method_version
         self.df_kwargs = df_kwargs or {}
 
+    @cached_property
+    def slack_hook(self):
+        """Slack API Hook."""
+        return SlackHook(
+            slack_conn_id=self.slack_conn_id,
+            base_url=self.slack_base_url,
+            timeout=self.slack_timeout,
+            proxy=self.slack_proxy,
+            retry_handlers=self.slack_retry_handlers,
+        )
+
+    @property
+    def _method_resolver(self):
+        if self.slack_method_version == "v1":
+            return self.slack_hook.send_file
+        return self.slack_hook.send_file_v1_to_v2
+
     def execute(self, context: Context) -> None:
         # Parse file format from filename
         output_file_format, _ = parse_filename(
@@ -102,13 +125,6 @@ class SqlToSlackApiFileOperator(BaseSqlToSlackOperator):
             supported_file_formats=self.SUPPORTED_FILE_FORMATS,
         )
 
-        slack_hook = SlackHook(
-            slack_conn_id=self.slack_conn_id,
-            base_url=self.slack_base_url,
-            timeout=self.slack_timeout,
-            proxy=self.slack_proxy,
-            retry_handlers=self.slack_retry_handlers,
-        )
         with NamedTemporaryFile(mode="w+", suffix=f"_{self.slack_filename}") 
as fp:
             # tempfile.NamedTemporaryFile used only for create and remove 
temporary file,
             # pandas will open file in correct mode itself depend on file type.
@@ -129,7 +145,7 @@ class SqlToSlackApiFileOperator(BaseSqlToSlackOperator):
                 # if SUPPORTED_FILE_FORMATS extended and no actual 
implementation for specific format.
                 raise AirflowException(f"Unexpected output file format: 
{output_file_format}")
 
-            slack_hook.send_file(
+            self._method_resolver(
                 channels=self.slack_channels,
                 file=output_file_name,
                 filename=self.slack_filename,
diff --git a/docs/apache-airflow-providers-slack/operators/slack_api.rst 
b/docs/apache-airflow-providers-slack/operators/slack_api.rst
index c8af3ab703..6b2debb405 100644
--- a/docs/apache-airflow-providers-slack/operators/slack_api.rst
+++ b/docs/apache-airflow-providers-slack/operators/slack_api.rst
@@ -55,10 +55,26 @@ SlackAPIFileOperator
 
 Use the :class:`~airflow.providers.slack.operators.slack.SlackAPIFileOperator` 
to send files to a Slack channel(s).
 
-
 Using the Operator
 ^^^^^^^^^^^^^^^^^^
 
+.. note::
+    Operator supports two methods for upload files, which controlled by 
``method_version``,
+    by default it use Slack SDK method ``upload_files`` however this might 
impact a performance and cause random API errors.
+    It is recommended to switch to Slack SDK method ``upload_files_v2`` by set 
``v2`` to ``method_version``,
+    however this action required to add additional scope to your application:
+
+    * **files:write** - for write files.
+    * **files:read** - for read files (not required if you use Slack SDK >= 
3.23.0).
+    * **channels:read** - get list of public channels, for convert Channel 
Name to Channel ID.
+    * **groups:read** - get list of private channels, for convert Channel Name 
to Channel ID
+    * **mpim:read** - additional permission for API method 
**conversations.list**
+    * **im:read** - additional permission for API method **conversations.list**
+
+    .. seealso::
+        - `Slack SDK 3.19.0 Release Notes 
<https://github.com/slackapi/python-slack-sdk/releases/tag/v3.19.0>`_
+        - `conversations.list API 
<https://api.slack.com/methods/conversations.list>`_
+
 You could send file attachment by specifying file path
 
 .. exampleinclude:: /../../tests/system/providers/slack/example_slack.py
diff --git a/docs/apache-airflow-providers-slack/operators/sql_to_slack.rst 
b/docs/apache-airflow-providers-slack/operators/sql_to_slack.rst
index de9fe136d9..2bf053f5a8 100644
--- a/docs/apache-airflow-providers-slack/operators/sql_to_slack.rst
+++ b/docs/apache-airflow-providers-slack/operators/sql_to_slack.rst
@@ -26,6 +26,24 @@ to Slack channel(s) through `Slack API 
<https://api.slack.com/>`__.
 Using the Operator
 ^^^^^^^^^^^^^^^^^^
 
+.. note::
+    Operator supports two methods for upload files, which controlled by 
``slack_method_version``,
+    by default it use Slack SDK method ``upload_files`` however this might 
impact a performance and cause random API errors.
+    It is recommended to switch to Slack SDK method ``upload_files_v2`` by set 
``v2`` to ``slack_method_version``,
+    however this action required to add additional scope to your application:
+
+    * **files:write** - for write files.
+    * **files:read** - for read files (not required if you use Slack SDK >= 
3.23.0).
+    * **channels:read** - get list of public channels, for convert Channel 
Name to Channel ID.
+    * **groups:read** - get list of private channels, for convert Channel Name 
to Channel ID
+    * **mpim:read** - additional permission for API method 
**conversations.list**
+    * **im:read** - additional permission for API method **conversations.list**
+
+    .. seealso::
+        - `Slack SDK 3.19.0 Release Notes 
<https://github.com/slackapi/python-slack-sdk/releases/tag/v3.19.0>`_
+        - `conversations.list API 
<https://api.slack.com/methods/conversations.list>`_
+
+
 This operator will execute a custom query in the provided SQL connection and 
publish a file to Slack channel(s).
 
 An example usage of the SqlToSlackApiFileOperator is as follows:
diff --git a/generated/provider_dependencies.json 
b/generated/provider_dependencies.json
index cdff33de1a..4d369c2186 100644
--- a/generated/provider_dependencies.json
+++ b/generated/provider_dependencies.json
@@ -1017,7 +1017,7 @@
     "deps": [
       "apache-airflow-providers-common-sql>=1.3.1",
       "apache-airflow>=2.6.0",
-      "slack_sdk>=3.0.0"
+      "slack_sdk>=3.19.0"
     ],
     "devel-deps": [],
     "cross-providers-deps": [
diff --git a/pyproject.toml b/pyproject.toml
index 28bb06d2ea..43cb43107a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -904,7 +904,7 @@ singularity = [
 ]
 slack = [
   "apache-airflow[common_sql]",
-  "slack_sdk>=3.0.0",
+  "slack_sdk>=3.19.0",
 ]
 smtp = [
 ]
diff --git a/tests/providers/slack/hooks/test_slack.py 
b/tests/providers/slack/hooks/test_slack.py
index 5dba70fbdd..023c6a9ebd 100644
--- a/tests/providers/slack/hooks/test_slack.py
+++ b/tests/providers/slack/hooks/test_slack.py
@@ -18,10 +18,8 @@
 from __future__ import annotations
 
 import json
-import os
 from typing import Any
 from unittest import mock
-from unittest.mock import patch
 
 import pytest
 from slack_sdk.errors import SlackApiError
@@ -73,7 +71,23 @@ def slack_api_connections():
         yield
 
 
[email protected]
+def mocked_client():
+    with mock.patch.object(SlackHook, "client") as m:
+        yield m
+
+
 class TestSlackHook:
+    @staticmethod
+    def fake_slack_response(*, data: dict | bytes, status_code: int = 200, 
**kwargs):
+        """Helper for generate fake slack response."""
+        # Mock other mandatory ``SlackResponse`` arguments
+        for mandatory_param in ("client", "http_verb", "api_url", "req_args", 
"headers"):
+            if mandatory_param not in kwargs:
+                kwargs[mandatory_param] = 
mock.MagicMock(name=f"fake-{mandatory_param}")
+
+        return SlackResponse(status_code=status_code, data=data, **kwargs)
+
     @pytest.mark.parametrize(
         "conn_id",
         [
@@ -217,12 +231,8 @@ class TestSlackHook:
             assert hook.get_conn() is client  # cached
             mock_webclient_cls.assert_called_once_with(**expected)
 
-    @mock.patch("airflow.providers.slack.hooks.slack.WebClient")
-    def test_call_with_failure(self, slack_client_class_mock):
-        slack_client_mock = mock.Mock()
-        slack_client_class_mock.return_value = slack_client_mock
-        expected_exception = SlackApiError(message="foo", response="bar")
-        slack_client_mock.api_call = mock.Mock(side_effect=expected_exception)
+    def test_call_with_failure(self, mocked_client):
+        mocked_client.api_call.side_effect = SlackApiError(message="foo", 
response="bar")
 
         slack_hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
         test_method = "test_method"
@@ -231,17 +241,14 @@ class TestSlackHook:
         with pytest.raises(SlackApiError):
             slack_hook.call(test_method, data=test_api_params)
 
-    @mock.patch("airflow.providers.slack.hooks.slack.WebClient")
-    def test_api_call(self, slack_client_class_mock):
-        slack_client_mock = mock.Mock()
-        slack_client_class_mock.return_value = slack_client_mock
-        slack_client_mock.api_call.return_value = {"ok": True}
+    def test_api_call(self, mocked_client):
+        mocked_client.api_call.return_value = {"ok": True}
 
         slack_hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
         test_api_json = {"channel": "test_channel"}
 
         slack_hook.call("chat.postMessage", json=test_api_json)
-        slack_client_mock.api_call.assert_called_with("chat.postMessage", 
json=test_api_json)
+        mocked_client.api_call.assert_called_with("chat.postMessage", 
json=test_api_json)
 
     @pytest.mark.parametrize(
         "response_data",
@@ -266,20 +273,12 @@ class TestSlackHook:
             b"some-binary-data",
         ],
     )
-    @mock.patch("airflow.providers.slack.hooks.slack.WebClient")
-    def test_hook_connection_success(self, mock_webclient_cls, response_data):
+    def test_hook_connection_success(self, mocked_client, response_data):
         """Test SlackHook success connection."""
-        mock_webclient = mock_webclient_cls.return_value
-        mock_webclient_call = mock_webclient.api_call
-        mock_webclient_call.return_value = SlackResponse(
-            status_code=200,
-            data=response_data,
-            # Mock other mandatory SlackResponse arguments
-            **{ma: mock.MagicMock for ma in ("client", "http_verb", "api_url", 
"req_args", "headers")},
-        )
+        mocked_client.api_call.return_value = 
self.fake_slack_response(status_code=200, data=response_data)
         hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
         conn_test = hook.test_connection()
-        mock_webclient_call.assert_called_once_with("auth.test")
+        mocked_client.api_call.assert_called_once_with("auth.test")
         assert conn_test[0]
 
     @pytest.mark.parametrize(
@@ -290,41 +289,36 @@ class TestSlackHook:
             b"some-binary-data",
         ],
     )
-    @mock.patch("airflow.providers.slack.hooks.slack.WebClient")
-    def test_hook_connection_failed(self, mock_webclient_cls, response_data):
+    def test_hook_connection_failed(self, mocked_client, response_data):
         """Test SlackHook failure connection."""
-        mock_webclient = mock_webclient_cls.return_value
-        mock_webclient_call = mock_webclient.api_call
-        mock_webclient_call.return_value = SlackResponse(
-            status_code=401,
-            data=response_data,
-            # Mock other mandatory SlackResponse arguments
-            **{ma: mock.MagicMock for ma in ("client", "http_verb", "api_url", 
"req_args", "headers")},
-        )
+        mocked_client.api_call.return_value = 
self.fake_slack_response(status_code=401, data=response_data)
         hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
         conn_test = hook.test_connection()
-        mock_webclient_call.assert_called_once_with("auth.test")
+        mocked_client.api_call.assert_called_once_with("auth.test")
         assert not conn_test[0]
 
-    @pytest.mark.parametrize("file,content", [(None, None), ("", ""), 
("foo.bar", "test-content")])
+    @pytest.mark.parametrize(
+        "file,content",
+        [
+            pytest.param(None, None, id="both-none"),
+            pytest.param("", "", id="both-empty"),
+            pytest.param("foo.bar", "test-content", id="both-specified"),
+        ],
+    )
     def test_send_file_wrong_parameters(self, file, content):
         hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
         error_message = r"Either `file` or `content` must be provided, not 
both\."
         with pytest.raises(ValueError, match=error_message):
             hook.send_file(file=file, content=content)
 
-    @mock.patch("airflow.providers.slack.hooks.slack.WebClient")
     @pytest.mark.parametrize("initial_comment", [None, "test comment"])
     @pytest.mark.parametrize("title", [None, "test title"])
     @pytest.mark.parametrize("filetype", [None, "auto"])
     @pytest.mark.parametrize("channels", [None, "#random", "#random,#general", 
("#random", "#general")])
     def test_send_file_path(
-        self, mock_webclient_cls, tmp_path_factory, initial_comment, title, 
filetype, channels
+        self, mocked_client, tmp_path_factory, initial_comment, title, 
filetype, channels
     ):
         """Test send file by providing filepath."""
-        mock_files_upload = mock.MagicMock()
-        mock_webclient_cls.return_value.files_upload = mock_files_upload
-
         tmp = tmp_path_factory.mktemp("test_send_file_path")
         file = tmp / "test.json"
         file.write_text('{"foo": "bar"}')
@@ -339,7 +333,7 @@ class TestSlackHook:
             filetype=filetype,
         )
 
-        mock_files_upload.assert_called_once_with(
+        mocked_client.files_upload.assert_called_once_with(
             channels=channels,
             file=mock.ANY,  # Validate file properties later
             filename="filename.mock",
@@ -349,17 +343,13 @@ class TestSlackHook:
         )
 
         # Validate file properties
-        mock_file = mock_files_upload.call_args.kwargs["file"]
+        mock_file = mocked_client.files_upload.call_args.kwargs["file"]
         assert mock_file.mode == "rb"
         assert mock_file.name == str(file)
 
-    @mock.patch("airflow.providers.slack.hooks.slack.WebClient")
     @pytest.mark.parametrize("filename", ["test.json", "1.parquet.snappy"])
-    def test_send_file_path_set_filename(self, mock_webclient_cls, 
tmp_path_factory, filename):
+    def test_send_file_path_set_filename(self, mocked_client, 
tmp_path_factory, filename):
         """Test set filename in send_file method if it not set."""
-        mock_files_upload = mock.MagicMock()
-        mock_webclient_cls.return_value.files_upload = mock_files_upload
-
         tmp = tmp_path_factory.mktemp("test_send_file_path_set_filename")
         file = tmp / filename
         file.write_text('{"foo": "bar"}')
@@ -367,23 +357,18 @@ class TestSlackHook:
         hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
         hook.send_file(file=file)
 
-        assert mock_files_upload.call_count == 1
-        call_args = mock_files_upload.call_args.kwargs
+        assert mocked_client.files_upload.call_count == 1
+        call_args = mocked_client.files_upload.call_args.kwargs
         assert "filename" in call_args
         assert call_args["filename"] == filename
 
-    @mock.patch("airflow.providers.slack.hooks.slack.WebClient")
     @pytest.mark.parametrize("initial_comment", [None, "test comment"])
     @pytest.mark.parametrize("title", [None, "test title"])
     @pytest.mark.parametrize("filetype", [None, "auto"])
     @pytest.mark.parametrize("filename", [None, "foo.bar"])
     @pytest.mark.parametrize("channels", [None, "#random", "#random,#general", 
("#random", "#general")])
-    def test_send_file_content(
-        self, mock_webclient_cls, initial_comment, title, filetype, channels, 
filename
-    ):
+    def test_send_file_content(self, mocked_client, initial_comment, title, 
filetype, channels, filename):
         """Test send file by providing content."""
-        mock_files_upload = mock.MagicMock()
-        mock_webclient_cls.return_value.files_upload = mock_files_upload
         hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
         hook.send_file(
             channels=channels,
@@ -393,7 +378,7 @@ class TestSlackHook:
             title=title,
             filetype=filetype,
         )
-        mock_files_upload.assert_called_once_with(
+        mocked_client.files_upload.assert_called_once_with(
             channels=channels,
             content='{"foo": "bar"}',
             filename=filename,
@@ -414,47 +399,204 @@ class TestSlackHook:
             
pytest.param("a://:abc@?timeout=123&base_url=base_url&proxy=proxy", 
id="no-prefix"),
         ],
     )
-    def test_backcompat_prefix_works(self, uri):
-        with patch.dict(os.environ, AIRFLOW_CONN_MY_CONN=uri):
-            hook = SlackHook(slack_conn_id="my_conn")
+    def test_backcompat_prefix_works(self, uri, monkeypatch):
+        monkeypatch.setenv("AIRFLOW_CONN_MY_CONN", uri)
+        hook = SlackHook(slack_conn_id="my_conn")
+        params = hook._get_conn_params()
+        assert params["token"] == "abc"
+        assert params["timeout"] == 123
+        assert params["base_url"] == "base_url"
+        assert params["proxy"] == "proxy"
+
+    def test_backcompat_prefix_both_causes_warning(self, monkeypatch):
+        monkeypatch.setenv("AIRFLOW_CONN_MY_CONN", 
"a://:abc@?extra__slack__timeout=111&timeout=222")
+        hook = SlackHook(slack_conn_id="my_conn")
+        with pytest.warns(Warning, match="Using value for `timeout`"):
             params = hook._get_conn_params()
-            assert params["token"] == "abc"
-            assert params["timeout"] == 123
-            assert params["base_url"] == "base_url"
-            assert params["proxy"] == "proxy"
-
-    def test_backcompat_prefix_both_causes_warning(self):
-        with patch.dict(
-            in_dict=os.environ,
-            
AIRFLOW_CONN_MY_CONN="a://:abc@?extra__slack__timeout=111&timeout=222",
-        ):
-            hook = SlackHook(slack_conn_id="my_conn")
-            with pytest.warns(Warning, match="Using value for `timeout`"):
-                params = hook._get_conn_params()
-                assert params["timeout"] == 222
-
-    def test_empty_string_ignored_prefixed(self):
-        with patch.dict(
-            in_dict=os.environ,
-            AIRFLOW_CONN_MY_CONN=json.dumps(
+            assert params["timeout"] == 222
+
+    def test_empty_string_ignored_prefixed(self, monkeypatch):
+        monkeypatch.setenv(
+            "AIRFLOW_CONN_MY_CONN",
+            json.dumps(
                 {"password": "hi", "extra": {"extra__slack__base_url": "", 
"extra__slack__proxy": ""}}
             ),
-        ):
-            hook = SlackHook(slack_conn_id="my_conn")
-            params = hook._get_conn_params()
-            assert "proxy" not in params
-            assert "base_url" not in params
-
-    def test_empty_string_ignored_non_prefixed(self):
-        with patch.dict(
-            in_dict=os.environ,
-            AIRFLOW_CONN_MY_CONN=json.dumps({"password": "hi", "extra": 
{"base_url": "", "proxy": ""}}),
-        ):
-            hook = SlackHook(slack_conn_id="my_conn")
-            params = hook._get_conn_params()
-            assert "proxy" not in params
-            assert "base_url" not in params
+        )
+        hook = SlackHook(slack_conn_id="my_conn")
+        params = hook._get_conn_params()
+        assert "proxy" not in params
+        assert "base_url" not in params
+
+    def test_empty_string_ignored_non_prefixed(self, monkeypatch):
+        monkeypatch.setenv(
+            "AIRFLOW_CONN_MY_CONN",
+            json.dumps({"password": "hi", "extra": {"base_url": "", "proxy": 
""}}),
+        )
+        hook = SlackHook(slack_conn_id="my_conn")
+        params = hook._get_conn_params()
+        assert "proxy" not in params
+        assert "base_url" not in params
 
     def test_default_conn_name(self):
         hook = SlackHook()
         assert hook.slack_conn_id == SlackHook.default_conn_name
+
+    def test_get_channel_id(self, mocked_client):
+        fake_responses = [
+            self.fake_slack_response(
+                data={
+                    "channels": [
+                        {"id": "C0000000000", "name": 
"development-first-pr-support"},
+                        {"id": "C0000000001", "name": "development"},
+                    ],
+                    "response_metadata": {"next_cursor": "FAKE"},
+                }
+            ),
+            self.fake_slack_response(data={"response_metadata": 
{"next_cursor": "FAKE"}}),
+            self.fake_slack_response(data={"channels": [{"id": "C0000000002", 
"name": "random"}]}),
+            # Below should not reach, because previous one doesn't contain 
``next_cursor``
+            self.fake_slack_response(data={"channels": [{"id": "C0000000003", 
"name": "troubleshooting"}]}),
+        ]
+        mocked_client.conversations_list.side_effect = fake_responses
+
+        hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
+
+        assert hook.get_channel_id("development") == "C0000000001"
+        mocked_client.conversations_list.assert_called()
+
+        mocked_client.conversations_list.reset_mock()
+        mocked_client.conversations_list.side_effect = fake_responses
+        assert hook.get_channel_id("development-first-pr-support") == 
"C0000000000"
+        # It should use cached values, so there is no new calls expected here
+        mocked_client.assert_not_called()
+
+        # Test pagination
+        mocked_client.conversations_list.side_effect = fake_responses
+        assert hook.get_channel_id("random") == "C0000000002"
+
+        # Test non-existed channels
+        mocked_client.conversations_list.side_effect = fake_responses
+        with pytest.raises(LookupError, match="Unable to find slack channel"):
+            hook.get_channel_id("troubleshooting")
+
+    def test_send_file_v2(self, mocked_client):
+        SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID).send_file_v2(
+            channel_id="C00000000", file_uploads={"file": "/foo/bar/file.txt", 
"filename": "foo.txt"}
+        )
+        mocked_client.files_upload_v2.assert_called_once_with(
+            channel="C00000000",
+            file_uploads=[{"file": "/foo/bar/file.txt", "filename": 
"foo.txt"}],
+            initial_comment=None,
+        )
+
+    def test_send_file_v2_multiple_files(self, mocked_client):
+        SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID).send_file_v2(
+            file_uploads=[
+                {"file": "/foo/bar/file.txt"},
+                {"content": "Some Text", "filename": "foo.txt"},
+            ],
+            initial_comment="Awesome File",
+        )
+        mocked_client.files_upload_v2.assert_called_once_with(
+            channel=None,
+            file_uploads=[
+                {"file": "/foo/bar/file.txt", "filename": "Uploaded file"},
+                {"content": "Some Text", "filename": "foo.txt"},
+            ],
+            initial_comment="Awesome File",
+        )
+
+    def test_send_file_v2_channel_name(self, mocked_client, caplog):
+        with mock.patch.object(SlackHook, "get_channel_id", 
return_value="C00") as mocked_get_channel_id:
+            warning_message = "consider replacing '#random' with the 
corresponding Channel ID 'C00'"
+            with pytest.warns(UserWarning, match=warning_message):
+                
SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID).send_file_v2(
+                    channel_id="#random", file_uploads={"file": 
"/foo/bar/file.txt"}
+                )
+            mocked_get_channel_id.assert_called_once_with("random")
+            mocked_client.files_upload_v2.assert_called_once_with(
+                channel="C00",
+                file_uploads=mock.ANY,
+                initial_comment=mock.ANY,
+            )
+
+    @pytest.mark.parametrize("initial_comment", [None, "test comment"])
+    @pytest.mark.parametrize("title", [None, "test title"])
+    @pytest.mark.parametrize("filename", [None, "foo.bar"])
+    @pytest.mark.parametrize("channel", [None, "#random"])
+    @pytest.mark.parametrize("filetype", [None, "auto"])
+    def test_send_file_v1_to_v2_content(self, initial_comment, title, 
filename, channel, filetype):
+        hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
+        with mock.patch.object(SlackHook, "send_file_v2") as 
mocked_send_file_v2:
+            hook.send_file_v1_to_v2(
+                channels=channel,
+                content='{"foo": "bar"}',
+                filename=filename,
+                initial_comment=initial_comment,
+                title=title,
+                filetype=filetype,
+            )
+            mocked_send_file_v2.assert_called_once_with(
+                channel_id=channel,
+                file_uploads={
+                    "content": '{"foo": "bar"}',
+                    "filename": filename,
+                    "title": title,
+                    "snippet_type": filetype,
+                },
+                initial_comment=initial_comment,
+            )
+
+    @pytest.mark.parametrize("initial_comment", [None, "test comment"])
+    @pytest.mark.parametrize("title", [None, "test title"])
+    @pytest.mark.parametrize("filename", [None, "foo.bar"])
+    @pytest.mark.parametrize("channel", [None, "#random"])
+    @pytest.mark.parametrize("filetype", [None, "auto"])
+    def test_send_file_v1_to_v2_file(self, initial_comment, title, filename, 
channel, filetype):
+        hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
+        with mock.patch.object(SlackHook, "send_file_v2") as 
mocked_send_file_v2:
+            hook.send_file_v1_to_v2(
+                channels=channel,
+                file="/foo/bar/spam.egg",
+                filename=filename,
+                initial_comment=initial_comment,
+                title=title,
+                filetype=filetype,
+            )
+            mocked_send_file_v2.assert_called_once_with(
+                channel_id=channel,
+                file_uploads={
+                    "file": "/foo/bar/spam.egg",
+                    "filename": filename or "spam.egg",
+                    "title": title,
+                    "snippet_type": filetype,
+                },
+                initial_comment=initial_comment,
+            )
+
+    @pytest.mark.parametrize(
+        "file,content",
+        [
+            pytest.param(None, None, id="both-none"),
+            pytest.param("", "", id="both-empty"),
+            pytest.param("foo.bar", "test-content", id="both-specified"),
+        ],
+    )
+    def test_send_file_v1_to_v2_wrong_parameters(self, file, content):
+        hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
+        error_message = "Either `file` or `content` must be provided, not both"
+        with pytest.raises(ValueError, match=error_message):
+            hook.send_file_v1_to_v2(file=file, content=content)
+
+    @pytest.mark.parametrize(
+        "channels, expected_calls",
+        [
+            pytest.param("#foo, #bar", 2, id="comma-separated-string"),
+            pytest.param(["#random", "#development", "#airflow-upgrades"], 3, 
id="list"),
+        ],
+    )
+    def test_send_file_v1_to_v2_multiple_channels(self, channels, 
expected_calls):
+        hook = SlackHook(slack_conn_id=SLACK_API_DEFAULT_CONN_ID)
+        with mock.patch.object(SlackHook, "send_file_v2") as 
mocked_send_file_v2:
+            hook.send_file_v1_to_v2(channels=channels, content="Fake")
+            assert mocked_send_file_v2.call_count == expected_calls
diff --git a/tests/providers/slack/operators/test_slack.py 
b/tests/providers/slack/operators/test_slack.py
index 52a28ef4d8..b389f12609 100644
--- a/tests/providers/slack/operators/test_slack.py
+++ b/tests/providers/slack/operators/test_slack.py
@@ -19,7 +19,6 @@ from __future__ import annotations
 
 import json
 from unittest import mock
-from unittest.mock import MagicMock
 
 import pytest
 
@@ -60,7 +59,10 @@ class TestSlackAPIOperator:
     def test_hook(self, mock_slack_hook_cls, slack_op_kwargs, 
hook_extra_kwargs):
         mock_slack_hook = mock_slack_hook_cls.return_value
         op = SlackAPIOperator(
-            task_id="test-mask-token", 
slack_conn_id=SLACK_API_TEST_CONNECTION_ID, **slack_op_kwargs
+            task_id="test-mask-token",
+            slack_conn_id=SLACK_API_TEST_CONNECTION_ID,
+            method="foo.Bar",
+            **slack_op_kwargs,
         )
         hook = op.hook
         assert hook == mock_slack_hook
@@ -69,6 +71,20 @@ class TestSlackAPIOperator:
             slack_conn_id=SLACK_API_TEST_CONNECTION_ID, **hook_extra_kwargs
         )
 
+    @pytest.mark.parametrize("slack_method", [pytest.param("", id="empty"), 
pytest.param(None, id="none")])
+    def test_empty_method(self, slack_method):
+        warning_message = "Define `method` parameter as empty string or None 
is deprecated"
+        with pytest.warns(AirflowProviderDeprecationWarning, 
match=warning_message):
+            # Should only raise a warning on task initialisation
+            op = SlackAPIOperator(
+                task_id="test-mask-token",
+                slack_conn_id=SLACK_API_TEST_CONNECTION_ID,
+                method=slack_method,
+            )
+
+        with pytest.raises(ValueError, match="Expected non empty `method` 
attribute"):
+            op.execute({})
+
 
 class TestSlackAPIPostOperator:
     def setup_method(self):
@@ -158,7 +174,7 @@ class TestSlackAPIPostOperator:
             slack_conn_id=SLACK_API_TEST_CONNECTION_ID,
         )
 
-        slack_api_post_operator.execute(context=MagicMock())
+        slack_api_post_operator.execute({})
 
         expected_api_params = {
             "channel": "#general",
@@ -211,47 +227,63 @@ class TestSlackAPIFileOperator:
         assert slack_api_post_operator.content == self.test_content
         assert not hasattr(slack_api_post_operator, "token")
 
-    @mock.patch("airflow.providers.slack.operators.slack.SlackHook.send_file")
     @pytest.mark.parametrize("initial_comment", [None, "foo-bar"])
     @pytest.mark.parametrize("title", [None, "Spam Egg"])
-    def test_api_call_params_with_content_args(self, mock_send_file, 
initial_comment, title):
-        SlackAPIFileOperator(
+    @pytest.mark.parametrize(
+        "method_version, method_name",
+        [
+            pytest.param("v1", "send_file", id="v1"),
+            pytest.param("v2", "send_file_v1_to_v2", id="v2"),
+        ],
+    )
+    def test_api_call_params_with_content_args(self, initial_comment, title, 
method_version, method_name):
+        op = SlackAPIFileOperator(
             task_id="slack",
             slack_conn_id=SLACK_API_TEST_CONNECTION_ID,
             content="test-content",
             channels="#test-channel",
             initial_comment=initial_comment,
             title=title,
-        ).execute(context=MagicMock())
-
-        mock_send_file.assert_called_once_with(
-            channels="#test-channel",
-            content="test-content",
-            file=None,
-            initial_comment=initial_comment,
-            title=title,
+            method_version=method_version,
         )
+        with 
mock.patch(f"airflow.providers.slack.operators.slack.SlackHook.{method_name}") 
as mock_send_file:
+            op.execute({})
+            mock_send_file.assert_called_once_with(
+                channels="#test-channel",
+                content="test-content",
+                file=None,
+                initial_comment=initial_comment,
+                title=title,
+            )
 
-    @mock.patch("airflow.providers.slack.operators.slack.SlackHook.send_file")
     @pytest.mark.parametrize("initial_comment", [None, "foo-bar"])
     @pytest.mark.parametrize("title", [None, "Spam Egg"])
-    def test_api_call_params_with_file_args(self, mock_send_file, 
initial_comment, title):
-        SlackAPIFileOperator(
+    @pytest.mark.parametrize(
+        "method_version, method_name",
+        [
+            pytest.param("v1", "send_file", id="v1"),
+            pytest.param("v2", "send_file_v1_to_v2", id="v2"),
+        ],
+    )
+    def test_api_call_params_with_file_args(self, initial_comment, title, 
method_version, method_name):
+        op = SlackAPIFileOperator(
             task_id="slack",
             slack_conn_id=SLACK_API_TEST_CONNECTION_ID,
             channels="C1234567890",
             filename="/dev/null",
             initial_comment=initial_comment,
             title=title,
-        ).execute(context=MagicMock())
-
-        mock_send_file.assert_called_once_with(
-            channels="C1234567890",
-            content=None,
-            file="/dev/null",
-            initial_comment=initial_comment,
-            title=title,
+            method_version=method_version,
         )
+        with 
mock.patch(f"airflow.providers.slack.operators.slack.SlackHook.{method_name}") 
as mock_send_file:
+            op.execute({})
+            mock_send_file.assert_called_once_with(
+                channels="C1234567890",
+                content=None,
+                file="/dev/null",
+                initial_comment=initial_comment,
+                title=title,
+            )
 
     def test_channel_deprecated(self):
         warning_message = (
diff --git a/tests/providers/slack/transfers/test_sql_to_slack.py 
b/tests/providers/slack/transfers/test_sql_to_slack.py
index 9d60f28b42..fd3c06189d 100644
--- a/tests/providers/slack/transfers/test_sql_to_slack.py
+++ b/tests/providers/slack/transfers/test_sql_to_slack.py
@@ -80,6 +80,13 @@ class TestSqlToSlackApiFileOperator:
             ),
         ],
     )
+    @pytest.mark.parametrize(
+        "method_version, method_name",
+        [
+            pytest.param("v1", "send_file", id="v1"),
+            pytest.param("v2", "send_file_v1_to_v2", id="v2"),
+        ],
+    )
     def test_send_file(
         self,
         mock_slack_hook_cls,
@@ -92,10 +99,12 @@ class TestSqlToSlackApiFileOperator:
         title,
         slack_op_kwargs: dict,
         hook_extra_kwargs: dict,
+        method_version,
+        method_name: str,
     ):
         # Mock Hook
         mock_send_file = mock.MagicMock()
-        mock_slack_hook_cls.return_value.send_file = mock_send_file
+        setattr(mock_slack_hook_cls.return_value, method_name, mock_send_file)
 
         # Mock returns pandas.DataFrame and expected method
         mock_df = mock.MagicMock()
@@ -110,10 +119,13 @@ class TestSqlToSlackApiFileOperator:
             "slack_channels": channels,
             "slack_initial_comment": initial_comment,
             "slack_title": title,
+            "slack_method_version": method_version,
             "df_kwargs": df_kwargs,
             **slack_op_kwargs,
         }
         op = SqlToSlackApiFileOperator(task_id="test_send_file", **op_kwargs)
+
+        mock.patch("airflow.providers.slack.transfers.sql_to_slack.SlackHook")
         op.execute(mock.MagicMock())
 
         mock_slack_hook_cls.assert_called_once_with(
diff --git a/tests/system/providers/slack/example_slack.py 
b/tests/system/providers/slack/example_slack.py
index bbb47374b3..78bd649798 100644
--- a/tests/system/providers/slack/example_slack.py
+++ b/tests/system/providers/slack/example_slack.py
@@ -83,6 +83,7 @@ with DAG(
         task_id="slack_file_upload_2",
         channels=SLACK_CHANNEL,
         content="file content in txt",
+        method_version="v2",
     )
     # [END slack_api_file_operator_content_howto_guide]
 

Reply via email to