diff --git a/poetry.lock b/poetry.lock index a2d5269..673c578 100644 --- a/poetry.lock +++ b/poetry.lock @@ -8,7 +8,7 @@ python-versions = ">=3.7,<4.0" [[package]] name = "aiohttp" -version = "3.8.4" +version = "3.8.5" description = "Async http client/server framework (asyncio)" category = "main" optional = true @@ -125,7 +125,7 @@ pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} [[package]] name = "certifi" -version = "2022.12.7" +version = "2023.7.22" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -176,11 +176,11 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7 [[package]] name = "cryptography" -version = "40.0.2" +version = "41.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." category = "main" optional = true -python-versions = ">=3.6" +python-versions = ">=3.7" [package.dependencies] cffi = ">=1.12" @@ -188,12 +188,12 @@ cffi = ">=1.12" [package.extras] docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -pep8test = ["black", "ruff", "mypy", "check-manifest"] -sdist = ["setuptools-rust (>=0.11.4)"] +nox = ["nox"] +pep8test = ["black", "ruff", "mypy", "check-sdist"] +sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-shard (>=0.1.2)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601"] +test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist", "pretend"] test-randomorder = ["pytest-randomly"] -tox = ["tox"] [[package]] name = "dill" @@ -287,7 +287,7 @@ smmap = ">=3.0.1,<6" [[package]] name = "gitpython" -version = "3.1.31" +version = "3.1.32" description = "GitPython is a Python library used to interact with Git repositories" category = "main" optional = true @@ -893,13 +893,13 @@ version = "2.31.0" description = "Python HTTP for Humans." category = "main" optional = true -python-versions = ">=3.7, <4" +python-versions = ">=3.7" [package.dependencies] certifi = ">=2017.4.17" charset-normalizer = ">=2,<4" idna = ">=2.5,<4" -urllib3 = ">=1.21.1,<1.27" +urllib3 = ">=1.21.1,<3" [package.extras] socks = ["PySocks (>=1.5.6,!=1.5.7)"] diff --git a/pyproject.toml b/pyproject.toml index 5470a7d..1a8946a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "virl2_client" -version = "2.6.0" +version = "2.6.1" description = "VIRL2 Client Library" authors = ["Simon Knight ", "Ralph Schmieder "] license = "Apache-2.0" diff --git a/tests/requirements.txt b/tests/requirements.txt index e4443e3..fe4b788 100644 --- a/tests/requirements.txt +++ b/tests/requirements.txt @@ -1,5 +1,5 @@ anyio==3.6.2; python_full_version >= "3.6.2" and python_version >= "3.7" -certifi==2022.12.7; python_version >= "3.7" +certifi==2023.7.22; python_version >= "3.7" cfgv==3.3.1; python_full_version >= "3.6.1" and python_version >= "3.8" colorama==0.4.6; python_version >= "3.7" and python_full_version < "3.0.0" and sys_platform == "win32" or sys_platform == "win32" and python_version >= "3.7" and python_full_version >= "3.7.0" distlib==0.3.6; python_version >= "3.8" diff --git a/tests/test_data/.file.qcow b/tests/test_data/.file.qcow new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/test_data/.file.qcow @@ -0,0 +1 @@ +test diff --git a/tests/test_data/file.qcow b/tests/test_data/file.qcow new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/test_data/file.qcow @@ -0,0 +1 @@ +test diff --git a/tests/test_data/file.qcow.qcow b/tests/test_data/file.qcow.qcow new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/test_data/file.qcow.qcow @@ -0,0 +1 @@ +test diff --git a/tests/test_data/file.tar.gz.qcow b/tests/test_data/file.tar.gz.qcow new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/test_data/file.tar.gz.qcow @@ -0,0 +1 @@ +test diff --git a/tests/test_data/qcow2.qcow2.qcow2 b/tests/test_data/qcow2.qcow2.qcow2 new file mode 100644 index 0000000..9daeafb --- /dev/null +++ b/tests/test_data/qcow2.qcow2.qcow2 @@ -0,0 +1 @@ +test diff --git a/tests/test_image_upload.py b/tests/test_image_upload.py index b40803d..7453bb6 100644 --- a/tests/test_image_upload.py +++ b/tests/test_image_upload.py @@ -21,14 +21,15 @@ import contextlib import pathlib -from unittest.mock import MagicMock +from io import BufferedReader +from unittest.mock import ANY, MagicMock import pytest from virl2_client.exceptions import InvalidImageFile from virl2_client.models.node_image_definitions import NodeImageDefinitions -wrong_format_list = [ +WRONG_FORMAT_LIST = [ "", ".", "file", @@ -41,7 +42,7 @@ ".file.", "file.qcow.", ] -not_supported_list = [ +NOT_SUPPORTED_LIST = [ " . ", "file.txt", "file.qcw", @@ -52,7 +53,7 @@ "file.qcow ", "file.qcow.tar.gz", ] -expected_pass_list = [ +EXPECTED_PASS_LIST = [ "file.qcow", "file.tar.gz.qcow", "file.qcow.qcow", @@ -61,6 +62,17 @@ ] +# This fixture is not meant to be used in tests - rather, it's here to easily manually +# update files when the expected_pass_list is changed. Just change autouse to True, +# then locally run test_image_upload_file, and this will generate all the files +# in the expected_pass_list into test_data. +@pytest.fixture +def create_test_files(change_test_dir): + for file_path in EXPECTED_PASS_LIST: + path = pathlib.Path("test_data") / file_path + path.write_text("test") + + @contextlib.contextmanager def windows_path(path: str): if "\\" in path: @@ -86,30 +98,54 @@ def windows_path(path: str): pytest.param("\\"), pytest.param("..\\..\\"), pytest.param("\\test\\"), + pytest.param("test_data/"), + ], +) +@pytest.mark.parametrize( + "rename", + [ + pytest.param(None), + pytest.param("renamed.qcow"), + pytest.param("renamed.qcow2"), + pytest.param("renamed"), ], ) -@pytest.mark.parametrize("usage", [pytest.param("name"), pytest.param("rename")]) @pytest.mark.parametrize( "test_string, message", - [pytest.param(test_str, "wrong format") for test_str in wrong_format_list] - + [pytest.param(test_str, "not supported") for test_str in not_supported_list] - + [pytest.param(test_str, "") for test_str in expected_pass_list], + [pytest.param(test_str, "wrong format") for test_str in WRONG_FORMAT_LIST] + + [pytest.param(test_str, "not supported") for test_str in NOT_SUPPORTED_LIST] + + [pytest.param(test_str, "") for test_str in EXPECTED_PASS_LIST], ) -def test_image_upload_file(usage: str, test_string: str, message: str, test_path: str): +def test_image_upload_file( + change_test_dir, + test_path: str, + rename: str, + test_string: str, + message: str, +): session = MagicMock() + session.post = MagicMock() nid = NodeImageDefinitions(session) - rename = None filename = test_path + test_string - if usage == "rename": - rename = test_string + if message in ("wrong format", "not supported"): + with pytest.raises(InvalidImageFile, match=message): + with windows_path(filename): + nid.upload_image_file(filename, rename) - try: + elif test_path == "test_data/": with windows_path(filename): nid.upload_image_file(filename, rename) - except FileNotFoundError: - assert message == "" - except InvalidImageFile as exc: - assert message in exc.args[0] + name = rename or test_string + files = {"field0": (name, ANY)} + headers = {"X-Original-File-Name": name} + session.post.assert_called_with("images/upload", files=files, headers=headers) + file = session.post.call_args.kwargs["files"]["field0"][1] + assert isinstance(file, BufferedReader) + assert pathlib.Path(file.name).resolve() == pathlib.Path(filename).resolve() + file.close() + else: - assert message == "" + with pytest.raises(FileNotFoundError): + with windows_path(filename): + nid.upload_image_file(filename, rename) diff --git a/virl2_client/models/node_image_definitions.py b/virl2_client/models/node_image_definitions.py index 8a1b525..5daf2c2 100644 --- a/virl2_client/models/node_image_definitions.py +++ b/virl2_client/models/node_image_definitions.py @@ -183,23 +183,11 @@ def download_image_definition(self, definition_id: str) -> str: url = "image_definitions/" + definition_id return self._session.get(url).json() - def upload_image_file( - self, - filename: str, - rename: Optional[str] = None, - chunk_size_mb: Optional[int] = None, - ) -> None: + def upload_image_file(self, filename: str, rename: Optional[str] = None) -> None: """ :param filename: the path of the image to upload :param rename: Optional filename to rename to - :param chunk_size_mb: Optional size of upload chunk (mb) - (deprecated since 2.2.0) """ - if chunk_size_mb is not None: - warnings.warn( - 'The argument "chunk_size_mb" is deprecated as it never worked', - DeprecationWarning, - ) extension_list = [".qcow", ".qcow2"] url = "images/upload" @@ -207,7 +195,7 @@ def upload_image_file( path = pathlib.Path(filename) extension = "".join(path.suffixes) last_ext = path.suffix - name = rename or path.stem + name = rename or path.name if extension == "" or name == "": message = ( diff --git a/virl2_client/virl2_client.py b/virl2_client/virl2_client.py index f8fedd9..93ff379 100644 --- a/virl2_client/virl2_client.py +++ b/virl2_client/virl2_client.py @@ -164,7 +164,7 @@ class ClientLibrary: """Python bindings for the REST API of a CML controller.""" # current client version - VERSION = Version("2.6.0") + VERSION = Version("2.6.1") # list of Version objects INCOMPATIBLE_CONTROLLER_VERSIONS = [ Version("2.0.0"),