github.com/60ke/go-ethereum@v1.10.2/graphql/graphql_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 graphql
    18  
    19  import (
    20  	"fmt"
    21  	"io/ioutil"
    22  	"math/big"
    23  	"net/http"
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/ethereum/go-ethereum/consensus/ethash"
    29  	"github.com/ethereum/go-ethereum/core"
    30  	"github.com/ethereum/go-ethereum/eth"
    31  	"github.com/ethereum/go-ethereum/eth/ethconfig"
    32  	"github.com/ethereum/go-ethereum/node"
    33  	"github.com/ethereum/go-ethereum/params"
    34  
    35  	"github.com/stretchr/testify/assert"
    36  )
    37  
    38  func TestBuildSchema(t *testing.T) {
    39  	ddir, err := ioutil.TempDir("", "graphql-buildschema")
    40  	if err != nil {
    41  		t.Fatalf("failed to create temporary datadir: %v", err)
    42  	}
    43  	// Copy config
    44  	conf := node.DefaultConfig
    45  	conf.DataDir = ddir
    46  	stack, err := node.New(&conf)
    47  	if err != nil {
    48  		t.Fatalf("could not create new node: %v", err)
    49  	}
    50  	// Make sure the schema can be parsed and matched up to the object model.
    51  	if err := newHandler(stack, nil, []string{}, []string{}); err != nil {
    52  		t.Errorf("Could not construct GraphQL handler: %v", err)
    53  	}
    54  }
    55  
    56  // Tests that a graphQL request is successfully handled when graphql is enabled on the specified endpoint
    57  func TestGraphQLBlockSerialization(t *testing.T) {
    58  	stack := createNode(t, true)
    59  	defer stack.Close()
    60  	// start node
    61  	if err := stack.Start(); err != nil {
    62  		t.Fatalf("could not start node: %v", err)
    63  	}
    64  
    65  	for i, tt := range []struct {
    66  		body string
    67  		want string
    68  		code int
    69  	}{
    70  		{ // Should return latest block
    71  			body: `{"query": "{block{number}}","variables": null}`,
    72  			want: `{"data":{"block":{"number":10}}}`,
    73  			code: 200,
    74  		},
    75  		{ // Should return info about latest block
    76  			body: `{"query": "{block{number,gasUsed,gasLimit}}","variables": null}`,
    77  			want: `{"data":{"block":{"number":10,"gasUsed":0,"gasLimit":11500000}}}`,
    78  			code: 200,
    79  		},
    80  		{
    81  			body: `{"query": "{block(number:0){number,gasUsed,gasLimit}}","variables": null}`,
    82  			want: `{"data":{"block":{"number":0,"gasUsed":0,"gasLimit":11500000}}}`,
    83  			code: 200,
    84  		},
    85  		{
    86  			body: `{"query": "{block(number:-1){number,gasUsed,gasLimit}}","variables": null}`,
    87  			want: `{"data":{"block":null}}`,
    88  			code: 200,
    89  		},
    90  		{
    91  			body: `{"query": "{block(number:-500){number,gasUsed,gasLimit}}","variables": null}`,
    92  			want: `{"data":{"block":null}}`,
    93  			code: 200,
    94  		},
    95  		{
    96  			body: `{"query": "{block(number:\"0\"){number,gasUsed,gasLimit}}","variables": null}`,
    97  			want: `{"data":{"block":{"number":0,"gasUsed":0,"gasLimit":11500000}}}`,
    98  			code: 200,
    99  		},
   100  		{
   101  			body: `{"query": "{block(number:\"-33\"){number,gasUsed,gasLimit}}","variables": null}`,
   102  			want: `{"data":{"block":null}}`,
   103  			code: 200,
   104  		},
   105  		{
   106  			body: `{"query": "{block(number:\"1337\"){number,gasUsed,gasLimit}}","variables": null}`,
   107  			want: `{"data":{"block":null}}`,
   108  			code: 200,
   109  		},
   110  		{
   111  			body: `{"query": "{block(number:\"0xbad\"){number,gasUsed,gasLimit}}","variables": null}`,
   112  			want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0xbad\": invalid syntax"}],"data":{}}`,
   113  			code: 400,
   114  		},
   115  		{ // hex strings are currently not supported. If that's added to the spec, this test will need to change
   116  			body: `{"query": "{block(number:\"0x0\"){number,gasUsed,gasLimit}}","variables": null}`,
   117  			want: `{"errors":[{"message":"strconv.ParseInt: parsing \"0x0\": invalid syntax"}],"data":{}}`,
   118  			code: 400,
   119  		},
   120  		{
   121  			body: `{"query": "{block(number:\"a\"){number,gasUsed,gasLimit}}","variables": null}`,
   122  			want: `{"errors":[{"message":"strconv.ParseInt: parsing \"a\": invalid syntax"}],"data":{}}`,
   123  			code: 400,
   124  		},
   125  		{
   126  			body: `{"query": "{bleh{number}}","variables": null}"`,
   127  			want: `{"errors":[{"message":"Cannot query field \"bleh\" on type \"Query\".","locations":[{"line":1,"column":2}]}]}`,
   128  			code: 400,
   129  		},
   130  		// should return `estimateGas` as decimal
   131  		{
   132  			body: `{"query": "{block{ estimateGas(data:{}) }}"}`,
   133  			want: `{"data":{"block":{"estimateGas":53000}}}`,
   134  			code: 200,
   135  		},
   136  		// should return `status` as decimal
   137  		{
   138  			body: `{"query": "{block {number call (data : {from : \"0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b\", to: \"0x6295ee1b4f6dd65047762f924ecd367c17eabf8f\", data :\"0x12a7b914\"}){data status}}}"}`,
   139  			want: `{"data":{"block":{"number":10,"call":{"data":"0x","status":1}}}}`,
   140  			code: 200,
   141  		},
   142  	} {
   143  		resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", strings.NewReader(tt.body))
   144  		if err != nil {
   145  			t.Fatalf("could not post: %v", err)
   146  		}
   147  		bodyBytes, err := ioutil.ReadAll(resp.Body)
   148  		if err != nil {
   149  			t.Fatalf("could not read from response body: %v", err)
   150  		}
   151  		if have := string(bodyBytes); have != tt.want {
   152  			t.Errorf("testcase %d %s,\nhave:\n%v\nwant:\n%v", i, tt.body, have, tt.want)
   153  		}
   154  		if tt.code != resp.StatusCode {
   155  			t.Errorf("testcase %d %s,\nwrong statuscode, have: %v, want: %v", i, tt.body, resp.StatusCode, tt.code)
   156  		}
   157  	}
   158  }
   159  
   160  // Tests that a graphQL request is not handled successfully when graphql is not enabled on the specified endpoint
   161  func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) {
   162  	stack := createNode(t, false)
   163  	defer stack.Close()
   164  	if err := stack.Start(); err != nil {
   165  		t.Fatalf("could not start node: %v", err)
   166  	}
   167  	body := strings.NewReader(`{"query": "{block{number}}","variables": null}`)
   168  	resp, err := http.Post(fmt.Sprintf("%s/graphql", stack.HTTPEndpoint()), "application/json", body)
   169  	if err != nil {
   170  		t.Fatalf("could not post: %v", err)
   171  	}
   172  	// make sure the request is not handled successfully
   173  	assert.Equal(t, http.StatusNotFound, resp.StatusCode)
   174  }
   175  
   176  func createNode(t *testing.T, gqlEnabled bool) *node.Node {
   177  	stack, err := node.New(&node.Config{
   178  		HTTPHost: "127.0.0.1",
   179  		HTTPPort: 0,
   180  		WSHost:   "127.0.0.1",
   181  		WSPort:   0,
   182  	})
   183  	if err != nil {
   184  		t.Fatalf("could not create node: %v", err)
   185  	}
   186  	if !gqlEnabled {
   187  		return stack
   188  	}
   189  	createGQLService(t, stack)
   190  	return stack
   191  }
   192  
   193  func createGQLService(t *testing.T, stack *node.Node) {
   194  	// create backend
   195  	ethConf := &ethconfig.Config{
   196  		Genesis: &core.Genesis{
   197  			Config:     params.AllEthashProtocolChanges,
   198  			GasLimit:   11500000,
   199  			Difficulty: big.NewInt(1048576),
   200  		},
   201  		Ethash: ethash.Config{
   202  			PowMode: ethash.ModeFake,
   203  		},
   204  		NetworkId:               1337,
   205  		TrieCleanCache:          5,
   206  		TrieCleanCacheJournal:   "triecache",
   207  		TrieCleanCacheRejournal: 60 * time.Minute,
   208  		TrieDirtyCache:          5,
   209  		TrieTimeout:             60 * time.Minute,
   210  		SnapshotCache:           5,
   211  	}
   212  	ethBackend, err := eth.New(stack, ethConf)
   213  	if err != nil {
   214  		t.Fatalf("could not create eth backend: %v", err)
   215  	}
   216  	// Create some blocks and import them
   217  	chain, _ := core.GenerateChain(params.AllEthashProtocolChanges, ethBackend.BlockChain().Genesis(),
   218  		ethash.NewFaker(), ethBackend.ChainDb(), 10, func(i int, gen *core.BlockGen) {})
   219  	_, err = ethBackend.BlockChain().InsertChain(chain)
   220  	if err != nil {
   221  		t.Fatalf("could not create import blocks: %v", err)
   222  	}
   223  	// create gql service
   224  	err = New(stack, ethBackend.APIBackend, []string{}, []string{})
   225  	if err != nil {
   226  		t.Fatalf("could not create graphql service: %v", err)
   227  	}
   228  }