diff options
Diffstat (limited to 'packages/meshbay-hub/tests')
| -rw-r--r-- | packages/meshbay-hub/tests/test_zip_size_limit.py | 107 |
1 files changed, 107 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_zip_size_limit.py b/packages/meshbay-hub/tests/test_zip_size_limit.py new file mode 100644 index 0000000..203c10c --- /dev/null +++ b/packages/meshbay-hub/tests/test_zip_size_limit.py @@ -0,0 +1,107 @@ +""" +The arbitrary ceiling on a directory zip. + +`downloadDirectory` is the one implementation behind every "download this +folder as a zip" button — Files' single folder, Files' multi-folder selection, +and the Photos album button (docs/photos.md §3) — so the limit is checked +once, there, and holds for all of them. + +Two things are worth pinning. That an oversized folder is refused *before* +`_openDownloadTarget`, because a save dialog for an archive that will never be +written is worse than no dialog at all. And that a folder at exactly the limit +still goes through, since an off-by-one here silently costs a whole megabyte +of allowance and nobody would ever notice. +""" + +import json +import shutil +import subprocess +from pathlib import Path + +import pytest + +STATIC = Path(__file__).resolve().parents[1] / "src" / "meshbay_hub" / "static" +FILE_UTILS = STATIC / "file-utils.js" + +pytestmark = pytest.mark.skipif( + shutil.which("node") is None or not FILE_UTILS.exists(), + reason="node or the SPA sources are not available") + +MIB = 1024 * 1024 + + +def _run(total_bytes, tmp_path): + """ + Call downloadDirectory over one folder holding `total_bytes`, and report + what it did: the errors it set, how many times it put a question to the + person, and how many transfers it started. + """ + sandbox = tmp_path / "static" + sandbox.mkdir() + for src in STATIC.glob("*.js"): + (sandbox / src.name).write_text(src.read_text(encoding="utf-8"), + encoding="utf-8") + (tmp_path / "package.json").write_text('{"type":"module"}') + + script = tmp_path / "case.mjs" + script.write_text(f""" +const store = new Map(); +globalThis.localStorage = {{ + getItem: k => (store.has(k) ? store.get(k) : null), + setItem: (k, v) => store.set(k, String(v)), + removeItem: k => store.delete(k), +}}; +// Node 22 defines `navigator` itself, so it is left alone; `window` is what +// platform.js reaches for to decide it is not running in the desktop app. +globalThis.window = globalThis; +const out = {{ errors: [], started: 0, asked: 0 }}; +// Reached only once the size check has passed: with no File System Access API +// under Node, downloadDirectory falls through to its build-in-memory path and +// asks first. Answering yes is what lets the at-the-limit case get as far as +// starting a transfer, and `asked` is how the refusal proves it never did. +globalThis.confirm = () => {{ out.asked += 1; return true; }}; + +const M = await import('{(sandbox / "file-utils.js").as_posix()}'); + +const transfers = {{ start: () => {{ out.started += 1; }} }}; +const transport = {{ connected: true }}; +// One file, in the folder itself — entriesUnder keys on `path`. +const entries = [{{ id: 'f1', name: 'big.bin', path: 'album', + size: {total_bytes}, added_at: 0 }}]; + +await M.downloadDirectory(transfers, transport, null, entries, 'album', {{ + setError: (m) => out.errors.push(m), +}}); + +out.limit = M.ZIP_MAX_BYTES; +console.log(JSON.stringify(out)); +""", encoding="utf-8") + + proc = subprocess.run(["node", str(script)], capture_output=True, text=True) + assert proc.returncode == 0, proc.stderr + return json.loads(proc.stdout) + + +def test_the_limit_is_512_mib(tmp_path): + """The number the refusal quotes is the number the module enforces.""" + assert _run(0, tmp_path)["limit"] == 512 * MIB + + +def test_an_oversized_folder_is_refused_before_anything_opens(tmp_path): + """ + One byte over. Nothing is started, and the person is told why — a silent + return would read as a dead button. + """ + result = _run(512 * MIB + 1, tmp_path) + assert result["started"] == 0, "no transfer may begin" + assert result["asked"] == 0, "nor may a save dialog have been put up first" + assert result["errors"] == ["group.zip_too_large"], ( + "the refusal must name its own key; t() falls back to the key with no " + "catalogue loaded, and test_locales.py holds the ten translations of it") + + +def test_a_folder_exactly_at_the_limit_still_downloads(tmp_path): + """The bound is inclusive: `> ZIP_MAX_BYTES`, not `>=`.""" + result = _run(512 * MIB, tmp_path) + assert result["errors"] == [] + assert result["started"] == 1 |