aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-common/tests/test_portable_name_parity.py
blob: 3a491a74b6cff8d3abf410e2af17dd14d0aa30f6 (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
"""
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"