diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-12 00:17:32 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-12 00:17:32 +0200 |
| commit | 14911aae21ec4975f7a805b91e3c4867cb41974b (patch) | |
| tree | 8c00230a88053905fd77938f3a9539efeb4c3094 /packages/meshbay-hub/tests | |
| parent | f6f107e02c7a4aa7326c9a82310a239327d81ed6 (diff) | |
| download | meshbay-14911aae21ec4975f7a805b91e3c4867cb41974b.tar.gz | |
feat: let the operator pause and resume a root's indexing (MNP 6.2)
A whole-root walk now checks its root before every file: removed or
ejected, it stops instead of reading the drive to the end; paused, it
finishes from the hash cache alone. A paused root is not watched, not
swept by reconcile and survives a restart (roster), its indexed files
stay served. Resume runs the reconcile sweep over the root, so entries
and their enrichment are kept.
Signed root_pause / root_resume over MNP, loopback routes, CLI
`root pause|resume`, a pause button on the indexing dock and
pause/resume in the roots table of Group settings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/tests')
| -rw-r--r-- | packages/meshbay-hub/tests/test_indexing_dock.py | 34 |
1 files changed, 33 insertions, 1 deletions
diff --git a/packages/meshbay-hub/tests/test_indexing_dock.py b/packages/meshbay-hub/tests/test_indexing_dock.py index e960950..1619b57 100644 --- a/packages/meshbay-hub/tests/test_indexing_dock.py +++ b/packages/meshbay-hub/tests/test_indexing_dock.py @@ -51,15 +51,18 @@ def _job(**over) -> dict: return job -def _frames(tmp_path, frames: list[tuple[int, list[dict]]], hide_at: int | None = None): +def _frames(tmp_path, frames: list[tuple[int, list[dict]]], hide_at: int | None = None, + pause_at: int | None = None): """Feed dockRows a sequence of (time ms, jobs) and return each frame's rows.""" return _run(tmp_path, f""" const frames = {json.dumps(frames)}; const hideAt = {json.dumps(hide_at)}; + const pauseAt = {json.dumps(pause_at)}; let memory = {{}}; const out = []; for (const [now, jobs] of frames) {{ if (hideAt === now) memory = m.hideRow(memory, 'g1'); + if (pauseAt === now) memory = m.pauseRow(memory, 'g1'); const r = m.dockRows(jobs, memory, now); memory = r.memory; out.push(r.rows.map((row) => [row.groupId, row.state])); @@ -120,6 +123,24 @@ def test_a_hidden_row_stays_hidden_until_the_group_is_idle(tmp_path): # ── Where it comes from ───────────────────────────────────────────────────── @needs_node +def test_a_paused_walk_leaves_at_once_and_is_not_called_finished(tmp_path): + """The node takes a moment to stop: its next reports still say scanning.""" + idle = _job(scanning=False, kind="") + assert _frames(tmp_path, [ + (0, [_job()]), (1000, [_job()]), (2000, [_job()]), (3000, [idle]), + ], pause_at=1000) == [[["g1", "running"]], [], [], []] + + +@needs_node +def test_pausing_one_root_does_not_hide_the_next_one_queued(tmp_path): + first = _job(root="results", queued=["archive"]) + second = _job(root="archive") + assert _frames(tmp_path, [ + (0, [first]), (1000, [first]), (2000, [second]), + ], pause_at=1000) == [[["g1", "running"]], [], [["g1", "running"]]] + + +@needs_node def test_the_loopback_answer_wins_and_the_hub_names_the_group(tmp_path): out = _run(tmp_path, """ const local = { g1: m.fromLoopback({ group_id: 'g1', group_name: 'node name', @@ -173,6 +194,17 @@ def test_only_an_operator_connection_feeds_the_dock(): "a member's connection must keep the sidebar dot only") +def test_the_pause_button_reaches_the_node_by_either_door(): + """The local node over loopback; any other through the page's connection, + signed by its operator — the dock holds no transport of its own.""" + dock = DOCK.read_text(encoding="utf-8") + assert "node.op('pauseRoot'" in dock + page = GROUP_PAGE.read_text(encoding="utf-8") + assert "transport.pauseRoot(groupId, rootName, transport.signFn)" in page + for at in [m.start() for m in re.finditer(r"reportIndexPush\(groupId, (?!null)", page)]: + assert "pauseFromDock" in page[at:at + 140] + + def test_leaving_a_group_clears_its_row(): page = GROUP_PAGE.read_text(encoding="utf-8") cleanup = page[page.index(" return () => {\n cancelled = true;"):] |