From 25258274a29d2989813234a1137a9da19eac0c4a Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sat, 6 Jun 2026 10:06:04 +0000 Subject: [PATCH] test(pykotor): cover case-aware install detection and ci json export progress Co-authored-by: PuritanWizard --- .../PyKotor/tests/cli/test_json_commands.py | 34 ++++++++++ .../common/test_kotor_install_detection.py | 68 +++++++++++++++++++ .../PyKotor/tests/tslpatcher/test_reader.py | 15 ++++ 3 files changed, 117 insertions(+) create mode 100644 Libraries/PyKotor/tests/common/test_kotor_install_detection.py diff --git a/Libraries/PyKotor/tests/cli/test_json_commands.py b/Libraries/PyKotor/tests/cli/test_json_commands.py index 67a02752f..358b45846 100644 --- a/Libraries/PyKotor/tests/cli/test_json_commands.py +++ b/Libraries/PyKotor/tests/cli/test_json_commands.py @@ -28,6 +28,7 @@ from pykotor.resource.type import ResourceType from pykotor.tools.resource_json import ( _serialize_mdl_face, + _supports_live_progress, export_installation_to_json_tree, iter_installation_resource_documents, serialize_file_resource_document, @@ -35,6 +36,39 @@ ) +def test_supports_live_progress_disabled_in_ci(monkeypatch: pytest.MonkeyPatch) -> None: + class _FakeTty: + def isatty(self) -> bool: + return True + + monkeypatch.setenv("CI", "true") + assert _supports_live_progress(_FakeTty()) is False + + +def test_supports_live_progress_disabled_in_github_actions( + monkeypatch: pytest.MonkeyPatch, +) -> None: + class _FakeTty: + def isatty(self) -> bool: + return True + + monkeypatch.delenv("CI", raising=False) + monkeypatch.setenv("GITHUB_ACTIONS", "true") + assert _supports_live_progress(_FakeTty()) is False + + +def test_supports_live_progress_enabled_for_interactive_tty( + monkeypatch: pytest.MonkeyPatch, +) -> None: + class _FakeTty: + def isatty(self) -> bool: + return True + + monkeypatch.delenv("CI", raising=False) + monkeypatch.delenv("GITHUB_ACTIONS", raising=False) + assert _supports_live_progress(_FakeTty()) is True + + def test_to_json_and_from_json_roundtrip_tlk(tmp_path: Path) -> None: input_path = tmp_path / "dialog.tlk" json_path = tmp_path / "dialog.tlk.json" diff --git a/Libraries/PyKotor/tests/common/test_kotor_install_detection.py b/Libraries/PyKotor/tests/common/test_kotor_install_detection.py new file mode 100644 index 000000000..59d49554e --- /dev/null +++ b/Libraries/PyKotor/tests/common/test_kotor_install_detection.py @@ -0,0 +1,68 @@ +"""Regression tests for CaseAwarePath-based KOTOR install directory detection.""" + +from __future__ import annotations + +import importlib +import sys +from pathlib import Path + +import pytest + +_INSTALL_DETECTORS = ( + ("pykotor.diff_tool.cli_utils", "is_kotor_install_dir"), + ("pykotor.tools.patching", "is_kotor_install_dir"), + ("pykotor.tslpatcher.diff.engine", "is_kotor_install_dir"), +) + + +@pytest.fixture(params=_INSTALL_DETECTORS, ids=[f"{module}.{name}" for module, name in _INSTALL_DETECTORS]) +def is_kotor_install_dir(request): + module_name, attr_name = request.param + module = importlib.import_module(module_name) + return getattr(module, attr_name) + + +def test_detects_install_with_chitin_key(tmp_path: Path, is_kotor_install_dir) -> None: + install = tmp_path / "KotorInstall" + install.mkdir() + (install / "chitin.key").write_bytes(b"") + + assert is_kotor_install_dir(install) is True + + +def test_rejects_folder_without_chitin_key(tmp_path: Path, is_kotor_install_dir) -> None: + folder = tmp_path / "not_install" + folder.mkdir() + + assert not is_kotor_install_dir(folder) + + +def test_rejects_regular_file(tmp_path: Path, is_kotor_install_dir) -> None: + file_path = tmp_path / "file.txt" + file_path.write_text("x", encoding="utf-8") + + assert not is_kotor_install_dir(file_path) + + +@pytest.mark.skipif( + sys.platform == "win32", + reason="Case-mismatch path semantics differ on Windows filesystems.", +) +def test_detects_case_mismatched_install_path(tmp_path: Path, is_kotor_install_dir) -> None: + install = tmp_path / "GameInstall" + install.mkdir() + (install / "chitin.key").write_bytes(b"") + + assert is_kotor_install_dir(tmp_path / "gameinstall") is True + + +@pytest.mark.skipif( + sys.platform == "win32", + reason="Case-mismatch path semantics differ on Windows filesystems.", +) +def test_detects_case_mismatched_chitin_key(tmp_path: Path, is_kotor_install_dir) -> None: + install = tmp_path / "install" + install.mkdir() + (install / "CHITIN.KEY").write_bytes(b"") + + assert is_kotor_install_dir(install) is True diff --git a/Libraries/PyKotor/tests/tslpatcher/test_reader.py b/Libraries/PyKotor/tests/tslpatcher/test_reader.py index 613bb544c..dd33567e5 100644 --- a/Libraries/PyKotor/tests/tslpatcher/test_reader.py +++ b/Libraries/PyKotor/tests/tslpatcher/test_reader.py @@ -250,6 +250,21 @@ def tearDown(self): if hasattr(self, "temp_dir"): shutil.rmtree(self.temp_dir, ignore_errors=True) + @unittest.skipIf( + sys.platform == "win32", + "Case-mismatch path semantics differ on Windows filesystems.", + ) + def test_from_filepath_resolves_case_mismatched_ini_path(self): + mod_dir = Path(self.temp_dir) / "ModDir" + mod_dir.mkdir() + ini_path = mod_dir / "changes.ini" + ini_path.write_text("[Settings]\nWindowCaption=CaseAware INI\n", encoding="utf-8") + + reader = ConfigReader.from_filepath(Path(self.temp_dir) / "moddir" / "changes.ini") + reader.load_settings() + + self.assertEqual(reader.config.window_title, "CaseAware INI") + def create_test_tlk(self, data: dict[int, dict[str, str]]) -> TLK: tlk = TLK() for v in data.values():