rjgoyln commented on code in PR #73778:
URL: https://github.com/apache/airflow/pull/73778#discussion_r4115139894


##########
providers/ftp/src/airflow/providers/ftp/hooks/ftp.py:
##########
@@ -318,20 +318,18 @@ def get_conn(self) -> ftplib.FTP:
             encoding = params.extra_dejson.get("encoding")
             self.encoding = encoding
 
-            if params.port:
-                ftplib.FTP_TLS.port = params.port
-
             # Construct FTP_TLS instance with SSL context to allow 
certificates to be validated by default
             context = ssl.create_default_context()
-            params.host = cast("str", params.host)
-            params.password = cast("str", params.password)
-            params.login = cast("str", params.login)
             if encoding:
-                self.conn = ftplib.FTP_TLS(
-                    params.host, params.login, params.password, 
context=context, encoding=encoding
-                )  # nosec: B321
+                self.conn = ftplib.FTP_TLS(context=context, encoding=encoding) 
 # nosec: B321
             else:
-                self.conn = ftplib.FTP_TLS(params.host, params.login, 
params.password, context=context)  # nosec: B321
+                self.conn = ftplib.FTP_TLS(context=context)  # nosec: B321
+            if params.host:

Review Comment:
   Now that the FTPS path calls `connect()` itself, it is the only one of the 
two hooks that connects silently — `FTPHook.get_conn` logs `Connecting via FTP 
to %s:%d` at the equivalent point. Worth the same line here: the port an FTPS 
connection actually dialled is precisely what this bug made impossible to tell 
from the logs.



##########
providers/ftp/tests/unit/ftp/hooks/test_ftp.py:
##########
@@ -262,6 +263,26 @@ def test_ftp_custom_port_and_login(self, mock_ftp):
         conn.login.assert_called_once_with("user", "pass123")
         conn.set_pasv.assert_called_once_with(True)
 
+    @mock.patch("ftplib.FTP_TLS.connect")
+    @mock.patch("ftplib.FTP_TLS.login")
+    @mock.patch("ftplib.FTP_TLS.set_pasv")
+    @mock.patch("ftplib.FTP_TLS.prot_p")
+    def test_ftps_custom_port_and_login(self, mock_prot_p, mock_set_pasv, 
mock_login, mock_connect):
+        from airflow.providers.ftp.hooks.ftp import FTPSHook
+
+        FTPSHook("ftp_custom_port_and_login").get_conn()
+
+        mock_connect.assert_called_once_with("localhost", 10000)
+        mock_login.assert_called_once_with("user", "pass123")
+        # The port must not leak into the default port of other FTP_TLS 
connections
+        assert ftplib.FTP_TLS.port == ftplib.FTP_PORT
+
+        mock_connect.reset_mock()
+        FTPSHook("ftp_passive").get_conn()
+
+        mock_connect.assert_called_once_with("localhost", 0)
+        mock_login.assert_called_once()

Review Comment:
   This last assertion is still matching the *first* hook's call, not the 
second one. `ftp_passive` has no login, so `FTPSHook("ftp_passive").get_conn()` 
never reaches `self.conn.login(...)`; printing the mock right here gives `1 
[call('user', 'pass123')]` — the same call line 276 already checked. It reads 
as "the second connection logged in too" while asserting nothing about the 
second connection, so I'd drop it (or `mock_login.reset_mock()` before the 
second `get_conn()` and assert `assert_not_called()`, if the intent was to pin 
the credential-less path).
   
   ```suggestion
           mock_connect.assert_called_once_with("localhost", 0)
   ```



##########
providers/ftp/src/airflow/providers/ftp/hooks/ftp.py:
##########
@@ -318,20 +318,18 @@ def get_conn(self) -> ftplib.FTP:
             encoding = params.extra_dejson.get("encoding")
             self.encoding = encoding
 
-            if params.port:
-                ftplib.FTP_TLS.port = params.port
-
             # Construct FTP_TLS instance with SSL context to allow 
certificates to be validated by default
             context = ssl.create_default_context()
-            params.host = cast("str", params.host)
-            params.password = cast("str", params.password)
-            params.login = cast("str", params.login)
             if encoding:
-                self.conn = ftplib.FTP_TLS(
-                    params.host, params.login, params.password, 
context=context, encoding=encoding
-                )  # nosec: B321
+                self.conn = ftplib.FTP_TLS(context=context, encoding=encoding) 
 # nosec: B321
             else:
-                self.conn = ftplib.FTP_TLS(params.host, params.login, 
params.password, context=context)  # nosec: B321
+                self.conn = ftplib.FTP_TLS(context=context)  # nosec: B321
+            if params.host:
+                # Pass the port to connect() rather than setting FTP_TLS.port, 
which would
+                # change the default port of every FTP_TLS connection in the 
process.
+                self.conn.connect(params.host, params.port or 0)

Review Comment:
   `or 0` is correct — `connect()` does `if port > 0: self.port = port`, so 0 
leaves the instance on ftplib's 21 — but it reads as "connect to port 0", and 
the test then has to assert `connect("localhost", 0)` for what is really the 
default port. `ftplib.FTP_PORT` states it, and matches `FTPHook.get_conn` a few 
lines up, which already spells the fallback out as `port: int = 
int(ftplib.FTP_PORT)`.
   
   ```suggestion
                   self.conn.connect(params.host, params.port or 
ftplib.FTP_PORT)
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to