From 47583c5e405f3011a526df73cc9485c46598f214 Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Thu, 15 Oct 2026 01:35:09 +0200 Subject: feat(cli): chat — read a group's last messages, post one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chat GROUP prints the last ten messages, oldest first (-N, as head -N, or -n N for another count, paging back past the node's 200), and -m posts one (- reads it from stdin). The application's construction: every epoch key from chat_keys_req, a post sealed under the current epoch through the device subkey and signed with this machine's key on the node, the sender's name inside the envelope; nothing sent past the node's ciphertext bound. A stored message is read signature first, then key, then content, and one that fails is shown as unreadable, never dropped. Not done here: the Tier 2 roster check on the signing device. Co-Authored-By: Claude Opus 5.5 --- packages/meshbay-cli/tests/test_cli_actions.py | 45 ++++++++++++++++++++++++++ 1 file changed, 45 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 e06fb9c..5443d9b 100644 --- a/packages/meshbay-cli/tests/test_cli_actions.py +++ b/packages/meshbay-cli/tests/test_cli_actions.py @@ -510,6 +510,51 @@ def test_search_finds_across_groups_once_per_file(hub, monkeypatch, capsys): assert main("search", "beach") == 0 +def test_chat_reads_the_last_messages_and_posts(node, monkeypatch, capsys): + from meshbay_cli.commands import chat + + async def open_group(account, group): + return node.session() + monkeypatch.setattr(chat, "open_group", open_group) + + for i in range(1, 231): # more than one page of the node's + node.say(f"message {i}") + node.say("forged", forge=True) + node.say("two lines\nand an \x1b[31m escape") + node.chat_epochs[2] = b"\x09" * 32 + node.say("from an epoch this member was not given", epoch=2) + del node.chat_epochs[2] + + assert main("chat", "Family") == 0 + lines = capsys.readouterr().out.splitlines() + assert len(lines) == 11 and lines[0].endswith("bob-test: message 224") + assert lines[7].endswith("bob-test: [this message cannot be read: signature]") + assert lines[8].endswith("bob-test: two lines") + assert lines[9].strip() == "and an \\x1b[31m escape" # shown, never run + assert "cannot be read: no key for its epoch" in lines[10] + + assert main("chat", "-5", "Family") == 0 + assert capsys.readouterr().out.splitlines()[0].endswith("message 229") + assert main("--json", "chat", "Family", "-n", "300") == 0 # paged back + got = json.loads(capsys.readouterr().out) + assert len(got) == 233 and got[0]["text"] == "message 1" + assert got[230]["unreadable"] == "signature" + + assert main("chat", "Family", "-m", "hello from the terminal") == 0 + assert "Posted to Family@alice-test" in capsys.readouterr().err + monkeypatch.setattr("sys.stdin", io.StringIO("piped\n")) + assert main("chat", "Family", "-m", "-") == 0 + capsys.readouterr() + assert main("chat", "-2", "Family") == 0 + out = capsys.readouterr().out.splitlines() + assert out[0].endswith(f"{USERNAME}: hello from the terminal") + assert out[1].endswith(f"{USERNAME}: piped") + + assert main("chat", "Family", "-m", " ") == 1 + assert main("chat", "Family", "-m", "x" * 70_000) == 1 # over the node's bound + assert main("chat", "-0", "Family") == 1 + + def test_every_action_is_run_here(): """Runs last in this module: every action the parser knows was run above.""" assert RUN == set(cli.ACTIONS), f"never run: {set(cli.ACTIONS) - RUN}" -- cgit v1.2.3