summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-node/tests/test_windows_root_shapes.py
blob: 56279b1c6200bb395746b7b0be472ca8e13a2aad (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
"""
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")