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]