Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions Libraries/PyKotor/tests/cli/test_diff_cli_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""Unit tests for pykotor.diff_tool.cli_utils path normalization helpers."""

from __future__ import annotations

import pathlib
import sys
import unittest

THIS_SCRIPT_PATH = pathlib.Path(__file__).resolve()
PYKOTOR_PATH = THIS_SCRIPT_PATH.parents[3].joinpath("src")
UTILITY_PATH = THIS_SCRIPT_PATH.parents[5].joinpath("Libraries", "Utility", "src")


def add_sys_path(p: pathlib.Path) -> None:
working_dir = str(p)
if working_dir not in sys.path:
sys.path.append(working_dir)


if PYKOTOR_PATH.joinpath("pykotor").exists():
add_sys_path(PYKOTOR_PATH)
if UTILITY_PATH.joinpath("utility").exists():
add_sys_path(UTILITY_PATH)

from pykotor.diff_tool.cli_utils import normalize_path_arg


class TestNormalizePathArg(unittest.TestCase):
def test_none_and_empty_return_none(self) -> None:
self.assertIsNone(normalize_path_arg(None))
self.assertIsNone(normalize_path_arg(""))
self.assertIsNone(normalize_path_arg(" "))

def test_strips_surrounding_quotes(self) -> None:
self.assertEqual(normalize_path_arg('"C:\\Games\\KOTOR"'), "C:\\Games\\KOTOR")
self.assertEqual(normalize_path_arg("'C:/Games/KOTOR'"), "C:/Games/KOTOR")

def test_strips_trailing_backslash_before_quote_escape(self) -> None:
self.assertEqual(
normalize_path_arg(r'"C:\Program Files\Steam\steamapps\common\swkotor\"'),
r"C:\Program Files\Steam\steamapps\common\swkotor",
)

def test_mangled_powershell_quote_space_path(self) -> None:
mangled = (
r'C:\Program Files\Steam\steamapps\common\swkotor" '
r'C:\Program Files\Steam\steamapps\common\swkotor'
)
self.assertEqual(
normalize_path_arg(mangled),
r"C:\Program Files\Steam\steamapps\common\swkotor",
)

def test_removes_embedded_quotes(self) -> None:
self.assertEqual(
normalize_path_arg('C:\\"Games"\\KOTOR'),
r"C:\Games\KOTOR",
)


if __name__ == "__main__":
try:
import pytest
except ImportError: # pragma: no cover
unittest.main()
else:
pytest.main(["-v", __file__])
137 changes: 137 additions & 0 deletions Libraries/PyKotor/tests/common/test_case_aware_consumers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""Regression tests for CaseAwarePath consumers added in case-aware application (#151)."""

from __future__ import annotations

import json
import os
import pathlib
import sys
import tempfile
import unittest

THIS_SCRIPT_PATH = pathlib.Path(__file__).resolve()
PYKOTOR_PATH = THIS_SCRIPT_PATH.parents[3].joinpath("src")
UTILITY_PATH = THIS_SCRIPT_PATH.parents[5].joinpath("Libraries", "Utility", "src")


def add_sys_path(p: pathlib.Path) -> None:
working_dir = str(p)
if working_dir not in sys.path:
sys.path.append(working_dir)


if PYKOTOR_PATH.joinpath("pykotor").exists():
add_sys_path(PYKOTOR_PATH)
if UTILITY_PATH.joinpath("utility").exists():
add_sys_path(UTILITY_PATH)

from pykotor.diff_tool.cli_utils import is_kotor_install_dir as cli_is_kotor_install_dir
from pykotor.tools.indoorkit import load_kits_unified
from pykotor.tools.path import CaseAwarePath, clear_cache
from pykotor.tslpatcher.diff.engine import is_kotor_install_dir as engine_is_kotor_install_dir
from pykotor.tslpatcher.reader import ConfigReader


class TestIsKotorInstallDirCaseAware(unittest.TestCase):
def test_exact_case_detects_installation(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
install_dir = pathlib.Path(tmp) / "KotorInstall"
install_dir.mkdir()
(install_dir / "chitin.key").write_bytes(b"mock key")

for checker in (cli_is_kotor_install_dir, engine_is_kotor_install_dir):
with self.subTest(checker=checker.__module__):
self.assertTrue(checker(install_dir))

@unittest.skipIf(
sys.platform == "win32",
"Case-mismatch path semantics differ on Windows filesystems.",
)
def test_case_mismatched_path_detects_installation(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
install_dir = pathlib.Path(tmp) / "KotorInstall"
install_dir.mkdir()
(install_dir / "chitin.key").write_bytes(b"mock key")

mismatched = pathlib.Path(tmp) / "kotorinstall"
for checker in (cli_is_kotor_install_dir, engine_is_kotor_install_dir):
with self.subTest(checker=checker.__module__):
self.assertTrue(checker(mismatched))

def test_missing_chitin_key_is_not_installation(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
install_dir = pathlib.Path(tmp) / "empty_dir"
install_dir.mkdir()

for checker in (cli_is_kotor_install_dir, engine_is_kotor_install_dir):
with self.subTest(checker=checker.__module__):
self.assertFalse(checker(install_dir))


class TestConfigReaderCaseAwarePath(unittest.TestCase):
@unittest.skipIf(
sys.platform == "win32",
"Case-mismatch path semantics differ on Windows filesystems.",
)
def test_from_filepath_resolves_case_mismatched_ini(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
mod_dir = pathlib.Path(tmp) / "ModData"
mod_dir.mkdir()
ini_path = mod_dir / "Changes.ini"
ini_path.write_text(
"[Settings]\nLookupGameFolder=0\nLookupGameNumber=1\n",
encoding="utf-8",
)

reader = ConfigReader.from_filepath(mod_dir / "changes.ini")
config = reader.load(reader.config)
self.assertEqual(config.game_number, 1)


class TestIndoorKitCaseAwarePath(unittest.TestCase):
@unittest.skipIf(
sys.platform == "win32",
"Case-mismatch path semantics differ on Windows filesystems.",
)
def test_load_kits_unified_resolves_case_mismatched_directory(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
kits_dir = pathlib.Path(tmp) / "KitsRoot"
kits_dir.mkdir()
kit_json = {"name": "Legacy", "id": "legacy", "doors": [], "components": []}
(kits_dir / "legacy.json").write_text(json.dumps(kit_json), encoding="utf-8")

kits, tile_kits = load_kits_unified(pathlib.Path(tmp) / "kitsroot")
self.assertEqual(len(kits), 1)
self.assertEqual(kits[0].id, "legacy")
self.assertEqual(tile_kits, [])


class TestCaseAwarePathCache(unittest.TestCase):
@unittest.skipIf(
sys.platform == "win32",
"Directory cache behavior is exercised on POSIX case-resolution paths.",
)
def test_clear_cache_allows_case_resolution_after_directory_change(self) -> None:
with tempfile.TemporaryDirectory() as tmp:
root = pathlib.Path(tmp) / "MixedCase"
root.mkdir()
child = root / "nested"
child.mkdir()

first = str(CaseAwarePath(root.parent / "mixedcase" / "nested"))
self.assertTrue(first.endswith(f"MixedCase{os.sep}nested"))

child.rename(root / "Renamed")
clear_cache()
second = str(CaseAwarePath(root.parent / "mixedcase" / "renamed"))
self.assertTrue(second.endswith(f"MixedCase{os.sep}Renamed"))
self.assertNotEqual(first, second)


if __name__ == "__main__":
try:
import pytest
except ImportError: # pragma: no cover
unittest.main()
else:
pytest.main(["-v", __file__])
Loading