summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-cli/tests/test_chat_files.py
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-10-15 09:57:18 +0200
committerChristophe Besson <cbesson@gmail.com>2026-10-15 09:57:18 +0200
commit2f90f4b60cd50f7e9649daaf8e76a120e608cff6 (patch)
tree9d3724602959b05602f7fadb8a45bd26d0fc9b91 /packages/meshbay-cli/tests/test_chat_files.py
parent5044bdfdfffceceecc0d92a776983ad361079839 (diff)
downloadmeshbay-2f90f4b60cd50f7e9649daaf8e76a120e608cff6.tar.gz
feat(cli): /put and /get in the interactive chat
/put FILE sends one local file, relative to the current directory or absolute, into the chat's folder, then posts the application's attachment message naming it ({"text": "", "attachment": {filename, size, type}}), so the page shows it as its paperclip's. /get NAME saves a file of that folder, named from it, into the current directory, never over a file already there. The chat's folder is the page's rule: the operator's choice while its root is writable and present, else the first such root; a chat_directory_ack moves it. A line above the one written in shows the transfer's progress. Attachment messages, from the page or from /put, read as [file: name, size] in the room and in `chat`, and as an `attachment` field with --json. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-cli/tests/test_chat_files.py')
-rw-r--r--packages/meshbay-cli/tests/test_chat_files.py44
1 files changed, 44 insertions, 0 deletions
diff --git a/packages/meshbay-cli/tests/test_chat_files.py b/packages/meshbay-cli/tests/test_chat_files.py
new file mode 100644
index 0000000..4c83cba
--- /dev/null
+++ b/packages/meshbay-cli/tests/test_chat_files.py
@@ -0,0 +1,44 @@
+"""Files in the chat: where they go, and how a message names one."""
+
+import json
+
+from meshbay_cli.client.chat import Message, attachment_text, chat_folder, read_attachment
+from meshbay_cli.commands.chat import as_json, print_message
+
+ROOTS = {"music": {"name": "music", "writable": False},
+ "share": {"name": "share", "writable": True},
+ "usb": {"name": "usb", "writable": True, "available": False}}
+
+
+def test_the_chat_folder_is_the_operators_choice_while_it_can_be_written():
+ assert chat_folder({"chat_directory": "share/chat"}, ROOTS) == "share/chat"
+ assert chat_folder({"chat_directory": "/share/chat/"}, ROOTS) == "share/chat"
+ # Read-only, unplugged, gone, or never chosen: the first writable root.
+ for chosen in ("music/x", "usb/x", "gone/x", ""):
+ assert chat_folder({"chat_directory": chosen}, ROOTS) == "share"
+ assert chat_folder({}, {"music": ROOTS["music"]}) == ""
+
+
+def test_an_attachment_reads_back_as_the_application_writes_it():
+ text = attachment_text("IMG 1.JPG", 1234)
+ assert json.loads(text) == {"text": "", "attachment": {
+ "filename": "IMG 1.JPG", "size": 1234, "type": "image"}}
+ assert read_attachment(text) == ("", {"filename": "IMG 1.JPG", "size": 1234,
+ "type": "image"})
+ assert json.loads(attachment_text("clip.mkv", 1))["attachment"]["type"] == "video"
+ assert json.loads(attachment_text("README", 1))["attachment"]["type"] == "file"
+
+
+def test_text_that_only_looks_like_json_stays_text():
+ for text in ('{"a": 1}', "{not json", '{"attachment": "x"}',
+ '{"attachment": {"filename": ""}}', "[1]", "hello"):
+ assert read_attachment(text) == (text, None)
+
+
+def test_the_chat_action_prints_a_file_by_name_and_size(capsys):
+ m = Message(1, 1_700_000_000, "bob", "look", attachment={
+ "filename": "a.pdf", "size": 2048, "type": "file"})
+ print_message(m)
+ out = capsys.readouterr().out.splitlines()
+ assert out[0].endswith("bob: [file: a.pdf, 2.0 KB]") and out[1].strip() == "look"
+ assert as_json(m)["attachment"] == {"filename": "a.pdf", "size": 2048, "type": "file"}