aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/tests
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/tests')
-rw-r--r--packages/meshbay-hub/tests/test_transfers.py109
1 files changed, 109 insertions, 0 deletions
diff --git a/packages/meshbay-hub/tests/test_transfers.py b/packages/meshbay-hub/tests/test_transfers.py
index f27d4fd..d19a458 100644
--- a/packages/meshbay-hub/tests/test_transfers.py
+++ b/packages/meshbay-hub/tests/test_transfers.py
@@ -770,3 +770,112 @@ def test_an_upload_handed_a_lease_it_cannot_recreate_is_not_offered_pause(tmp_pa
t.cancel(id);
""", tmp_path)
assert out == ["status:running"]
+
+
+def test_pausing_one_transfer_leaves_the_others_alone(tmp_path):
+ """Reported: three downloads running, one upload paused, and the three
+ downloads lost their pause buttons.
+
+ The button is drawn from `pausable` and the status, so this asks the store
+ what it says about the other three at the moment one of them pauses.
+ """
+ out = _run(_lease_stub() + """
+ const t = new TransferStore();
+ const leases = [];
+ const mk = (kind, name) => t.start({
+ kind, name, total: 100, pausable: true,
+ makeLease: () => { const l = new L(); leases.push(l); return l; },
+ run: async ({ signal, from }) => {
+ for (let i = from || 0; i < 40; i++) {
+ await new Promise(r => setTimeout(r, 5));
+ if (signal.aborted) { const e = new Error('c'); e.name = 'AbortError'; throw e; }
+ if (signal.paused) {
+ signal.resumeFrom = i;
+ const e = new Error('p'); e.name = 'PausedError'; throw e;
+ }
+ }
+ },
+ });
+ mk('download', 'd1'); mk('download', 'd2'); mk('download', 'd3');
+ mk('upload', 'u1');
+ await new Promise(r => setTimeout(r, 5));
+ for (const l of leases) l.grant();
+ await new Promise(r => setTimeout(r, 20));
+ const up = t.list().find(i => i.kind === 'upload');
+ say('before:' + t.list().filter(
+ i => i.kind === 'download' && i.pausable && i.status === 'running').length);
+ t.pause(up.id);
+ await new Promise(r => setTimeout(r, 40));
+ const rows = t.list();
+ say('after:' + rows.filter(
+ i => i.kind === 'download' && i.pausable && i.status === 'running').length);
+ say('statuses:' + rows.map(i => i.kind[0] + ':' + i.status).join(','));
+ for (const r of rows) t.cancel(r.id);
+ """, tmp_path)
+ assert out[0] == "before:3"
+ assert out[1] == "after:3", (
+ f"pausing the upload changed the downloads — {out[2]}")
+
+
+def test_a_paused_transfer_is_not_filed_under_finished(tmp_path):
+ """"Finished" was defined by exclusion — everything that is not running,
+ queued or preparing — so it quietly swallowed `paused` the day pausing
+ shipped. A transfer somebody stopped on purpose then sat beside the ones
+ that are actually over, offering a resume button in the section of things
+ that cannot be resumed.
+
+ The three filters are lifted out of `app.js` and run, rather than described
+ here: a copy of them in this file would agree with a broken version by
+ construction.
+ """
+ src = (STATIC / "app.js").read_text()
+ start = src.index(" const running = items.filter(")
+ block = src[start:src.index("const active =", start)]
+
+ script = tmp_path / "groups.mjs"
+ script.write_text("""
+const items = [
+ { id: 1, status: 'running' },
+ { id: 2, status: 'queued' },
+ { id: 3, status: 'preparing' },
+ { id: 4, status: 'paused' },
+ { id: 5, status: 'done' },
+ { id: 6, status: 'failed' },
+ { id: 7, status: 'cancelled' },
+];
+""" + block + """
+const seen = { running, waiting, paused, finished };
+console.log(JSON.stringify(Object.fromEntries(
+ Object.entries(seen).map(([k, v]) => [k, v.map(i => i.id)]))));
+""")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+ assert proc.returncode == 0, proc.stderr
+ groups = json.loads(proc.stdout)
+
+ assert groups["paused"] == [4]
+ assert groups["finished"] == [5, 6, 7], (
+ f"paused landed in {groups['finished']}")
+ assert groups["running"] == [1] and groups["waiting"] == [2, 3]
+ # Every row appears exactly once: a state added later that lands in no group
+ # is a transfer the panel simply does not show.
+ placed = sum((groups[k] for k in groups), [])
+ assert sorted(placed) == [1, 2, 3, 4, 5, 6, 7]
+
+
+def test_a_paused_transfer_still_counts_as_active(tmp_path):
+ """The badge says how much is going on. A paused transfer is not over — the
+ person means to come back to it — so counting it as nothing would be a
+ panel that says "0" over work that is still there."""
+ src = (STATIC / "app.js").read_text()
+ start = src.index(" const running = items.filter(")
+ block = src[start:src.index("\n\n", src.index("const active =", start))]
+
+ script = tmp_path / "active.mjs"
+ script.write_text("""
+const items = [{ id: 1, status: 'paused' }, { id: 2, status: 'done' }];
+""" + block + """
+console.log(JSON.stringify({ active }));
+""")
+ proc = subprocess.run(["node", str(script)], capture_output=True, text=True)
+ assert proc.returncode == 0, proc.stderr
+ assert json.loads(proc.stdout)["active"] == 1