summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_windows_root_shapes.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-node/tests/test_windows_root_shapes.py')
-rw-r--r--packages/meshbay-node/tests/test_windows_root_shapes.py149
1 files changed, 149 insertions, 0 deletions
diff --git a/packages/meshbay-node/tests/test_windows_root_shapes.py b/packages/meshbay-node/tests/test_windows_root_shapes.py
new file mode 100644
index 0000000..5c5da25
--- /dev/null
+++ b/packages/meshbay-node/tests/test_windows_root_shapes.py
@@ -0,0 +1,149 @@
+"""
+The root model against the shapes Windows produces.
+
+CLAUDE.md is explicit that exFAT/NTFS and Windows are the common case, not an
+edge case: most operators are expected to share from an external drive on
+Windows. The RO/RW refactor added two booleans and a config rewriter, and the
+booleans are path-independent — but the rewriter, the name derivation and the
+collision check all touch paths, and none of them has ever run on Windows here.
+
+What this can check without Windows is the *shape* work: drive letters through
+`as_posix()`, a path with no basename to derive a name from, UNC, and a
+case-insensitive collision. `PureWindowsPath` is used deliberately — the plain
+`Path` on this machine is a `PosixPath`, where a backslash is an ordinary
+filename character, which is the mistake that made
+`test_a_backslash_path_written_into_node_toml_stays_parseable` fail everywhere
+but the platform it was written for.
+
+What it cannot check is the filesystem itself: `ReadDirectoryChangesW` dropping
+events under load, `MAX_PATH`, and whether an eject actually lets a drive be
+removed. Those need a person with Windows, and §4.4 of the refactor plan is
+where that is written down.
+"""
+
+import os
+import tempfile
+import tomllib
+from pathlib import Path, PureWindowsPath
+
+import pytest
+
+from meshbay_node.roots import RootError, RootSet, derive_name
+
+BS = chr(92)
+
+
+# ── Paths into node.toml ─────────────────────────────────────────────────────
+
+@pytest.mark.parametrize("raw,expected", [
+ (f"D:{BS}Movies", "D:/Movies"),
+ (f"E:{BS}Music{BS}Albums", "E:/Music/Albums"),
+ (f"C:{BS}Users{BS}alice{BS}Media", "C:/Users/alice/Media"),
+ (f"{BS}{BS}server{BS}share{BS}Media", "//server/share/Media"),
+])
+def test_a_windows_path_survives_the_config_file(raw, expected):
+ """
+ `ops` writes `as_posix()` into a TOML basic string, where a raw backslash
+ is an escape — `\\U` and `\\a` are the ones that bite — so the file would
+ not parse at all. pathlib reads the forward-slash form back on Windows.
+ """
+ posix = PureWindowsPath(raw).as_posix()
+ assert posix == expected
+ parsed = tomllib.loads(f'path = "{posix}"\n')
+ assert parsed["path"] == expected
+
+
+def test_a_raw_windows_path_would_not_parse():
+ """The counter-property: without `as_posix()` there is no config file."""
+ with pytest.raises(tomllib.TOMLDecodeError):
+ tomllib.loads(f'path = "C:{BS}Users{BS}alice{BS}Media"\n')
+
+
+# ── Naming a drive ───────────────────────────────────────────────────────────
+
+@pytest.mark.parametrize("raw,name", [
+ (f"D:{BS}Movies", "Movies"),
+ (f"E:{BS}Music{BS}Albums", "Albums"),
+ (f"{BS}{BS}server{BS}share{BS}Media", "Media"),
+])
+def test_a_name_is_derived_from_the_last_segment(raw, name):
+ assert PureWindowsPath(raw).name == name
+
+
+@pytest.mark.parametrize("raw", [f"D:{BS}", f"E:{BS}", f"{BS}{BS}server{BS}share"])
+def test_a_drive_root_has_no_name_to_derive(raw):
+ """
+ Sharing a whole drive is an ordinary thing to do on Windows and there is
+ nothing to call it, so the operator has to say. Refused with that as the
+ message rather than named "" or "D:".
+ """
+ p = PureWindowsPath(raw)
+ if p.name:
+ pytest.skip(f"{raw!r} has a basename on this platform")
+ with pytest.raises(RootError, match="explicit"):
+ derive_name(p)
+
+
+def test_naming_it_explicitly_works():
+ with tempfile.TemporaryDirectory() as d:
+ roots = RootSet.build([{"path": d, "name": "Films"}])
+ assert roots.names == ["Films"]
+
+
+# ── Case, which Windows makes real ───────────────────────────────────────────
+
+def test_two_roots_differing_only_in_case_are_refused():
+ """
+ On NTFS and exFAT `Movies` and `MOVIES` are the same directory to the
+ filesystem and two roots to a case-sensitive comparison — which would index
+ one tree twice, and make deleting a file from one copy break the other.
+ """
+ with tempfile.TemporaryDirectory() as d:
+ os.makedirs(os.path.join(d, "Movies"))
+ os.makedirs(os.path.join(d, "other"))
+ with pytest.raises(RootError, match="regard to case"):
+ RootSet.build([
+ {"path": os.path.join(d, "Movies")},
+ {"path": os.path.join(d, "other"), "name": "MOVIES"},
+ ])
+
+
+def test_a_root_is_found_by_name_without_regard_to_case():
+ """
+ What a client sends is what a person typed or a path it split, and on
+ Windows those disagree about case routinely.
+ """
+ with tempfile.TemporaryDirectory() as d:
+ os.makedirs(os.path.join(d, "Movies"))
+ roots = RootSet.build([{"path": os.path.join(d, "Movies")}])
+ for spelling in ("Movies", "movies", "MOVIES", "MoViEs"):
+ assert roots.by_name(spelling) is not None, spelling
+
+
+# ── The two flags ────────────────────────────────────────────────────────────
+
+def test_the_flags_do_not_touch_paths():
+ """
+ `writable` and `removable` are booleans and stay booleans on every
+ platform. Stated as a test because it is the reason the rest of the
+ refactor needed no Windows work: what did need it is above.
+ """
+ with tempfile.TemporaryDirectory() as d:
+ os.makedirs(os.path.join(d, "USB"))
+ roots = RootSet.build([{"path": os.path.join(d, "USB"),
+ "writable": True, "removable": True}])
+ described = roots.describe()[0]
+ assert described["writable"] is True
+ assert described["removable"] is True
+ assert "path" not in described
+
+
+def test_an_ejected_removable_root_is_unavailable_wherever_it_runs():
+ with tempfile.TemporaryDirectory() as d:
+ os.makedirs(os.path.join(d, "USB"))
+ roots = RootSet.build([{"path": os.path.join(d, "USB"),
+ "removable": True, "ejected": True}])
+ assert roots.roots[0].available is False
+ assert Path(roots.roots[0].path).is_dir(), (
+ "the directory is still there; `ejected` is the operator's answer, "
+ "not the filesystem's")