From 988a0eecd522431980174910db28e45a63ff2710 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Thu, 15 Oct 2026 12:32:24 +0200 Subject: feat(cli): account, rm -r and mkdir, Tab completion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit account shows the account and does what the sign-up form and the Profile page do: register (the passphrase floor and the hub's proof of work checked here, the recovery key printed once), verify, change the passphrase, reset a forgotten one, change the e-mail address, sign out everywhere, delete. A new passphrase carries the copy of the account's keys each node keeps for browsers first — opened under the old M, the same keys sealed under the new one — and only then tells the hub; a refusal sets the copies back. A reset restores them from the recovery copy. The connections that do it never make keys nor ask for a device approval. This machine keeps the M it changed away from, so a node out of reach is carried at its next connection; a copy under an M it does not know is no longer rewritten, which locked the browsers out after a passphrase changed elsewhere. rm -r removes a folder and all it holds, from the node's operator key: the files, then the folders deepest first, one confirmation that counts them. mkdir makes folders, --parents the missing ones (not -p, the passphrase). completion bash|powershell prints the script that makes Tab complete actions, verbs, options and the paths of the groups this machine is signed in to, cached two minutes; the deb and rpm packages install the bash one. Co-Authored-By: Claude Opus 5.5 --- packages/meshbay-cli/tests/test_cli_actions.py | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) (limited to 'packages/meshbay-cli/tests/test_cli_actions.py') diff --git a/packages/meshbay-cli/tests/test_cli_actions.py b/packages/meshbay-cli/tests/test_cli_actions.py index bcbe0c4..006c183 100644 --- a/packages/meshbay-cli/tests/test_cli_actions.py +++ b/packages/meshbay-cli/tests/test_cli_actions.py @@ -205,6 +205,25 @@ def test_a_usage_error_is_exit_1_not_the_sign_in_code(capsys): +def test_account_shows_the_account(hub, capsys): + assert main("--hub", "https://hub.test", "-u", USERNAME, "--password", PASSPHRASE, + "login") == 0 + capsys.readouterr() + assert main("account") == 0 + out = capsys.readouterr().out + assert f"Account: {USERNAME}" in out and "https://hub.test" in out + assert main("--json", "account", "show") == 0 + assert json.loads(capsys.readouterr().out)["username"] == USERNAME + + +def test_completion_prints_each_shells_script(capsys): + assert main("completion", "bash") == 0 + assert "complete -F _meshbay_cli meshbay-cli" in capsys.readouterr().out + assert main("completion", "powershell") == 0 + assert "Register-ArgumentCompleter -Native" in capsys.readouterr().out + assert main("completion", "fish") == 1 + + def test_help_prints_what_help_prints(capsys): assert cli.main(["--help"]) == 0 whole = capsys.readouterr().out @@ -472,6 +491,65 @@ def test_rm_deletes_only_what_this_account_may_and_asks_first(node, monkeypatch, assert not node.files +def test_rm_r_removes_a_folder_and_all_it_holds_from_the_operator_key(node, capsys): + from fake_hub import USER_ID + node.add("share/trip/day 1", "a.jpg", b"A" * 2000, uploader_id=USER_ID) + node.add("share/trip/day 2", "b.jpg", b"B" * 1000, uploader_id="someone-else") + node.add("share/trip", "notes.txt", b"n", uploader_id=USER_ID) + node.dirs |= {"share/trip/empty", "share/kept"} + kept = node.add("share/kept", "c.txt", b"c", uploader_id=USER_ID) + + # Not the operator: refused whole, nothing deleted, not even its own files. + assert main("rm", "-r", "--yes", "Family/share/trip") == 8 + assert "operator" in capsys.readouterr().err + assert len(node.files) == 4 + # A root is never removed. + node.role = "operator" + assert main("rm", "-r", "--yes", "Family/share") == 8 + assert "root" in capsys.readouterr().err + + assert main("--json", "rm", "-r", "--yes", "Family/share/trip") == 0 + out = json.loads(capsys.readouterr().out) + assert len(out["deleted"]) == 3 and out["failed"] == [] + assert out["folders_removed"][-1] == "Family@alice-test/share/trip" # deepest first + assert {"Family@alice-test/share/trip/empty", "Family@alice-test/share/trip/day 1", + "Family@alice-test/share/trip/day 2"} <= set(out["folders_removed"]) + assert set(node.files) == {kept["id"]} + assert not any(d == "share/trip" or d.startswith("share/trip/") for d in node.dirs) + + +def test_rm_r_says_what_it_will_delete_before_it_asks(node, monkeypatch, capsys): + node.role = "operator" + for i in range(12): + node.add("share/big", f"f{i}.bin", b"x" * 1024) + monkeypatch.setattr("sys.stdin", _Terminal("n\n")) + assert main("rm", "-r", "Family/share/big") == 1 + asked = capsys.readouterr().err + assert "Delete 12 files (12.0 KB) and 1 folder" in asked + assert "Family@alice-test/share/big/" in asked + assert len(node.files) == 12 + + +def test_mkdir_makes_folders_where_the_root_accepts_them(node, capsys): + assert main("mkdir", "Family/share/2026") == 0 + assert "share/2026" in node.dirs + assert main("mkdir", "Family/share/2026") == 8 # already there + assert main("mkdir", "Family/share/a/b/c") == 3 # no parent + assert "--parents makes it too" in capsys.readouterr().err + assert main("--json", "mkdir", "--parents", "Family/share/a/b/c", "Family/share/2026") == 0 + assert json.loads(capsys.readouterr().out)["made"] == [ + "Family@alice-test/share/a", "Family@alice-test/share/a/b", + "Family@alice-test/share/a/b/c"] + node.add("share", "Notes.txt", b"n") + assert main("mkdir", "Family/share/notes.TXT") == 8 # a file, without case + assert "(a file)" in capsys.readouterr().err + assert main("mkdir", "Family/share/bad\x01name") == 8 + assert main("mkdir", "Family/share") == 1 # a root is not made here + node.roots[0]["writable"] = False + assert main("mkdir", "Family/share/x") == 8 + assert "read-only" in capsys.readouterr().err + + def test_search_finds_across_groups_once_per_file(hub, monkeypatch, capsys): from fake_node import FakeNode from meshbay_cli.client.session import DeviceLinkPending -- cgit v1.2.3