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}