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
|
"""
The browser makes a name writable everywhere exactly as Python does.
`static/portable-name.js` renames a file at the moment a member saves it;
`meshbay_common.paths.sanitize_for_download` is the same rule in Python. Two
copies of a rule that differ decide differently which files get renamed, so the
real module runs under node here against the real Python.
Skipped when node is unavailable; that is a coverage gap, not a pass.
"""
import json
import shutil
import subprocess
from pathlib import Path
import pytest
from meshbay_common.paths import is_portable_name, sanitize_for_download
PORTABLE_JS = (Path(__file__).resolve().parents[2]
/ "meshbay-hub" / "src" / "meshbay_hub" / "static" / "portable-name.js")
pytestmark = pytest.mark.skipif(
shutil.which("node") is None or not PORTABLE_JS.exists(),
reason="node or portable-name.js unavailable — parity cannot be checked",
)
NAMES = [
"plain.txt", "Réunion 12:30.pdf", 'a<b>c:d"e/f\\g|h?i*j.txt', "tab\tnew\nline",
"ends with dot.", "ends with space ", "trailing . . ", "CON", "con.txt", "aux.tar.gz",
"COM1", "com10.txt", "LPT9.log", "nul.", ".", "..", "", " ", ".bashrc", "...",
"名前:ファイル.mkv", "emoji 🙂?.png", "\x01\x1f.bin", "prn .txt", "Con",
"a.b.c", "COM1.", "normal name (2).mp4",
]
_HARNESS = r"""
const fs = require('fs');
const src = fs.readFileSync(process.argv[2], 'utf8').replace(/^export /gm, '');
const M = new Function(src + '\nreturn { portableName, portablePath };')();
const names = JSON.parse(fs.readFileSync(process.argv[3], 'utf8'));
process.stdout.write(JSON.stringify({
names: names.map((n) => M.portableName(n)),
path: M.portablePath('Top:Folder/sub//aux.txt'),
}));
"""
@pytest.fixture(scope="module")
def js(tmp_path_factory):
d = tmp_path_factory.mktemp("portable")
(d / "harness.js").write_text(_HARNESS, encoding="utf-8")
(d / "names.json").write_text(json.dumps(NAMES), encoding="utf-8")
proc = subprocess.run(
["node", str(d / "harness.js"), str(PORTABLE_JS), str(d / "names.json")],
capture_output=True, text=True, encoding="utf-8", timeout=60)
if proc.returncode != 0:
pytest.fail(f"node harness failed:\n{proc.stderr}")
return json.loads(proc.stdout)
@pytest.mark.parametrize("i,name", list(enumerate(NAMES)))
def test_the_browser_renames_as_python_does(i, name, js):
assert js["names"][i] == sanitize_for_download(name)
@pytest.mark.parametrize("name", NAMES)
def test_what_comes_out_can_be_written_everywhere(name):
out = sanitize_for_download(name)
assert is_portable_name(out), (name, out)
assert sanitize_for_download(out) == out
def test_a_portable_name_is_left_alone():
assert sanitize_for_download("plain.txt") == "plain.txt"
def test_a_path_is_made_portable_segment_by_segment(js):
assert js["path"] == "Top_Folder/sub//aux_.txt"
|