aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_downloads.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/test_downloads.py')
-rw-r--r--packages/meshbay-hub/tests/test_downloads.py56
1 files changed, 56 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_downloads.py b/packages/meshbay-hub/tests/test_downloads.py
index cd0cded..41d61bf 100644
--- a/packages/meshbay-hub/tests/test_downloads.py
+++ b/packages/meshbay-hub/tests/test_downloads.py
@@ -108,3 +108,59 @@ def test_the_open_action_reads_the_file_back(tmp_path):
target = src[src.index("export async function openTarget"):]
assert "getFile()" in target and "window.open(" in target
assert "revokeObjectURL" in target, "the blob URL must not be leaked"
+
+
+# ── Streaming to disk without the File System Access API ────────────────────
+
+SW = STATIC / "sw.js"
+
+
+def test_the_worker_only_answers_its_own_urls():
+ """
+ It is registered at the root scope, so it sees every request the page makes.
+ Anything that is not a download of ours has to fall through untouched — a
+ service worker that answers more than it should is a cache bug waiting to
+ happen.
+ """
+ src = SW.read_text()
+ assert "startsWith(PREFIX)" in src
+ assert "self.location.origin" in src, "cross-origin requests must fall through"
+ # The API, not the word: the file explains in prose that it caches nothing.
+ for api in ("caches.open", "caches.match", "cache.put"):
+ assert api not in src, f"this worker must not cache anything ({api})"
+
+
+def test_the_download_is_announced_as_an_attachment():
+ src = SW.read_text()
+ assert "Content-Disposition" in src and "attachment" in src
+ assert "filename*=UTF-8''" in src, "a name with accents would be mangled"
+ assert "Content-Length" in src
+
+
+def test_a_length_is_only_promised_when_it_is_known(tmp_path):
+ """
+ An archive is assembled as it goes and is larger than the files in it.
+ Announcing the sum of their sizes would truncate the download at that mark.
+ """
+ src = SW.read_text()
+ assert "if (entry.size > 0)" in src
+
+ app = (STATIC / "app.js").read_text()
+ zip_call = app[app.index("const target = await _openDownloadTarget(suggested"):]
+ zip_call = zip_call[:zip_call.index(");") + 2]
+ assert zip_call.rstrip().endswith(", 0);"), (
+ "the zip download announces a Content-Length it will not match")
+
+
+def test_backpressure_is_real(tmp_path):
+ """
+ The point of the service worker path is not holding the file. A stream that
+ is transferred gives `writer.write()` something to wait on; posting chunks
+ to a port would queue them in memory and look identical from here.
+ """
+ src = DOWNLOADS.read_text()
+ fn = src[src.index("export async function openStreamedDownload"):]
+ assert "new TransformStream()" in fn
+ assert "[readable]" in fn, "the readable half must be transferred, not copied"
+ assert "writer.write(bytes)" in fn
+ assert "return null" in fn, "a browser that cannot transfer streams must say so"