github.com/mailgun/holster/v4@v4.20.0/election/rpc_test.go (about) 1 package election_test 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "fmt" 8 "io" 9 "net/http" 10 "net/http/httptest" 11 "testing" 12 13 "github.com/mailgun/holster/v4/election" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 func TestRPCRequest(t *testing.T) { 19 for _, tt := range []struct { 20 name string 21 in election.RPCRequest 22 out string 23 }{ 24 { 25 name: "heartbeat", 26 in: election.RPCRequest{ 27 RPC: election.HeartBeatRPC, 28 Request: election.HeartBeatReq{ 29 From: "node1", 30 Term: 1, 31 }, 32 }, 33 out: `{"rpc":"heartbeat","request":{"from":"node1","term":1}}`, 34 }, 35 { 36 name: "vote", 37 in: election.RPCRequest{ 38 RPC: election.VoteRPC, 39 Request: election.VoteReq{ 40 Candidate: "node1", 41 Term: 1, 42 }, 43 }, 44 out: `{"rpc":"vote","request":{"candidate":"node1","term":1}}`, 45 }, 46 { 47 name: "reset", 48 in: election.RPCRequest{ 49 RPC: election.ResetElectionRPC, 50 Request: election.ResetElectionReq{}, 51 }, 52 out: `{"rpc":"reset-election","request":{}}`, 53 }, 54 { 55 name: "resign", 56 in: election.RPCRequest{ 57 RPC: election.ResignRPC, 58 Request: election.ResignReq{}, 59 }, 60 out: `{"rpc":"resign","request":{}}`, 61 }, 62 { 63 name: "set-peers", 64 in: election.RPCRequest{ 65 RPC: election.SetPeersRPC, 66 Request: election.SetPeersReq{Peers: []string{"n0", "n1"}}, 67 }, 68 out: `{"rpc":"set-peers","request":{"peers":["n0","n1"]}}`, 69 }, 70 { 71 name: "get-state", 72 in: election.RPCRequest{ 73 RPC: election.GetStateRPC, 74 Request: election.GetStateReq{}, 75 }, 76 out: `{"rpc":"get-state","request":{}}`, 77 }, 78 } { 79 t.Run(tt.name, func(t *testing.T) { 80 b, err := json.Marshal(tt.in) 81 require.NoError(t, err) 82 assert.Equal(t, tt.out, string(b)) 83 84 var in election.RPCRequest 85 err = json.Unmarshal(b, &in) 86 require.NoError(t, err) 87 assert.Equal(t, tt.in, in) 88 }) 89 } 90 91 } 92 93 func TestRPCResponse(t *testing.T) { 94 for _, tt := range []struct { 95 name string 96 in election.RPCResponse 97 out string 98 }{ 99 { 100 name: "heartbeat", 101 in: election.RPCResponse{ 102 RPC: election.HeartBeatRPC, 103 Response: election.HeartBeatResp{ 104 From: "node1", 105 Term: 1, 106 }, 107 }, 108 out: `{"rpc":"heartbeat","response":{"from":"node1","term":1}}`, 109 }, 110 { 111 name: "vote", 112 in: election.RPCResponse{ 113 RPC: election.VoteRPC, 114 Response: election.VoteResp{ 115 Candidate: "node1", 116 Term: 1, 117 Granted: true, 118 }, 119 }, 120 out: `{"rpc":"vote","response":{"candidate":"node1","term":1,"granted":true}}`, 121 }, 122 { 123 name: "reset", 124 in: election.RPCResponse{ 125 RPC: election.ResetElectionRPC, 126 Response: election.ResetElectionResp{}, 127 }, 128 out: `{"rpc":"reset-election","response":{}}`, 129 }, 130 { 131 name: "resign", 132 in: election.RPCResponse{ 133 RPC: election.ResignRPC, 134 Response: election.ResignResp{ 135 Success: true, 136 }, 137 }, 138 out: `{"rpc":"resign","response":{"success":true}}`, 139 }, 140 { 141 name: "set-peers", 142 in: election.RPCResponse{ 143 RPC: election.SetPeersRPC, 144 Response: election.SetPeersResp{}, 145 }, 146 out: `{"rpc":"set-peers","response":{}}`, 147 }, 148 { 149 name: "get-state", 150 in: election.RPCResponse{ 151 RPC: election.GetStateRPC, 152 Response: election.GetStateResp{ 153 Leader: "n0", 154 Peers: []string{"n0", "n1"}, 155 State: "follower", 156 }, 157 }, 158 out: `{"rpc":"get-state","response":{"leader":"n0","state":"follower","peers":["n0","n1"]}}`, 159 }, 160 } { 161 t.Run(tt.name, func(t *testing.T) { 162 b, err := json.Marshal(tt.in) 163 require.NoError(t, err) 164 assert.Equal(t, tt.out, string(b)) 165 166 var in election.RPCResponse 167 err = json.Unmarshal(b, &in) 168 require.NoError(t, err) 169 assert.Equal(t, tt.in, in) 170 171 }) 172 } 173 } 174 175 func TestHTTPServer(t *testing.T) { 176 ctx := context.Background() 177 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 178 var in election.RPCRequest 179 180 b, err := io.ReadAll(r.Body) 181 require.NoError(t, err) 182 require.NoError(t, json.Unmarshal(b, &in)) 183 var resp election.RPCResponse 184 185 switch in.RPC { 186 case election.HeartBeatRPC: 187 resp = election.RPCResponse{ 188 RPC: election.HeartBeatRPC, 189 Response: election.HeartBeatResp{ 190 From: "node1", 191 Term: 10, 192 }, 193 } 194 default: 195 resp = election.RPCResponse{ 196 Error: fmt.Sprintf("invalid rpc request '%s'", in.RPC), 197 } 198 } 199 out, err := json.Marshal(resp) 200 require.NoError(t, err) 201 _, err = w.Write(out) 202 require.NoError(t, err) 203 })) 204 defer ts.Close() 205 206 // Marshall our request 207 b, err := json.Marshal(election.RPCRequest{ 208 RPC: election.HeartBeatRPC, 209 Request: election.HeartBeatReq{ 210 Term: 10, 211 From: "node10", 212 }, 213 }) 214 require.NoError(t, err) 215 216 // Send the request to the server 217 req, err := http.NewRequestWithContext(ctx, http.MethodPost, ts.URL, bytes.NewBuffer(b)) 218 require.NoError(t, err) 219 resp, err := http.DefaultClient.Do(req) 220 require.NoError(t, err) 221 defer func() { 222 err := resp.Body.Close() 223 require.NoError(t, err) 224 }() 225 226 // Unmarshall the response 227 var rpcResp election.RPCResponse 228 b, err = io.ReadAll(resp.Body) 229 require.NoError(t, err) 230 err = json.Unmarshal(b, &rpcResp) 231 require.NoError(t, err) 232 233 // Should have the response we expect 234 hb := rpcResp.Response.(election.HeartBeatResp) 235 assert.Equal(t, uint64(10), hb.Term) 236 assert.Equal(t, "node1", hb.From) 237 238 // Send an unknown rpc request to the server 239 req, err = http.NewRequestWithContext(ctx, http.MethodPost, ts.URL, bytes.NewBuffer([]byte(`{"rpc":"unknown"}`))) 240 require.NoError(t, err) 241 resp, err = http.DefaultClient.Do(req) 242 require.NoError(t, err) 243 defer func() { 244 err := resp.Body.Close() 245 require.NoError(t, err) 246 }() 247 248 // Unmarshall the response 249 b, err = io.ReadAll(resp.Body) 250 require.NoError(t, err) 251 err = json.Unmarshal(b, &rpcResp) 252 require.NoError(t, err) 253 254 // Should have the response we expect 255 assert.Equal(t, "invalid rpc request 'unknown'", rpcResp.Error) 256 }