client_test.go

  1package daemonclient
  2
  3import (
  4	"encoding/json"
  5	"net"
  6	"testing"
  7
  8	"github.com/floatpane/matcha/daemonrpc"
  9)
 10
 11func mockDaemon(t *testing.T) (*Client, *daemonrpc.Conn) {
 12	t.Helper()
 13	serverConn, clientConn := net.Pipe()
 14
 15	server := daemonrpc.NewConn(serverConn)
 16	client := &Client{
 17		conn:    daemonrpc.NewConn(clientConn),
 18		pending: make(map[uint64]chan *daemonrpc.Response),
 19		events:  make(chan *daemonrpc.Event, 64),
 20		done:    make(chan struct{}),
 21	}
 22	go client.readLoop()
 23
 24	return client, server
 25}
 26
 27func TestClient_Ping(t *testing.T) {
 28	client, server := mockDaemon(t)
 29	defer client.Close()
 30	defer server.Close()
 31
 32	// Mock server: respond to Ping.
 33	go func() {
 34		msg, err := server.ReceiveMessage()
 35		if err != nil {
 36			t.Error(err)
 37			return
 38		}
 39		if msg.Request.Method != daemonrpc.MethodPing {
 40			t.Errorf("method = %q, want Ping", msg.Request.Method)
 41		}
 42		server.SendResponse(msg.Request.ID, daemonrpc.PingResult{Pong: true})
 43	}()
 44
 45	if err := client.Ping(); err != nil {
 46		t.Fatal(err)
 47	}
 48}
 49
 50func TestClient_Status(t *testing.T) {
 51	client, server := mockDaemon(t)
 52	defer client.Close()
 53	defer server.Close()
 54
 55	go func() {
 56		msg, _ := server.ReceiveMessage()
 57		server.SendResponse(msg.Request.ID, daemonrpc.StatusResult{
 58			Running:  true,
 59			Uptime:   120,
 60			Accounts: []string{"alice@example.com"},
 61			PID:      12345,
 62		})
 63	}()
 64
 65	status, err := client.Status()
 66	if err != nil {
 67		t.Fatal(err)
 68	}
 69	if !status.Running {
 70		t.Error("expected running=true")
 71	}
 72	if status.PID != 12345 {
 73		t.Errorf("PID = %d, want 12345", status.PID)
 74	}
 75	if len(status.Accounts) != 1 || status.Accounts[0] != "alice@example.com" {
 76		t.Errorf("accounts = %v, want [alice@example.com]", status.Accounts)
 77	}
 78}
 79
 80func TestClient_CallError(t *testing.T) {
 81	client, server := mockDaemon(t)
 82	defer client.Close()
 83	defer server.Close()
 84
 85	go func() {
 86		msg, _ := server.ReceiveMessage()
 87		server.SendError(msg.Request.ID, daemonrpc.ErrCodeNotFound, "method not found")
 88	}()
 89
 90	var result daemonrpc.PingResult
 91	err := client.Call("NonExistent", nil, &result)
 92	if err == nil {
 93		t.Fatal("expected error")
 94	}
 95	if err.Error() != "method not found" {
 96		t.Errorf("error = %q, want 'method not found'", err.Error())
 97	}
 98}
 99
100func TestClient_Events(t *testing.T) {
101	client, server := mockDaemon(t)
102	defer client.Close()
103	defer server.Close()
104
105	// Server pushes an event.
106	go func() {
107		server.SendEvent(daemonrpc.EventNewMail, daemonrpc.NewMailEvent{
108			AccountID: "acc1",
109			Folder:    "INBOX",
110		})
111	}()
112
113	ev := <-client.Events()
114	if ev.Type != daemonrpc.EventNewMail {
115		t.Errorf("type = %q, want NewMail", ev.Type)
116	}
117
118	var data daemonrpc.NewMailEvent
119	if err := json.Unmarshal(ev.Data, &data); err != nil {
120		t.Fatal(err)
121	}
122	if data.AccountID != "acc1" {
123		t.Errorf("account_id = %q, want acc1", data.AccountID)
124	}
125}
126
127func TestClient_ConcurrentCalls(t *testing.T) {
128	client, server := mockDaemon(t)
129	defer client.Close()
130	defer server.Close()
131
132	// Server handles two requests.
133	go func() {
134		for i := 0; i < 2; i++ {
135			msg, err := server.ReceiveMessage()
136			if err != nil {
137				return
138			}
139			server.SendResponse(msg.Request.ID, daemonrpc.PingResult{Pong: true})
140		}
141	}()
142
143	errs := make(chan error, 2)
144	for i := 0; i < 2; i++ {
145		go func() {
146			errs <- client.Ping()
147		}()
148	}
149
150	for i := 0; i < 2; i++ {
151		if err := <-errs; err != nil {
152			t.Errorf("call %d failed: %v", i, err)
153		}
154	}
155}