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

kaxil 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 82f45f42261 Fix Test connection rejecting a connection that has no 
port (#73298)
82f45f42261 is described below

commit 82f45f42261dc01f61a75e01f5307f033ca4fa6c
Author: Kaxil Naik <[email protected]>
AuthorDate: Sun Sep 20 12:54:28 2026 +0100

    Fix Test connection rejecting a connection that has no port (#73298)
---
 .../core_api/routes/public/connections.py          |  6 ++++-
 .../core_api/routes/public/test_connections.py     | 31 ++++++++++++++++++++++
 2 files changed, 36 insertions(+), 1 deletion(-)

diff --git 
a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/connections.py 
b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/connections.py
index e828b932dea..082cdd26173 100644
--- a/airflow-core/src/airflow/api_fastapi/core_api/routes/public/connections.py
+++ b/airflow-core/src/airflow/api_fastapi/core_api/routes/public/connections.py
@@ -114,10 +114,14 @@ def _same_endpoint(requested: str | int | None, stored: 
str | int | None) -> boo
     The UI sends empty string for hidden unused host/port fields; the ORM 
stores
     those as NULL. Treat blank as unset so connection types that do not use
     host/port still reuse stored credentials.
+
+    Port arrives as ``0`` rather than blank, because the UI builds its body 
with
+    ``Number(connection.port)`` and ``Number(null)`` is ``0``. No connection 
addresses
+    port 0, so it means the same thing as blank here and is normalised with it.
     """
 
     def _norm(value: str | int | None) -> str | int | None:
-        return None if value is None or value == "" else value
+        return None if value in (None, "", 0) else value
 
     return _norm(requested) == _norm(stored)
 
diff --git 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_connections.py
 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_connections.py
index 84563368126..3f94a3a763e 100644
--- 
a/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_connections.py
+++ 
b/airflow-core/tests/unit/api_fastapi/core_api/routes/public/test_connections.py
@@ -1470,6 +1470,37 @@ class TestConnection(TestConnectionEndpoint):
         assert response.status_code == 200
         assert json.loads(captured["extra"])["private_key_file"] == stored_path
 
+    @mock.patch.dict(os.environ, {"AIRFLOW__CORE__TEST_CONNECTION": "Enabled"})
+    def test_should_reuse_stored_credentials_when_the_ui_sends_port_zero(self, 
test_client, session):
+        """The UI builds its body with ``Number(port)``, so a stored NULL port 
arrives as 0."""
+        session.add(
+            Connection(
+                conn_id=TEST_CONN_ID,
+                conn_type="sqlite",
+                host=None,
+                port=None,
+                password="stored_password",
+            )
+        )
+        session.commit()
+
+        def mock_test_connection(self):
+            return True, "mocked"
+
+        # Exactly what TestConnectionButton.tsx sends for a connection with no 
host or port.
+        body = {
+            "connection_id": TEST_CONN_ID,
+            "conn_type": "sqlite",
+            "host": "",
+            "port": 0,
+            "password": "***",
+        }
+
+        with mock.patch.object(Connection, "test_connection", 
mock_test_connection):
+            response = test_client.post("/connections/test", json=body)
+
+        assert response.status_code == 200, response.json()
+
     @mock.patch.dict(os.environ, {"AIRFLOW__CORE__TEST_CONNECTION": "Enabled"})
     def test_should_reject_overridden_target_when_password_is_masked(self, 
test_client, session):
         """A masked password is not caller-supplied credentials for a new 
destination."""

Reply via email to