diff --git a/test/__init__.py b/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..57ceb0b --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,13 @@ +import pytest +import json + +update_data= dict(current_version="1.1", description="Snapshot version.", downloads=dict(Windows32="https://google.com_32", Windows64="https://google.com_64")) +@pytest.fixture +def file_data(): + global update_data + yield json.dumps(update_data) + +@pytest.fixture +def json_data(): + global update_data + yield update_data \ No newline at end of file diff --git a/test/helloworld_v2_win64.zip b/test/helloworld_v2_win64.zip new file mode 100644 index 0000000..ed28a27 Binary files /dev/null and b/test/helloworld_v2_win64.zip differ diff --git a/test/test_core.py b/test/test_core.py new file mode 100644 index 0000000..13f18e7 --- /dev/null +++ b/test/test_core.py @@ -0,0 +1,164 @@ +import sys +import os +import pytest +import requests_mock +from unittest import mock +from json.decoder import JSONDecodeError +from requests.exceptions import HTTPError +from updater import core + +app_name: str = "a simple app" +current_version: str = "0.15" +endpoint: str = "https://gitlab.mcvsoftware.com" + +# Fake a win32api module, as this fails on non-windows platforms without this hack. +# ToDo: Find a way to do this better. +win32api = mock.Mock(name="win32api") +win32api.__name__ = "win32api" +sys.modules["win32api"] = win32api + +def test_requests_session(): + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + updater.create_session() + assert hasattr(updater, "session") + assert app_name in updater.session.headers.get("User-Agent") + assert current_version in updater.session.headers.get("User-Agent") + +def test_get_update_information_valid_json(file_data, json_data): + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + updater.create_session() + with requests_mock.Mocker(session=updater.session) as mocked: + mocked.get(endpoint, json=json_data) + contents = updater.get_update_information() + assert contents == json_data + +def test_get_update_information_invalid_json(file_data, json_data): + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + updater.create_session() + with requests_mock.Mocker(session=updater.session) as mocked: + mocked.get(endpoint, text="thisisnotjson") + with pytest.raises(JSONDecodeError): + contents = updater.get_update_information() + +def test_get_update_information_not_found(): + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + updater.create_session() + with requests_mock.Mocker(session=updater.session) as mocked: + mocked.get(endpoint, status_code=404) + with pytest.raises(HTTPError): + contents = updater.get_update_information() + +def test_version_data_no_update(json_data): + global app_name, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=json_data.get("current_version")) + updater.create_session() + results = updater.get_version_data(json_data) + assert results == (False, False, False) + +@pytest.mark.parametrize("platform, architecture", [ + ("Windows", ("32bit", "")), + ("Windows", ("64bit", "")), +]) +def test_version_data_update_available(json_data, platform, architecture): + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + updater.create_session() + with mock.patch("platform.system", return_value=platform): + with mock.patch("platform.architecture", return_value=architecture): + results = updater.get_version_data(json_data) + k = platform+architecture[0][:2] + assert results == (json_data["current_version"], json_data["description"], json_data["downloads"][k]) + +def test_version_data_architecture_not_found(json_data): + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + updater.create_session() + with mock.patch("platform.system", return_value="nonos"): + with mock.patch("platform.architecture", return_value=("31bits", "")): + with pytest.raises(KeyError): + results = updater.get_version_data(json_data) + +def test_download_update(): + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + updater.create_session() + with mock.patch("pubsub.pub.sendMessage") as sendMessage_mock: + with mock.patch("io.open") as open_mock: + with requests_mock.Mocker(session=updater.session) as mocker: + updatefile = os.path.join(os.path.dirname(__file__), "helloworld_v2_win64.zip") + mocker.get("http://downloads.update.org/update.zip", body=open(updatefile, "rb")) + result = updater.download_update(update_url="http://downloads.update.org/update.zip", update_destination="update.zip") + open_mock.assert_called_once_with("update.zip", "w+b") + assert result == "update.zip" + assert sendMessage_mock.call_count > 0 + +def test_extract_archive(): + # This only tests if archive extraction methods were called successfully and with the right parameters. + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + zipfile_opened = mock.MagicMock() + with mock.patch("zipfile.ZipFile", return_value=zipfile_opened) as zipfile_mock: + result = updater.extract_update("update.zip", os.path.dirname(__file__)) + assert result == os.path.dirname(__file__) + zipfile_mock.called_once_with("update.zip") + zipfile_opened.extractall.assert_called_once() + # Test a password protected update file. + zipfile_opened_with_password = mock.MagicMock() + with mock.patch("zipfile.ZipFile", return_value=zipfile_opened_with_password) as zipfile_mock: + # Set a password manually. + updater.password = "MyLongPassword" + result2 = updater.extract_update("update.zip", os.path.dirname(__file__)) + assert result2 == os.path.dirname(__file__) + zipfile_mock.called_once_with("update.zip") + zipfile_opened_with_password.setpassword.called_once_with("MyLongPassword") + zipfile_opened_with_password.extractall.assert_called_once() + +@pytest.mark.parametrize("system", [("Windows"), ("Darwin"), ("Linux")]) +def test_move_bootstrap(system): + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + # provide a fake extraction path. + extracted_path = os.path.dirname(__file__) + # supposedly, the bootstrap file should be moved to the parent path. + + with mock.patch("platform.system", return_value=system): + with mock.patch("os.rename") as os_rename: + expected_path = os.path.abspath(os.path.join(extracted_path, "..", updater.bootstrap_name())) + result = updater.move_bootstrap(extracted_path) + assert result == expected_path + os_rename.assert_called_once() + +@pytest.mark.parametrize("system", [("Windows"), ("Darwin"), ("Linux")]) +def test_execute_bootstrap(system): + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + with mock.patch("platform.system", return_value=system): + with mock.patch("os.stat") as os_stat: + with mock.patch("subprocess.Popen") as subprocess_popen: + with mock.patch("os.chmod") as os_chmod: + with mock.patch("win32api.ShellExecute") as win32api_ShellExecute: + bootstrap_path = os.path.join(os.path.dirname(__file__), updater.bootstrap_name()) + source_path = os.path.dirname(__file__) + updater.execute_bootstrap(bootstrap_path, source_path) + if system == "Windows": + win32api_ShellExecute.assert_called_once() + else: + os_chmod.assert_called_once() + os_stat.assert_called_once() + subprocess_popen.assert_called_once() + +@pytest.mark.parametrize("system, bootstrap_file", [ + ("Windows", "bootstrap.exe"), + ("Darwin", "bootstrap-mac.sh"), + ("Linux", "bootstrap-lin.sh") + ]) +def test_bootstrap_name(system, bootstrap_file): + global app_name, current_version, endpoint + updater = core.updaterCore(endpoint=endpoint, app_name=app_name, current_version=current_version) + with mock.patch("platform.system", return_value=system): + result = updater.bootstrap_name() + assert result == bootstrap_file