github.com/dgraph-io/dgraph@v1.2.8/graphql/e2e/common/common.go (about)

     1  /*
     2   *    Copyright 2019 Dgraph Labs, Inc. and Contributors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package common
    18  
    19  import (
    20  	"bytes"
    21  	"compress/gzip"
    22  	"context"
    23  	"encoding/json"
    24  	"fmt"
    25  	"io/ioutil"
    26  	"net/http"
    27  	"strconv"
    28  	"strings"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/dgraph-io/dgo/v2"
    33  	"github.com/dgraph-io/dgo/v2/protos/api"
    34  	"github.com/dgraph-io/dgraph/x"
    35  	"github.com/google/uuid"
    36  	"github.com/pkg/errors"
    37  	"github.com/stretchr/testify/require"
    38  	"google.golang.org/grpc"
    39  )
    40  
    41  const (
    42  	graphqlURL      = "http://localhost:8180/graphql"
    43  	graphqlAdminURL = "http://localhost:8180/admin"
    44  	alphagRPC       = "localhost:9180"
    45  
    46  	graphqlAdminTestURL      = "http://localhost:8280/graphql"
    47  	graphqlAdminTestAdminURL = "http://localhost:8280/admin"
    48  	alphaAdminTestgRPC       = "localhost:9280"
    49  )
    50  
    51  // GraphQLParams is parameters for the constructing a GraphQL query - that's
    52  // http POST with this body, or http GET with this in the query string.
    53  //
    54  // https://graphql.org/learn/serving-over-http/ says:
    55  //
    56  // POST
    57  // ----
    58  // 'A standard GraphQL POST request should use the application/json content type,
    59  // and include a JSON-encoded body of the following form:
    60  // {
    61  // 	  "query": "...",
    62  // 	  "operationName": "...",
    63  // 	  "variables": { "myVariable": "someValue", ... }
    64  // }
    65  // operationName and variables are optional fields. operationName is only
    66  // required if multiple operations are present in the query.'
    67  //
    68  //
    69  // GET
    70  // ---
    71  //
    72  // http://myapi/graphql?query={me{name}}
    73  // "Query variables can be sent as a JSON-encoded string in an additional query parameter
    74  // called variables. If the query contains several named operations, an operationName query
    75  // parameter can be used to control which one should be executed."
    76  //
    77  // acceptGzip sends "Accept-Encoding: gzip" header to the server, which would return the
    78  // response after gzip.
    79  // gzipEncoding would compress the request to the server and add "Content-Encoding: gzip"
    80  // header to the same.
    81  
    82  type GraphQLParams struct {
    83  	Query         string                 `json:"query"`
    84  	OperationName string                 `json:"operationName"`
    85  	Variables     map[string]interface{} `json:"variables"`
    86  	acceptGzip    bool
    87  	gzipEncoding  bool
    88  }
    89  
    90  type requestExecutor func(t *testing.T, url string, params *GraphQLParams) *GraphQLResponse
    91  
    92  // GraphQLResponse GraphQL response structure.
    93  // see https://graphql.github.io/graphql-spec/June2018/#sec-Response
    94  type GraphQLResponse struct {
    95  	Data       json.RawMessage        `json:"data,omitempty"`
    96  	Errors     x.GqlErrorList         `json:"errors,omitempty"`
    97  	Extensions map[string]interface{} `json:"extensions,omitempty"`
    98  }
    99  
   100  type country struct {
   101  	ID     string   `json:"id,omitempty"`
   102  	Name   string   `json:"name,omitempty"`
   103  	States []*state `json:"states,omitempty"`
   104  }
   105  
   106  type author struct {
   107  	ID         string     `json:"id,omitempty"`
   108  	Name       string     `json:"name,omitempty"`
   109  	Dob        *time.Time `json:"dob,omitempty"`
   110  	Reputation float32    `json:"reputation,omitempty"`
   111  	Country    *country   `json:"country,omitempty"`
   112  	Posts      []*post    `json:"posts,omitempty"`
   113  }
   114  
   115  type post struct {
   116  	PostID      string    `json:"postID,omitempty"`
   117  	Title       string    `json:"title,omitempty"`
   118  	Text        string    `json:"text,omitempty"`
   119  	Tags        []string  `json:"tags,omitempty"`
   120  	NumLikes    int       `json:"numLikes,omitempty"`
   121  	IsPublished bool      `json:"isPublished,omitempty"`
   122  	PostType    string    `json:"postType,omitempty"`
   123  	Author      *author   `json:"author,omitempty"`
   124  	Category    *category `json:"category,omitempty"`
   125  }
   126  
   127  type category struct {
   128  	ID    string `json:"id,omitempty"`
   129  	Name  string `json:"name,omitempty"`
   130  	Posts []post `json:"posts,omitempty"`
   131  }
   132  
   133  type state struct {
   134  	ID      string   `json:"id,omitempty"`
   135  	Name    string   `json:"name,omitempty"`
   136  	Code    string   `json:"xcode,omitempty"`
   137  	Country *country `json:"country,omitempty"`
   138  }
   139  
   140  func BootstrapServer(schema, data []byte) {
   141  	err := checkGraphQLLayerStarted(graphqlAdminURL)
   142  	if err != nil {
   143  		panic(fmt.Sprintf("Waited for GraphQL test server to become available, but it never did.\n"+
   144  			"Got last error %+v", err.Error()))
   145  	}
   146  
   147  	err = checkGraphQLLayerStarted(graphqlAdminTestAdminURL)
   148  	if err != nil {
   149  		panic(fmt.Sprintf("Waited for GraphQL AdminTest server to become available, "+
   150  			"but it never did.\n Got last error: %+v", err.Error()))
   151  	}
   152  
   153  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   154  	defer cancel()
   155  	d, err := grpc.DialContext(ctx, alphagRPC, grpc.WithInsecure())
   156  	if err != nil {
   157  		panic(err)
   158  	}
   159  	client := dgo.NewDgraphClient(api.NewDgraphClient(d))
   160  
   161  	err = addSchema(graphqlAdminURL, string(schema))
   162  	if err != nil {
   163  		panic(err)
   164  	}
   165  
   166  	err = populateGraphQLData(client, data)
   167  	if err != nil {
   168  		panic(err)
   169  	}
   170  
   171  	err = checkGraphQLHealth(graphqlAdminURL, []string{"Healthy"})
   172  	if err != nil {
   173  		panic(err)
   174  	}
   175  
   176  	if err = d.Close(); err != nil {
   177  		panic(err)
   178  	}
   179  }
   180  
   181  // RunAll runs all the test functions in this package as sub tests.
   182  func RunAll(t *testing.T) {
   183  	// admin tests
   184  	t.Run("admin", admin)
   185  
   186  	// schema tests
   187  	t.Run("graphql descriptions", graphQLDescriptions)
   188  
   189  	// encoding
   190  	t.Run("gzip compression", gzipCompression)
   191  	t.Run("gzip compression header", gzipCompressionHeader)
   192  	t.Run("gzip compression no header", gzipCompressionNoHeader)
   193  
   194  	// query tests
   195  	t.Run("get request", getRequest)
   196  	t.Run("get query empty variable", getQueryEmptyVariable)
   197  	t.Run("query by type", queryByType)
   198  	t.Run("uid alias", uidAlias)
   199  	t.Run("order at root", orderAtRoot)
   200  	t.Run("page at root", pageAtRoot)
   201  	t.Run("regexp", regExp)
   202  	t.Run("multiple search indexes", multipleSearchIndexes)
   203  	t.Run("multiple search indexes wrong field", multipleSearchIndexesWrongField)
   204  	t.Run("hash search", hashSearch)
   205  	t.Run("deep filter", deepFilter)
   206  	t.Run("many queries", manyQueries)
   207  	t.Run("query order at root", queryOrderAtRoot)
   208  	t.Run("queries with error", queriesWithError)
   209  	t.Run("date filters", dateFilters)
   210  	t.Run("float filters", floatFilters)
   211  	t.Run("int filters", intFilters)
   212  	t.Run("boolean filters", booleanFilters)
   213  	t.Run("term filters", termFilters)
   214  	t.Run("full text filters", fullTextFilters)
   215  	t.Run("string exact filters", stringExactFilters)
   216  	t.Run("scalar list filters", scalarListFilters)
   217  	t.Run("skip directive", skipDirective)
   218  	t.Run("include directive", includeDirective)
   219  	t.Run("include and skip directive", includeAndSkipDirective)
   220  	t.Run("query by mutliple ids", queryByMultipleIds)
   221  	t.Run("enum filter", enumFilter)
   222  	t.Run("default enum filter", defaultEnumFilter)
   223  	t.Run("query by multiple invalid ids", queryByMultipleInvalidIds)
   224  	t.Run("query typename", queryTypename)
   225  	t.Run("query nested typename", queryNestedTypename)
   226  	t.Run("typename for interface", typenameForInterface)
   227  
   228  	t.Run("get state by xid", getStateByXid)
   229  	t.Run("get state without args", getStateWithoutArgs)
   230  	t.Run("get state by both xid and uid", getStateByBothXidAndUid)
   231  	t.Run("query state by xid", queryStateByXid)
   232  	t.Run("query state by xid regex", queryStateByXidRegex)
   233  	t.Run("multiple operations", multipleOperations)
   234  	t.Run("query post with author", queryPostWithAuthor)
   235  
   236  	// mutation tests
   237  	t.Run("add mutation", addMutation)
   238  	t.Run("update mutation by ids", updateMutationByIds)
   239  	t.Run("update mutation by name", updateMutationByName)
   240  	t.Run("update mutation by name no match", updateMutationByNameNoMatch)
   241  	t.Run("update delete", updateDelete)
   242  	t.Run("filter in update", filterInUpdate)
   243  	t.Run("selection in add object", testSelectionInAddObject)
   244  	t.Run("delete mutation with multiple ids", deleteMutationWithMultipleIds)
   245  	t.Run("delete mutation with single id", deleteMutationWithSingleID)
   246  	t.Run("delete mutation by name", deleteMutationByName)
   247  	t.Run("delete wrong id", deleteWrongID)
   248  	t.Run("many mutations", manyMutations)
   249  	t.Run("mutations with deep filter", mutationWithDeepFilter)
   250  	t.Run("many mutations with query error", manyMutationsWithQueryError)
   251  	t.Run("query interface after add mutation", queryInterfaceAfterAddMutation)
   252  	t.Run("add mutation with xid", addMutationWithXID)
   253  	t.Run("deep mutations", deepMutations)
   254  	t.Run("add multiple mutations", testMultipleMutations)
   255  	t.Run("deep XID mutations", deepXIDMutations)
   256  	t.Run("error in multiple mutations", addMultipleMutationWithOneError)
   257  
   258  	// error tests
   259  	t.Run("graphql completion on", graphQLCompletionOn)
   260  	t.Run("request validation errors", requestValidationErrors)
   261  	t.Run("panic catcher", panicCatcher)
   262  	t.Run("deep mutation errors", deepMutationErrors)
   263  
   264  }
   265  
   266  func gunzipData(data []byte) ([]byte, error) {
   267  	b := bytes.NewBuffer(data)
   268  
   269  	r, err := gzip.NewReader(b)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  
   274  	var resB bytes.Buffer
   275  	if _, err := resB.ReadFrom(r); err != nil {
   276  		return nil, err
   277  	}
   278  	return resB.Bytes(), nil
   279  }
   280  
   281  func gzipData(data []byte) ([]byte, error) {
   282  	var b bytes.Buffer
   283  	gz := gzip.NewWriter(&b)
   284  
   285  	if _, err := gz.Write(data); err != nil {
   286  		return nil, err
   287  	}
   288  
   289  	if err := gz.Close(); err != nil {
   290  		return nil, err
   291  	}
   292  	return b.Bytes(), nil
   293  }
   294  
   295  // This tests that if a request has gzip header but the body is
   296  // not compressed, then it should return an error
   297  func gzipCompressionHeader(t *testing.T) {
   298  	queryCountry := &GraphQLParams{
   299  		Query: `query {
   300  			queryCountry {
   301  				name
   302  			}
   303  		}`,
   304  	}
   305  
   306  	req, err := queryCountry.createGQLPost(graphqlURL)
   307  	require.NoError(t, err)
   308  
   309  	req.Header.Set("Content-Encoding", "gzip")
   310  
   311  	resData, err := runGQLRequest(req)
   312  	require.NoError(t, err)
   313  
   314  	var result *GraphQLResponse
   315  	err = json.Unmarshal(resData, &result)
   316  	require.NoError(t, err)
   317  	require.NotNil(t, result.Errors)
   318  	require.Contains(t, result.Errors[0].Message, "Unable to parse gzip")
   319  }
   320  
   321  // This tests that if a req's body is compressed but the
   322  // header is not present, then it should return an error
   323  func gzipCompressionNoHeader(t *testing.T) {
   324  	queryCountry := &GraphQLParams{
   325  		Query: `query {
   326  			queryCountry {
   327  				name
   328  			}
   329  		}`,
   330  		gzipEncoding: true,
   331  	}
   332  
   333  	req, err := queryCountry.createGQLPost(graphqlURL)
   334  	require.NoError(t, err)
   335  
   336  	req.Header.Del("Content-Encoding")
   337  	resData, err := runGQLRequest(req)
   338  	require.NoError(t, err)
   339  
   340  	var result *GraphQLResponse
   341  	err = json.Unmarshal(resData, &result)
   342  	require.NoError(t, err)
   343  	require.NotNil(t, result.Errors)
   344  	require.Contains(t, result.Errors[0].Message, "Not a valid GraphQL request body")
   345  }
   346  
   347  func getRequest(t *testing.T) {
   348  	add(t, getExecutor)
   349  }
   350  
   351  func getQueryEmptyVariable(t *testing.T) {
   352  	queryCountry := &GraphQLParams{
   353  		Query: `query {
   354  			queryCountry {
   355  				name
   356  			}
   357  		}`,
   358  	}
   359  	req, err := queryCountry.createGQLGet(graphqlURL)
   360  	require.NoError(t, err)
   361  
   362  	q := req.URL.Query()
   363  	q.Del("variables")
   364  	req.URL.RawQuery = q.Encode()
   365  
   366  	res := queryCountry.Execute(t, req)
   367  	require.Nil(t, res.Errors)
   368  }
   369  
   370  // Execute takes a HTTP request from either ExecuteAsPost or ExecuteAsGet
   371  // and executes the request
   372  func (params *GraphQLParams) Execute(t *testing.T, req *http.Request) *GraphQLResponse {
   373  	res, err := runGQLRequest(req)
   374  	require.NoError(t, err)
   375  
   376  	var result *GraphQLResponse
   377  	if params.acceptGzip {
   378  		res, err = gunzipData(res)
   379  		require.NoError(t, err)
   380  		require.Contains(t, req.Header.Get("Accept-Encoding"), "gzip")
   381  	}
   382  	err = json.Unmarshal(res, &result)
   383  	require.NoError(t, err)
   384  
   385  	requireContainsRequestID(t, result)
   386  
   387  	return result
   388  
   389  }
   390  
   391  // ExecuteAsPost builds a HTTP POST request from the GraphQL input structure
   392  // and executes the request to url.
   393  func (params *GraphQLParams) ExecuteAsPost(t *testing.T, url string) *GraphQLResponse {
   394  	req, err := params.createGQLPost(url)
   395  	require.NoError(t, err)
   396  
   397  	return params.Execute(t, req)
   398  }
   399  
   400  // ExecuteAsGet builds a HTTP GET request from the GraphQL input structure
   401  // and executes the request to url.
   402  func (params *GraphQLParams) ExecuteAsGet(t *testing.T, url string) *GraphQLResponse {
   403  	req, err := params.createGQLGet(url)
   404  	require.NoError(t, err)
   405  
   406  	return params.Execute(t, req)
   407  }
   408  
   409  func getExecutor(t *testing.T, url string, params *GraphQLParams) *GraphQLResponse {
   410  	return params.ExecuteAsGet(t, url)
   411  }
   412  
   413  func postExecutor(t *testing.T, url string, params *GraphQLParams) *GraphQLResponse {
   414  	return params.ExecuteAsPost(t, url)
   415  }
   416  
   417  func (params *GraphQLParams) createGQLGet(url string) (*http.Request, error) {
   418  	req, err := http.NewRequest("GET", url, nil)
   419  	if err != nil {
   420  		return nil, err
   421  	}
   422  
   423  	q := req.URL.Query()
   424  	q.Add("query", params.Query)
   425  	q.Add("operationName", params.OperationName)
   426  
   427  	variableString, err := json.Marshal(params.Variables)
   428  	if err != nil {
   429  		return nil, err
   430  	}
   431  	q.Add("variables", string(variableString))
   432  
   433  	req.URL.RawQuery = q.Encode()
   434  	if params.acceptGzip {
   435  		req.Header.Set("Accept-Encoding", "gzip")
   436  	}
   437  	return req, nil
   438  }
   439  
   440  func (params *GraphQLParams) createGQLPost(url string) (*http.Request, error) {
   441  	body, err := json.Marshal(params)
   442  	if err != nil {
   443  		return nil, err
   444  	}
   445  
   446  	if params.gzipEncoding {
   447  		if body, err = gzipData(body); err != nil {
   448  			return nil, err
   449  		}
   450  	}
   451  
   452  	req, err := http.NewRequest("POST", url, bytes.NewBuffer(body))
   453  	if err != nil {
   454  		return nil, err
   455  	}
   456  	req.Header.Set("Content-Type", "application/json")
   457  	if params.gzipEncoding {
   458  		req.Header.Set("Content-Encoding", "gzip")
   459  	}
   460  
   461  	if params.acceptGzip {
   462  		req.Header.Set("Accept-Encoding", "gzip")
   463  	}
   464  
   465  	return req, nil
   466  }
   467  
   468  // runGQLRequest runs a HTTP GraphQL request and returns the data or any errors.
   469  func runGQLRequest(req *http.Request) ([]byte, error) {
   470  	client := &http.Client{Timeout: 5 * time.Second}
   471  	resp, err := client.Do(req)
   472  	if err != nil {
   473  		return nil, err
   474  	}
   475  
   476  	// GraphQL server should always return OK, even when there are errors
   477  	if status := resp.StatusCode; status != http.StatusOK {
   478  		return nil, errors.Errorf("unexpected status code: %v", status)
   479  	}
   480  
   481  	if strings.ToLower(resp.Header.Get("Content-Type")) != "application/json" {
   482  		return nil, errors.Errorf("unexpected content type: %v", resp.Header.Get("Content-Type"))
   483  	}
   484  
   485  	if resp.Header.Get("Access-Control-Allow-Origin") != "*" {
   486  		return nil, errors.Errorf("cors headers weren't set in response")
   487  	}
   488  
   489  	defer resp.Body.Close()
   490  	body, err := ioutil.ReadAll(resp.Body)
   491  	if err != nil {
   492  		return nil, errors.Errorf("unable to read response body: %v", err)
   493  	}
   494  
   495  	return body, nil
   496  }
   497  
   498  func requireContainsRequestID(t *testing.T, resp *GraphQLResponse) {
   499  
   500  	v, ok := resp.Extensions["requestID"]
   501  	require.True(t, ok,
   502  		"GraphQL response didn't contain a request ID - response was:\n%s",
   503  		serializeOrError(resp))
   504  
   505  	str, ok := v.(string)
   506  	require.True(t, ok, "GraphQL requestID is not a string - response was:\n%s",
   507  		serializeOrError(resp))
   508  
   509  	_, err := uuid.Parse(str)
   510  	require.NoError(t, err, "GraphQL requestID is not a UUID - response was:\n%s",
   511  		serializeOrError(resp))
   512  }
   513  
   514  func requireUID(t *testing.T, uid string) {
   515  	_, err := strconv.ParseUint(uid, 0, 64)
   516  	require.NoError(t, err)
   517  }
   518  
   519  func requireNoGQLErrors(t *testing.T, resp *GraphQLResponse) {
   520  	require.Nil(t, resp.Errors,
   521  		"required no GraphQL errors, but received :\n%s", serializeOrError(resp.Errors))
   522  }
   523  
   524  func serializeOrError(toSerialize interface{}) string {
   525  	byts, err := json.Marshal(toSerialize)
   526  	if err != nil {
   527  		return "unable to serialize because " + err.Error()
   528  	}
   529  	return string(byts)
   530  }
   531  
   532  func populateGraphQLData(client *dgo.Dgraph, data []byte) error {
   533  	// Helps in local dev to not re-add data multiple times.
   534  	countries, err := allCountriesAdded()
   535  	if err != nil {
   536  		return errors.Wrap(err, "couldn't determine if GraphQL data had already been added")
   537  	}
   538  	if len(countries) > 0 {
   539  		return nil
   540  	}
   541  
   542  	mu := &api.Mutation{
   543  		CommitNow: true,
   544  		SetJson:   data,
   545  	}
   546  	_, err = client.NewTxn().Mutate(context.Background(), mu)
   547  	if err != nil {
   548  		return errors.Wrap(err, "Unable to add GraphQL test data")
   549  	}
   550  
   551  	return nil
   552  }
   553  
   554  func allCountriesAdded() ([]*country, error) {
   555  	body, err := json.Marshal(&GraphQLParams{Query: `query { queryCountry { name } }`})
   556  	if err != nil {
   557  		return nil, errors.Wrap(err, "unable to build GraphQL query")
   558  	}
   559  
   560  	req, err := http.NewRequest("POST", graphqlURL, bytes.NewBuffer(body))
   561  	if err != nil {
   562  		return nil, errors.Wrap(err, "unable to build GraphQL request")
   563  	}
   564  	req.Header.Set("Content-Type", "application/json")
   565  
   566  	resp, err := runGQLRequest(req)
   567  	if err != nil {
   568  		return nil, errors.Wrap(err, "error running GraphQL query")
   569  	}
   570  
   571  	var result struct {
   572  		Data struct {
   573  			QueryCountry []*country
   574  		}
   575  	}
   576  	err = json.Unmarshal(resp, &result)
   577  	if err != nil {
   578  		return nil, errors.Wrap(err, "error trying to unmarshal GraphQL query result")
   579  	}
   580  
   581  	return result.Data.QueryCountry, nil
   582  }
   583  
   584  func checkGraphQLLayerStarted(url string) error {
   585  	var err error
   586  	retries := 6
   587  	sleep := 10 * time.Second
   588  
   589  	// Because of how the test containers are brought up, there's no guarantee
   590  	// that the GraphQL layer is running by now.  So we
   591  	// need to try and connect and potentially retry a few times.
   592  	for retries > 0 {
   593  		retries--
   594  
   595  		// In local dev, we might already have an instance Healthy.  In CI,
   596  		// we expect the GraphQL layer to be waiting for a first schema.
   597  		err = checkGraphQLHealth(url, []string{"NoGraphQLSchema", "Healthy"})
   598  		if err == nil {
   599  			return nil
   600  		}
   601  		time.Sleep(sleep)
   602  	}
   603  	return err
   604  }
   605  
   606  func checkGraphQLHealth(url string, status []string) error {
   607  	health := &GraphQLParams{
   608  		Query: `query {
   609  			health {
   610  				message
   611  				status
   612  			}
   613  		}`,
   614  	}
   615  	req, err := health.createGQLPost(url)
   616  	if err != nil {
   617  		return errors.Wrap(err, "while creating gql post")
   618  	}
   619  
   620  	resp, err := runGQLRequest(req)
   621  	if err != nil {
   622  		return errors.Wrap(err, "error running GraphQL query")
   623  	}
   624  
   625  	var healthResult struct {
   626  		Data struct {
   627  			Health struct {
   628  				Message string
   629  				Status  string
   630  			}
   631  		}
   632  		Errors x.GqlErrorList
   633  	}
   634  
   635  	err = json.Unmarshal(resp, &healthResult)
   636  	if err != nil {
   637  		return errors.Wrap(err, "error trying to unmarshal GraphQL query result")
   638  	}
   639  
   640  	if len(healthResult.Errors) > 0 {
   641  		return healthResult.Errors
   642  	}
   643  
   644  	for _, s := range status {
   645  		if healthResult.Data.Health.Status == s {
   646  			return nil
   647  		}
   648  	}
   649  
   650  	return errors.Errorf("GraphQL server was not at right health: found %s",
   651  		healthResult.Data.Health.Status)
   652  }
   653  
   654  func addSchema(url string, schema string) error {
   655  	add := &GraphQLParams{
   656  		Query: `mutation updateGQLSchema($sch: String!) {
   657  			updateGQLSchema(input: { set: { schema: $sch }}) {
   658  				gqlSchema {
   659  					schema
   660  				}
   661  			}
   662  		}`,
   663  		Variables: map[string]interface{}{"sch": schema},
   664  	}
   665  	req, err := add.createGQLPost(url)
   666  	if err != nil {
   667  		return errors.Wrap(err, "error running GraphQL query")
   668  	}
   669  
   670  	resp, err := runGQLRequest(req)
   671  	if err != nil {
   672  		return errors.Wrap(err, "error running GraphQL query")
   673  	}
   674  
   675  	var addResult struct {
   676  		Data struct {
   677  			UpdateGQLSchema struct {
   678  				GQLSchema struct {
   679  					Schema string
   680  				}
   681  			}
   682  		}
   683  	}
   684  
   685  	err = json.Unmarshal(resp, &addResult)
   686  	if err != nil {
   687  		return errors.Wrap(err, "error trying to unmarshal GraphQL mutation result")
   688  	}
   689  
   690  	if addResult.Data.UpdateGQLSchema.GQLSchema.Schema == "" {
   691  		return errors.New("GraphQL schema mutation failed")
   692  	}
   693  
   694  	return nil
   695  }