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."""