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

shahar1 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 9290c2662cf Add missing tests for sftp exceptions (#72456)
9290c2662cf is described below

commit 9290c2662cff1f151ade96c638ee013ac846a2a0
Author: Vitor Antoniazzi <[email protected]>
AuthorDate: Wed Sep 23 21:14:57 2026 +0100

    Add missing tests for sftp exceptions (#72456)
    
    providers/sftp/src/airflow/providers/sftp/exceptions.py had no dedicated 
test
    module and was not covered indirectly. The only raise site for
    ConnectionNotOpenedException is the handle_connection_management decorator 
in
    the sftp hook, which was untested as well, so the new module drives that
    decorator against a stub hook and drops the OVERLOOKED_TESTS entry.
    
    Closes: #72268
---
 .../tests/unit/always/test_project_structure.py    |  1 -
 providers/sftp/tests/unit/sftp/test_exceptions.py  | 82 ++++++++++++++++++++++
 2 files changed, 82 insertions(+), 1 deletion(-)

diff --git a/airflow-core/tests/unit/always/test_project_structure.py 
b/airflow-core/tests/unit/always/test_project_structure.py
index 8eade3cc627..106df1e7336 100644
--- a/airflow-core/tests/unit/always/test_project_structure.py
+++ b/airflow-core/tests/unit/always/test_project_structure.py
@@ -133,7 +133,6 @@ class TestProjectStructure:
             
"providers/snowflake/tests/unit/snowflake/triggers/test_snowflake_trigger.py",
             "providers/standard/tests/unit/standard/operators/test_empty.py",
             
"providers/standard/tests/unit/standard/sensors/test_external_task.py",
-            "providers/sftp/tests/unit/sftp/test_exceptions.py",
         ]
         modules_files: list[pathlib.Path] = list(
             
AIRFLOW_PROVIDERS_ROOT_PATH.glob("**/src/airflow/providers/**/*.py")
diff --git a/providers/sftp/tests/unit/sftp/test_exceptions.py 
b/providers/sftp/tests/unit/sftp/test_exceptions.py
new file mode 100644
index 00000000000..81cafaa296e
--- /dev/null
+++ b/providers/sftp/tests/unit/sftp/test_exceptions.py
@@ -0,0 +1,82 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from __future__ import annotations
+
+from contextlib import contextmanager
+from unittest.mock import MagicMock
+
+import pytest
+
+from airflow.providers.common.compat.sdk import AirflowException
+from airflow.providers.sftp.exceptions import ConnectionNotOpenedException
+from airflow.providers.sftp.hooks.sftp import handle_connection_management
+
+
+class StubHook:
+    """Minimal stand-in for ``SFTPHook`` that exercises only the decorator."""
+
+    def __init__(self, *, use_managed_conn: bool, conn=None, 
managed_conn=None):
+        self.use_managed_conn = use_managed_conn
+        self.conn = conn
+        self._managed_conn = managed_conn
+        self.conn_seen_by_call: list[object] = []
+        self.managed_conn_entries = 0
+
+    @contextmanager
+    def get_managed_conn(self):
+        self.managed_conn_entries += 1
+        yield self._managed_conn
+
+    @handle_connection_management
+    def do_work(self, value: int) -> int:
+        self.conn_seen_by_call.append(self.conn)
+        return value * 2
+
+
+class TestConnectionNotOpenedException:
+    def test_derives_from_airflow_exception(self):
+        # Callers catch the base class, so narrowing this later would be a 
breaking change.
+        assert issubclass(ConnectionNotOpenedException, AirflowException)
+
+
+class TestHandleConnectionManagement:
+    def test_unmanaged_without_open_connection_raises(self):
+        hook = StubHook(use_managed_conn=False, conn=None)
+
+        with pytest.raises(ConnectionNotOpenedException, 
match=r"hook\.get_managed_conn\(\)"):
+            hook.do_work(21)
+
+        assert hook.conn_seen_by_call == []
+        assert hook.managed_conn_entries == 0
+
+    def test_unmanaged_with_open_connection_delegates(self):
+        conn = MagicMock()
+        hook = StubHook(use_managed_conn=False, conn=conn)
+
+        assert hook.do_work(21) == 42
+        assert hook.conn_seen_by_call == [conn]
+        assert hook.managed_conn_entries == 0
+
+    def test_managed_opens_connection_instead_of_raising(self):
+        managed_conn = MagicMock()
+        hook = StubHook(use_managed_conn=True, conn=None, 
managed_conn=managed_conn)
+
+        assert hook.do_work(21) == 42
+        assert hook.managed_conn_entries == 1
+        # The managed connection is set on the hook for the duration of the 
call.
+        assert hook.conn_seen_by_call == [managed_conn]

Reply via email to