Skip to content

Commit d8134e4

Browse files
committed
Update code
1 parent 7c8ee38 commit d8134e4

3 files changed

Lines changed: 152 additions & 3 deletions

File tree

nettacker/core/lib/ftp.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ class FtpLibrary(BaseLibrary):
88

99
def brute_force(self, host, port, username, password, timeout):
1010
connection = self.client(timeout=timeout)
11-
connection.connect(host, port)
12-
connection.login(username, password)
13-
connection.close()
11+
try:
12+
connection.connect(host, port)
13+
connection.login(username, password)
14+
finally:
15+
connection.close()
1416

1517
return {
1618
"host": host,

tests/core/lib/test_ftp.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import ftplib
2+
from unittest.mock import MagicMock, patch
3+
4+
import pytest
5+
6+
from nettacker.core.lib.ftp import FtpEngine, FtpLibrary
7+
8+
HOST = "10.0.0.1"
9+
PORT = 21
10+
USER = "user"
11+
PASS = "pass"
12+
TIMEOUT = 5
13+
TIMEOUT_CONNECT_FAIL = 1
14+
TIMEOUT_CLIENT = 30
15+
WRONG_PASS = "wrongpass"
16+
17+
18+
@pytest.fixture
19+
def ftp_client_mocks():
20+
with patch.object(FtpLibrary, "client") as mock_ftp_class:
21+
mock_connection = MagicMock(spec=ftplib.FTP)
22+
mock_ftp_class.return_value = mock_connection
23+
yield mock_ftp_class, mock_connection
24+
25+
26+
class TestFtpEngine:
27+
def test_engine_has_correct_library(self):
28+
assert FtpEngine.library == FtpLibrary
29+
30+
def test_engine_instantiates(self):
31+
engine = FtpEngine()
32+
assert engine is not None
33+
34+
35+
class TestFtpLibrary:
36+
@pytest.mark.parametrize(
37+
"host,port,username,password,timeout",
38+
[
39+
(HOST, PORT, USER, PASS, TIMEOUT),
40+
("192.168.1.1", 2121, "admin", "x", 60),
41+
],
42+
)
43+
def test_successful_login_returns_dict(
44+
self, ftp_client_mocks, host, port, username, password, timeout
45+
):
46+
_, _ = ftp_client_mocks
47+
lib = FtpLibrary()
48+
result = lib.brute_force(host, port, username, password, timeout)
49+
assert result == {
50+
"host": host,
51+
"port": port,
52+
"username": username,
53+
"password": password,
54+
}
55+
56+
def test_successful_login_calls_connect(self, ftp_client_mocks):
57+
_, mock_connection = ftp_client_mocks
58+
lib = FtpLibrary()
59+
lib.brute_force(HOST, PORT, USER, PASS, TIMEOUT)
60+
61+
mock_connection.connect.assert_called_once_with(HOST, PORT)
62+
63+
def test_successful_login_calls_login(self, ftp_client_mocks):
64+
_, mock_connection = ftp_client_mocks
65+
lib = FtpLibrary()
66+
lib.brute_force(HOST, PORT, USER, PASS, TIMEOUT)
67+
68+
mock_connection.login.assert_called_once_with(USER, PASS)
69+
70+
def test_successful_login_calls_close(self, ftp_client_mocks):
71+
_, mock_connection = ftp_client_mocks
72+
lib = FtpLibrary()
73+
lib.brute_force(HOST, PORT, USER, PASS, TIMEOUT)
74+
75+
mock_connection.close.assert_called_once()
76+
77+
@pytest.mark.parametrize(
78+
"exc",
79+
[
80+
pytest.param(TimeoutError("Connection timed out"), id="timeout_error"),
81+
pytest.param(OSError(51, "Network unreachable"), id="oserror"),
82+
],
83+
)
84+
def test_connect_failure_propagates(self, ftp_client_mocks, exc):
85+
_, mock_connection = ftp_client_mocks
86+
mock_connection.connect.side_effect = exc
87+
88+
lib = FtpLibrary()
89+
with pytest.raises(type(exc)):
90+
lib.brute_force(HOST, PORT, USER, PASS, TIMEOUT_CONNECT_FAIL)
91+
92+
mock_connection.close.assert_called_once()
93+
94+
@pytest.mark.parametrize(
95+
"exc",
96+
[
97+
pytest.param(ftplib.error_perm("530 Login incorrect"), id="error_perm"),
98+
pytest.param(ftplib.error_temp("421 Service not available"), id="error_temp"),
99+
],
100+
)
101+
def test_login_ftp_error_propagates(self, ftp_client_mocks, exc):
102+
_, mock_connection = ftp_client_mocks
103+
mock_connection.login.side_effect = exc
104+
105+
lib = FtpLibrary()
106+
with pytest.raises(type(exc)):
107+
lib.brute_force(HOST, PORT, USER, WRONG_PASS, TIMEOUT)
108+
109+
mock_connection.close.assert_called_once()
110+
111+
def test_timeout_passed_to_ftp_constructor(self, ftp_client_mocks):
112+
mock_ftp_class, _ = ftp_client_mocks
113+
lib = FtpLibrary()
114+
lib.brute_force(HOST, PORT, USER, PASS, timeout=TIMEOUT_CLIENT)
115+
116+
mock_ftp_class.assert_called_once_with(timeout=TIMEOUT_CLIENT)

tests/core/lib/test_ftps.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import ftplib
2+
from unittest.mock import MagicMock, patch
3+
4+
from nettacker.core.lib.ftp import FtpEngine, FtpLibrary
5+
from nettacker.core.lib.ftps import FtpsEngine, FtpsLibrary
6+
7+
8+
class TestFtpsEngine:
9+
def test_engine_has_correct_library(self):
10+
assert FtpsEngine.library == FtpsLibrary
11+
12+
def test_engine_subclasses_ftp_engine(self):
13+
assert issubclass(FtpsEngine, FtpEngine)
14+
15+
16+
class TestFtpsLibrary:
17+
def test_library_subclasses_ftp_library(self):
18+
assert issubclass(FtpsLibrary, FtpLibrary)
19+
20+
def test_client_is_ftp_tls(self):
21+
assert FtpsLibrary.client is ftplib.FTP_TLS
22+
23+
@patch.object(FtpsLibrary, "client")
24+
def test_brute_force_uses_ftp_tls_client(self, mock_ftp_tls_class):
25+
mock_connection = MagicMock(spec=ftplib.FTP_TLS)
26+
mock_ftp_tls_class.return_value = mock_connection
27+
28+
lib = FtpsLibrary()
29+
lib.brute_force("10.0.0.1", 21, "user", "pass", timeout=30)
30+
31+
mock_ftp_tls_class.assert_called_once_with(timeout=30)

0 commit comments

Comments
 (0)