aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests/test_transfers.py
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests/test_transfers.py')
-rw-r--r--packages/meshbay-hub/tests/test_transfers.py116
1 files changed, 116 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_transfers.py b/packages/meshbay-hub/tests/test_transfers.py
index e805afc..7e21409 100644
--- a/packages/meshbay-hub/tests/test_transfers.py
+++ b/packages/meshbay-hub/tests/test_transfers.py
@@ -435,3 +435,119 @@ def test_the_slot_is_asked_for_after_there_is_somewhere_to_write():
assert fn.index("_openDownloadTarget") < fn.index("openTransfer"), (
"downloadEntry asks for a transfer slot before it has anywhere to "
"write — the grant expires before the download can use it")
+
+
+# ── the row exists from the click ───────────────────────────────────────────
+
+def test_the_row_appears_before_the_target_is_open(tmp_path):
+ """
+ Opening a target is the slow part — the streamed path waits for the worker
+ twice, a Save As dialog waits for a person — and the row used to be created
+ only after it returned. Three clicks produced no panel at all, not even the
+ icon, and then several rows at once.
+ """
+ out = _run("""
+ const t = new TransferStore();
+ let release;
+ const opened = new Promise(r => { release = r; });
+ t.start({ kind: 'download', name: 'film.mkv', total: 10,
+ prepare: async () => { await opened; return { name: 'saved.mkv' }; },
+ run: async () => { say('ran'); } });
+ const shot = (when) => say(when + '=' + t.list().length + ':'
+ + t.list().map(i => i.status + '/' + i.name).join(','));
+ shot('click');
+ release();
+ await new Promise(r => setTimeout(r, 10));
+ shot('after');
+ """, tmp_path)
+ # Tagged, not indexed. An earlier version counted pushes by hand and was one
+ # out, which reads exactly like a failing assertion about the code.
+ seen = dict(line.split("=", 1) for line in out
+ if isinstance(line, str) and "=" in line)
+ assert {"click", "after"} <= set(seen), f"probe produced: {out}"
+ assert seen["click"] == "1:preparing/film.mkv", (
+ f"no row, or the wrong one, at the moment of the click: {seen['click']}")
+ assert seen["after"] == "1:done/saved.mkv", (
+ f"the row must keep the name it was saved under: {seen['after']}")
+
+
+def test_a_dismissed_dialog_leaves_nothing_behind(tmp_path):
+ """Dismissing a Save As dialog is not a failure and not a cancellation:
+ nothing was started, so nothing should be left on screen explaining it."""
+ out = _run("""
+ const t = new TransferStore();
+ t.start({ kind: 'download', name: 'film.mkv', total: 10,
+ prepare: async () => false,
+ run: async () => { say('ran'); } });
+ say('at click:', t.list().length);
+ await new Promise(r => setTimeout(r, 10));
+ say('after:', t.list().length, out.includes('ran'));
+ """, tmp_path)
+ assert out[1] == 1
+ assert out[3] == 0, "a dismissed dialog left a row behind"
+ assert out[4] is False
+
+
+def test_the_slot_is_only_asked_for_once_there_is_somewhere_to_write(tmp_path):
+ """
+ A granted slot must be taken up within the node's deadline, and opening a
+ target can outlast it. Asking first cost two of three downloads.
+ """
+ out = _run("""
+ const t = new TransferStore();
+ let release;
+ const opened = new Promise(r => { release = r; });
+ let asked = false;
+ t.start({ kind: 'download', name: 'f', total: 10,
+ prepare: async () => { await opened; return true; },
+ makeLease: () => { asked = true; return {
+ state: 'granted', ahead: 0, tr: 'x',
+ acquire: () => Promise.resolve(), release: () => {} }; },
+ run: async () => {} });
+ say('while preparing, asked?', asked);
+ release();
+ await new Promise(r => setTimeout(r, 10));
+ say('after preparing, asked?', asked, t.list()[0].status);
+ """, tmp_path)
+ assert out[1] is False, "the slot was taken before there was a target"
+ assert out[3] is True
+ assert out[4] == "done"
+
+
+def test_a_target_that_cannot_be_opened_fails_the_row_it_already_has(tmp_path):
+ """The refusal above the memory ceiling lands in the panel, on the row that
+ is already there, rather than in a console nobody opens."""
+ out = _run("""
+ const t = new TransferStore();
+ t.start({ kind: 'download', name: 'film.mkv', total: 10,
+ prepare: async () => { throw new Error('too large for memory'); },
+ run: async () => { say('ran'); } });
+ await new Promise(r => setTimeout(r, 10));
+ const it = t.list()[0];
+ say(it.status, it.error, out.includes('ran'));
+ """, tmp_path)
+ assert out[0] == "failed"
+ assert "too large" in out[1]
+ assert out[2] is False
+
+
+def test_a_transport_is_not_closed_under_a_preparing_transfer(tmp_path):
+ """It has no lease yet and has moved no bytes, but closing its transport
+ would strand it exactly like a queued one."""
+ out = _run("""
+ const t = new TransferStore();
+ let release;
+ const opened = new Promise(r => { release = r; });
+ let closed = false;
+ const transport = { close() { closed = true; } };
+ t.start({ kind: 'download', name: 'f', total: 10, transport,
+ prepare: async () => { await opened; return true; },
+ run: async () => {} });
+ t.releaseWhenIdle(transport);
+ say('closed while preparing:', closed);
+ release();
+ await new Promise(r => setTimeout(r, 10));
+ say('closed after:', closed);
+ """, tmp_path)
+ assert out[1] is False
+ assert out[3] is True