This is an automated email from the ASF dual-hosted git repository.
jscheffl 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 2c9e759c7a4 Fix SFTPHookAsync test fixture to return async context
managers (#66678)
2c9e759c7a4 is described below
commit 2c9e759c7a4620f2d7de90393fb225cc4c024ca5
Author: Jarek Potiuk <[email protected]>
AuthorDate: Mon May 11 07:09:27 2026 +0200
Fix SFTPHookAsync test fixture to return async context managers (#66678)
After #64465 changed the production code from
``sftp = await ssh_conn.start_sftp_client()`` to
``async with ssh_conn.start_sftp_client() as sftp:`` (and likewise for
``sftp.open()``), the ``sftp_hook_mocked`` fixture in
``providers/sftp/tests/conftest.py`` started returning coroutines from
those calls — ``AsyncMock(spec=SSHClientConnection)`` auto-makes
``start_sftp_client`` an ``AsyncMock`` whose call yields a coroutine,
not an async context manager — and every ``TestSFTPHookAsync`` test
that uses the fixture failed with ``TypeError: 'coroutine' object does
not support the asynchronous context manager protocol``.
In real asyncssh, both ``start_sftp_client()`` and ``SFTPClient.open()``
are decorated so a call returns an object that is both awaitable and
async-context-manageable; the production code only uses the
context-manager half. Override the auto-spec'd AsyncMocks with sync
MagicMocks that return a context-manager mock with ``__aenter__`` /
``__aexit__`` configured.
Restores 28 ``TestSFTPHookAsync`` tests in
``providers/sftp/tests/unit/sftp/hooks/test_sftp.py`` (blocking CI on
unrelated PRs since #64465 merged).
---
providers/sftp/tests/conftest.py | 17 +++++++++++++----
1 file changed, 13 insertions(+), 4 deletions(-)
diff --git a/providers/sftp/tests/conftest.py b/providers/sftp/tests/conftest.py
index 352f17c22a3..89b41e6625f 100644
--- a/providers/sftp/tests/conftest.py
+++ b/providers/sftp/tests/conftest.py
@@ -18,7 +18,7 @@ from __future__ import annotations
from collections.abc import Generator
from typing import TYPE_CHECKING, Any
-from unittest.mock import AsyncMock, patch
+from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from asyncssh import SFTPClient, SSHClientConnection
@@ -39,12 +39,21 @@ def sftp_hook_mocked() -> Generator[tuple[SFTPHookAsync,
SFTPClient], Any, None]
sftp_client_mock = AsyncMock(spec=SFTPClient)
sftp_client_mock.readdir.return_value = []
-
- client_connection_mock = AsyncMock(spec=SSHClientConnection)
- sftp_cm_mock = client_connection_mock.start_sftp_client.return_value
+ # asyncssh's SFTPClient.open() is decorated to be both awaitable and
async-context-
+ # manageable; the production code uses ``async with sftp.open(...) as
remote_file``,
+ # so the mock must return a context manager from a *synchronous* call. The
spec
+ # auto-makes it an AsyncMock (call → coroutine); override it with a sync
MagicMock.
+ sftp_client_mock.open = MagicMock()
+
+ # asyncssh's start_sftp_client() has the same shape — production uses it as
+ # ``async with ssh_conn.start_sftp_client() as sftp:``. Same override
pattern.
+ sftp_cm_mock = MagicMock()
sftp_cm_mock.__aenter__ = AsyncMock(return_value=sftp_client_mock)
sftp_cm_mock.__aexit__ = AsyncMock(return_value=None)
+ client_connection_mock = AsyncMock(spec=SSHClientConnection)
+ client_connection_mock.start_sftp_client =
MagicMock(return_value=sftp_cm_mock)
+
with patch("airflow.providers.sftp.hooks.sftp.SFTPHookAsync._get_conn") as
mock_get_conn:
mock_get_conn.return_value.__aenter__.return_value =
client_connection_mock