import os import shutil import sys from pathlib import Path from typing import Dict, List, Optional import pytest import requests from aqt.commercial import CommercialInstaller, QtPackageInfo, QtPackageManager from aqt.exceptions import DiskAccessNotPermitted from aqt.helper import Settings, download_installer, get_os_name, get_qt_account_path, get_qt_installer_name from aqt.installer import Cli from aqt.metadata import Version class CompletedProcess: def __init__(self, args, returncode): self.args = args self.returncode = returncode self.stdout = None self.stderr = None # Test data MOCK_XML_RESPONSE = """ """ TEST_EMAIL = os.getenv("AQT_TEST_EMAIL") TEST_PASSWORD = os.getenv("AQT_TEST_PASSWORD") class MockResponse: def __init__(self, status_code: int = 200, content: bytes = b"", text: str = "", headers: Dict = None): self.status_code = status_code self.content = content self.text = text self.headers = headers or {} self.ok = status_code == 200 def raise_for_status(self): if not self.ok: raise requests.HTTPError(f"HTTP Error: {self.status_code}") def iter_content(self, chunk_size=None): yield self.content @pytest.fixture def commercial_installer(): return CommercialInstaller( target="desktop", arch="gcc_64", version="6.8.0", username=TEST_EMAIL, password=TEST_PASSWORD, output_dir="./test_output", ) @pytest.mark.enable_socket @pytest.mark.parametrize( "cmd, expected_arch, expected_err", [ pytest.param( "install-qt-official desktop {} 6.8.0", {"windows": "win64_msvc2022_64", "linux": "linux_gcc_64", "mac": "clang_64"}, "No Qt account credentials found", ), ], ) def test_cli_login_qt_commercial(capsys, monkeypatch, cmd, expected_arch, expected_err): """Test commercial Qt installation command""" # Detect current platform current_platform = sys.platform.lower() arch = expected_arch[current_platform] cmd = cmd.format(arch) if get_qt_account_path().exists(): os.remove(get_qt_account_path()) cli = Cli() cli._setup_settings() cli.run(cmd.split()) out, err = capsys.readouterr() assert expected_err in err or expected_err in out def test_package_manager_init(): """Test QtPackageManager initialization""" manager = QtPackageManager( arch="gcc_64", version=Version("6.8.0"), target="desktop", username=TEST_EMAIL, password=TEST_PASSWORD, ) assert manager.arch == "gcc_64" assert str(manager.version) == "6.8.0" assert manager.target == "desktop" assert manager.username == TEST_EMAIL assert manager.password == TEST_PASSWORD @pytest.mark.parametrize( "xml_content, expected_packages", [ ( MOCK_XML_RESPONSE, [ QtPackageInfo(name="qt.qt6.680.gcc_64", displayname="Desktop gcc", version="6.8.0-0-202312011"), QtPackageInfo(name="qt.qt6.680.addons.qtquick3d", displayname="Qt Quick 3D", version="6.8.0-0-202312011"), ], ) ], ) def test_parse_packages_xml(xml_content: str, expected_packages: List[QtPackageInfo]): """Test parsing of package XML data""" manager = QtPackageManager(arch="gcc_64", version=Version("6.8.0"), target="desktop") manager._parse_packages_xml(xml_content) assert len(manager.packages) == len(expected_packages) for actual, expected in zip(manager.packages, expected_packages): assert actual.name == expected.name assert actual.displayname == expected.displayname assert actual.version == expected.version def test_commercial_installer_auto_answers(): """Test generation of auto-answer options""" auto_answers = CommercialInstaller.get_auto_answers() assert "OperationDoesNotExistError=Ignore" in auto_answers assert "OverwriteTargetDirectory=No" in auto_answers assert "telemetry-question=No" in auto_answers @pytest.mark.parametrize( "installer_path, override, username, password, output_dir, no_unattended, expected_cmd", [ ( "/path/to/installer", None, "user", "pass", "./output", False, [ "/path/to/installer", "--accept-licenses", "--accept-obligations", "--confirm-command", "--email", "user", "--pw", "pass", "--root", str(Path("./output").absolute()), "--auto-answer", CommercialInstaller.get_auto_answers(), ], ), ( "/path/to/installer", ["--override", "arg1", "arg2"], None, None, None, True, ["/path/to/installer", "--override", "arg1", "arg2"], ), ], ) def test_build_command( installer_path: str, override: Optional[List[str]], username: Optional[str], password: Optional[str], output_dir: Optional[str], no_unattended: bool, expected_cmd: List[str], ): """Test building of installer command""" cmd = CommercialInstaller.build_command( installer_path, override=override, username=username, password=password, output_dir=output_dir, no_unattended=no_unattended, ) assert cmd == expected_cmd @pytest.mark.enable_socket @pytest.mark.parametrize( "os, osarch, expected_suffix", [ pytest.param("windows", "windows", ".exe"), pytest.param("windows", "windows-x64", ".exe"), pytest.param("windows", "windows-arm64", ".exe"), pytest.param("linux", "linux", ".run"), pytest.param("linux", "linux-x64", ".run"), pytest.param("linux", "linux-arm64", ".run"), pytest.param("mac", "mac-x64", ".dmg"), ], ) def test_commercial_installer_names(monkeypatch, os, osarch, expected_suffix): """Test installer names finder""" monkeypatch.setattr("aqt.helper.get_os_arch", lambda: osarch) installer_name = get_qt_installer_name() assert installer_name.endswith(expected_suffix) if os == get_os_name() and osarch in ["windows", "linux", "mac-x64"]: target_path = Settings.qt_installer_temp_path / Path(installer_name) base_url = Settings.baseurl timeout = (Settings.connection_timeout, Settings.response_timeout) download_installer(base_url, installer_name, target_path, timeout) assert True @pytest.mark.parametrize( "modules, expected_command", [ (None, ["install", "qt.qt6.680.gcc_64"]), (["qtquick3d"], ["install", "qt.qt6.680.gcc_64", "qt.qt6.680.addons.qtquick3d"]), (["all"], ["install", "qt.qt6.680.gcc_64", "qt.qt6.680.addons.qtquick3d"]), ], ) def test_get_install_command(monkeypatch, modules: Optional[List[str]], expected_command: List[str]): """Test generation of install commands""" manager = QtPackageManager(arch="gcc_64", version=Version("6.8.0"), target="desktop") def mock_gather_packages(self, installer_path: str) -> None: self.packages = [ QtPackageInfo(name="qt.qt6.680.gcc_64", displayname="Desktop gcc", version="6.8.0"), QtPackageInfo(name="qt.qt6.680.addons.qtquick3d", displayname="Qt Quick 3D", version="6.8.0"), ] monkeypatch.setattr(QtPackageManager, "gather_packages", mock_gather_packages) command = manager.get_install_command(modules, "./temp") assert command == expected_command @pytest.mark.enable_socket @pytest.mark.parametrize( "cmd, arch_dict, details, expected_command", [ ( "install-qt-official desktop {} 6.8.1 " "--outputdir ./install-qt-official --email {} --pw {}", {"windows": "win64_msvc2022_64", "linux": "linux_gcc_64", "mac": "clang_64"}, ["./install-qt-official", "qt6", "681"], "qt-unified-{}-x64-online.run --email ******** --pw ******** --root {} " "--accept-licenses --accept-obligations " "--confirm-command " "--auto-answer OperationDoesNotExistError=Ignore,OverwriteTargetDirectory=No," "stopProcessesForUpdates=Cancel,installationErrorWithCancel=Cancel,installationErrorWithIgnore=Ignore," "AssociateCommonFiletypes=Yes,telemetry-question=No install qt.{}.{}.{}", ), ( "install-qt-official desktop {} 6.8.1 --outputdir ./install-qt-official --email {} --pw {}", {"windows": "win64_msvc2022_64", "linux": "linux_gcc_64", "mac": "clang_64"}, ["./install-qt-official", "qt6", "681"], "qt-unified-{}-x64-online.run --email ******** --pw ******** --root {} " "--accept-licenses --accept-obligations " "--confirm-command " "--auto-answer OperationDoesNotExistError=Ignore,OverwriteTargetDirectory=Yes," "stopProcessesForUpdates=Cancel,installationErrorWithCancel=Cancel,installationErrorWithIgnore=Ignore," "AssociateCommonFiletypes=Yes,telemetry-question=No install qt.{}.{}.{}", ), ( "install-qt linux desktop 6.8.1 {} --outputdir ./install-qt-flag --use-official-installer {} {}", {"windows": "win64_msvc2022_64", "linux": "linux_gcc_64", "mac": "clang_64"}, ["./install-qt-official", "qt6", "681"], "{} --email ******** --pw ******** --root {} " "--accept-licenses --accept-obligations " "--confirm-command " "--auto-answer OperationDoesNotExistError=Ignore,OverwriteTargetDirectory=Yes," "stopProcessesForUpdates=Cancel,installationErrorWithCancel=Cancel,installationErrorWithIgnore=Ignore," "AssociateCommonFiletypes=Yes,telemetry-question=No install qt.{}.{}.{}", ), ], ) def test_install_qt_commercial( capsys, tmp_path, monkeypatch, cmd: str, arch_dict: dict[str, str], details: list[str], expected_command: str ) -> None: """Test commercial Qt installation command""" def mock_safely_run(*args, **kwargs): return CompletedProcess(args=args[0], returncode=0) def mock_get_default_local_cache_path(*args, **kwargs): return tmp_path.joinpath("cache") monkeypatch.setattr("aqt.commercial.safely_run", mock_safely_run) monkeypatch.setattr("aqt.helper.get_default_local_cache_path", mock_get_default_local_cache_path) current_platform = sys.platform.lower() arch = arch_dict[current_platform] abs_out = Path(details[0]).absolute() # Get the email and password from the test parameters email = TEST_EMAIL password = TEST_PASSWORD formatted_cmd = cmd.format(arch, email, password) if expected_command.startswith("install-qt-official"): formatted_expected = expected_command.format(current_platform, abs_out, *details[1:], arch) else: formatted_expected = expected_command.format(get_qt_installer_name(), abs_out, *details[1:], arch) cli = Cli() cli._setup_settings() # First test the normal installation command try: cli.run(formatted_cmd.split()) except AttributeError: out = " ".join(capsys.readouterr()) assert str(out).find(formatted_expected) >= 0 abs_out.joinpath(f"6.8.{str(details[2])[-1]}").mkdir(exist_ok=True, parents=True) # Create a new command with the temp directory new_cmd = ( f"install-qt-official desktop {arch} 6.8.{str(details[2])[-1]} --outputdir {abs_out} --email {email} " f"--pw {password}" ) # This should raise DiskAccessNotPermitted only for the first test (680) if details[2] == "680": with pytest.raises(DiskAccessNotPermitted) as exc_info: cli.run(new_cmd.split()) assert "Target directory" in str(exc_info.value) assert "already exists" in str(exc_info.value) else: cli.run(new_cmd.split()) def modify_qt_config(content, endwith): """ Takes content of INI file as string and returns modified content """ lines = content.splitlines() in_qt_commercial = False modified = [] for line in lines: if line.strip() == "[qtofficial]": in_qt_commercial = True if endwith is not None and endwith in ["Yes", "No"] and "overwrite_target_directory : " in line: line = f"overwrite_target_directory : {endwith}" elif in_qt_commercial and "overwrite_target_directory : No" in line: line = "overwrite_target_directory : Yes" elif in_qt_commercial and "overwrite_target_directory : Yes" in line: line = "overwrite_target_directory : No" modified.append(line) return "\n".join(modified) script_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(script_dir, "../aqt/settings.ini") with open(config_path, "r") as f: content = f.read() if expected_command.startswith("install-qt-official"): modified_content = modify_qt_config(content, None) else: modified_content = modify_qt_config(content, "No") with open(config_path, "w") as f: f.write(modified_content) Settings._initialize() shutil.rmtree(abs_out) @pytest.mark.parametrize( "args, expected_error", [ (["list-qt-official", "--bad-flag"], "usage: aqt [-h] [-c CONFIG]"), ], ) def test_list_qt_commercial_errors(capsys, args, expected_error): """Test error handling in list-qt-official command""" cli = Cli() with pytest.raises(SystemExit): cli.run(args) _, err = capsys.readouterr() assert expected_error in err