unctools 0.1.0__tar.gz → 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unctools-0.1.0/unctools.egg-info → unctools-0.2.0}/PKG-INFO +1 -1
- unctools-0.2.0/docs/api-stability.md +51 -0
- {unctools-0.1.0 → unctools-0.2.0}/tests/basic_functionality_test.py +8 -10
- {unctools-0.1.0 → unctools-0.2.0}/tests/test_converter.py +1 -19
- {unctools-0.1.0 → unctools-0.2.0}/tests/test_converter_v2.py +2 -7
- unctools-0.2.0/tests/test_import_stability.py +97 -0
- unctools-0.2.0/tests/test_operations.py +361 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools/__init__.py +15 -10
- {unctools-0.1.0 → unctools-0.2.0}/unctools/converter.py +97 -18
- {unctools-0.1.0 → unctools-0.2.0}/unctools/detector.py +153 -4
- unctools-0.2.0/unctools/operations.py +42 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools/utils/compat.py +3 -71
- {unctools-0.1.0 → unctools-0.2.0/unctools.egg-info}/PKG-INFO +1 -1
- {unctools-0.1.0 → unctools-0.2.0}/unctools.egg-info/SOURCES.txt +2 -0
- unctools-0.1.0/tests/test_operations.py +0 -649
- unctools-0.1.0/unctools/operations.py +0 -562
- {unctools-0.1.0 → unctools-0.2.0}/LICENSE +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/MANIFEST.in +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/README.md +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/docs/implementation-guide.md +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/docs/implementation-summary.md +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/docs/integration-guide.md +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/examples/basic_usage.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/examples/batch_operations.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/examples/windows_zone_fix.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/pyproject.toml +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/setup.cfg +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/setup.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/tests/__init__.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/tests/conftest.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/tests/test_detector.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/tests/test_framework.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/tests/test_win32net_warning.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/tests/test_windows.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/tests/test_windows_imports.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools/utils/__init__.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools/utils/logger.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools/utils/validation.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools/windows/__init__.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools/windows/network.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools/windows/registry.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools/windows/security.py +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools.egg-info/dependency_links.txt +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools.egg-info/not-zip-safe +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools.egg-info/requires.txt +0 -0
- {unctools-0.1.0 → unctools-0.2.0}/unctools.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# API Stability
|
|
2
|
+
|
|
3
|
+
UNCtools is the L0 path-identity layer of the
|
|
4
|
+
[DazzleLib stack](https://github.com/DazzleLib/.github/blob/main/docs/STACK-MAP.md).
|
|
5
|
+
Its public surface is locked and machine-checked by
|
|
6
|
+
`tests/test_import_stability.py` -- if that canary fails, a consumer somewhere
|
|
7
|
+
breaks: follow the policy below, never silently fix the test.
|
|
8
|
+
|
|
9
|
+
## Policy
|
|
10
|
+
|
|
11
|
+
1. **Locked symbols never vanish silently.** Removal/rename ships a NOISY
|
|
12
|
+
`DeprecationWarning` shim naming the new home and removal version,
|
|
13
|
+
registered in the stack's alias register, removed on schedule.
|
|
14
|
+
2. **The layer charter is not negotiable** (STACK-MAP rule 3a): this library
|
|
15
|
+
may probe the filesystem read-only to answer identity questions; it never
|
|
16
|
+
mutates or transfers content. Functions that do are rejected, not deprecated.
|
|
17
|
+
3. **Name hygiene** (STACK-MAP rule 7): before exporting a public symbol,
|
|
18
|
+
grep the stack -- same-name-different-semantics requires a layer-teaching
|
|
19
|
+
rename (that is how `classify_path_origin` got its name).
|
|
20
|
+
|
|
21
|
+
## Locked surface (0.2.0)
|
|
22
|
+
|
|
23
|
+
| Module | Symbols |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `unctools` (top level) | `convert_to_local`, `convert_to_unc`, `batch_convert`, `get_unc_path_elements`, `build_unc_path`, `is_unc_path`, `is_network_drive`, `is_subst_drive`, `classify_path_origin`, `get_network_mappings`, `detect_path_issues`, `file_exists`, `is_path_accessible`, `find_accessible_path`, `configure_logging`, `get_version` |
|
|
26
|
+
| `unctools.converter` | `UNCConverter`, `convert_to_local`, `convert_to_unc`, `batch_convert`, `get_unc_path_elements`, `build_unc_path`, `refresh_mappings`, `get_mappings`, `parse_unc_path`, `join_unc_path` |
|
|
27
|
+
| `unctools.detector` | the origin classifiers + probes listed above, plus `get_drive_type`, `get_subst_target`, `get_network_target`, `is_server_in_intranet_zone` |
|
|
28
|
+
| `unctools.windows.*` | Windows-only security/network/registry surface (unchanged in 0.2.0) |
|
|
29
|
+
|
|
30
|
+
## Active deprecations
|
|
31
|
+
|
|
32
|
+
| Symbol | Replacement | Warns since | Removed in |
|
|
33
|
+
|---|---|---|---|
|
|
34
|
+
| `get_path_type` | `classify_path_origin` | 0.2.0 | 0.3.0 |
|
|
35
|
+
| `unctools.operations` (module facade) | top-level imports / `converter` / `detector` | 0.2.0 | 0.3.0 |
|
|
36
|
+
|
|
37
|
+
## Known consumers
|
|
38
|
+
|
|
39
|
+
| Consumer | Symbols | Notes |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| dazzlecmd (`safedel/_volumes.py`, `fixpath.py`) | `get_drive_type`, `is_network_drive`, `is_unc_path`, `convert_to_local` | optional imports today; harden in stack P4 |
|
|
42
|
+
| dazzlesum | top-level conversion/probe block | soft-imports today; harden in stack P4 |
|
|
43
|
+
| modified_datetime_fix | mixed (incl. a vendored copy to retire) | stack P4 |
|
|
44
|
+
| dazzle-filekit | `[unctools]` extra pin (no runtime import) | becomes a real edge only if/when filekit consumes identity at runtime |
|
|
45
|
+
|
|
46
|
+
## Consolidation candidates (0.3.0)
|
|
47
|
+
|
|
48
|
+
- `parse_unc_path`/`join_unc_path` vs `get_unc_path_elements`/`build_unc_path`
|
|
49
|
+
(near-duplicates; the latter preserve forward slashes in the relative part)
|
|
50
|
+
- dazzle-lib adoption: derive errors from `dazzle_lib.PathIdentityError`
|
|
51
|
+
(deliberately deferred from 0.2.0 to keep the surgery diff reviewable)
|
|
@@ -62,9 +62,9 @@ def run_tests():
|
|
|
62
62
|
print("\nTesting core module imports...")
|
|
63
63
|
try:
|
|
64
64
|
from unctools import (
|
|
65
|
-
convert_to_local, convert_to_unc,
|
|
65
|
+
convert_to_local, convert_to_unc, classify_path_origin,
|
|
66
66
|
is_unc_path, is_network_drive, is_subst_drive,
|
|
67
|
-
|
|
67
|
+
file_exists, batch_convert
|
|
68
68
|
)
|
|
69
69
|
check(True, "Import core functions")
|
|
70
70
|
except ImportError as e:
|
|
@@ -111,12 +111,12 @@ def run_tests():
|
|
|
111
111
|
test_unc_path = r"\\server\share\folder"
|
|
112
112
|
check(is_unc_path(test_unc_path), f"is_unc_path({test_unc_path}) should be True")
|
|
113
113
|
|
|
114
|
-
# Test
|
|
114
|
+
# Test origin classification (normalize_path removed in 0.2.0, D4)
|
|
115
115
|
try:
|
|
116
|
-
|
|
117
|
-
check(True, f"
|
|
116
|
+
origin = classify_path_origin(test_path)
|
|
117
|
+
check(True, f"classify_path_origin({test_path}) => {origin}")
|
|
118
118
|
except Exception as e:
|
|
119
|
-
check(False, f"
|
|
119
|
+
check(False, f"classify_path_origin({test_path}): {e}")
|
|
120
120
|
|
|
121
121
|
# Test path conversion (just ensure it doesn't error)
|
|
122
122
|
try:
|
|
@@ -157,10 +157,8 @@ def run_tests():
|
|
|
157
157
|
temp_filename = f.name
|
|
158
158
|
f.write("UNCtools test file")
|
|
159
159
|
|
|
160
|
-
# Test safe_open
|
|
161
|
-
|
|
162
|
-
content = f.read()
|
|
163
|
-
check(content == "UNCtools test file", f"safe_open() and read content: '{content}'")
|
|
160
|
+
# Test file_exists probe (safe_open removed in 0.2.0, D7)
|
|
161
|
+
check(file_exists(temp_filename), f"file_exists({temp_filename}) should be True")
|
|
164
162
|
|
|
165
163
|
# Clean up
|
|
166
164
|
os.unlink(temp_filename)
|
|
@@ -10,7 +10,7 @@ from pathlib import Path
|
|
|
10
10
|
from unittest import mock
|
|
11
11
|
|
|
12
12
|
from unctools.converter import (
|
|
13
|
-
UNCConverter, convert_to_local, convert_to_unc,
|
|
13
|
+
UNCConverter, convert_to_local, convert_to_unc,
|
|
14
14
|
parse_unc_path, join_unc_path
|
|
15
15
|
)
|
|
16
16
|
|
|
@@ -180,24 +180,6 @@ class TestModuleFunctions:
|
|
|
180
180
|
assert result == Path(TEST_UNC_PATH)
|
|
181
181
|
mock_converter.convert_to_unc.assert_called_once_with(TEST_LOCAL_PATH)
|
|
182
182
|
|
|
183
|
-
def test_normalize_path(self):
|
|
184
|
-
"""Test normalize_path function."""
|
|
185
|
-
# Test with prefer_unc=False (default)
|
|
186
|
-
with mock.patch('unctools.converter.convert_to_local') as mock_convert_local:
|
|
187
|
-
mock_convert_local.return_value = Path(TEST_LOCAL_PATH)
|
|
188
|
-
|
|
189
|
-
result = normalize_path(TEST_UNC_PATH)
|
|
190
|
-
assert result == Path(TEST_LOCAL_PATH)
|
|
191
|
-
mock_convert_local.assert_called_once_with(Path(TEST_UNC_PATH))
|
|
192
|
-
|
|
193
|
-
# Test with prefer_unc=True
|
|
194
|
-
with mock.patch('unctools.converter.convert_to_unc') as mock_convert_unc:
|
|
195
|
-
mock_convert_unc.return_value = Path(TEST_UNC_PATH)
|
|
196
|
-
|
|
197
|
-
result = normalize_path(TEST_LOCAL_PATH, prefer_unc=True)
|
|
198
|
-
assert result == Path(TEST_UNC_PATH)
|
|
199
|
-
mock_convert_unc.assert_called_once_with(Path(TEST_LOCAL_PATH))
|
|
200
|
-
|
|
201
183
|
def test_parse_unc_path(self):
|
|
202
184
|
"""Test parse_unc_path function."""
|
|
203
185
|
# Test with a valid UNC path
|
|
@@ -23,7 +23,7 @@ from tests.test_framework import (
|
|
|
23
23
|
# Import UNCtools
|
|
24
24
|
import unctools
|
|
25
25
|
from unctools.converter import (
|
|
26
|
-
UNCConverter, convert_to_local, convert_to_unc,
|
|
26
|
+
UNCConverter, convert_to_local, convert_to_unc,
|
|
27
27
|
parse_unc_path, join_unc_path
|
|
28
28
|
)
|
|
29
29
|
|
|
@@ -155,12 +155,7 @@ def test_module_functions():
|
|
|
155
155
|
path = convert_to_unc(TEST_LOCAL_PATH)
|
|
156
156
|
assert_is_not_none(path)
|
|
157
157
|
|
|
158
|
-
#
|
|
159
|
-
path = normalize_path(TEST_UNC_PATH)
|
|
160
|
-
assert_is_not_none(path)
|
|
161
|
-
|
|
162
|
-
path = normalize_path(TEST_LOCAL_PATH, prefer_unc=True)
|
|
163
|
-
assert_is_not_none(path)
|
|
158
|
+
# normalize_path was removed in 0.2.0 (D4) -- the explicit converts ARE the API
|
|
164
159
|
|
|
165
160
|
def test_parse_unc_path():
|
|
166
161
|
"""Test parse_unc_path function."""
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Import-stability canary + 0.2.0 surgery contract (see docs/api-stability.md).
|
|
2
|
+
|
|
3
|
+
Locked surface: if this test fails, a consumer breaks -- follow the
|
|
4
|
+
api-stability process (noisy shim, register, slate removal), never a silent fix.
|
|
5
|
+
|
|
6
|
+
Also asserts the 0.2.0 surgery outcomes (STACK-MAP D4/D7/D8):
|
|
7
|
+
- deleted content-I/O wrappers are GONE (probe-not-mutate, rule 3a)
|
|
8
|
+
- moved survivors live in their new homes (and the top-level namespace)
|
|
9
|
+
- the get_path_type -> classify_path_origin shim warns (alias A4)
|
|
10
|
+
- the operations facade warns on import (removed in 0.3.0)
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
import importlib
|
|
14
|
+
import warnings
|
|
15
|
+
|
|
16
|
+
import pytest
|
|
17
|
+
|
|
18
|
+
LOCKED_SURFACE = {
|
|
19
|
+
"unctools": [
|
|
20
|
+
# identity conversion
|
|
21
|
+
"convert_to_local", "convert_to_unc", "batch_convert",
|
|
22
|
+
"get_unc_path_elements", "build_unc_path",
|
|
23
|
+
# origin classification + probes
|
|
24
|
+
"is_unc_path", "is_network_drive", "is_subst_drive",
|
|
25
|
+
"classify_path_origin", "get_network_mappings", "detect_path_issues",
|
|
26
|
+
"file_exists", "is_path_accessible", "find_accessible_path",
|
|
27
|
+
# package plumbing
|
|
28
|
+
"configure_logging", "get_version",
|
|
29
|
+
],
|
|
30
|
+
"unctools.converter": [
|
|
31
|
+
"UNCConverter", "convert_to_local", "convert_to_unc", "batch_convert",
|
|
32
|
+
"get_unc_path_elements", "build_unc_path", "refresh_mappings",
|
|
33
|
+
],
|
|
34
|
+
"unctools.detector": [
|
|
35
|
+
"is_unc_path", "is_network_drive", "is_subst_drive",
|
|
36
|
+
"classify_path_origin", "get_network_mappings", "detect_path_issues",
|
|
37
|
+
"file_exists", "is_path_accessible", "find_accessible_path",
|
|
38
|
+
],
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
DELETED_FOREVER = {
|
|
42
|
+
# D7: content I/O has no home at L0 (probe, never mutate)
|
|
43
|
+
"unctools.operations": ["safe_open", "safe_copy", "batch_copy",
|
|
44
|
+
"process_files", "replace_in_file",
|
|
45
|
+
"batch_replace_in_files"],
|
|
46
|
+
# D8: case handling merged into dazzle-filekit
|
|
47
|
+
"unctools.utils.compat": ["path_exists_case_sensitive",
|
|
48
|
+
"get_case_sensitive_path"],
|
|
49
|
+
# D4: the explicit converts ARE the API
|
|
50
|
+
"unctools.converter": ["normalize_path"],
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_locked_surface_importable():
|
|
55
|
+
missing = []
|
|
56
|
+
for module_name, symbols in LOCKED_SURFACE.items():
|
|
57
|
+
module = importlib.import_module(module_name)
|
|
58
|
+
for symbol in symbols:
|
|
59
|
+
if not hasattr(module, symbol):
|
|
60
|
+
missing.append(f"{module_name}.{symbol}")
|
|
61
|
+
assert not missing, f"Locked API symbols missing: {missing}"
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_deleted_symbols_stay_deleted():
|
|
65
|
+
present = []
|
|
66
|
+
for module_name, symbols in DELETED_FOREVER.items():
|
|
67
|
+
with warnings.catch_warnings():
|
|
68
|
+
warnings.simplefilter("ignore") # operations facade warns on import
|
|
69
|
+
module = importlib.import_module(module_name)
|
|
70
|
+
for symbol in symbols:
|
|
71
|
+
if hasattr(module, symbol):
|
|
72
|
+
present.append(f"{module_name}.{symbol}")
|
|
73
|
+
assert not present, (
|
|
74
|
+
f"Deleted-by-contract symbols re-appeared: {present} -- "
|
|
75
|
+
f"these were removed by STACK-MAP D4/D7/D8 and must not return."
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_get_path_type_shim_warns_and_delegates():
|
|
80
|
+
"""A4: old name works through 0.2.x, warns naming the new home."""
|
|
81
|
+
import unctools
|
|
82
|
+
with pytest.warns(DeprecationWarning, match="classify_path_origin"):
|
|
83
|
+
result = unctools.get_path_type("C:\\")
|
|
84
|
+
assert result == unctools.classify_path_origin("C:\\")
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def test_operations_facade_warns_on_import():
|
|
88
|
+
"""The dissolved operations module is a 0.2.x-only facade (gone in 0.3.0)."""
|
|
89
|
+
import sys
|
|
90
|
+
sys.modules.pop("unctools.operations", None)
|
|
91
|
+
with pytest.warns(DeprecationWarning, match="0.3.0"):
|
|
92
|
+
import unctools.operations # noqa: F401
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_version_is_0_2_0():
|
|
96
|
+
import unctools
|
|
97
|
+
assert unctools.__version__ == "0.2.0"
|
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tests for the unctools.operations module using the test framework.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
import logging
|
|
10
|
+
import shutil
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
from unittest import mock
|
|
13
|
+
|
|
14
|
+
# Add parent directory to path for imports
|
|
15
|
+
sys.path.insert(0, os.path.abspath(os.path.dirname(os.path.dirname(__file__))))
|
|
16
|
+
|
|
17
|
+
# Import test framework
|
|
18
|
+
from tests.test_framework import (
|
|
19
|
+
TestSuite, assert_true, assert_false, assert_equal,
|
|
20
|
+
assert_not_equal, assert_is_none, assert_is_not_none,
|
|
21
|
+
skip_if_not_windows, skip_if_windows, skip_if_no_module,
|
|
22
|
+
run_test_suites, SkipTest
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Import UNCtools
|
|
26
|
+
import unctools
|
|
27
|
+
# 0.2.0 (STACK-MAP D7): content-I/O wrappers were deleted; survivors moved
|
|
28
|
+
# to their layer-correct homes. Tests import from the new locations.
|
|
29
|
+
from unctools.converter import (
|
|
30
|
+
batch_convert, get_unc_path_elements, build_unc_path
|
|
31
|
+
)
|
|
32
|
+
from unctools.detector import (
|
|
33
|
+
file_exists, is_path_accessible, find_accessible_path
|
|
34
|
+
)
|
|
35
|
+
from unctools.detector import is_unc_path, PATH_TYPE_UNC
|
|
36
|
+
|
|
37
|
+
# Configure logging
|
|
38
|
+
logging.basicConfig(level=logging.INFO,
|
|
39
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
40
|
+
|
|
41
|
+
# Test data
|
|
42
|
+
TEST_UNC_PATH = "\\\\server\\share\\folder\\file.txt"
|
|
43
|
+
TEST_LOCAL_PATH = "C:\\Users\\username\\Documents\\file.txt"
|
|
44
|
+
|
|
45
|
+
class TestEnvironment:
|
|
46
|
+
"""Manages a temporary test environment with files."""
|
|
47
|
+
|
|
48
|
+
def __init__(self):
|
|
49
|
+
"""Initialize the test environment."""
|
|
50
|
+
self.temp_dir = None
|
|
51
|
+
self.test_files = []
|
|
52
|
+
self.output_dir = None
|
|
53
|
+
|
|
54
|
+
def setup(self):
|
|
55
|
+
"""Set up the test environment."""
|
|
56
|
+
# Create a temporary directory
|
|
57
|
+
self.temp_dir = tempfile.mkdtemp(prefix='unctools_test_')
|
|
58
|
+
|
|
59
|
+
# Create an output directory for copy tests
|
|
60
|
+
self.output_dir = os.path.join(self.temp_dir, 'output')
|
|
61
|
+
os.makedirs(self.output_dir, exist_ok=True)
|
|
62
|
+
|
|
63
|
+
# Create test files
|
|
64
|
+
self._create_test_files()
|
|
65
|
+
|
|
66
|
+
# Return self for convenience
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def teardown(self):
|
|
70
|
+
"""Clean up the test environment."""
|
|
71
|
+
# Remove the temporary directory
|
|
72
|
+
if self.temp_dir and os.path.exists(self.temp_dir):
|
|
73
|
+
shutil.rmtree(self.temp_dir)
|
|
74
|
+
self.temp_dir = None
|
|
75
|
+
self.test_files = []
|
|
76
|
+
self.output_dir = None
|
|
77
|
+
|
|
78
|
+
def _create_test_files(self):
|
|
79
|
+
"""Create test files in the temporary directory."""
|
|
80
|
+
# Create files with different content
|
|
81
|
+
file_paths = []
|
|
82
|
+
|
|
83
|
+
# Create a simple text file
|
|
84
|
+
text_file = os.path.join(self.temp_dir, 'text_file.txt')
|
|
85
|
+
with open(text_file, 'w') as f:
|
|
86
|
+
f.write('This is a test file.')
|
|
87
|
+
file_paths.append(text_file)
|
|
88
|
+
|
|
89
|
+
# Create a nested directory
|
|
90
|
+
nested_dir = os.path.join(self.temp_dir, 'nested')
|
|
91
|
+
os.makedirs(nested_dir, exist_ok=True)
|
|
92
|
+
|
|
93
|
+
# Create a file in the nested directory
|
|
94
|
+
nested_file = os.path.join(nested_dir, 'nested_file.txt')
|
|
95
|
+
with open(nested_file, 'w') as f:
|
|
96
|
+
f.write('This is a nested file.')
|
|
97
|
+
file_paths.append(nested_file)
|
|
98
|
+
|
|
99
|
+
# Create binary file
|
|
100
|
+
binary_file = os.path.join(self.temp_dir, 'binary_file.bin')
|
|
101
|
+
with open(binary_file, 'wb') as f:
|
|
102
|
+
f.write(b'\x00\x01\x02\x03\x04')
|
|
103
|
+
file_paths.append(binary_file)
|
|
104
|
+
|
|
105
|
+
# Create multiple text files for batch operations
|
|
106
|
+
#for i in range(3):
|
|
107
|
+
# batch_file = os.path.join(self.temp_dir, f'batch_file_{i}.txt')
|
|
108
|
+
# with open(batch_file, 'w') as f:
|
|
109
|
+
# f.write(f'This is batch file {i}.')
|
|
110
|
+
# file_paths.append(batch_file)
|
|
111
|
+
|
|
112
|
+
# Store the file paths
|
|
113
|
+
self.test_files = file_paths
|
|
114
|
+
|
|
115
|
+
return file_paths
|
|
116
|
+
|
|
117
|
+
# Create test suite setup and teardown functions
|
|
118
|
+
def setup_test_environment():
|
|
119
|
+
"""Set up a test environment for tests."""
|
|
120
|
+
env = TestEnvironment()
|
|
121
|
+
return env.setup()
|
|
122
|
+
|
|
123
|
+
def teardown_test_environment(env=None):
|
|
124
|
+
"""Clean up the test environment."""
|
|
125
|
+
if env:
|
|
126
|
+
env.teardown()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def test_file_exists(env):
|
|
130
|
+
"""Test file_exists function."""
|
|
131
|
+
# Test with an existing file
|
|
132
|
+
test_file = env.test_files[0]
|
|
133
|
+
assert_true(file_exists(test_file), "Existing file should be detected")
|
|
134
|
+
|
|
135
|
+
# Test with a non-existent file
|
|
136
|
+
non_existent = os.path.join(env.temp_dir, 'non_existent.txt')
|
|
137
|
+
assert_false(file_exists(non_existent), "Non-existent file should not be detected")
|
|
138
|
+
|
|
139
|
+
# Test with a directory
|
|
140
|
+
assert_true(file_exists(env.temp_dir), "Directory should be detected as existing")
|
|
141
|
+
|
|
142
|
+
# Test with convert_paths behavior
|
|
143
|
+
# Mock convert_to_local and convert_to_unc to return alternate paths
|
|
144
|
+
# and os.path.exists to return True for the converted path
|
|
145
|
+
|
|
146
|
+
def mock_convert_to_local(path):
|
|
147
|
+
return Path(str(path) + ".local")
|
|
148
|
+
|
|
149
|
+
def mock_convert_to_unc(path):
|
|
150
|
+
return Path(str(path) + ".unc")
|
|
151
|
+
|
|
152
|
+
def mock_is_unc_path(path):
|
|
153
|
+
path_str = str(path) # Convert Path to string before checking
|
|
154
|
+
return path_str.startswith("\\\\") or path_str.startswith("//")
|
|
155
|
+
|
|
156
|
+
def mock_exists(path):
|
|
157
|
+
return str(path).endswith(".local") or str(path).endswith(".unc")
|
|
158
|
+
|
|
159
|
+
with mock.patch('unctools.converter.convert_to_local', side_effect=mock_convert_to_local), \
|
|
160
|
+
mock.patch('unctools.converter.convert_to_unc', side_effect=mock_convert_to_unc), \
|
|
161
|
+
mock.patch('unctools.detector.is_unc_path', side_effect=mock_is_unc_path), \
|
|
162
|
+
mock.patch('os.path.exists', side_effect=mock_exists):
|
|
163
|
+
|
|
164
|
+
# Test with a UNC path (should try convert_to_local)
|
|
165
|
+
assert_true(file_exists(TEST_UNC_PATH, check_both_paths=True),
|
|
166
|
+
"UNC path should be detected via local path conversion")
|
|
167
|
+
|
|
168
|
+
# Test with a local path (should try convert_to_unc)
|
|
169
|
+
assert_true(file_exists(TEST_LOCAL_PATH, check_both_paths=True),
|
|
170
|
+
"Local path should be detected via UNC path conversion")
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_batch_convert(env):
|
|
174
|
+
"""Test batch_convert function."""
|
|
175
|
+
# Get a list of test files
|
|
176
|
+
test_files = env.test_files[:3] # Take the first 3 files
|
|
177
|
+
|
|
178
|
+
# Mock convert_to_local and convert_to_unc to return predictable results
|
|
179
|
+
def mock_convert_to_local(path):
|
|
180
|
+
return Path(str(path) + ".local")
|
|
181
|
+
|
|
182
|
+
def mock_convert_to_unc(path):
|
|
183
|
+
return Path(str(path) + ".unc")
|
|
184
|
+
|
|
185
|
+
with mock.patch('unctools.converter.convert_to_local', side_effect=mock_convert_to_local), \
|
|
186
|
+
mock.patch('unctools.converter.convert_to_unc', side_effect=mock_convert_to_unc):
|
|
187
|
+
|
|
188
|
+
# Test batch convert to UNC
|
|
189
|
+
results = batch_convert([str(f) for f in test_files], to_unc=True)
|
|
190
|
+
|
|
191
|
+
# Verify results
|
|
192
|
+
assert_equal(len(results), len(test_files),
|
|
193
|
+
"Should have results for all input files")
|
|
194
|
+
|
|
195
|
+
for original, converted in results.items():
|
|
196
|
+
assert_equal(converted, original + ".unc",
|
|
197
|
+
f"Converted path for {original} should end with .unc")
|
|
198
|
+
|
|
199
|
+
# Test batch convert to local
|
|
200
|
+
results = batch_convert([str(f) for f in test_files], to_unc=False)
|
|
201
|
+
|
|
202
|
+
# Verify results
|
|
203
|
+
assert_equal(len(results), len(test_files),
|
|
204
|
+
"Should have results for all input files")
|
|
205
|
+
|
|
206
|
+
for original, converted in results.items():
|
|
207
|
+
assert_equal(converted, original + ".local",
|
|
208
|
+
f"Converted path for {original} should end with .local")
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def test_get_unc_path_elements(env):
|
|
213
|
+
"""Test get_unc_path_elements function."""
|
|
214
|
+
# Test with a valid UNC path
|
|
215
|
+
result = get_unc_path_elements(TEST_UNC_PATH)
|
|
216
|
+
assert_equal(result, ("server", "share", "folder\\file.txt"),
|
|
217
|
+
"Should extract the correct components")
|
|
218
|
+
|
|
219
|
+
# Test with a UNC path without a relative path
|
|
220
|
+
result = get_unc_path_elements("\\\\server\\share")
|
|
221
|
+
assert_equal(result, ("server", "share", ""),
|
|
222
|
+
"Should extract the correct components without relative path")
|
|
223
|
+
|
|
224
|
+
# Test with a non-UNC path
|
|
225
|
+
result = get_unc_path_elements(TEST_LOCAL_PATH)
|
|
226
|
+
assert_is_none(result, "Should return None for non-UNC path")
|
|
227
|
+
|
|
228
|
+
# Test with forward slashes
|
|
229
|
+
result = get_unc_path_elements("//server/share/folder/file.txt")
|
|
230
|
+
assert_equal(result, ("server", "share", "folder/file.txt"),
|
|
231
|
+
"Should extract the correct components with forward slashes")
|
|
232
|
+
|
|
233
|
+
def test_build_unc_path(env=None):
|
|
234
|
+
"""Test build_unc_path function."""
|
|
235
|
+
# Note: This function doesn't need the env parameter but should accept it
|
|
236
|
+
# for consistency with other test functions.
|
|
237
|
+
|
|
238
|
+
# Test with all components
|
|
239
|
+
result = build_unc_path("server", "share", "folder\\file.txt")
|
|
240
|
+
assert_equal(result, "\\\\server\\share\\folder\\file.txt",
|
|
241
|
+
"Should build the correct UNC path")
|
|
242
|
+
|
|
243
|
+
# Test without relative path
|
|
244
|
+
result = build_unc_path("server", "share")
|
|
245
|
+
assert_equal(result, "\\\\server\\share",
|
|
246
|
+
"Should build the correct UNC path without relative path")
|
|
247
|
+
|
|
248
|
+
# Test with empty relative path
|
|
249
|
+
result = build_unc_path("server", "share", "")
|
|
250
|
+
assert_equal(result, "\\\\server\\share",
|
|
251
|
+
"Should build the correct UNC path with empty relative path")
|
|
252
|
+
|
|
253
|
+
# Test with relative path that starts with backslash
|
|
254
|
+
result = build_unc_path("server", "share", "\\folder\\file.txt")
|
|
255
|
+
assert_equal(result, "\\\\server\\share\\folder\\file.txt",
|
|
256
|
+
"Should build the correct UNC path and handle leading backslash")
|
|
257
|
+
|
|
258
|
+
def test_is_path_accessible(env):
|
|
259
|
+
"""Test is_path_accessible function."""
|
|
260
|
+
# Test with an accessible file
|
|
261
|
+
test_file = env.test_files[0]
|
|
262
|
+
assert_true(is_path_accessible(test_file), "File should be accessible")
|
|
263
|
+
|
|
264
|
+
# Test with an accessible directory
|
|
265
|
+
assert_true(is_path_accessible(env.temp_dir), "Directory should be accessible")
|
|
266
|
+
|
|
267
|
+
# Test with a non-existent file
|
|
268
|
+
non_existent = os.path.join(env.temp_dir, 'non_existent.txt')
|
|
269
|
+
assert_false(is_path_accessible(non_existent), "Non-existent file should not be accessible")
|
|
270
|
+
|
|
271
|
+
# Test with convert_paths behavior
|
|
272
|
+
# Mock conversion and path access functions
|
|
273
|
+
def mock_convert_to_local(path):
|
|
274
|
+
return Path(env.test_files[0]) # Return a valid file
|
|
275
|
+
|
|
276
|
+
def mock_convert_to_unc(path):
|
|
277
|
+
return Path(env.test_files[0]) # Return a valid file
|
|
278
|
+
|
|
279
|
+
def mock_is_unc_path(path):
|
|
280
|
+
path_str = str(path)
|
|
281
|
+
return path_str.startswith("\\\\") or path_str.startswith("//")
|
|
282
|
+
|
|
283
|
+
with mock.patch('unctools.converter.convert_to_local', side_effect=mock_convert_to_local), \
|
|
284
|
+
mock.patch('unctools.converter.convert_to_unc', side_effect=mock_convert_to_unc), \
|
|
285
|
+
mock.patch('unctools.detector.is_unc_path', side_effect=mock_is_unc_path):
|
|
286
|
+
|
|
287
|
+
# Test with a UNC path (should try convert_to_local)
|
|
288
|
+
assert_true(is_path_accessible(TEST_UNC_PATH, check_both_paths=True),
|
|
289
|
+
"UNC path should be accessible after conversion")
|
|
290
|
+
|
|
291
|
+
# Test with a non-UNC path (should try convert_to_unc)
|
|
292
|
+
assert_true(is_path_accessible(non_existent, check_both_paths=True),
|
|
293
|
+
"Path should be accessible after conversion")
|
|
294
|
+
|
|
295
|
+
def test_find_accessible_path(env):
|
|
296
|
+
"""Test find_accessible_path function."""
|
|
297
|
+
# Test with an accessible file
|
|
298
|
+
test_file = env.test_files[0]
|
|
299
|
+
path = find_accessible_path(test_file)
|
|
300
|
+
assert_is_not_none(path, "Accessible path should be found")
|
|
301
|
+
assert_equal(str(path), test_file, "Found path should match the original")
|
|
302
|
+
|
|
303
|
+
# Test with a non-existent file
|
|
304
|
+
non_existent = os.path.join(env.temp_dir, 'non_existent.txt')
|
|
305
|
+
|
|
306
|
+
# Mock conversion and path access functions
|
|
307
|
+
def mock_convert_to_local(path):
|
|
308
|
+
return Path(env.test_files[0]) # Return a valid file
|
|
309
|
+
|
|
310
|
+
def mock_convert_to_unc(path):
|
|
311
|
+
return Path(env.test_files[0]) # Return a valid file
|
|
312
|
+
|
|
313
|
+
def mock_is_unc_path(path):
|
|
314
|
+
path_str = str(path)
|
|
315
|
+
return path_str.startswith("\\\\") or path_str.startswith("//")
|
|
316
|
+
|
|
317
|
+
def mock_is_path_accessible(path, check_both_paths=True):
|
|
318
|
+
return str(path) == env.test_files[0] or \
|
|
319
|
+
str(path) == str(Path(env.test_files[0]))
|
|
320
|
+
|
|
321
|
+
with mock.patch('unctools.converter.convert_to_local', side_effect=mock_convert_to_local), \
|
|
322
|
+
mock.patch('unctools.converter.convert_to_unc', side_effect=mock_convert_to_unc), \
|
|
323
|
+
mock.patch('unctools.detector.is_unc_path', side_effect=mock_is_unc_path), \
|
|
324
|
+
mock.patch('unctools.detector.is_path_accessible', side_effect=mock_is_path_accessible):
|
|
325
|
+
|
|
326
|
+
# Test with a UNC path (should try convert_to_local)
|
|
327
|
+
path = find_accessible_path(TEST_UNC_PATH)
|
|
328
|
+
assert_is_not_none(path, "Accessible path should be found after conversion")
|
|
329
|
+
|
|
330
|
+
# Test with a non-UNC path (should try convert_to_unc)
|
|
331
|
+
path = find_accessible_path(non_existent)
|
|
332
|
+
assert_is_not_none(path, "Accessible path should be found after conversion")
|
|
333
|
+
|
|
334
|
+
# Test with a path that can't be made accessible
|
|
335
|
+
with mock.patch('unctools.detector.is_path_accessible', return_value=False):
|
|
336
|
+
path = find_accessible_path(non_existent)
|
|
337
|
+
assert_is_none(path, "No accessible path should be found")
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def run_tests():
|
|
342
|
+
"""Run all operations tests."""
|
|
343
|
+
suite = TestSuite("UNCtools Operations Tests")
|
|
344
|
+
|
|
345
|
+
# Set suite setup and teardown
|
|
346
|
+
suite.set_setup(setup_test_environment)
|
|
347
|
+
suite.set_teardown(teardown_test_environment)
|
|
348
|
+
|
|
349
|
+
# Add tests
|
|
350
|
+
suite.add_test(test_file_exists)
|
|
351
|
+
suite.add_test(test_batch_convert)
|
|
352
|
+
suite.add_test(test_get_unc_path_elements)
|
|
353
|
+
suite.add_test(test_build_unc_path)
|
|
354
|
+
suite.add_test(test_is_path_accessible)
|
|
355
|
+
suite.add_test(test_find_accessible_path)
|
|
356
|
+
|
|
357
|
+
# Run suite
|
|
358
|
+
return run_test_suites([suite])
|
|
359
|
+
|
|
360
|
+
if __name__ == "__main__":
|
|
361
|
+
sys.exit(run_tests())
|
|
@@ -32,7 +32,7 @@ Advanced Windows functionality:
|
|
|
32
32
|
mappings = get_network_mappings()
|
|
33
33
|
"""
|
|
34
34
|
|
|
35
|
-
__version__ = "0.
|
|
35
|
+
__version__ = "0.2.0"
|
|
36
36
|
|
|
37
37
|
import os
|
|
38
38
|
import sys
|
|
@@ -42,16 +42,21 @@ from pathlib import Path
|
|
|
42
42
|
# Set up package-level logger
|
|
43
43
|
logger = logging.getLogger(__name__)
|
|
44
44
|
|
|
45
|
-
# Import core functionality into the main namespace
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
# Import core functionality into the main namespace.
|
|
46
|
+
# 0.2.0 (STACK-MAP D4/D7/D8): normalize_path and the content-I/O wrappers
|
|
47
|
+
# (safe_open, safe_copy, batch_copy, process_files, replace_in_file*) were
|
|
48
|
+
# REMOVED; get_path_type was renamed classify_path_origin (deprecated shim
|
|
49
|
+
# kept through 0.2.x); the operations module dissolved into converter
|
|
50
|
+
# (path algebra) and detector (read-only probes).
|
|
51
|
+
from .converter import (
|
|
52
|
+
convert_to_local, convert_to_unc,
|
|
53
|
+
batch_convert, get_unc_path_elements, build_unc_path,
|
|
50
54
|
)
|
|
51
|
-
from .
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
+
from .detector import (
|
|
56
|
+
is_unc_path, is_network_drive, is_subst_drive,
|
|
57
|
+
classify_path_origin, get_path_type, # get_path_type = deprecated shim (A4)
|
|
58
|
+
get_network_mappings, detect_path_issues,
|
|
59
|
+
file_exists, is_path_accessible, find_accessible_path,
|
|
55
60
|
)
|
|
56
61
|
|
|
57
62
|
# Determine if we're running on Windows
|