github.com/jincm/wesharechain@v0.0.0-20210122032815-1537409ce26a/chain/swarm/storage/mock/explorer/explorer_test.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum 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-ethereum 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-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package explorer 18 19 import ( 20 "encoding/binary" 21 "encoding/json" 22 "fmt" 23 "io/ioutil" 24 "net/http" 25 "net/http/httptest" 26 "net/url" 27 "os" 28 "sort" 29 "strconv" 30 "strings" 31 "testing" 32 33 "github.com/ethereum/go-ethereum/common" 34 "github.com/ethereum/go-ethereum/swarm/storage/mock" 35 "github.com/ethereum/go-ethereum/swarm/storage/mock/db" 36 "github.com/ethereum/go-ethereum/swarm/storage/mock/mem" 37 ) 38 39 // TestHandler_memGlobalStore runs a set of tests 40 // to validate handler with mem global store. 41 func TestHandler_memGlobalStore(t *testing.T) { 42 t.Parallel() 43 44 globalStore := mem.NewGlobalStore() 45 46 testHandler(t, globalStore) 47 } 48 49 // TestHandler_dbGlobalStore runs a set of tests 50 // to validate handler with database global store. 51 func TestHandler_dbGlobalStore(t *testing.T) { 52 t.Parallel() 53 54 dir, err := ioutil.TempDir("", "swarm-mock-explorer-db-") 55 if err != nil { 56 t.Fatal(err) 57 } 58 defer os.RemoveAll(dir) 59 60 globalStore, err := db.NewGlobalStore(dir) 61 if err != nil { 62 t.Fatal(err) 63 } 64 defer globalStore.Close() 65 66 testHandler(t, globalStore) 67 } 68 69 // testHandler stores data distributed by node addresses 70 // and validates if this data is correctly retrievable 71 // by using the http.Handler returned by NewHandler function. 72 // This test covers all HTTP routes and various get parameters 73 // on them to check paginated results. 74 func testHandler(t *testing.T, globalStore mock.GlobalStorer) { 75 const ( 76 nodeCount = 350 77 keyCount = 250 78 keysOnNodeCount = 150 79 ) 80 81 // keys for every node 82 nodeKeys := make(map[string][]string) 83 84 // a node address that is not present in global store 85 invalidAddr := "0x7b8b72938c254cf002c4e1e714d27e022be88d93" 86 87 // a key that is not present in global store 88 invalidKey := "f9824192fb515cfb" 89 90 for i := 1; i <= nodeCount; i++ { 91 b := make([]byte, 8) 92 binary.BigEndian.PutUint64(b, uint64(i)) 93 addr := common.BytesToAddress(b).Hex() 94 nodeKeys[addr] = make([]string, 0) 95 } 96 97 for i := 1; i <= keyCount; i++ { 98 b := make([]byte, 8) 99 binary.BigEndian.PutUint64(b, uint64(i)) 100 101 key := common.Bytes2Hex(b) 102 103 var c int 104 for addr := range nodeKeys { 105 nodeKeys[addr] = append(nodeKeys[addr], key) 106 c++ 107 if c >= keysOnNodeCount { 108 break 109 } 110 } 111 } 112 113 // sort keys for every node as they are expected to be 114 // sorted in HTTP responses 115 for _, keys := range nodeKeys { 116 sort.Strings(keys) 117 } 118 119 // nodes for every key 120 keyNodes := make(map[string][]string) 121 122 // construct a reverse mapping of nodes for every key 123 for addr, keys := range nodeKeys { 124 for _, key := range keys { 125 keyNodes[key] = append(keyNodes[key], addr) 126 } 127 } 128 129 // sort node addresses with case insensitive sort, 130 // as hex letters in node addresses are in mixed caps 131 for _, addrs := range keyNodes { 132 sortCaseInsensitive(addrs) 133 } 134 135 // find a key that is not stored at the address 136 var ( 137 unmatchedAddr string 138 unmatchedKey string 139 ) 140 for addr, keys := range nodeKeys { 141 for key := range keyNodes { 142 var found bool 143 for _, k := range keys { 144 if k == key { 145 found = true 146 break 147 } 148 } 149 if !found { 150 unmatchedAddr = addr 151 unmatchedKey = key 152 } 153 break 154 } 155 if unmatchedAddr != "" { 156 break 157 } 158 } 159 // check if unmatched key/address pair is found 160 if unmatchedAddr == "" || unmatchedKey == "" { 161 t.Fatalf("could not find a key that is not associated with a node") 162 } 163 164 // store the data 165 for addr, keys := range nodeKeys { 166 for _, key := range keys { 167 err := globalStore.Put(common.HexToAddress(addr), common.Hex2Bytes(key), []byte("data")) 168 if err != nil { 169 t.Fatal(err) 170 } 171 } 172 } 173 174 handler := NewHandler(globalStore, nil) 175 176 // this subtest confirms that it has uploaded key and that it does not have invalid keys 177 t.Run("has key", func(t *testing.T) { 178 for addr, keys := range nodeKeys { 179 for _, key := range keys { 180 testStatusResponse(t, handler, "/api/has-key/"+addr+"/"+key, http.StatusOK) 181 testStatusResponse(t, handler, "/api/has-key/"+invalidAddr+"/"+key, http.StatusNotFound) 182 } 183 testStatusResponse(t, handler, "/api/has-key/"+addr+"/"+invalidKey, http.StatusNotFound) 184 } 185 testStatusResponse(t, handler, "/api/has-key/"+invalidAddr+"/"+invalidKey, http.StatusNotFound) 186 testStatusResponse(t, handler, "/api/has-key/"+unmatchedAddr+"/"+unmatchedKey, http.StatusNotFound) 187 }) 188 189 // this subtest confirms that all keys are are listed in correct order with expected pagination 190 t.Run("keys", func(t *testing.T) { 191 var allKeys []string 192 for key := range keyNodes { 193 allKeys = append(allKeys, key) 194 } 195 sort.Strings(allKeys) 196 197 t.Run("limit 0", testKeys(handler, allKeys, 0, "")) 198 t.Run("limit default", testKeys(handler, allKeys, mock.DefaultLimit, "")) 199 t.Run("limit 2x default", testKeys(handler, allKeys, 2*mock.DefaultLimit, "")) 200 t.Run("limit 0.5x default", testKeys(handler, allKeys, mock.DefaultLimit/2, "")) 201 t.Run("limit max", testKeys(handler, allKeys, mock.MaxLimit, "")) 202 t.Run("limit 2x max", testKeys(handler, allKeys, 2*mock.MaxLimit, "")) 203 t.Run("limit negative", testKeys(handler, allKeys, -10, "")) 204 }) 205 206 // this subtest confirms that all keys are are listed for every node in correct order 207 // and that for one node different pagination options are correct 208 t.Run("node keys", func(t *testing.T) { 209 var limitCheckAddr string 210 211 for addr, keys := range nodeKeys { 212 testKeys(handler, keys, 0, addr)(t) 213 if limitCheckAddr == "" { 214 limitCheckAddr = addr 215 } 216 } 217 testKeys(handler, nil, 0, invalidAddr)(t) 218 219 limitCheckKeys := nodeKeys[limitCheckAddr] 220 t.Run("limit 0", testKeys(handler, limitCheckKeys, 0, limitCheckAddr)) 221 t.Run("limit default", testKeys(handler, limitCheckKeys, mock.DefaultLimit, limitCheckAddr)) 222 t.Run("limit 2x default", testKeys(handler, limitCheckKeys, 2*mock.DefaultLimit, limitCheckAddr)) 223 t.Run("limit 0.5x default", testKeys(handler, limitCheckKeys, mock.DefaultLimit/2, limitCheckAddr)) 224 t.Run("limit max", testKeys(handler, limitCheckKeys, mock.MaxLimit, limitCheckAddr)) 225 t.Run("limit 2x max", testKeys(handler, limitCheckKeys, 2*mock.MaxLimit, limitCheckAddr)) 226 t.Run("limit negative", testKeys(handler, limitCheckKeys, -10, limitCheckAddr)) 227 }) 228 229 // this subtest confirms that all nodes are are listed in correct order with expected pagination 230 t.Run("nodes", func(t *testing.T) { 231 var allNodes []string 232 for addr := range nodeKeys { 233 allNodes = append(allNodes, addr) 234 } 235 sortCaseInsensitive(allNodes) 236 237 t.Run("limit 0", testNodes(handler, allNodes, 0, "")) 238 t.Run("limit default", testNodes(handler, allNodes, mock.DefaultLimit, "")) 239 t.Run("limit 2x default", testNodes(handler, allNodes, 2*mock.DefaultLimit, "")) 240 t.Run("limit 0.5x default", testNodes(handler, allNodes, mock.DefaultLimit/2, "")) 241 t.Run("limit max", testNodes(handler, allNodes, mock.MaxLimit, "")) 242 t.Run("limit 2x max", testNodes(handler, allNodes, 2*mock.MaxLimit, "")) 243 t.Run("limit negative", testNodes(handler, allNodes, -10, "")) 244 }) 245 246 // this subtest confirms that all nodes are are listed that contain a a particular key in correct order 247 // and that for one key different node pagination options are correct 248 t.Run("key nodes", func(t *testing.T) { 249 var limitCheckKey string 250 251 for key, addrs := range keyNodes { 252 testNodes(handler, addrs, 0, key)(t) 253 if limitCheckKey == "" { 254 limitCheckKey = key 255 } 256 } 257 testNodes(handler, nil, 0, invalidKey)(t) 258 259 limitCheckKeys := keyNodes[limitCheckKey] 260 t.Run("limit 0", testNodes(handler, limitCheckKeys, 0, limitCheckKey)) 261 t.Run("limit default", testNodes(handler, limitCheckKeys, mock.DefaultLimit, limitCheckKey)) 262 t.Run("limit 2x default", testNodes(handler, limitCheckKeys, 2*mock.DefaultLimit, limitCheckKey)) 263 t.Run("limit 0.5x default", testNodes(handler, limitCheckKeys, mock.DefaultLimit/2, limitCheckKey)) 264 t.Run("limit max", testNodes(handler, limitCheckKeys, mock.MaxLimit, limitCheckKey)) 265 t.Run("limit 2x max", testNodes(handler, limitCheckKeys, 2*mock.MaxLimit, limitCheckKey)) 266 t.Run("limit negative", testNodes(handler, limitCheckKeys, -10, limitCheckKey)) 267 }) 268 } 269 270 // testsKeys returns a test function that validates wantKeys against a series of /api/keys 271 // HTTP responses with provided limit and node options. 272 func testKeys(handler http.Handler, wantKeys []string, limit int, node string) func(t *testing.T) { 273 return func(t *testing.T) { 274 t.Helper() 275 276 wantLimit := limit 277 if wantLimit <= 0 { 278 wantLimit = mock.DefaultLimit 279 } 280 if wantLimit > mock.MaxLimit { 281 wantLimit = mock.MaxLimit 282 } 283 wantKeysLen := len(wantKeys) 284 var i int 285 var startKey string 286 for { 287 var wantNext string 288 start := i * wantLimit 289 end := (i + 1) * wantLimit 290 if end < wantKeysLen { 291 wantNext = wantKeys[end] 292 } else { 293 end = wantKeysLen 294 } 295 testKeysResponse(t, handler, node, startKey, limit, KeysResponse{ 296 Keys: wantKeys[start:end], 297 Next: wantNext, 298 }) 299 if wantNext == "" { 300 break 301 } 302 startKey = wantNext 303 i++ 304 } 305 } 306 } 307 308 // testNodes returns a test function that validates wantAddrs against a series of /api/nodes 309 // HTTP responses with provided limit and key options. 310 func testNodes(handler http.Handler, wantAddrs []string, limit int, key string) func(t *testing.T) { 311 return func(t *testing.T) { 312 t.Helper() 313 314 wantLimit := limit 315 if wantLimit <= 0 { 316 wantLimit = mock.DefaultLimit 317 } 318 if wantLimit > mock.MaxLimit { 319 wantLimit = mock.MaxLimit 320 } 321 wantAddrsLen := len(wantAddrs) 322 var i int 323 var startKey string 324 for { 325 var wantNext string 326 start := i * wantLimit 327 end := (i + 1) * wantLimit 328 if end < wantAddrsLen { 329 wantNext = wantAddrs[end] 330 } else { 331 end = wantAddrsLen 332 } 333 testNodesResponse(t, handler, key, startKey, limit, NodesResponse{ 334 Nodes: wantAddrs[start:end], 335 Next: wantNext, 336 }) 337 if wantNext == "" { 338 break 339 } 340 startKey = wantNext 341 i++ 342 } 343 } 344 } 345 346 // testStatusResponse validates a response made on url if it matches 347 // the expected StatusResponse. 348 func testStatusResponse(t *testing.T, handler http.Handler, url string, code int) { 349 t.Helper() 350 351 resp := httpGet(t, handler, url) 352 353 if resp.StatusCode != code { 354 t.Errorf("got status code %v, want %v", resp.StatusCode, code) 355 } 356 if got := resp.Header.Get("Content-Type"); got != jsonContentType { 357 t.Errorf("got Content-Type header %q, want %q", got, jsonContentType) 358 } 359 var r StatusResponse 360 if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { 361 t.Fatal(err) 362 } 363 if r.Code != code { 364 t.Errorf("got response code %v, want %v", r.Code, code) 365 } 366 if r.Message != http.StatusText(code) { 367 t.Errorf("got response message %q, want %q", r.Message, http.StatusText(code)) 368 } 369 } 370 371 // testKeysResponse validates response returned from handler on /api/keys 372 // with node, start and limit options against KeysResponse. 373 func testKeysResponse(t *testing.T, handler http.Handler, node, start string, limit int, want KeysResponse) { 374 t.Helper() 375 376 u, err := url.Parse("/api/keys") 377 if err != nil { 378 t.Fatal(err) 379 } 380 q := u.Query() 381 if node != "" { 382 q.Set("node", node) 383 } 384 if start != "" { 385 q.Set("start", start) 386 } 387 if limit != 0 { 388 q.Set("limit", strconv.Itoa(limit)) 389 } 390 u.RawQuery = q.Encode() 391 392 resp := httpGet(t, handler, u.String()) 393 394 if resp.StatusCode != http.StatusOK { 395 t.Errorf("got status code %v, want %v", resp.StatusCode, http.StatusOK) 396 } 397 if got := resp.Header.Get("Content-Type"); got != jsonContentType { 398 t.Errorf("got Content-Type header %q, want %q", got, jsonContentType) 399 } 400 var r KeysResponse 401 if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { 402 t.Fatal(err) 403 } 404 if fmt.Sprint(r.Keys) != fmt.Sprint(want.Keys) { 405 t.Errorf("got keys %v, want %v", r.Keys, want.Keys) 406 } 407 if r.Next != want.Next { 408 t.Errorf("got next %s, want %s", r.Next, want.Next) 409 } 410 } 411 412 // testNodesResponse validates response returned from handler on /api/nodes 413 // with key, start and limit options against NodesResponse. 414 func testNodesResponse(t *testing.T, handler http.Handler, key, start string, limit int, want NodesResponse) { 415 t.Helper() 416 417 u, err := url.Parse("/api/nodes") 418 if err != nil { 419 t.Fatal(err) 420 } 421 q := u.Query() 422 if key != "" { 423 q.Set("key", key) 424 } 425 if start != "" { 426 q.Set("start", start) 427 } 428 if limit != 0 { 429 q.Set("limit", strconv.Itoa(limit)) 430 } 431 u.RawQuery = q.Encode() 432 433 resp := httpGet(t, handler, u.String()) 434 435 if resp.StatusCode != http.StatusOK { 436 t.Errorf("got status code %v, want %v", resp.StatusCode, http.StatusOK) 437 } 438 if got := resp.Header.Get("Content-Type"); got != jsonContentType { 439 t.Errorf("got Content-Type header %q, want %q", got, jsonContentType) 440 } 441 var r NodesResponse 442 if err := json.NewDecoder(resp.Body).Decode(&r); err != nil { 443 t.Fatal(err) 444 } 445 if fmt.Sprint(r.Nodes) != fmt.Sprint(want.Nodes) { 446 t.Errorf("got nodes %v, want %v", r.Nodes, want.Nodes) 447 } 448 if r.Next != want.Next { 449 t.Errorf("got next %s, want %s", r.Next, want.Next) 450 } 451 } 452 453 // httpGet uses httptest recorder to provide a response on handler's url. 454 func httpGet(t *testing.T, handler http.Handler, url string) (r *http.Response) { 455 t.Helper() 456 457 req, err := http.NewRequest(http.MethodGet, url, nil) 458 if err != nil { 459 t.Fatal(err) 460 } 461 w := httptest.NewRecorder() 462 handler.ServeHTTP(w, req) 463 return w.Result() 464 } 465 466 // sortCaseInsensitive performs a case insensitive sort on a string slice. 467 func sortCaseInsensitive(s []string) { 468 sort.Slice(s, func(i, j int) bool { 469 return strings.ToLower(s[i]) < strings.ToLower(s[j]) 470 }) 471 }