omarchy-send/internal/client/message_test.go
28allday 7ab3f905ee Send and read plain-text messages
Adds LocalSend-compatible text messaging alongside file transfer.

Wire format (matches the LocalSend app): a message is a single "file" with
fileType "text/plain" whose content rides in the prepare-upload `preview`
field. The receiver returns an empty token set, so nothing is uploaded — the
text is read straight from the preview.

- client: SendMessage builds that single-file prepare-upload (no body upload).
- server: detect a message (one text file with non-empty preview), surface it
  on a new Messages() channel instead of saving a file, and respond with an
  empty file set. Messages bypass the accept prompt (auto-received); the PIN
  gate still applies.
- app: bridge the server's messages channel to the TUI as MessageMsg.
- tui: a new Messages tab lists received messages (enter to read full, d to
  delete); press `m` on a device to compose and send one. Incoming messages
  show a footer notice.

Tests: end-to-end send→receive (text intact, sender preserved, nothing written
to disk) and unit coverage of the message-detection rule.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 21:40:40 +01:00

54 lines
1.6 KiB
Go

package client
import (
"context"
"os"
"testing"
"time"
"omarchy-send/internal/discovery"
"omarchy-send/internal/protocol"
"omarchy-send/internal/server"
)
// A sent message must arrive on the receiver's Messages channel with its text
// and sender intact, and must NOT be written to disk as a file.
func TestSendMessageEndToEnd(t *testing.T) {
recvDir := t.TempDir()
recvInfo := protocol.DeviceInfo{
Alias: "recv", Version: protocol.ProtocolVersion, Port: 53990, Protocol: "http",
}
// AutoAccept is false on purpose: messages must bypass the accept prompt.
srv := server.New(server.Options{Info: recvInfo, ReceiveDir: recvDir, AutoAccept: false})
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
if err := srv.Start(ctx); err != nil {
t.Fatalf("server start: %v", err)
}
go func() {
for range srv.Transfers() {
}
}()
time.Sleep(50 * time.Millisecond)
sender := New(protocol.DeviceInfo{Alias: "sender", Fingerprint: "snd1", Version: "2.1", Protocol: "http"})
peer := discovery.Peer{Info: recvInfo, IP: "127.0.0.1"}
sender.SendMessage(peer, "hello over the wire\nsecond line")
select {
case m := <-srv.Messages():
if m.Text != "hello over the wire\nsecond line" {
t.Fatalf("message text = %q", m.Text)
}
if m.From != "sender" {
t.Errorf("message from = %q, want \"sender\"", m.From)
}
case <-time.After(3 * time.Second):
t.Fatal("timed out waiting for the message")
}
// A message is not a file: nothing should land in the receive dir.
if entries, _ := os.ReadDir(recvDir); len(entries) != 0 {
t.Fatalf("message should not be saved; found %d entries in receive dir", len(entries))
}
}