Usage¶
After installing pytest_localftpserver the fixture ftpserver
is available for
your pytest test functions. Note that you can’t use fixtures outside of functions and
need to pass them as arguments.
Basic usage¶
A basic example of using pytest_localftpserver would be, if you wanted to test code, which uploads a file to a FTP-server.
import os
def test_your_code_to_upload_files(ftpserver):
your_code_to_upload_files(host="localhost",
port=ftpserver.server_port,
username=ftpserver.username,
password=ftpserver.password,
files=["testfile.txt"])
uploaded_file_path = os.path.join(ftpserver.server_home, "testfile.txt")
with open("testfile.txt") as original, open(uploaded_file_path) as uploaded:
assert original.read() == uploaded.read()
Note
Like most public FTP-servers pytest_localftpserver doesn’t allow the anonymous user to upload files. The anonymous user is only allowed to browse the folder structure and download files. If you want to upload files you need to use the registered user, with its password.
An other common use case would be retrieving a file from a FTP-server.
import os
from shutil import copyfile
def test_your_code_retrieving_files(ftpserver):
dest_path = os.path.join(ftpserver.anon_root, "testfile.txt")
copyfile("testfile.txt", dest_path)
your_code_retrieving_files(host="localhost",
port=ftpserver.server_port
file_paths=[{"remote": "testfile.txt",
"local": "testfile_downloaded.txt"
}])
with open("testfile.txt") as original, open("testfile_downloaded.txt") as downloaded:
assert original.read() == downloaded.read()
Login with the TLS server¶
This example utilizes methods of the the high-level interface, which are explained in Getting login credentials and Gaining information about the content of files on the server.
The below example test logs into the TLS ftpserver, creates the file testfile.txt
, with content ‘test text’ and
checks if it was written properly.
from ftplib import FTP_TLS
from ssl import SSLContext
try:
from ssl import PROTOCOL_TLS
except Exception:
from ssl import PROTOCOL_SSLv23 as PROTOCOL_TLS
def test_TLS_login(ftpserver_TLS):
if PYTHON3:
ssl_context = SSLContext(PROTOCOL_TLS)
ssl_context.load_cert_chain(certfile=DEFAULT_CERTFILE)
ftp = FTP_TLS(context=ssl_context)
else:
ftp = FTP_TLS(certfile=DEFAULT_CERTFILE)
login_dict = ftpserver_TLS.get_login_data()
ftp.connect(login_dict["host"], login_dict["port"])
ftp.login(login_dict["user"], login_dict["passwd"])
ftp.prot_p()
ftp.cwd("/")
filename = "testfile.txt"
file_path_local = tmpdir.join(filename)
file_path_local.write("test text")
with open(str(file_path_local), "rb") as f:
ftp.storbinary("STOR "+filename, f)
ftp.quit()
file_list = list(ftpserver_TLS.get_file_contents()
assert file_list == [{"path": "testfile.txt", "content": "test text"}]
High-Level Interface¶
To allow you a faster and more comfortable handling of common ftp tasks a high-level
interface was implemented. Most of the following methods have the keyword anon
, which
allows to switch between the registered (anon=False) and the anonymous (anon=True) user.
For more information on how those methods work, take a look at the API Documentation .
Note
The following examples aren’t working code, since the aren’t called from
within a function, which means that the ftpserver
fixture isn’t available.
They are thought to be a quick overview of the available functionality and
its output.
Getting login credentials¶
To quickly get all needed login data you can use get_login_data
, which will either return
a dict or an url to log into the ftp:
>>> ftpserver.get_login_data()
{"host": "localhost", "port": 8888, "user": "fakeusername", "passwd": "qweqwe"}
>>> ftpserver.get_login_data(style="url", anon=False)
ftp://fakeusername:qweqwe@localhost:8888
>>> ftpserver.get_login_data(style="url", anon=True)
ftp://localhost:8888
Populating the FTP server with files and folders¶
To test ftp download capabilities of your code, you might want to populate the files on the server.
To “upload” files to the server you can use the method put_files
:
>>> ftpserver.put_files("test_folder/test_file", style="rel_path", anon=False)
["test_file"]
>>> ftpserver.put_files("test_folder/test_file", style="url", anon=False)
["ftp://fakeusername:qweqwe@localhost:8888/test_file"]
>>> ftpserver.put_files("test_folder/test_file", style="url", anon=True)
["ftp://localhost:8888/test_file"]
>>> ftpserver.put_files({"src": "test_folder/test_file",
... "dest": "remote_folder/uploaded_file"},
... style="url", anon=True)
["ftp://localhost:8888/remote_folder/uploaded_file"]
>>> ftpserver.put_files("test_folder/test_file", return_content=True)
[{"path": "test_file", "content": "some text in test_file"}]
>>> ftpserver.put_files("test_file.zip", return_content=True, read_mode="rb")
[{"path": "test_file.zip", "content": b'PK\\x03\\x04\\x14\\x00\\x00...'}]
>>> ftpserver.put_files("test_file", return_paths="new")
UserWarning: test_file does already exist and won't be overwritten.
Set `overwrite` to True to overwrite it anyway.
[]
>>> ftpserver.put_files("test_file", return_paths="new", overwrite=True)
["test_file"]
>>> ftpserver.put_files("test_file3", return_paths="all")
["test_file", "remote_folder/uploaded_file", "test_file.zip"]
Resetting files on the server¶
Since ftpserver
is a module scope fixture, you might want to make sure that uploaded files
get deleted after/before a test. This can be done by using the method reset_tmp_dirs
.
filesystem before:
+---server_home
| +---test_file1
| +---test_folder
| +---test_file2
|
+---anon_root
+---test_file3
+---test_folder
+---test_file4
>>> ftpserver.reset_tmp_dirs()
filesystem after:
+---server_home
|
+---anon_root
Gaining information on which files are on the server¶
If you want to know which files are on the server, i.e. if you want to know if your
file upload functionality is working, you can use the get_file_paths
method, which will
yield the paths to all files on the server.
filesystem
+---server_home
| +---test_file1
| +---test_folder
| +---test_file2
|
+---anon_root
+---test_file3
+---test_folder
+---test_file4
>>> list(ftpserver.get_file_paths(style="rel_path", anon=False))
["test_file1", "test_folder/test_file2"]
>>> list(ftpserver.get_file_paths(style="rel_path", anon=True))
["test_file3", "test_folder/test_file4"]
Gaining information about the content of files on the server¶
If you are interested in the content of a specific file, multiple files or all files,
i.e. to verify that your file upload functionality did work properly, you can use the
get_file_contents
method.
filesystem
+---server_home
+---test_file1.txt
+---test_folder
+---test_file2.zip
>>> list(ftpserver.get_file_contents())
[{"path": "test_file1.txt", "content": "test text"},
{"path": "test_folder/test_file2.txt", "content": "test text2"}]
>>> list(ftpserver.get_file_contents("test_file1.txt"))
[{"path": "test_file1.txt", "content": "test text"}]
>>> list(ftpserver.get_file_contents("test_file1.txt", style="url"))
[{"path": "ftp://fakeusername:qweqwe@localhost:8888/test_file1.txt",
"content": "test text"}]
>>> list(ftpserver.get_file_contents(["test_file1.txt", "test_folder/test_file2.zip"],
... read_mode="rb"))
[{"path": "test_file1.txt", "content": b"test text"},
{"path": "test_folder/test_file2.zip", "content": b'PK\\x03\\x04\\x14\\x00\\x00...'}]
Configuration¶
To configure custom values for for the username, the users password, the ftp port and/or
the location of the users home folder on the local storage, you need to set the environment
variables FTP_USER
, FTP_PASS
, FTP_PORT
, FTP_HOME
, FTP_FIXTURE_SCOPE
,
FTP_PORT_TLS
, FTP_HOME_TLS
and FTP_CERTFILE
.
Environment variable | Usage |
---|---|
FTP_USER |
Username of the registered user. |
FTP_PASS |
Password of the registered user. |
FTP_PORT |
Port for the normal ftp server to run on. |
FTP_HOME |
Home folder (host system) of the registered user. |
FTP_FIXTURE_SCOPE |
Scope/lifetime of the fixture. |
FTP_PORT_TLS |
Port for the TLS ftp server to run on. |
FTP_HOME_TLS |
Home folder (host system) of the registered user, used by the TLS ftp server. |
FTP_CERTFILE |
Certificate (host system) to be used by the TLS ftp server. |
You can either set environment variables on a system level or use tools such as pytest-env or tox, which would be the recommended way.
Note
You might run into OSError: [Errno 48] Address already in use
when setting a fixed port
(FTP_PORT
/ FTP_PORT_TLS
).
This is due to the server still listening on that port, which prevents it from adding another listener
on that port. When using pythons buildin ftplib
, you should use the
quit method
to terminate the connection, since it’s the ‘the “polite” way to close a connection’ and lets the
server know that the client isn’t just experiencing connection problems, but won’t come back.
Configuration with pytest-env¶
The configuration of pytest-env is done in the pytest.ini
file.
The following example configuration will use the username benz
, the password erni1
,
the ftp port 31175
and the home folder /home/ftp_test
.
For the encrypted version of the fixture it uses port 31176
, the home folder /home/ftp_test
and
the certificate ./tests/test_keycert.pem
:
$ cat pytest.ini
[pytest]
env =
FTP_USER=benz
FTP_PASS=erni1
FTP_HOME = /home/ftp_test
FTP_PORT=31175
FTP_FIXTURE_SCOPE=function
# only affects ftpserver_TLS
FTP_PORT_TLS = 31176
FTP_HOME_TLS = /home/ftp_test_TLS
FTP_CERTFILE = ./tests/test_keycert.pem
Configuration with Tox¶
The configuration of tox is done in the tox.ini
file.
The following example configuration will run the tests in the folder tests
on
python 3.6+ and use the username benz
, the password erni1
,
the tempfolder of each virtual environment the tests are run in ({envtmpdir}
) and
the ftp port 31175
.
For the encrypted version of the fixture it uses port 31176
and the certificate
{toxinidir}/tests/test_keycert.pem
:
$ cat tox.ini
[tox]
envlist = py{36,37,38,39,310}
[testenv]
setenv =
FTP_USER=benz
FTP_PASS=erni1
FTP_HOME = {envtmpdir}
FTP_PORT=31175
FTP_FIXTURE_SCOPE=function
# only affects ftpserver_TLS
FTP_PORT_TLS = 31176
FTP_HOME_TLS = /home/ftp_test_TLS
FTP_CERTFILE = {toxinidir}/tests/test_keycert.pem
commands =
pytest tests