github.com/dgraph-io/dgraph@v1.2.8/graphql/e2e/common/mutation.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  // Tests that mutate the GraphQL database should return the database state to what it
    20  // was at the begining of the test.  The GraphQL query tests rely on a fixed input
    21  // dataset and mutating and leaving unexpected data will result in flaky tests.
    22  
    23  import (
    24  	"context"
    25  	"encoding/json"
    26  	"fmt"
    27  	"sort"
    28  	"testing"
    29  
    30  	"github.com/dgraph-io/dgo/v2"
    31  	"github.com/dgraph-io/dgo/v2/protos/api"
    32  	"github.com/dgraph-io/dgraph/testutil"
    33  	"github.com/dgraph-io/dgraph/x"
    34  	"github.com/google/go-cmp/cmp"
    35  	"github.com/google/go-cmp/cmp/cmpopts"
    36  	"github.com/stretchr/testify/require"
    37  	"google.golang.org/grpc"
    38  )
    39  
    40  // TestAddMutation tests that add mutations work as expected.  There's a few angles
    41  // that need testing:
    42  // - add single object,
    43  // - add object with reference to existing object, and
    44  // - add where @hasInverse edges need linking.
    45  //
    46  // These really need to run as one test because the created uid from the Country
    47  // needs to flow to the author, etc.
    48  func addMutation(t *testing.T) {
    49  	add(t, postExecutor)
    50  }
    51  
    52  func add(t *testing.T, executeRequest requestExecutor) {
    53  	var newCountry *country
    54  	var newAuthor *author
    55  	var newPost *post
    56  
    57  	// Add single object :
    58  	// Country is a single object not linked to anything else.
    59  	// So only need to check that it gets added as expected.
    60  	newCountry = addCountry(t, executeRequest)
    61  
    62  	// addCountry() asserts that the mutation response was as expected.
    63  	// Let's also check that what's in the DB is what we expect.
    64  	requireCountry(t, newCountry.ID, newCountry, false, executeRequest)
    65  
    66  	// Add object with reference to existing object :
    67  	// An Author links to an existing country.  So need to check that the author
    68  	// was added and that it has the link to the right Country.
    69  	newAuthor = addAuthor(t, newCountry.ID, executeRequest)
    70  	requireAuthor(t, newAuthor.ID, newAuthor, executeRequest)
    71  
    72  	// Add with @hasInverse :
    73  	// Posts link to an Author and the Author has a link back to all their Posts.
    74  	// So need to check that the Post was added to the right Author
    75  	// AND that the Author's posts now includes the new post.
    76  	newPost = addPost(t, newAuthor.ID, newCountry.ID, executeRequest)
    77  	requirePost(t, newPost.PostID, newPost, true, executeRequest)
    78  
    79  	cleanUp(t, []*country{newCountry}, []*author{newAuthor}, []*post{newPost})
    80  }
    81  
    82  func addCountry(t *testing.T, executeRequest requestExecutor) *country {
    83  	addCountryParams := &GraphQLParams{
    84  		Query: `mutation addCountry($name: String!) {
    85  			addCountry(input: [{ name: $name }]) {
    86  				country {
    87  					id
    88  					name
    89  				}
    90  			}
    91  		}`,
    92  		Variables: map[string]interface{}{"name": "Testland"},
    93  	}
    94  	addCountryExpected := `
    95  		{ "addCountry": { "country": [{ "id": "_UID_", "name": "Testland" }] } }`
    96  
    97  	gqlResponse := executeRequest(t, graphqlURL, addCountryParams)
    98  	requireNoGQLErrors(t, gqlResponse)
    99  
   100  	var expected, result struct {
   101  		AddCountry struct {
   102  			Country []*country
   103  		}
   104  	}
   105  	err := json.Unmarshal([]byte(addCountryExpected), &expected)
   106  	require.NoError(t, err)
   107  	err = json.Unmarshal([]byte(gqlResponse.Data), &result)
   108  	require.NoError(t, err)
   109  
   110  	require.Equal(t, len(result.AddCountry.Country), 1)
   111  	requireUID(t, result.AddCountry.Country[0].ID)
   112  
   113  	// Always ignore the ID of the object that was just created.  That ID is
   114  	// minted by Dgraph.
   115  	opt := cmpopts.IgnoreFields(country{}, "ID")
   116  	if diff := cmp.Diff(expected, result, opt); diff != "" {
   117  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   118  	}
   119  
   120  	return result.AddCountry.Country[0]
   121  }
   122  
   123  // requireCountry enforces that node with ID uid in the GraphQL store is of type
   124  // Country and is value expectedCountry.
   125  func requireCountry(t *testing.T, uid string, expectedCountry *country, includeStates bool,
   126  	executeRequest requestExecutor) {
   127  
   128  	params := &GraphQLParams{
   129  		Query: `query getCountry($id: ID!, $includeStates: Boolean!) {
   130  			getCountry(id: $id) {
   131  				id
   132  				name
   133  				states(order: { asc: xcode }) @include(if: $includeStates) {
   134  					id
   135  					xcode
   136  					name
   137  				}
   138  			}
   139  		}`,
   140  		Variables: map[string]interface{}{"id": uid, "includeStates": includeStates},
   141  	}
   142  	gqlResponse := executeRequest(t, graphqlURL, params)
   143  	requireNoGQLErrors(t, gqlResponse)
   144  
   145  	var result struct {
   146  		GetCountry *country
   147  	}
   148  	err := json.Unmarshal([]byte(gqlResponse.Data), &result)
   149  	require.NoError(t, err)
   150  
   151  	if diff := cmp.Diff(expectedCountry, result.GetCountry, ignoreOpts()...); diff != "" {
   152  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   153  	}
   154  }
   155  
   156  func addAuthor(t *testing.T, countryUID string,
   157  	executeRequest requestExecutor) *author {
   158  
   159  	addAuthorParams := &GraphQLParams{
   160  		Query: `mutation addAuthor($author: AddAuthorInput!) {
   161  			addAuthor(input: [$author]) {
   162  			  	author {
   163  					id
   164  					name
   165  					dob
   166  					reputation
   167  					country {
   168  						id
   169  				  		name
   170  					}
   171  					posts {
   172  						title
   173  						text
   174  					}
   175  			  	}
   176  			}
   177  		}`,
   178  		Variables: map[string]interface{}{"author": map[string]interface{}{
   179  			"name":       "Test Author",
   180  			"dob":        "2010-01-01T05:04:33Z",
   181  			"reputation": 7.75,
   182  			"country":    map[string]interface{}{"id": countryUID},
   183  		}},
   184  	}
   185  
   186  	addAuthorExpected := fmt.Sprintf(`{ "addAuthor": {
   187  		"author": [{
   188  			"id": "_UID_",
   189  			"name": "Test Author",
   190  			"dob": "2010-01-01T05:04:33Z",
   191  			"reputation": 7.75,
   192  			"country": {
   193  				"id": "%s",
   194  				"name": "Testland"
   195  			},
   196  			"posts": []
   197  		}]
   198  	} }`, countryUID)
   199  
   200  	gqlResponse := executeRequest(t, graphqlURL, addAuthorParams)
   201  	requireNoGQLErrors(t, gqlResponse)
   202  
   203  	var expected, result struct {
   204  		AddAuthor struct {
   205  			Author []*author
   206  		}
   207  	}
   208  	err := json.Unmarshal([]byte(addAuthorExpected), &expected)
   209  	require.NoError(t, err)
   210  	err = json.Unmarshal([]byte(gqlResponse.Data), &result)
   211  	require.NoError(t, err)
   212  
   213  	require.Equal(t, len(result.AddAuthor.Author), 1)
   214  	requireUID(t, result.AddAuthor.Author[0].ID)
   215  
   216  	opt := cmpopts.IgnoreFields(author{}, "ID")
   217  	if diff := cmp.Diff(expected, result, opt); diff != "" {
   218  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   219  	}
   220  
   221  	return result.AddAuthor.Author[0]
   222  }
   223  
   224  func requireAuthor(t *testing.T, authorID string, expectedAuthor *author,
   225  	executeRequest requestExecutor) {
   226  
   227  	params := &GraphQLParams{
   228  		Query: `query getAuthor($id: ID!) {
   229  			getAuthor(id: $id) {
   230  				id
   231  				name
   232  				dob
   233  				reputation
   234  				country {
   235  					id
   236  					name
   237  				}
   238  				posts(order: { asc: title }) {
   239  					postID
   240  					title
   241  					text
   242  					tags
   243  					category {
   244  						id
   245  						name
   246  					}
   247  				}
   248  			}
   249  		}`,
   250  		Variables: map[string]interface{}{"id": authorID},
   251  	}
   252  	gqlResponse := executeRequest(t, graphqlURL, params)
   253  	requireNoGQLErrors(t, gqlResponse)
   254  
   255  	var result struct {
   256  		GetAuthor *author
   257  	}
   258  	err := json.Unmarshal([]byte(gqlResponse.Data), &result)
   259  	require.NoError(t, err)
   260  
   261  	if diff := cmp.Diff(expectedAuthor, result.GetAuthor, ignoreOpts()...); diff != "" {
   262  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   263  	}
   264  }
   265  
   266  func ignoreOpts() []cmp.Option {
   267  	return []cmp.Option{
   268  		cmpopts.IgnoreFields(author{}, "ID"),
   269  		cmpopts.IgnoreFields(country{}, "ID"),
   270  		cmpopts.IgnoreFields(post{}, "PostID"),
   271  		cmpopts.IgnoreFields(state{}, "ID"),
   272  		cmpopts.IgnoreFields(category{}, "ID"),
   273  	}
   274  }
   275  
   276  func deepMutations(t *testing.T) {
   277  	deepMutationsTest(t, postExecutor)
   278  }
   279  
   280  func deepMutationsTest(t *testing.T, executeRequest requestExecutor) {
   281  	newCountry := addCountry(t, executeRequest)
   282  
   283  	auth := &author{
   284  		Name:    "New Author",
   285  		Country: newCountry,
   286  		Posts: []*post{
   287  			{
   288  				Title:    "A New Post",
   289  				Text:     "Text of new post",
   290  				Tags:     []string{},
   291  				Category: &category{Name: "A Category"},
   292  			},
   293  			{
   294  				Title: "Another New Post",
   295  				Text:  "Text of other new post",
   296  				Tags:  []string{},
   297  			},
   298  		},
   299  	}
   300  
   301  	newAuth := addMultipleAuthorFromRef(t, []*author{auth}, executeRequest)[0]
   302  	requireAuthor(t, newAuth.ID, newAuth, executeRequest)
   303  
   304  	anotherCountry := addCountry(t, executeRequest)
   305  
   306  	patchSet := &author{
   307  		Posts: []*post{
   308  			{
   309  				Title:    "Creating in an update",
   310  				Text:     "Text of new post",
   311  				Category: newAuth.Posts[0].Category,
   312  				Tags:     []string{},
   313  			},
   314  		},
   315  		// Country: anotherCountry,
   316  		// FIXME: Won't work till https://github.com/dgraph-io/dgraph/pull/4411 is merged
   317  	}
   318  
   319  	patchRemove := &author{
   320  		Posts: []*post{newAuth.Posts[0]},
   321  	}
   322  
   323  	expectedAuthor := &author{
   324  		Name: "New Author",
   325  		// Country: anotherCountry,
   326  		Country: newCountry,
   327  		Posts:   []*post{newAuth.Posts[1], patchSet.Posts[0]},
   328  	}
   329  
   330  	updateAuthorParams := &GraphQLParams{
   331  		Query: `mutation updateAuthor($id: ID!, $set: AuthorPatch!, $remove: AuthorPatch!) {
   332  			updateAuthor(
   333  				input: {
   334  					filter: {id: [$id]},
   335  					set: $set,
   336  					remove: $remove
   337  				}
   338  			) {
   339  			  	author {
   340  					id
   341  					name
   342  					country {
   343  						id
   344  				  		name
   345  					}
   346  					posts {
   347  						title
   348  						text
   349  						tags
   350  						category {
   351  							id
   352  							name
   353  						}
   354  					}
   355  			  	}
   356  			}
   357  		}`,
   358  		Variables: map[string]interface{}{
   359  			"id":     newAuth.ID,
   360  			"set":    patchSet,
   361  			"remove": patchRemove,
   362  		},
   363  	}
   364  
   365  	gqlResponse := executeRequest(t, graphqlURL, updateAuthorParams)
   366  	requireNoGQLErrors(t, gqlResponse)
   367  
   368  	var result struct {
   369  		UpdateAuthor struct {
   370  			Author []*author
   371  		}
   372  	}
   373  	err := json.Unmarshal([]byte(gqlResponse.Data), &result)
   374  	require.NoError(t, err)
   375  	require.Len(t, result.UpdateAuthor.Author, 1)
   376  
   377  	if diff :=
   378  		cmp.Diff(expectedAuthor, result.UpdateAuthor.Author[0], ignoreOpts()...); diff != "" {
   379  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   380  	}
   381  
   382  	requireAuthor(t, newAuth.ID, expectedAuthor, executeRequest)
   383  	p := &post{
   384  		PostID: newAuth.Posts[0].PostID,
   385  		Title:  newAuth.Posts[0].Title,
   386  		Text:   newAuth.Posts[0].Text,
   387  		Tags:   []string{},
   388  		Author: nil,
   389  	}
   390  	requirePost(t, newAuth.Posts[0].PostID, p, false, executeRequest)
   391  
   392  	cleanUp(t,
   393  		[]*country{newCountry, anotherCountry},
   394  		[]*author{newAuth},
   395  		[]*post{newAuth.Posts[0], newAuth.Posts[0], patchSet.Posts[0]})
   396  }
   397  
   398  func testMultipleMutations(t *testing.T) {
   399  	newCountry := addCountry(t, postExecutor)
   400  
   401  	auth1 := &author{
   402  		Name:    "New Author1",
   403  		Country: newCountry,
   404  		Posts: []*post{
   405  			{
   406  				Title: "A New Post",
   407  				Text:  "Text of new post",
   408  				Tags:  []string{},
   409  			},
   410  			{
   411  				Title: "Another New Post",
   412  				Text:  "Text of other new post",
   413  				Tags:  []string{},
   414  			},
   415  		},
   416  	}
   417  
   418  	auth2 := &author{
   419  		Name:    "New Author2",
   420  		Country: newCountry,
   421  		Posts: []*post{
   422  			{
   423  				Title: "A Wonder Post",
   424  				Text:  "Text of wonder post",
   425  				Tags:  []string{},
   426  			},
   427  			{
   428  				Title: "Another Wonder Post",
   429  				Text:  "Text of other wonder post",
   430  				Tags:  []string{},
   431  			},
   432  		},
   433  	}
   434  
   435  	expectedAuthors := []*author{auth1, auth2}
   436  	newAuths := addMultipleAuthorFromRef(t, expectedAuthors, postExecutor)
   437  
   438  	for _, auth := range newAuths {
   439  		postSort := func(i, j int) bool {
   440  			return auth.Posts[i].Title < auth.Posts[j].Title
   441  		}
   442  		sort.Slice(auth.Posts, postSort)
   443  	}
   444  
   445  	for i := range expectedAuthors {
   446  		for j := range expectedAuthors[i].Posts {
   447  			expectedAuthors[i].Posts[j].PostID = newAuths[i].Posts[j].PostID
   448  		}
   449  	}
   450  
   451  	for i := range newAuths {
   452  		requireAuthor(t, newAuths[i].ID, expectedAuthors[i], postExecutor)
   453  		require.Equal(t, len(newAuths[i].Posts), 2)
   454  		for j := range newAuths[i].Posts {
   455  			expectedAuthors[i].Posts[j].Author = &author{
   456  				ID:      newAuths[i].ID,
   457  				Name:    expectedAuthors[i].Name,
   458  				Dob:     expectedAuthors[i].Dob,
   459  				Country: expectedAuthors[i].Country,
   460  			}
   461  			requirePost(t, newAuths[i].Posts[j].PostID, expectedAuthors[i].Posts[j],
   462  				true, postExecutor)
   463  		}
   464  	}
   465  
   466  	cleanUp(t,
   467  		[]*country{newCountry},
   468  		newAuths,
   469  		append(newAuths[0].Posts, newAuths[1].Posts...))
   470  }
   471  
   472  func addMultipleAuthorFromRef(t *testing.T, newAuthor []*author,
   473  	executeRequest requestExecutor) []*author {
   474  	addAuthorParams := &GraphQLParams{
   475  		Query: `mutation addAuthor($author: [AddAuthorInput!]!) {
   476  			addAuthor(input: $author) {
   477  			  	author {
   478  					id
   479  					name
   480  					reputation
   481  					country {
   482  						id
   483  				  		name
   484  					}
   485  					posts(order: { asc: title }) {
   486  						postID
   487  						title
   488  						text
   489  						tags
   490  						category {
   491  							id
   492  							name
   493  						}
   494  					}
   495  			  	}
   496  			}
   497  		}`,
   498  		Variables: map[string]interface{}{"author": newAuthor},
   499  	}
   500  
   501  	gqlResponse := executeRequest(t, graphqlURL, addAuthorParams)
   502  	requireNoGQLErrors(t, gqlResponse)
   503  
   504  	var result struct {
   505  		AddAuthor struct {
   506  			Author []*author
   507  		}
   508  	}
   509  	err := json.Unmarshal([]byte(gqlResponse.Data), &result)
   510  	require.NoError(t, err)
   511  
   512  	for i := range result.AddAuthor.Author {
   513  		requireUID(t, result.AddAuthor.Author[i].ID)
   514  	}
   515  
   516  	authorSort := func(i, j int) bool {
   517  		return result.AddAuthor.Author[i].Name < result.AddAuthor.Author[j].Name
   518  	}
   519  	sort.Slice(result.AddAuthor.Author, authorSort)
   520  	if diff := cmp.Diff(newAuthor, result.AddAuthor.Author, ignoreOpts()...); diff != "" {
   521  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   522  	}
   523  
   524  	return result.AddAuthor.Author
   525  
   526  }
   527  
   528  func deepXIDMutations(t *testing.T) {
   529  	deepXIDTest(t, postExecutor)
   530  }
   531  
   532  func deepXIDTest(t *testing.T, executeRequest requestExecutor) {
   533  	newCountry := &country{
   534  		Name: "A Country",
   535  		States: []*state{
   536  			{Name: "Alphabet", Code: "ABC"},
   537  			{Name: "A State", Code: "XYZ"},
   538  		},
   539  	}
   540  
   541  	// mutations get run serially, each in their own transaction, so the addState
   542  	// sets up the "XZY" xid that's used by the following mutation.
   543  	addCountryParams := &GraphQLParams{
   544  		Query: `mutation addCountry($input: AddCountryInput!) {
   545  			addState(input: [{ xcode: "XYZ", name: "A State" }]) {
   546  				state { id xcode name }
   547  			}
   548  
   549  			addCountry(input: [$input])
   550  			{
   551  				country {
   552  					id
   553  					name
   554  					states(order: { asc: xcode }) {
   555  						id
   556  						xcode
   557  						name
   558  					}
   559  				}
   560  			}
   561  		}`,
   562  		Variables: map[string]interface{}{"input": newCountry},
   563  	}
   564  
   565  	gqlResponse := executeRequest(t, graphqlURL, addCountryParams)
   566  	requireNoGQLErrors(t, gqlResponse)
   567  
   568  	var addResult struct {
   569  		AddState struct {
   570  			State []*state
   571  		}
   572  		AddCountry struct {
   573  			Country []*country
   574  		}
   575  	}
   576  	err := json.Unmarshal([]byte(gqlResponse.Data), &addResult)
   577  	require.NoError(t, err)
   578  
   579  	require.NotNil(t, addResult)
   580  	require.NotNil(t, addResult.AddState)
   581  	require.NotNil(t, addResult.AddCountry)
   582  
   583  	// because the two mutations are linked by an XID, the addCountry mutation shouldn't
   584  	// have created a new state for "XYZ", so the UIDs should be the same
   585  	require.Equal(t, addResult.AddState.State[0].ID, addResult.AddCountry.Country[0].States[1].ID)
   586  
   587  	if diff := cmp.Diff(newCountry, addResult.AddCountry.Country[0], ignoreOpts()...); diff != "" {
   588  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   589  	}
   590  
   591  	patchSet := &country{
   592  		States: []*state{{Code: "DEF", Name: "Definitely A State"}},
   593  	}
   594  
   595  	patchRemove := &country{
   596  		States: []*state{{Code: "XYZ"}},
   597  	}
   598  
   599  	expectedCountry := &country{
   600  		Name:   "A Country",
   601  		States: []*state{newCountry.States[0], patchSet.States[0]},
   602  	}
   603  
   604  	updateCountryParams := &GraphQLParams{
   605  		Query: `mutation updateCountry($id: ID!, $set: CountryPatch!, $remove: CountryPatch!) {
   606  			addState(input: [{ xcode: "DEF", name: "Definitely A State" }]) {
   607  				state { id }
   608  			}
   609  
   610  			updateCountry(
   611  				input: {
   612  					filter: {id: [$id]},
   613  					set: $set,
   614  					remove: $remove
   615  				}
   616  			) {
   617  				country {
   618  					id
   619  					name
   620  					states(order: { asc: xcode }) {
   621  						id
   622  						xcode
   623  						name
   624  					}
   625  				}
   626  			}
   627  		}`,
   628  		Variables: map[string]interface{}{
   629  			"id":     addResult.AddCountry.Country[0].ID,
   630  			"set":    patchSet,
   631  			"remove": patchRemove,
   632  		},
   633  	}
   634  
   635  	gqlResponse = executeRequest(t, graphqlURL, updateCountryParams)
   636  	requireNoGQLErrors(t, gqlResponse)
   637  
   638  	var updResult struct {
   639  		AddState struct {
   640  			State []*state
   641  		}
   642  		UpdateCountry struct {
   643  			Country []*country
   644  		}
   645  	}
   646  	err = json.Unmarshal([]byte(gqlResponse.Data), &updResult)
   647  	require.NoError(t, err)
   648  	require.Len(t, updResult.UpdateCountry.Country, 1)
   649  
   650  	if diff :=
   651  		cmp.Diff(expectedCountry, updResult.UpdateCountry.Country[0], ignoreOpts()...); diff != "" {
   652  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   653  	}
   654  
   655  	requireCountry(t, addResult.AddCountry.Country[0].ID, expectedCountry, true, executeRequest)
   656  
   657  	// The "XYZ" state should have its country set back to null like it was before it was
   658  	// linked to the country
   659  	requireState(t, addResult.AddState.State[0].ID, addResult.AddState.State[0], executeRequest)
   660  
   661  	// No need to cleanup states ATM because, beyond this test,
   662  	// there's no queries that rely on them
   663  	cleanUp(t, []*country{addResult.AddCountry.Country[0]}, []*author{}, []*post{})
   664  }
   665  
   666  func addPost(t *testing.T, authorID, countryID string,
   667  	executeRequest requestExecutor) *post {
   668  
   669  	addPostParams := &GraphQLParams{
   670  		Query: `mutation addPost($post: AddPostInput!) {
   671  			addPost(input: [$post]) {
   672  			  post {
   673  				postID
   674  				title
   675  				text
   676  				isPublished
   677  				tags
   678  				numLikes
   679  				author {
   680  					id
   681  					name
   682  					country {
   683  						id
   684  						name
   685  					}
   686  				}
   687  			  }
   688  			}
   689  		}`,
   690  		Variables: map[string]interface{}{"post": map[string]interface{}{
   691  			"title":       "Test Post",
   692  			"text":        "This post is just a test.",
   693  			"isPublished": true,
   694  			"numLikes":    1000,
   695  			"tags":        []string{"example", "test"},
   696  			"author":      map[string]interface{}{"id": authorID},
   697  		}},
   698  	}
   699  
   700  	addPostExpected := fmt.Sprintf(`{ "addPost": {
   701  		"post": [{
   702  			"postID": "_UID_",
   703  			"title": "Test Post",
   704  			"text": "This post is just a test.",
   705  			"isPublished": true,
   706  			"tags": ["example", "test"],
   707  			"numLikes": 1000,
   708  			"author": {
   709  				"id": "%s",
   710  				"name": "Test Author",
   711  				"country": {
   712  					"id": "%s",
   713  					"name": "Testland"
   714  				}
   715  			}
   716  		}]
   717  	} }`, authorID, countryID)
   718  
   719  	gqlResponse := executeRequest(t, graphqlURL, addPostParams)
   720  	requireNoGQLErrors(t, gqlResponse)
   721  
   722  	var expected, result struct {
   723  		AddPost struct {
   724  			Post []*post
   725  		}
   726  	}
   727  	err := json.Unmarshal([]byte(addPostExpected), &expected)
   728  	require.NoError(t, err)
   729  	err = json.Unmarshal([]byte(gqlResponse.Data), &result)
   730  	require.NoError(t, err)
   731  
   732  	requireUID(t, result.AddPost.Post[0].PostID)
   733  
   734  	opt := cmpopts.IgnoreFields(post{}, "PostID")
   735  	if diff := cmp.Diff(expected, result, opt); diff != "" {
   736  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   737  	}
   738  
   739  	return result.AddPost.Post[0]
   740  }
   741  
   742  func requirePost(
   743  	t *testing.T,
   744  	postID string,
   745  	expectedPost *post,
   746  	getAuthor bool,
   747  	executeRequest requestExecutor) {
   748  
   749  	params := &GraphQLParams{
   750  		Query: `query getPost($id: ID!, $getAuthor: Boolean!)  {
   751  			getPost(postID: $id) {
   752  				postID
   753  				title
   754  				text
   755  				isPublished
   756  				tags
   757  				numLikes
   758  				author @include(if: $getAuthor) {
   759  					id
   760  					name
   761  					country {
   762  						id
   763  						name
   764  					}
   765  				}
   766  			}
   767  		}`,
   768  		Variables: map[string]interface{}{
   769  			"id":        postID,
   770  			"getAuthor": getAuthor,
   771  		},
   772  	}
   773  
   774  	gqlResponse := executeRequest(t, graphqlURL, params)
   775  	requireNoGQLErrors(t, gqlResponse)
   776  
   777  	var result struct {
   778  		GetPost *post
   779  	}
   780  	err := json.Unmarshal([]byte(gqlResponse.Data), &result)
   781  	require.NoError(t, err)
   782  
   783  	if diff := cmp.Diff(expectedPost, result.GetPost); diff != "" {
   784  		t.Errorf("result mismatch (-want +got):\n%s", diff)
   785  	}
   786  }
   787  
   788  func updateMutationByIds(t *testing.T) {
   789  	newCountry := addCountry(t, postExecutor)
   790  	anotherCountry := addCountry(t, postExecutor)
   791  
   792  	t.Run("update Country", func(t *testing.T) {
   793  		filter := map[string]interface{}{
   794  			"id": []string{newCountry.ID, anotherCountry.ID},
   795  		}
   796  		newName := "updated name"
   797  		updateCountry(t, filter, newName, true)
   798  		newCountry.Name = newName
   799  		anotherCountry.Name = newName
   800  
   801  		requireCountry(t, newCountry.ID, newCountry, false, postExecutor)
   802  		requireCountry(t, anotherCountry.ID, anotherCountry, false, postExecutor)
   803  	})
   804  
   805  	cleanUp(t, []*country{newCountry, anotherCountry}, []*author{}, []*post{})
   806  }
   807  
   808  func nameRegexFilter(name string) map[string]interface{} {
   809  	return map[string]interface{}{
   810  		"name": map[string]interface{}{
   811  			"regexp": "/" + name + "/",
   812  		},
   813  	}
   814  }
   815  
   816  func updateMutationByName(t *testing.T) {
   817  	// Create two countries, update name of the first. Then do a conditional mutation which
   818  	// should only update the name of the second country.
   819  	newCountry := addCountry(t, postExecutor)
   820  	t.Run("update Country", func(t *testing.T) {
   821  		filter := nameRegexFilter(newCountry.Name)
   822  		newName := "updated name"
   823  		updateCountry(t, filter, newName, true)
   824  		newCountry.Name = newName
   825  		requireCountry(t, newCountry.ID, newCountry, false, postExecutor)
   826  	})
   827  
   828  	anotherCountry := addCountry(t, postExecutor)
   829  	// Update name for country where name is anotherCountry.Name
   830  	t.Run("update country by name", func(t *testing.T) {
   831  		filter := nameRegexFilter(anotherCountry.Name)
   832  		anotherCountry.Name = "updated another country name"
   833  		updateCountry(t, filter, anotherCountry.Name, true)
   834  	})
   835  
   836  	t.Run("check updated Country", func(t *testing.T) {
   837  		// newCountry should not have been updated.
   838  		requireCountry(t, newCountry.ID, newCountry, false, postExecutor)
   839  		requireCountry(t, anotherCountry.ID, anotherCountry, false, postExecutor)
   840  	})
   841  
   842  	cleanUp(t, []*country{newCountry, anotherCountry}, []*author{}, []*post{})
   843  }
   844  
   845  func updateMutationByNameNoMatch(t *testing.T) {
   846  	// The countries shouldn't get updated as the query shouldn't match any nodes.
   847  	newCountry := addCountry(t, postExecutor)
   848  	anotherCountry := addCountry(t, postExecutor)
   849  	t.Run("update Country", func(t *testing.T) {
   850  		filter := nameRegexFilter("no match")
   851  		updateCountry(t, filter, "new name", false)
   852  		requireCountry(t, newCountry.ID, newCountry, false, postExecutor)
   853  		requireCountry(t, anotherCountry.ID, anotherCountry, false, postExecutor)
   854  	})
   855  
   856  	cleanUp(t, []*country{newCountry, anotherCountry}, []*author{}, []*post{})
   857  }
   858  
   859  func updateDelete(t *testing.T) {
   860  	newCountry := addCountry(t, postExecutor)
   861  	newAuthor := addAuthor(t, newCountry.ID, postExecutor)
   862  	newPost := addPost(t, newAuthor.ID, newCountry.ID, postExecutor)
   863  
   864  	filter := map[string]interface{}{
   865  		"postID": []string{newPost.PostID},
   866  	}
   867  	delPatch := map[string]interface{}{
   868  		"text":        "This post is just a test.",
   869  		"isPublished": nil,
   870  		"tags":        []string{"test", "notatag"},
   871  		"numLikes":    999,
   872  	}
   873  
   874  	updateParams := &GraphQLParams{
   875  		Query: `mutation updPost($filter: PostFilter!, $del: PostPatch!) {
   876  			updatePost(input: { filter: $filter, remove: $del }) {
   877  				post {
   878  					text
   879  					isPublished
   880  					tags
   881  					numLikes
   882  				}
   883  			}
   884  		}`,
   885  		Variables: map[string]interface{}{"filter": filter, "del": delPatch},
   886  	}
   887  
   888  	gqlResponse := updateParams.ExecuteAsPost(t, graphqlURL)
   889  	requireNoGQLErrors(t, gqlResponse)
   890  
   891  	require.JSONEq(t, `{
   892  			"updatePost": {
   893  				"post": [
   894  					{
   895  						"text": null,
   896  						"isPublished": null,
   897  						"tags": ["example"],
   898  						"numLikes": 1000
   899  					}
   900  				]
   901  			}
   902  		}`,
   903  		string([]byte(gqlResponse.Data)))
   904  
   905  	newPost.Text = ""                  // was deleted because the given val was correct
   906  	newPost.Tags = []string{"example"} // the intersection of the tags was deleted
   907  	newPost.IsPublished = false        // must have been deleted because was set to nil in the patch
   908  	// newPost.NumLikes stays the same because the value in the patch was wrong
   909  	requirePost(t, newPost.PostID, newPost, true, postExecutor)
   910  
   911  	cleanUp(t, []*country{newCountry}, []*author{newAuthor}, []*post{newPost})
   912  }
   913  
   914  func updateCountry(t *testing.T, filter map[string]interface{}, newName string, shouldUpdate bool) {
   915  	updateParams := &GraphQLParams{
   916  		Query: `mutation newName($filter: CountryFilter!, $newName: String!) {
   917  			updateCountry(input: { filter: $filter, set: { name: $newName } }) {
   918  				country {
   919  					id
   920  					name
   921  				}
   922  			}
   923  		}`,
   924  		Variables: map[string]interface{}{"filter": filter, "newName": newName},
   925  	}
   926  
   927  	gqlResponse := updateParams.ExecuteAsPost(t, graphqlURL)
   928  	requireNoGQLErrors(t, gqlResponse)
   929  
   930  	var result struct {
   931  		UpdateCountry struct {
   932  			Country []*country
   933  		}
   934  	}
   935  
   936  	err := json.Unmarshal([]byte(gqlResponse.Data), &result)
   937  	require.NoError(t, err)
   938  	if shouldUpdate {
   939  		require.NotEqual(t, 0, len(result.UpdateCountry.Country))
   940  	}
   941  	for _, c := range result.UpdateCountry.Country {
   942  		require.NotNil(t, c.ID)
   943  		require.Equal(t, newName, c.Name)
   944  	}
   945  }
   946  
   947  func filterInUpdate(t *testing.T) {
   948  	countries := make([]country, 0, 4)
   949  	for i := 0; i < 4; i++ {
   950  		country := addCountry(t, postExecutor)
   951  		country.Name = "updatedValue"
   952  		countries = append(countries, *country)
   953  	}
   954  	countries[3].Name = "Testland"
   955  
   956  	cases := map[string]struct {
   957  		Filter          map[string]interface{}
   958  		FilterCountries map[string]interface{}
   959  		Expected        int
   960  		Countries       []*country
   961  	}{
   962  		"Eq filter": {
   963  			Filter: map[string]interface{}{
   964  				"name": map[string]interface{}{
   965  					"eq": "Testland",
   966  				},
   967  				"and": map[string]interface{}{
   968  					"id": []string{countries[0].ID, countries[1].ID},
   969  				},
   970  			},
   971  			FilterCountries: map[string]interface{}{
   972  				"id": []string{countries[1].ID},
   973  			},
   974  			Expected:  1,
   975  			Countries: []*country{&countries[0], &countries[1]},
   976  		},
   977  
   978  		"ID Filter": {
   979  			Filter: map[string]interface{}{
   980  				"id": []string{countries[2].ID},
   981  			},
   982  			FilterCountries: map[string]interface{}{
   983  				"id": []string{countries[2].ID, countries[3].ID},
   984  			},
   985  			Expected:  1,
   986  			Countries: []*country{&countries[2], &countries[3]},
   987  		},
   988  	}
   989  
   990  	for name, test := range cases {
   991  		t.Run(name, func(t *testing.T) {
   992  			updateParams := &GraphQLParams{
   993  				Query: `mutation newName($filter: CountryFilter!, $newName: String!,
   994  					 $filterCountries: CountryFilter!) {
   995  			updateCountry(input: { filter: $filter, set: { name: $newName } }) {
   996  				country(filter: $filterCountries) {
   997  					id
   998  					name
   999  				}
  1000  			}
  1001  		}`,
  1002  				Variables: map[string]interface{}{
  1003  					"filter":          test.Filter,
  1004  					"newName":         "updatedValue",
  1005  					"filterCountries": test.FilterCountries,
  1006  				},
  1007  			}
  1008  
  1009  			gqlResponse := updateParams.ExecuteAsPost(t, graphqlURL)
  1010  			requireNoGQLErrors(t, gqlResponse)
  1011  
  1012  			var result struct {
  1013  				UpdateCountry struct {
  1014  					Country []*country
  1015  				}
  1016  			}
  1017  
  1018  			err := json.Unmarshal([]byte(gqlResponse.Data), &result)
  1019  			require.NoError(t, err)
  1020  
  1021  			require.Equal(t, len(result.UpdateCountry.Country), test.Expected)
  1022  			for i := 0; i < test.Expected; i++ {
  1023  				require.Equal(t, result.UpdateCountry.Country[i].Name, "updatedValue")
  1024  			}
  1025  
  1026  			for _, country := range test.Countries {
  1027  				requireCountry(t, country.ID, country, false, postExecutor)
  1028  			}
  1029  			cleanUp(t, test.Countries, nil, nil)
  1030  		})
  1031  	}
  1032  }
  1033  
  1034  func deleteMutationWithMultipleIds(t *testing.T) {
  1035  	country := addCountry(t, postExecutor)
  1036  	anotherCountry := addCountry(t, postExecutor)
  1037  	t.Run("delete Country", func(t *testing.T) {
  1038  		deleteCountryExpected := `{"deleteCountry" : { "msg": "Deleted" } }`
  1039  		filter := map[string]interface{}{"id": []string{country.ID, anotherCountry.ID}}
  1040  		deleteCountry(t, filter, deleteCountryExpected, nil)
  1041  	})
  1042  
  1043  	t.Run("check Country is deleted", func(t *testing.T) {
  1044  		requireCountry(t, country.ID, nil, false, postExecutor)
  1045  		requireCountry(t, anotherCountry.ID, nil, false, postExecutor)
  1046  	})
  1047  }
  1048  
  1049  func deleteMutationWithSingleID(t *testing.T) {
  1050  	newCountry := addCountry(t, postExecutor)
  1051  	anotherCountry := addCountry(t, postExecutor)
  1052  	t.Run("delete Country", func(t *testing.T) {
  1053  		deleteCountryExpected := `{"deleteCountry" : { "msg": "Deleted" } }`
  1054  		filter := map[string]interface{}{"id": []string{newCountry.ID}}
  1055  		deleteCountry(t, filter, deleteCountryExpected, nil)
  1056  	})
  1057  
  1058  	// In this case anotherCountry shouldn't be deleted.
  1059  	t.Run("check Country is deleted", func(t *testing.T) {
  1060  		requireCountry(t, newCountry.ID, nil, false, postExecutor)
  1061  		requireCountry(t, anotherCountry.ID, anotherCountry, false, postExecutor)
  1062  	})
  1063  	cleanUp(t, []*country{anotherCountry}, nil, nil)
  1064  }
  1065  
  1066  func deleteMutationByName(t *testing.T) {
  1067  	newCountry := addCountry(t, postExecutor)
  1068  	anotherCountry := addCountry(t, postExecutor)
  1069  	anotherCountry.Name = "New country"
  1070  	filter := map[string]interface{}{
  1071  		"id": []string{anotherCountry.ID},
  1072  	}
  1073  	updateCountry(t, filter, anotherCountry.Name, true)
  1074  
  1075  	deleteCountryExpected := `{"deleteCountry" : { "msg": "Deleted" } }`
  1076  	t.Run("delete Country", func(t *testing.T) {
  1077  		filter := map[string]interface{}{
  1078  			"name": map[string]interface{}{
  1079  				"regexp": "/" + newCountry.Name + "/",
  1080  			},
  1081  		}
  1082  		deleteCountry(t, filter, deleteCountryExpected, nil)
  1083  	})
  1084  
  1085  	// In this case anotherCountry shouldn't be deleted.
  1086  	t.Run("check Country is deleted", func(t *testing.T) {
  1087  		requireCountry(t, newCountry.ID, nil, false, postExecutor)
  1088  		requireCountry(t, anotherCountry.ID, anotherCountry, false, postExecutor)
  1089  	})
  1090  	cleanUp(t, []*country{anotherCountry}, nil, nil)
  1091  }
  1092  
  1093  func deleteCountry(
  1094  	t *testing.T,
  1095  	filter map[string]interface{},
  1096  	deleteCountryExpected string,
  1097  	expectedErrors x.GqlErrorList) {
  1098  
  1099  	deleteCountryParams := &GraphQLParams{
  1100  		Query: `mutation deleteCountry($filter: CountryFilter!) {
  1101  			deleteCountry(filter: $filter) { msg }
  1102  		}`,
  1103  		Variables: map[string]interface{}{"filter": filter},
  1104  	}
  1105  
  1106  	gqlResponse := deleteCountryParams.ExecuteAsPost(t, graphqlURL)
  1107  	require.JSONEq(t, deleteCountryExpected, string(gqlResponse.Data))
  1108  
  1109  	if diff := cmp.Diff(expectedErrors, gqlResponse.Errors); diff != "" {
  1110  		t.Errorf("errors mismatch (-want +got):\n%s", diff)
  1111  	}
  1112  }
  1113  
  1114  func deleteAuthor(
  1115  	t *testing.T,
  1116  	authorID string,
  1117  	deleteAuthorExpected string,
  1118  	expectedErrors x.GqlErrorList) {
  1119  
  1120  	deleteAuthorParams := &GraphQLParams{
  1121  		Query: `mutation deleteAuthor($filter: AuthorFilter!) {
  1122  			deleteAuthor(filter: $filter) { msg }
  1123  		}`,
  1124  		Variables: map[string]interface{}{
  1125  			"filter": map[string]interface{}{
  1126  				"id": []string{authorID},
  1127  			},
  1128  		},
  1129  	}
  1130  
  1131  	gqlResponse := deleteAuthorParams.ExecuteAsPost(t, graphqlURL)
  1132  
  1133  	require.JSONEq(t, deleteAuthorExpected, string(gqlResponse.Data))
  1134  
  1135  	if diff := cmp.Diff(expectedErrors, gqlResponse.Errors); diff != "" {
  1136  		t.Errorf("errors mismatch (-want +got):\n%s", diff)
  1137  	}
  1138  }
  1139  
  1140  func deletePost(
  1141  	t *testing.T,
  1142  	postID string,
  1143  	deletePostExpected string,
  1144  	expectedErrors x.GqlErrorList) {
  1145  
  1146  	deletePostParams := &GraphQLParams{
  1147  		Query: `mutation deletePost($filter: PostFilter!) {
  1148  			deletePost(filter: $filter) { msg }
  1149  		}`,
  1150  		Variables: map[string]interface{}{"filter": map[string]interface{}{
  1151  			"postID": []string{postID},
  1152  		}},
  1153  	}
  1154  
  1155  	gqlResponse := deletePostParams.ExecuteAsPost(t, graphqlURL)
  1156  
  1157  	require.JSONEq(t, deletePostExpected, string(gqlResponse.Data))
  1158  
  1159  	if diff := cmp.Diff(expectedErrors, gqlResponse.Errors); diff != "" {
  1160  		t.Errorf("errors mismatch (-want +got):\n%s", diff)
  1161  	}
  1162  }
  1163  
  1164  func deleteWrongID(t *testing.T) {
  1165  	t.Skip()
  1166  	// Skipping the test for now because wrong type of node while deleting is not an error.
  1167  	// After Dgraph returns the number of nodes modified from upsert, modify this test to check
  1168  	// count of nodes modified is 0.
  1169  	//
  1170  	// FIXME: Test cases : with a wrongID, a malformed ID "blah", and maybe a filter that
  1171  	// doesn't match anything.
  1172  	newCountry := addCountry(t, postExecutor)
  1173  	newAuthor := addAuthor(t, newCountry.ID, postExecutor)
  1174  
  1175  	expectedData := `{ "deleteCountry": null }`
  1176  	expectedErrors := x.GqlErrorList{
  1177  		&x.GqlError{Message: `input: couldn't complete deleteCountry because ` +
  1178  			fmt.Sprintf(`input: Node with id %s is not of type Country`, newAuthor.ID)}}
  1179  
  1180  	filter := map[string]interface{}{"id": []string{newAuthor.ID}}
  1181  	deleteCountry(t, filter, expectedData, expectedErrors)
  1182  
  1183  	cleanUp(t, []*country{newCountry}, []*author{newAuthor}, []*post{})
  1184  }
  1185  
  1186  func manyMutations(t *testing.T) {
  1187  	newCountry := addCountry(t, postExecutor)
  1188  	multiMutationParams := &GraphQLParams{
  1189  		Query: `mutation addCountries($name1: String!, $filter: CountryFilter!, $name2: String!) {
  1190  			add1: addCountry(input: [{ name: $name1 }]) {
  1191  				country {
  1192  					id
  1193  					name
  1194  				}
  1195  			}
  1196  
  1197  			deleteCountry(filter: $filter) { msg }
  1198  
  1199  			add2: addCountry(input: [{ name: $name2 }]) {
  1200  				country {
  1201  					id
  1202  					name
  1203  				}
  1204  			}
  1205  		}`,
  1206  		Variables: map[string]interface{}{
  1207  			"name1": "Testland1", "filter": map[string]interface{}{
  1208  				"id": []string{newCountry.ID}}, "name2": "Testland2"},
  1209  	}
  1210  	multiMutationExpected := `{
  1211  		"add1": { "country": [{ "id": "_UID_", "name": "Testland1" }] },
  1212  		"deleteCountry" : { "msg": "Deleted" },
  1213  		"add2": { "country": [{ "id": "_UID_", "name": "Testland2" }] }
  1214  	}`
  1215  
  1216  	gqlResponse := multiMutationParams.ExecuteAsPost(t, graphqlURL)
  1217  	requireNoGQLErrors(t, gqlResponse)
  1218  
  1219  	var expected, result struct {
  1220  		Add1 struct {
  1221  			Country []*country
  1222  		}
  1223  		DeleteCountry struct {
  1224  			Msg string
  1225  		}
  1226  		Add2 struct {
  1227  			Country []*country
  1228  		}
  1229  	}
  1230  	err := json.Unmarshal([]byte(multiMutationExpected), &expected)
  1231  	require.NoError(t, err)
  1232  	err = json.Unmarshal([]byte(gqlResponse.Data), &result)
  1233  	require.NoError(t, err)
  1234  
  1235  	opt := cmpopts.IgnoreFields(country{}, "ID")
  1236  	if diff := cmp.Diff(expected, result, opt); diff != "" {
  1237  		t.Errorf("result mismatch (-want +got):\n%s", diff)
  1238  	}
  1239  
  1240  	t.Run("country deleted", func(t *testing.T) {
  1241  		requireCountry(t, newCountry.ID, nil, false, postExecutor)
  1242  	})
  1243  
  1244  	cleanUp(t, append(result.Add1.Country, result.Add2.Country...), []*author{}, []*post{})
  1245  }
  1246  
  1247  func testSelectionInAddObject(t *testing.T) {
  1248  	newCountry := addCountry(t, postExecutor)
  1249  	newAuth := addAuthor(t, newCountry.ID, postExecutor)
  1250  
  1251  	post1 := &post{
  1252  		Title:  "Test1",
  1253  		Author: newAuth,
  1254  	}
  1255  
  1256  	post2 := &post{
  1257  		Title:  "Test2",
  1258  		Author: newAuth,
  1259  	}
  1260  
  1261  	cases := map[string]struct {
  1262  		Filter   map[string]interface{}
  1263  		First    int
  1264  		Offset   int
  1265  		Sort     map[string]interface{}
  1266  		Expected []*post
  1267  	}{
  1268  		"Pagination": {
  1269  			First:  1,
  1270  			Offset: 1,
  1271  			Sort: map[string]interface{}{
  1272  				"desc": "title",
  1273  			},
  1274  			Expected: []*post{post1},
  1275  		},
  1276  		"Filter": {
  1277  			Filter: map[string]interface{}{
  1278  				"title": map[string]interface{}{
  1279  					"anyoftext": "Test1",
  1280  				},
  1281  			},
  1282  			Expected: []*post{post1},
  1283  		},
  1284  		"Sort": {
  1285  			Sort: map[string]interface{}{
  1286  				"desc": "title",
  1287  			},
  1288  			Expected: []*post{post2, post1},
  1289  		},
  1290  	}
  1291  
  1292  	for name, test := range cases {
  1293  		t.Run(name, func(t *testing.T) {
  1294  			addPostParams := &GraphQLParams{
  1295  				Query: `mutation addPost($posts: [AddPostInput!]!, $filter:
  1296  					PostFilter, $first: Int, $offset: Int, $sort: PostOrder) {
  1297  				addPost(input: $posts) {
  1298  				  post (first:$first, offset:$offset, filter:$filter, order:$sort){
  1299  					postID
  1300  					title
  1301  				  }
  1302  				}
  1303  			}`,
  1304  				Variables: map[string]interface{}{
  1305  					"posts":  []*post{post1, post2},
  1306  					"first":  test.First,
  1307  					"offset": test.Offset,
  1308  					"sort":   test.Sort,
  1309  					"filter": test.Filter,
  1310  				},
  1311  			}
  1312  
  1313  			gqlResponse := postExecutor(t, graphqlURL, addPostParams)
  1314  			requireNoGQLErrors(t, gqlResponse)
  1315  			var result struct {
  1316  				AddPost struct {
  1317  					Post []*post
  1318  				}
  1319  			}
  1320  
  1321  			err := json.Unmarshal([]byte(gqlResponse.Data), &result)
  1322  			require.NoError(t, err)
  1323  
  1324  			opt := cmpopts.IgnoreFields(post{}, "PostID", "Author")
  1325  			if diff := cmp.Diff(test.Expected, result.AddPost.Post, opt); diff != "" {
  1326  				t.Errorf("result mismatch (-want +got):\n%s", diff)
  1327  			}
  1328  
  1329  			cleanUp(t, []*country{}, []*author{}, result.AddPost.Post)
  1330  		})
  1331  
  1332  	}
  1333  
  1334  	cleanUp(t, []*country{newCountry}, []*author{newAuth}, []*post{})
  1335  
  1336  }
  1337  
  1338  // After a successful mutation, the following query is executed.  That query can
  1339  // contain any depth or filtering that makes sense for the schema.
  1340  //
  1341  // I this case, we set up an author with existing posts, then add another post.
  1342  // The filter is down inside post->author->posts and finds just one of the
  1343  // author's posts.
  1344  func mutationWithDeepFilter(t *testing.T) {
  1345  
  1346  	newCountry := addCountry(t, postExecutor)
  1347  	newAuthor := addAuthor(t, newCountry.ID, postExecutor)
  1348  
  1349  	// Make sure they have a post not found by the filter
  1350  	newPost := addPost(t, newAuthor.ID, newCountry.ID, postExecutor)
  1351  
  1352  	addPostParams := &GraphQLParams{
  1353  		Query: `mutation addPost($post: AddPostInput!) {
  1354  			addPost(input: [$post]) {
  1355  			  post {
  1356  				postID
  1357  				author {
  1358  					posts(filter: { title: { allofterms: "find me" }}) {
  1359  						title
  1360  					}
  1361  				}
  1362  			  }
  1363  			}
  1364  		}`,
  1365  		Variables: map[string]interface{}{"post": map[string]interface{}{
  1366  			"title":  "find me : a test of deep search after mutation",
  1367  			"author": map[string]interface{}{"id": newAuthor.ID},
  1368  		}},
  1369  	}
  1370  
  1371  	// Expect the filter to find just the new post, not any of the author's existing posts.
  1372  	addPostExpected := `{ "addPost": {
  1373  		"post": [{
  1374  			"postID": "_UID_",
  1375  			"author": {
  1376  				"posts": [ { "title": "find me : a test of deep search after mutation" } ]
  1377  			}
  1378  		}]
  1379  	} }`
  1380  
  1381  	gqlResponse := addPostParams.ExecuteAsPost(t, graphqlURL)
  1382  	requireNoGQLErrors(t, gqlResponse)
  1383  
  1384  	var expected, result struct {
  1385  		AddPost struct {
  1386  			Post []*post
  1387  		}
  1388  	}
  1389  	err := json.Unmarshal([]byte(addPostExpected), &expected)
  1390  	require.NoError(t, err)
  1391  	err = json.Unmarshal([]byte(gqlResponse.Data), &result)
  1392  	require.NoError(t, err)
  1393  
  1394  	requireUID(t, result.AddPost.Post[0].PostID)
  1395  
  1396  	opt := cmpopts.IgnoreFields(post{}, "PostID")
  1397  	if diff := cmp.Diff(expected, result, opt); diff != "" {
  1398  		t.Errorf("result mismatch (-want +got):\n%s", diff)
  1399  	}
  1400  
  1401  	cleanUp(t, []*country{newCountry}, []*author{newAuthor},
  1402  		[]*post{newPost, result.AddPost.Post[0]})
  1403  }
  1404  
  1405  // TestManyMutationsWithQueryError : If there are multiple mutations and an error
  1406  // occurs in the mutation, then then following mutations aren't executed.  That's
  1407  // tested by TestManyMutationsWithError in the resolver tests.
  1408  //
  1409  // However, there can also be an error in the query following a mutation, but
  1410  // that shouldn't stop the following mutations because the actual mutation
  1411  // went through without error.
  1412  func manyMutationsWithQueryError(t *testing.T) {
  1413  	newCountry := addCountry(t, postExecutor)
  1414  
  1415  	// delete the country's name.
  1416  	// The schema states type Country `{ ... name: String! ... }`
  1417  	// so a query error will be raised if we ask for the country's name in a
  1418  	// query.  Don't think a GraphQL update can do this ATM, so do through Dgraph.
  1419  	d, err := grpc.Dial(alphagRPC, grpc.WithInsecure())
  1420  	require.NoError(t, err)
  1421  	client := dgo.NewDgraphClient(api.NewDgraphClient(d))
  1422  	mu := &api.Mutation{
  1423  		CommitNow: true,
  1424  		DelNquads: []byte(fmt.Sprintf("<%s> <Country.name> * .", newCountry.ID)),
  1425  	}
  1426  	_, err = client.NewTxn().Mutate(context.Background(), mu)
  1427  	require.NoError(t, err)
  1428  
  1429  	// add1 - should succeed
  1430  	// add2 - should succeed and also return an error (country doesn't have a name)
  1431  	// add3 - should succeed
  1432  	multiMutationParams := &GraphQLParams{
  1433  		Query: `mutation addCountries($countryID: ID!) {
  1434  			add1: addAuthor(input: [{ name: "A. N. Author", country: { id: $countryID }}]) {
  1435  				author {
  1436  					id
  1437  					name
  1438  					country {
  1439  						id
  1440  					}
  1441  				}
  1442  			}
  1443  
  1444  			add2: addAuthor(input: [{ name: "Ann Other Author", country: { id: $countryID }}]) {
  1445  				author {
  1446  					id
  1447  					name
  1448  					country {
  1449  						id
  1450  						name
  1451  					}
  1452  				}
  1453  			}
  1454  
  1455  			add3: addCountry(input: [{ name: "abc" }]) {
  1456  				country {
  1457  					id
  1458  					name
  1459  				}
  1460  			}
  1461  		}`,
  1462  		Variables: map[string]interface{}{"countryID": newCountry.ID},
  1463  	}
  1464  	expectedData := fmt.Sprintf(`{
  1465  		"add1": { "author": [{ "id": "_UID_", "name": "A. N. Author", "country": { "id": "%s" } }] },
  1466  		"add2": { "author": [{ "id": "_UID_", "name": "Ann Other Author", "country": null }] },
  1467  		"add3": { "country": [{ "id": "_UID_", "name": "abc" }] }
  1468  	}`, newCountry.ID)
  1469  
  1470  	expectedErrors := x.GqlErrorList{
  1471  		&x.GqlError{Message: `Non-nullable field 'name' (type String!) was not present ` +
  1472  			`in result from Dgraph.  GraphQL error propagation triggered.`,
  1473  			Locations: []x.Location{{Line: 18, Column: 7}},
  1474  			Path:      []interface{}{"add2", "author", float64(0), "country", "name"}}}
  1475  
  1476  	gqlResponse := multiMutationParams.ExecuteAsPost(t, graphqlURL)
  1477  
  1478  	if diff := cmp.Diff(expectedErrors, gqlResponse.Errors); diff != "" {
  1479  		t.Errorf("errors mismatch (-want +got):\n%s", diff)
  1480  	}
  1481  
  1482  	var expected, result struct {
  1483  		Add1 struct {
  1484  			Author []*author
  1485  		}
  1486  		Add2 struct {
  1487  			Author []*author
  1488  		}
  1489  		Add3 struct {
  1490  			Country []*country
  1491  		}
  1492  	}
  1493  	err = json.Unmarshal([]byte(expectedData), &expected)
  1494  	require.NoError(t, err)
  1495  
  1496  	err = json.Unmarshal([]byte(gqlResponse.Data), &result)
  1497  	require.NoError(t, err)
  1498  
  1499  	opt1 := cmpopts.IgnoreFields(author{}, "ID")
  1500  	opt2 := cmpopts.IgnoreFields(country{}, "ID")
  1501  	if diff := cmp.Diff(expected, result, opt1, opt2); diff != "" {
  1502  		t.Errorf("result mismatch (-want +got):\n%s", diff)
  1503  	}
  1504  
  1505  	cleanUp(t,
  1506  		[]*country{newCountry, result.Add3.Country[0]},
  1507  		[]*author{result.Add1.Author[0], result.Add2.Author[0]},
  1508  		[]*post{})
  1509  }
  1510  
  1511  func cleanUp(t *testing.T, countries []*country, authors []*author, posts []*post) {
  1512  	t.Run("cleaning up", func(t *testing.T) {
  1513  		for _, post := range posts {
  1514  			deletePost(t, post.PostID, `{"deletePost" : { "msg": "Deleted" } }`, nil)
  1515  		}
  1516  
  1517  		for _, author := range authors {
  1518  			deleteAuthor(t, author.ID, `{"deleteAuthor" : { "msg": "Deleted" } }`, nil)
  1519  		}
  1520  
  1521  		for _, country := range countries {
  1522  			filter := map[string]interface{}{"id": []string{country.ID}}
  1523  			deleteCountry(t, filter, `{"deleteCountry" : { "msg": "Deleted" } }`, nil)
  1524  		}
  1525  	})
  1526  }
  1527  
  1528  type starship struct {
  1529  	ID     string  `json:"id"`
  1530  	Name   string  `json:"name"`
  1531  	Length float64 `json:"length"`
  1532  }
  1533  
  1534  func addStarship(t *testing.T) *starship {
  1535  	addStarshipParams := &GraphQLParams{
  1536  		Query: `mutation addStarship($starship: AddStarshipInput!) {
  1537  			addStarship(input: [$starship]) {
  1538  				starship {
  1539  					id
  1540  					name
  1541  					length
  1542  			  	}
  1543  			}
  1544  		}`,
  1545  		Variables: map[string]interface{}{"starship": map[string]interface{}{
  1546  			"name":   "Millennium Falcon",
  1547  			"length": 2,
  1548  		}},
  1549  	}
  1550  
  1551  	gqlResponse := addStarshipParams.ExecuteAsPost(t, graphqlURL)
  1552  	requireNoGQLErrors(t, gqlResponse)
  1553  
  1554  	addStarshipExpected := fmt.Sprintf(`{"addStarship":{
  1555  		"starship":[{
  1556  			"name":"Millennium Falcon",
  1557  			"length":2
  1558  		}]
  1559  	}}`)
  1560  
  1561  	var expected, result struct {
  1562  		AddStarship struct {
  1563  			Starship []*starship
  1564  		}
  1565  	}
  1566  	err := json.Unmarshal([]byte(addStarshipExpected), &expected)
  1567  	require.NoError(t, err)
  1568  	err = json.Unmarshal([]byte(gqlResponse.Data), &result)
  1569  	require.NoError(t, err)
  1570  
  1571  	requireUID(t, result.AddStarship.Starship[0].ID)
  1572  
  1573  	opt := cmpopts.IgnoreFields(starship{}, "ID")
  1574  	if diff := cmp.Diff(expected, result, opt); diff != "" {
  1575  		t.Errorf("result mismatch (-want +got):\n%s", diff)
  1576  	}
  1577  
  1578  	return result.AddStarship.Starship[0]
  1579  }
  1580  
  1581  func addHuman(t *testing.T, starshipID string) string {
  1582  	addHumanParams := &GraphQLParams{
  1583  		Query: `mutation addHuman($human: AddHumanInput!) {
  1584  			addHuman(input: [$human]) {
  1585  				human {
  1586  					id
  1587  			  	}
  1588  			}
  1589  		}`,
  1590  		Variables: map[string]interface{}{"human": map[string]interface{}{
  1591  			"name":         "Han",
  1592  			"ename":        "Han_employee",
  1593  			"totalCredits": 10,
  1594  			"appearsIn":    []string{"EMPIRE"},
  1595  			"starships": []map[string]interface{}{{
  1596  				"id": starshipID,
  1597  			}},
  1598  		}},
  1599  	}
  1600  
  1601  	gqlResponse := addHumanParams.ExecuteAsPost(t, graphqlURL)
  1602  	requireNoGQLErrors(t, gqlResponse)
  1603  
  1604  	var result struct {
  1605  		AddHuman struct {
  1606  			Human []struct {
  1607  				ID string
  1608  			}
  1609  		}
  1610  	}
  1611  	err := json.Unmarshal([]byte(gqlResponse.Data), &result)
  1612  	require.NoError(t, err)
  1613  
  1614  	requireUID(t, result.AddHuman.Human[0].ID)
  1615  	return result.AddHuman.Human[0].ID
  1616  }
  1617  
  1618  func addDroid(t *testing.T) string {
  1619  	addDroidParams := &GraphQLParams{
  1620  		Query: `mutation addDroid($droid: AddDroidInput!) {
  1621  			addDroid(input: [$droid]) {
  1622  				droid {
  1623  					id
  1624  				}
  1625  			}
  1626  		}`,
  1627  		Variables: map[string]interface{}{"droid": map[string]interface{}{
  1628  			"name":            "R2-D2",
  1629  			"primaryFunction": "Robot",
  1630  			"appearsIn":       []string{"EMPIRE"},
  1631  		}},
  1632  	}
  1633  
  1634  	gqlResponse := addDroidParams.ExecuteAsPost(t, graphqlURL)
  1635  	requireNoGQLErrors(t, gqlResponse)
  1636  
  1637  	var result struct {
  1638  		AddDroid struct {
  1639  			Droid []struct {
  1640  				ID string
  1641  			}
  1642  		}
  1643  	}
  1644  	err := json.Unmarshal([]byte(gqlResponse.Data), &result)
  1645  	require.NoError(t, err)
  1646  
  1647  	requireUID(t, result.AddDroid.Droid[0].ID)
  1648  	return result.AddDroid.Droid[0].ID
  1649  }
  1650  
  1651  func updateCharacter(t *testing.T, id string) {
  1652  	updateCharacterParams := &GraphQLParams{
  1653  		Query: `mutation updateCharacter($character: UpdateCharacterInput!) {
  1654  			updateCharacter(input: $character) {
  1655  				character {
  1656  					name
  1657  				}
  1658  			}
  1659  		}`,
  1660  		Variables: map[string]interface{}{"character": map[string]interface{}{
  1661  			"filter": map[string]interface{}{
  1662  				"id": []string{id},
  1663  			},
  1664  			"set": map[string]interface{}{
  1665  				"name": "Han Solo",
  1666  			},
  1667  		}},
  1668  	}
  1669  
  1670  	gqlResponse := updateCharacterParams.ExecuteAsPost(t, graphqlURL)
  1671  	requireNoGQLErrors(t, gqlResponse)
  1672  }
  1673  
  1674  func queryInterfaceAfterAddMutation(t *testing.T) {
  1675  	newStarship := addStarship(t)
  1676  	humanID := addHuman(t, newStarship.ID)
  1677  	droidID := addDroid(t)
  1678  	updateCharacter(t, humanID)
  1679  
  1680  	t.Run("test query all characters", func(t *testing.T) {
  1681  		queryCharacterParams := &GraphQLParams{
  1682  			Query: `query {
  1683  			queryCharacter {
  1684  			  name
  1685  			  appearsIn
  1686  			  ... on Human {
  1687  				starships {
  1688  					name
  1689  					length
  1690  				}
  1691  				totalCredits
  1692  			  }
  1693  			  ... on Droid {
  1694  				primaryFunction
  1695  			  }
  1696  			}
  1697  		  }`,
  1698  		}
  1699  
  1700  		gqlResponse := queryCharacterParams.ExecuteAsPost(t, graphqlURL)
  1701  		requireNoGQLErrors(t, gqlResponse)
  1702  
  1703  		expected := `{
  1704  			"queryCharacter": [
  1705  			  {
  1706  				"name": "Han Solo",
  1707  				"appearsIn": ["EMPIRE"],
  1708  				"starships": [
  1709  				  {
  1710  					"name": "Millennium Falcon",
  1711  					"length": 2
  1712  				  }
  1713  				],
  1714  				"totalCredits": 10
  1715  			  },
  1716  			  {
  1717  				"name": "R2-D2",
  1718  				"appearsIn": ["EMPIRE"],
  1719  				"primaryFunction": "Robot"
  1720  			  }
  1721  			]
  1722  		  }`
  1723  
  1724  		testutil.CompareJSON(t, expected, string(gqlResponse.Data))
  1725  	})
  1726  
  1727  	t.Run("test query characters by name", func(t *testing.T) {
  1728  		queryCharacterByNameParams := &GraphQLParams{
  1729  			Query: `query {
  1730  		queryCharacter(filter: { name: { eq: "Han Solo" } }) {
  1731  		  name
  1732  		  appearsIn
  1733  		  ... on Human {
  1734  			starships {
  1735  				name
  1736  				length
  1737  			}
  1738  			totalCredits
  1739  		  }
  1740  		  ... on Droid {
  1741  			primaryFunction
  1742  		  }
  1743  		}
  1744  	  }`,
  1745  		}
  1746  
  1747  		gqlResponse := queryCharacterByNameParams.ExecuteAsPost(t, graphqlURL)
  1748  		requireNoGQLErrors(t, gqlResponse)
  1749  
  1750  		expected := `{
  1751  		"queryCharacter": [
  1752  		  {
  1753  			"name": "Han Solo",
  1754  			"appearsIn": ["EMPIRE"],
  1755  			"starships": [
  1756  			  {
  1757  				"name": "Millennium Falcon",
  1758  				"length": 2
  1759  			  }
  1760  			],
  1761  			"totalCredits": 10
  1762  		  }
  1763  		]
  1764  	  }`
  1765  		testutil.CompareJSON(t, expected, string(gqlResponse.Data))
  1766  	})
  1767  
  1768  	t.Run("test query all humans", func(t *testing.T) {
  1769  		queryHumanParams := &GraphQLParams{
  1770  			Query: `query {
  1771  		queryHuman {
  1772  		  name
  1773  		  appearsIn
  1774  		  starships {
  1775  			name
  1776  			length
  1777  		  }
  1778  		  totalCredits
  1779  		}
  1780  	  }`,
  1781  		}
  1782  
  1783  		gqlResponse := queryHumanParams.ExecuteAsPost(t, graphqlURL)
  1784  		requireNoGQLErrors(t, gqlResponse)
  1785  
  1786  		expected := `{
  1787  		"queryHuman": [
  1788  		  {
  1789  			"name": "Han Solo",
  1790  			"appearsIn": ["EMPIRE"],
  1791  			"starships": [
  1792  			  {
  1793  				"name": "Millennium Falcon",
  1794  				"length": 2
  1795  			  }
  1796  			],
  1797  			"totalCredits": 10
  1798  		  }
  1799  		]
  1800  	  }`
  1801  		testutil.CompareJSON(t, expected, string(gqlResponse.Data))
  1802  	})
  1803  
  1804  	t.Run("test query humans by name", func(t *testing.T) {
  1805  		queryHumanParamsByName := &GraphQLParams{
  1806  			Query: `query {
  1807  		queryHuman(filter: { name: { eq: "Han Solo" } }) {
  1808  		  name
  1809  		  appearsIn
  1810  		  starships {
  1811  			name
  1812  			length
  1813  		  }
  1814  		  totalCredits
  1815  		}
  1816  	  }`,
  1817  		}
  1818  
  1819  		gqlResponse := queryHumanParamsByName.ExecuteAsPost(t, graphqlURL)
  1820  		requireNoGQLErrors(t, gqlResponse)
  1821  
  1822  		expected := `{
  1823  		"queryHuman": [
  1824  		  {
  1825  			"name": "Han Solo",
  1826  			"appearsIn": ["EMPIRE"],
  1827  			"starships": [
  1828  			  {
  1829  				"name": "Millennium Falcon",
  1830  				"length": 2
  1831  			  }
  1832  			],
  1833  			"totalCredits": 10
  1834  		  }
  1835  		]
  1836  	  }`
  1837  
  1838  		testutil.CompareJSON(t, expected, string(gqlResponse.Data))
  1839  	})
  1840  
  1841  	cleanupStarwars(t, newStarship.ID, humanID, droidID)
  1842  }
  1843  
  1844  func cleanupStarwars(t *testing.T, starshipID, humanID, droidID string) {
  1845  	// Delete everything
  1846  	multiMutationParams := &GraphQLParams{
  1847  		Query: `mutation cleanup($starshipFilter: StarshipFilter!, $humanFilter: HumanFilter!,
  1848  			$droidFilter: DroidFilter!) {
  1849  		deleteStarship(filter: $starshipFilter) { msg }
  1850  
  1851  		deleteHuman(filter: $humanFilter) { msg }
  1852  
  1853  		deleteDroid(filter: $droidFilter) { msg }
  1854  	}`,
  1855  		Variables: map[string]interface{}{
  1856  			"starshipFilter": map[string]interface{}{
  1857  				"id": []string{starshipID},
  1858  			},
  1859  			"humanFilter": map[string]interface{}{
  1860  				"id": []string{humanID},
  1861  			},
  1862  			"droidFilter": map[string]interface{}{
  1863  				"id": []string{droidID},
  1864  			},
  1865  		},
  1866  	}
  1867  	multiMutationExpected := `{
  1868  	"deleteStarship": { "msg": "Deleted" },
  1869  	"deleteHuman" : { "msg": "Deleted" },
  1870  	"deleteDroid": { "msg": "Deleted" }
  1871  }`
  1872  
  1873  	gqlResponse := multiMutationParams.ExecuteAsPost(t, graphqlURL)
  1874  	requireNoGQLErrors(t, gqlResponse)
  1875  
  1876  	var expected, result struct {
  1877  		DeleteStarhip struct {
  1878  			Msg string
  1879  		}
  1880  		DeleteHuman struct {
  1881  			Msg string
  1882  		}
  1883  		DeleteDroid struct {
  1884  			Msg string
  1885  		}
  1886  	}
  1887  
  1888  	err := json.Unmarshal([]byte(multiMutationExpected), &expected)
  1889  	require.NoError(t, err)
  1890  	err = json.Unmarshal([]byte(gqlResponse.Data), &result)
  1891  	require.NoError(t, err)
  1892  
  1893  	if diff := cmp.Diff(expected, result); diff != "" {
  1894  		t.Errorf("result mismatch (-want +got):\n%s", diff)
  1895  	}
  1896  }
  1897  
  1898  func requireState(t *testing.T, uid string, expectedState *state,
  1899  	executeRequest requestExecutor) {
  1900  
  1901  	params := &GraphQLParams{
  1902  		Query: `query getState($id: ID!) {
  1903  			getState(id: $id) {
  1904  				id
  1905  				xcode
  1906  				name
  1907  				country {
  1908  					id
  1909  					name
  1910  				}
  1911  			}
  1912  		}`,
  1913  		Variables: map[string]interface{}{"id": uid},
  1914  	}
  1915  	gqlResponse := executeRequest(t, graphqlURL, params)
  1916  	requireNoGQLErrors(t, gqlResponse)
  1917  
  1918  	var result struct {
  1919  		GetState *state
  1920  	}
  1921  	err := json.Unmarshal([]byte(gqlResponse.Data), &result)
  1922  	require.NoError(t, err)
  1923  
  1924  	if diff := cmp.Diff(expectedState, result.GetState); diff != "" {
  1925  		t.Errorf("result mismatch (-want +got):\n%s", diff)
  1926  	}
  1927  }
  1928  
  1929  func addState(t *testing.T, name string, executeRequest requestExecutor) *state {
  1930  	addStateParams := &GraphQLParams{
  1931  		Query: `mutation addState($xcode: String!, $name: String!) {
  1932  			addState(input: [{ xcode: $xcode, name: $name }]) {
  1933  				state {
  1934  					id
  1935  					xcode
  1936  					name
  1937  				}
  1938  			}
  1939  		}`,
  1940  		Variables: map[string]interface{}{"name": name, "xcode": "cal"},
  1941  	}
  1942  	addStateExpected := `
  1943  		{ "addState": { "state": [{ "id": "_UID_", "name": "` + name + `", "xcode": "cal" } ]} }`
  1944  
  1945  	gqlResponse := executeRequest(t, graphqlURL, addStateParams)
  1946  	requireNoGQLErrors(t, gqlResponse)
  1947  
  1948  	var expected, result struct {
  1949  		AddState struct {
  1950  			State []*state
  1951  		}
  1952  	}
  1953  	err := json.Unmarshal([]byte(addStateExpected), &expected)
  1954  	require.NoError(t, err)
  1955  	err = json.Unmarshal([]byte(gqlResponse.Data), &result)
  1956  	require.NoError(t, err)
  1957  
  1958  	requireUID(t, result.AddState.State[0].ID)
  1959  
  1960  	// Always ignore the ID of the object that was just created.  That ID is
  1961  	// minted by Dgraph.
  1962  	opt := cmpopts.IgnoreFields(state{}, "ID")
  1963  	if diff := cmp.Diff(expected, result, opt); diff != "" {
  1964  		t.Errorf("result mismatch (-want +got):\n%s", diff)
  1965  	}
  1966  
  1967  	return result.AddState.State[0]
  1968  }
  1969  
  1970  func deleteState(
  1971  	t *testing.T,
  1972  	filter map[string]interface{},
  1973  	deleteStateExpected string,
  1974  	expectedErrors x.GqlErrorList) {
  1975  
  1976  	deleteStateParams := &GraphQLParams{
  1977  		Query: `mutation deleteState($filter: StateFilter!) {
  1978  			deleteState(filter: $filter) { msg }
  1979  		}`,
  1980  		Variables: map[string]interface{}{"filter": filter},
  1981  	}
  1982  
  1983  	gqlResponse := deleteStateParams.ExecuteAsPost(t, graphqlURL)
  1984  	require.JSONEq(t, deleteStateExpected, string(gqlResponse.Data))
  1985  
  1986  	if diff := cmp.Diff(expectedErrors, gqlResponse.Errors); diff != "" {
  1987  		t.Errorf("errors mismatch (-want +got):\n%s", diff)
  1988  	}
  1989  }
  1990  
  1991  func addMutationWithXid(t *testing.T, executeRequest requestExecutor) {
  1992  	newState := addState(t, "California", executeRequest)
  1993  	requireState(t, newState.ID, newState, executeRequest)
  1994  
  1995  	// Try add again, it should fail this time.
  1996  	name := "Calgary"
  1997  	addStateParams := &GraphQLParams{
  1998  		Query: `mutation addState($xcode: String!, $name: String!) {
  1999  			addState(input: [{ xcode: $xcode, name: $name }]) {
  2000  				state {
  2001  					id
  2002  					xcode
  2003  					name
  2004  				}
  2005  			}
  2006  		}`,
  2007  		Variables: map[string]interface{}{"name": name, "xcode": "cal"},
  2008  	}
  2009  
  2010  	gqlResponse := executeRequest(t, graphqlURL, addStateParams)
  2011  	require.NotNil(t, gqlResponse.Errors)
  2012  	require.Contains(t, gqlResponse.Errors[0].Error(),
  2013  		"because id cal already exists for type State")
  2014  
  2015  	deleteStateExpected := `{"deleteState" : { "msg": "Deleted" } }`
  2016  	filter := map[string]interface{}{"xcode": map[string]interface{}{"eq": "cal"}}
  2017  	deleteState(t, filter, deleteStateExpected, nil)
  2018  }
  2019  
  2020  func addMutationWithXID(t *testing.T) {
  2021  	addMutationWithXid(t, postExecutor)
  2022  }
  2023  
  2024  func addMultipleMutationWithOneError(t *testing.T) {
  2025  	newCountry := addCountry(t, postExecutor)
  2026  	newAuth := addAuthor(t, newCountry.ID, postExecutor)
  2027  
  2028  	badAuth := &author{
  2029  		ID: "0x0",
  2030  	}
  2031  
  2032  	goodPost := &post{
  2033  		Title:       "Test Post",
  2034  		Text:        "This post is just a test.",
  2035  		IsPublished: true,
  2036  		NumLikes:    1000,
  2037  		Author:      newAuth,
  2038  	}
  2039  
  2040  	badPost := &post{
  2041  		Title:       "Test Post",
  2042  		Text:        "This post is just a test.",
  2043  		IsPublished: true,
  2044  		NumLikes:    1000,
  2045  		Author:      badAuth,
  2046  	}
  2047  
  2048  	anotherGoodPost := &post{
  2049  		Title:       "Another Test Post",
  2050  		Text:        "This is just another post",
  2051  		IsPublished: true,
  2052  		NumLikes:    1000,
  2053  		Author:      newAuth,
  2054  	}
  2055  
  2056  	addPostParams := &GraphQLParams{
  2057  		Query: `mutation addPost($posts: [AddPostInput!]!) {
  2058  			addPost(input: $posts) {
  2059  			  post {
  2060  				postID
  2061  				title
  2062  				author {
  2063  					id
  2064  				}
  2065  			  }
  2066  			}
  2067  		}`,
  2068  		Variables: map[string]interface{}{"posts": []*post{goodPost, badPost,
  2069  			anotherGoodPost}},
  2070  	}
  2071  
  2072  	gqlResponse := postExecutor(t, graphqlURL, addPostParams)
  2073  
  2074  	addPostExpected := fmt.Sprintf(`{ "addPost": {
  2075  		"post": [{
  2076  			"title": "Text Post",
  2077  			"author": {
  2078  				"id": "%s"
  2079  			}
  2080  		}, {
  2081  			"title": "Another Test Post",
  2082  			"author": {
  2083  				"id": "%s"
  2084  			}
  2085  		}]
  2086  	} }`, newAuth.ID, newAuth.ID)
  2087  
  2088  	var expected, result struct {
  2089  		AddPost struct {
  2090  			Post []*post
  2091  		}
  2092  	}
  2093  	err := json.Unmarshal([]byte(addPostExpected), &expected)
  2094  	require.NoError(t, err)
  2095  	err = json.Unmarshal([]byte(gqlResponse.Data), &result)
  2096  	require.NoError(t, err)
  2097  
  2098  	require.Contains(t, gqlResponse.Errors[0].Error(),
  2099  		`couldn't rewrite query for mutation addPost because ID "0x0" isn't a Author`)
  2100  
  2101  	cleanUp(t, []*country{newCountry}, []*author{newAuth}, result.AddPost.Post)
  2102  }