diff options
Diffstat (limited to 'packages/meshbay-hub/tests')
| -rw-r--r-- | packages/meshbay-hub/tests/test_transfers.py | 109 |
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 |