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  }