"""Tests for the frozen-exe build script + frozen-aware config default. The PyInstaller run itself is Windows-only and slow; these tests cover the pure logic the build script and the CLI rely on. The build script is PowerShell (``scripts/build_exe.ps1``); the tests assert on its content and on the CLI behaviors the produced exe depends on. """ import sys from pathlib import Path from unittest import mock import pytest from opcda_diagnozer.main import DEFAULT_CONFIG, _default_config_path BUILD_SCRIPT = Path(__file__).resolve().parents[1] / "scripts" / "build_exe.ps1" def test_default_config_path_in_source_mode() -> None: """Not frozen: the default stays the plain relative name (CWD lookup).""" with mock.patch.object(sys, "frozen", False, create=True): assert _default_config_path() == DEFAULT_CONFIG def test_default_config_path_when_frozen_resolves_next_to_exe( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: """Frozen: config.yaml lives next to the exe — never the service CWD (System32).""" exe_dir = tmp_path / "Program Files" / "OPCDA-Diagnozer" exe_dir.mkdir(parents=True) exe = exe_dir / "opcda-diagnozer.exe" exe.touch() monkeypatch.setattr(sys, "frozen", True, raising=False) monkeypatch.setattr(sys, "executable", str(exe)) assert _default_config_path() == str(exe_dir / DEFAULT_CONFIG) @pytest.mark.skipif(sys.platform != "win32", reason="the exe is Windows-only") class TestBuildScript: """Static assertions on scripts/build_exe.ps1 (no PyInstaller invocation).""" def test_hidden_imports_cover_lazy_modules(self) -> None: """Every lazy import in the package must be declared to PyInstaller.""" content = BUILD_SCRIPT.read_text(encoding="utf-8") # the deferred COM imports (opc/automation.py, opc/discovery.py) assert "'pythoncom'" in content assert "'win32com.client'" in content assert "'opcda_diagnozer.opc.automation'" in content # the lazily-imported browse module (main.py verbs) assert "'opcda_diagnozer.opc.browse'" in content # the Phase 3 service host stays in the bundle from day one assert "'opcda_diagnozer.service'" in content def test_version_info_template_uses_pyi_keywords(self) -> None: """The embedded version-info must use PyInstaller's exact keyword names. FixedFileInfo.__init__ takes filevers/prodvers/mask/flags/OS/fileType/ subtype/date — anything else (fileVers, fileDate, subType) raises a TypeError inside PyInstaller's eval at build time. """ content = BUILD_SCRIPT.read_text(encoding="utf-8") assert "filevers=(" in content assert "fileType=0x1" in content assert "subtype=0x0" in content assert "date=(0, 0)" in content # the rejected spellings must NOT appear assert "fileVers=" not in content assert "fileDate=" not in content assert "subType=" not in content def test_bitness_guards_present(self) -> None: """The script must refuse a 64-bit interpreter and verify the PE.""" content = BUILD_SCRIPT.read_text(encoding="utf-8") assert "struct.calcsize('P') * 8" in content # interpreter bitness check assert "0x14C" in content # PE machine = IMAGE_FILE_MACHINE_I386 check