Two related additions for using omarchy-send beyond the focused TUI: Headless send (no TUI / no TTY) — for scripts, cron, SSH sessions: omarchy-send --to "<alias>" --message "<text>" [--send-pin N] [--wait 15s] Resolves the peer by alias over multicast (discovery only, not the HTTP receiver, so it co-exists with a running instance), sends, prints a one-line result, and exits non-zero on not-found / failure. New discovery.FindPeer/Snapshot and client.SendMessageSync (returns the error directly, incl. ErrPinRequired). Bugfix surfaced by the above: the official LocalSend client answers a message prepare-upload with HTTP 204 No Content (the text rides in the preview field, nothing to upload). prepareUpload only accepted 200, so message sends to official peers failed with "prepare-upload status 204" in BOTH the new headless path and the existing TUI. Now treats 204 as success (empty response). Desktop notifications: a running receiver raises a notify-send notification on an incoming message or file offer, so mako shows it on Omarchy/Hyprland even when the TUI isn't focused. New internal/notify (best-effort; self-disabling on headless boxes with no notify-send / session bus). Off-switch: --no-notify flag, cfg.NoNotify, and a Settings 'n' toggle wired to a live atomic gate via Controller.SetNotify so it takes effect without a restart. Tests: discovery/find_test, client/message_sync_test, client/prepare_204_test, app/events_test. Race-clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
70 lines
2 KiB
Go
70 lines
2 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
|
|
"omarchy-send/internal/protocol"
|
|
"omarchy-send/internal/server"
|
|
"omarchy-send/internal/transfer"
|
|
)
|
|
|
|
// An inbound message must drive the notify callback with the sender's name and
|
|
// the message text, so a backgrounded receiver surfaces it on the desktop.
|
|
func TestBridgeServerNotifiesOnMessage(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
accepts := make(chan server.AcceptRequest)
|
|
transfers := make(chan transfer.Event)
|
|
messages := make(chan server.ReceivedMessage, 1)
|
|
|
|
type call struct{ summary, body string }
|
|
got := make(chan call, 1)
|
|
notify := func(summary, body string) { got <- call{summary, body} }
|
|
|
|
BridgeServer(ctx, accepts, transfers, messages, func(tea.Msg) {}, notify)
|
|
|
|
messages <- server.ReceivedMessage{From: "Strong Onion", Text: "dinner's ready"}
|
|
|
|
select {
|
|
case c := <-got:
|
|
if c.summary != "Message from Strong Onion" {
|
|
t.Errorf("summary = %q", c.summary)
|
|
}
|
|
if c.body != "dinner's ready" {
|
|
t.Errorf("body = %q", c.body)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatal("notify was not called for an inbound message")
|
|
}
|
|
}
|
|
|
|
// incomingFilesBody names a single file and counts multiple ones.
|
|
func TestIncomingFilesBody(t *testing.T) {
|
|
one := server.AcceptRequest{Files: map[string]protocol.FileMetadata{
|
|
"a": {FileName: "photo.jpg"},
|
|
}}
|
|
if got := incomingFilesBody(one); got != "photo.jpg" {
|
|
t.Errorf("single file body = %q, want photo.jpg", got)
|
|
}
|
|
|
|
many := server.AcceptRequest{Files: map[string]protocol.FileMetadata{
|
|
"a": {FileName: "x"}, "b": {FileName: "y"}, "c": {FileName: "z"},
|
|
}}
|
|
if got := incomingFilesBody(many); got != "3 files" {
|
|
t.Errorf("multi file body = %q, want \"3 files\"", got)
|
|
}
|
|
}
|
|
|
|
func TestNonEmptyAlias(t *testing.T) {
|
|
if got := nonEmptyAlias(""); got != "a device" {
|
|
t.Errorf("empty alias = %q", got)
|
|
}
|
|
if got := nonEmptyAlias("Slate Starburst"); got != "Slate Starburst" {
|
|
t.Errorf("alias = %q", got)
|
|
}
|
|
}
|