github.com/core-coin/go-core/v2@v2.1.9/node/api_test.go (about) 1 // Copyright 2020 by the Authors 2 // This file is part of the go-core library. 3 // 4 // The go-core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package node 18 19 import ( 20 "bytes" 21 "io" 22 "net" 23 "net/http" 24 "net/url" 25 "strings" 26 "testing" 27 28 "github.com/stretchr/testify/assert" 29 30 "github.com/core-coin/go-core/v2/rpc" 31 ) 32 33 // This test uses the admin_startRPC and admin_startWS APIs, 34 // checking whether the HTTP server is started correctly. 35 func TestStartRPC(t *testing.T) { 36 type test struct { 37 name string 38 cfg Config 39 fn func(*testing.T, *Node, *privateAdminAPI) 40 41 // Checks. These run after the node is configured and all API calls have been made. 42 wantReachable bool // whether the HTTP server should be reachable at all 43 wantHandlers bool // whether RegisterHandler handlers should be accessible 44 wantRPC bool // whether JSON-RPC/HTTP should be accessible 45 wantWS bool // whether JSON-RPC/WS should be accessible 46 } 47 48 tests := []test{ 49 { 50 name: "all off", 51 cfg: Config{}, 52 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 53 }, 54 wantReachable: false, 55 wantHandlers: false, 56 wantRPC: false, 57 wantWS: false, 58 }, 59 { 60 name: "rpc enabled through config", 61 cfg: Config{HTTPHost: "127.0.0.1"}, 62 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 63 }, 64 wantReachable: true, 65 wantHandlers: true, 66 wantRPC: true, 67 wantWS: false, 68 }, 69 { 70 name: "rpc enabled through API", 71 cfg: Config{}, 72 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 73 _, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil) 74 assert.NoError(t, err) 75 }, 76 wantReachable: true, 77 wantHandlers: true, 78 wantRPC: true, 79 wantWS: false, 80 }, 81 { 82 name: "rpc start again after failure", 83 cfg: Config{}, 84 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 85 // Listen on a random port. 86 listener, err := net.Listen("tcp", "127.0.0.1:0") 87 if err != nil { 88 t.Fatal("can't listen:", err) 89 } 90 defer listener.Close() 91 port := listener.Addr().(*net.TCPAddr).Port 92 93 // Now try to start RPC on that port. This should fail. 94 _, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil) 95 if err == nil { 96 t.Fatal("StartRPC should have failed on port", port) 97 } 98 99 // Try again after unblocking the port. It should work this time. 100 listener.Close() 101 _, err = api.StartRPC(sp("127.0.0.1"), ip(port), nil, nil, nil) 102 assert.NoError(t, err) 103 }, 104 wantReachable: true, 105 wantHandlers: true, 106 wantRPC: true, 107 wantWS: false, 108 }, 109 { 110 name: "rpc stopped through API", 111 cfg: Config{HTTPHost: "127.0.0.1"}, 112 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 113 _, err := api.StopRPC() 114 assert.NoError(t, err) 115 }, 116 wantReachable: false, 117 wantHandlers: false, 118 wantRPC: false, 119 wantWS: false, 120 }, 121 { 122 name: "rpc stopped twice", 123 cfg: Config{HTTPHost: "127.0.0.1"}, 124 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 125 _, err := api.StopRPC() 126 assert.NoError(t, err) 127 128 _, err = api.StopRPC() 129 assert.NoError(t, err) 130 }, 131 wantReachable: false, 132 wantHandlers: false, 133 wantRPC: false, 134 wantWS: false, 135 }, 136 { 137 name: "ws enabled through config", 138 cfg: Config{WSHost: "127.0.0.1"}, 139 wantReachable: true, 140 wantHandlers: false, 141 wantRPC: false, 142 wantWS: true, 143 }, 144 { 145 name: "ws enabled through API", 146 cfg: Config{}, 147 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 148 _, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil) 149 assert.NoError(t, err) 150 }, 151 wantReachable: true, 152 wantHandlers: false, 153 wantRPC: false, 154 wantWS: true, 155 }, 156 { 157 name: "ws stopped through API", 158 cfg: Config{WSHost: "127.0.0.1"}, 159 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 160 _, err := api.StopWS() 161 assert.NoError(t, err) 162 }, 163 wantReachable: false, 164 wantHandlers: false, 165 wantRPC: false, 166 wantWS: false, 167 }, 168 { 169 name: "ws stopped twice", 170 cfg: Config{WSHost: "127.0.0.1"}, 171 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 172 _, err := api.StopWS() 173 assert.NoError(t, err) 174 175 _, err = api.StopWS() 176 assert.NoError(t, err) 177 }, 178 wantReachable: false, 179 wantHandlers: false, 180 wantRPC: false, 181 wantWS: false, 182 }, 183 { 184 name: "ws enabled after RPC", 185 cfg: Config{HTTPHost: "127.0.0.1"}, 186 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 187 wsport := n.http.port 188 _, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil) 189 assert.NoError(t, err) 190 }, 191 wantReachable: true, 192 wantHandlers: true, 193 wantRPC: true, 194 wantWS: true, 195 }, 196 { 197 name: "ws enabled after RPC then stopped", 198 cfg: Config{HTTPHost: "127.0.0.1"}, 199 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 200 wsport := n.http.port 201 _, err := api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil) 202 assert.NoError(t, err) 203 204 _, err = api.StopWS() 205 assert.NoError(t, err) 206 }, 207 wantReachable: true, 208 wantHandlers: true, 209 wantRPC: true, 210 wantWS: false, 211 }, 212 { 213 name: "rpc stopped with ws enabled", 214 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 215 _, err := api.StartRPC(sp("127.0.0.1"), ip(0), nil, nil, nil) 216 assert.NoError(t, err) 217 218 wsport := n.http.port 219 _, err = api.StartWS(sp("127.0.0.1"), ip(wsport), nil, nil) 220 assert.NoError(t, err) 221 222 _, err = api.StopRPC() 223 assert.NoError(t, err) 224 }, 225 wantReachable: false, 226 wantHandlers: false, 227 wantRPC: false, 228 wantWS: false, 229 }, 230 { 231 name: "rpc enabled after ws", 232 fn: func(t *testing.T, n *Node, api *privateAdminAPI) { 233 _, err := api.StartWS(sp("127.0.0.1"), ip(0), nil, nil) 234 assert.NoError(t, err) 235 236 wsport := n.http.port 237 _, err = api.StartRPC(sp("127.0.0.1"), ip(wsport), nil, nil, nil) 238 assert.NoError(t, err) 239 }, 240 wantReachable: true, 241 wantHandlers: true, 242 wantRPC: true, 243 wantWS: true, 244 }, 245 } 246 247 for _, test := range tests { 248 test := test 249 t.Run(test.name, func(t *testing.T) { 250 t.Parallel() 251 // Apply some sane defaults. 252 config := test.cfg 253 // config.Logger = testlog.Logger(t, log.LvlDebug) 254 config.P2P.NoDiscovery = true 255 256 // Create Node. 257 stack, err := New(&config) 258 if err != nil { 259 t.Fatal("can't create node:", err) 260 } 261 defer stack.Close() 262 263 // Register the test handler. 264 stack.RegisterHandler("test", "/test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 265 w.Write([]byte("OK")) 266 })) 267 268 if err := stack.Start(); err != nil { 269 t.Fatal("can't start node:", err) 270 } 271 272 // Run the API call hook. 273 if test.fn != nil { 274 test.fn(t, stack, &privateAdminAPI{stack}) 275 } 276 277 // Check if the HTTP endpoints are available. 278 baseURL := stack.HTTPEndpoint() 279 reachable := checkReachable(baseURL) 280 handlersAvailable := checkBodyOK(baseURL + "/test") 281 rpcAvailable := checkRPC(baseURL) 282 wsAvailable := checkRPC(strings.Replace(baseURL, "http://", "ws://", 1)) 283 if reachable != test.wantReachable { 284 t.Errorf("HTTP server is %sreachable, want it %sreachable", not(reachable), not(test.wantReachable)) 285 } 286 if handlersAvailable != test.wantHandlers { 287 t.Errorf("RegisterHandler handlers %savailable, want them %savailable", not(handlersAvailable), not(test.wantHandlers)) 288 } 289 if rpcAvailable != test.wantRPC { 290 t.Errorf("HTTP RPC %savailable, want it %savailable", not(rpcAvailable), not(test.wantRPC)) 291 } 292 if wsAvailable != test.wantWS { 293 t.Errorf("WS RPC %savailable, want it %savailable", not(wsAvailable), not(test.wantWS)) 294 } 295 }) 296 } 297 } 298 299 // checkReachable checks if the TCP endpoint in rawurl is open. 300 func checkReachable(rawurl string) bool { 301 u, err := url.Parse(rawurl) 302 if err != nil { 303 panic(err) 304 } 305 conn, err := net.Dial("tcp", u.Host) 306 if err != nil { 307 return false 308 } 309 conn.Close() 310 return true 311 } 312 313 // checkBodyOK checks whether the given HTTP URL responds with 200 OK and body "OK". 314 func checkBodyOK(url string) bool { 315 resp, err := http.Get(url) 316 if err != nil { 317 return false 318 } 319 defer resp.Body.Close() 320 321 if resp.StatusCode != 200 { 322 return false 323 } 324 buf := make([]byte, 2) 325 if _, err = io.ReadFull(resp.Body, buf); err != nil { 326 return false 327 } 328 return bytes.Equal(buf, []byte("OK")) 329 } 330 331 // checkRPC checks whether JSON-RPC works against the given URL. 332 func checkRPC(url string) bool { 333 c, err := rpc.Dial(url) 334 if err != nil { 335 return false 336 } 337 defer c.Close() 338 339 _, err = c.SupportedModules() 340 return err == nil 341 } 342 343 // string/int pointer helpers. 344 func sp(s string) *string { return &s } 345 func ip(i int) *int { return &i } 346 347 func not(ok bool) string { 348 if ok { 349 return "" 350 } 351 return "not " 352 }