Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(GraphQL): fix bug in custom resolver, now body need not have all the fields. #6054

Merged
merged 14 commits into from
Jul 29, 2020
Merged
81 changes: 80 additions & 1 deletion graphql/e2e/custom_logic/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,20 @@ func postFavMoviesHandler(w http.ResponseWriter, r *http.Request) {
check2(w.Write(getDefaultResponse()))
}

func postFavMoviesWithBodyHandler(w http.ResponseWriter, r *http.Request) {
err := verifyRequest(r, expectedRequest{
method: http.MethodPost,
urlSuffix: "/0x123?name=Author",
body: `{"id":"0x123","name":"Author"}`,
headers: nil,
})
if err != nil {
check2(w.Write([]byte(err.Error())))
return
}
check2(w.Write(getDefaultResponse()))
}

func verifyHeadersHandler(w http.ResponseWriter, r *http.Request) {
err := verifyRequest(r, expectedRequest{
method: http.MethodGet,
Expand Down Expand Up @@ -384,6 +398,36 @@ func favMoviesCreateHandler(w http.ResponseWriter, r *http.Request) {
]`)))
}

func favMoviesCreateWithNullBodyHandler(w http.ResponseWriter, r *http.Request) {
err := verifyRequest(r, expectedRequest{
method: http.MethodPost,
urlSuffix: "/favMoviesCreateWithNullBody",
body: `{"movies":[{"director":[{"name":"Dir1"}],"name":"Mov1"},{"name":null}]}`,
headers: nil,
})
if err != nil {
check2(w.Write([]byte(err.Error())))
return
}

check2(w.Write([]byte(`[
{
"id": "0x1",
"name": "Mov1",
"director": [
{
"id": "0x2",
"name": "Dir1"
}
]
},
{
"id": "0x3",
"name": null
}
]`)))
}

func favMoviesUpdateHandler(w http.ResponseWriter, r *http.Request) {
err := verifyRequest(r, expectedRequest{
method: http.MethodPatch,
Expand Down Expand Up @@ -792,6 +836,39 @@ func userNameHandler(w http.ResponseWriter, r *http.Request) {
nameHandler(w, r, &inputBody)
}

func userNameWithoutAddressHandler(w http.ResponseWriter, r *http.Request) {
expectedRequest := expectedRequest{
body: `{"uid":"0x5"}`,
}

b, err := ioutil.ReadAll(r.Body)
fmt.Println(b, err)
if err != nil {
err = getError("Unable to read request body", err.Error())
check2(w.Write([]byte(err.Error())))
return
}

if string(b) != expectedRequest.body {
err = getError("Unexpected value for request body", string(b))
}
if err != nil {
check2(w.Write([]byte(err.Error())))
return
}

var inputBody input
if err := json.Unmarshal(b, &inputBody); err != nil {
fmt.Println("while doing JSON unmarshal: ", err)
check2(w.Write([]byte(err.Error())))
return
}

n := fmt.Sprintf(`"%s"`, inputBody.Name())
check2(fmt.Fprint(w, n))

}

func carHandler(w http.ResponseWriter, r *http.Request) {
var inputBody input
err := getInput(r, &inputBody)
Expand Down Expand Up @@ -1134,6 +1211,7 @@ func main() {
// for queries
http.HandleFunc("/favMovies/", getFavMoviesHandler)
http.HandleFunc("/favMoviesPost/", postFavMoviesHandler)
http.HandleFunc("/favMoviesPostWithBody/", postFavMoviesWithBodyHandler)
http.HandleFunc("/verifyHeaders", verifyHeadersHandler)
http.HandleFunc("/verifyCustomNameHeaders", verifyCustomNameHeadersHandler)
http.HandleFunc("/twitterfollowers", twitterFollwerHandler)
Expand All @@ -1142,7 +1220,7 @@ func main() {
http.HandleFunc("/favMoviesCreate", favMoviesCreateHandler)
http.HandleFunc("/favMoviesUpdate/", favMoviesUpdateHandler)
http.HandleFunc("/favMoviesDelete/", favMoviesDeleteHandler)

http.HandleFunc("/favMoviesCreateWithNullBody", favMoviesCreateWithNullBodyHandler)
// The endpoints below are for testing custom resolution of fields within type definitions.
// for testing batch mode
http.HandleFunc("/userNames", userNamesHandler)
Expand All @@ -1154,6 +1232,7 @@ func main() {

// for testing single mode
http.HandleFunc("/userName", userNameHandler)
http.HandleFunc("/userNameWithoutAddress", userNameWithoutAddressHandler)
http.HandleFunc("/checkHeadersForUserName", userNameHandlerWithHeaders)
http.HandleFunc("/car", carHandler)
http.HandleFunc("/class", classHandler)
Expand Down
213 changes: 213 additions & 0 deletions graphql/e2e/custom_logic/custom_logic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const (
alphaURL = "http://localhost:8180/graphql"
alphaAdminURL = "http://localhost:8180/admin"
subscriptionEndpoint = "ws://localhost:8180/graphql"
groupOnegRPC = "localhost:9180"
customTypes = `type MovieDirector @remote {
id: ID!
name: String!
Expand Down Expand Up @@ -158,6 +159,42 @@ func TestCustomPostQuery(t *testing.T) {
require.JSONEq(t, expected, string(result.Data))
}

func TestCustomPostQueryWithBody(t *testing.T) {
schema := customTypes + `
type Query {
myFavoriteMoviesPost(id: ID!, name: String!, num: Int): [Movie] @custom(http: {
url: "http://mock:8888/favMoviesPostWithBody/$id?name=$name",
body:"{id:$id,name:$name,num:$num}"
method: "POST"
})
}`
updateSchemaRequireNoGQLErrors(t, schema)
time.Sleep(2 * time.Second)

query := `
query {
myFavoriteMoviesPost(id: "0x123", name: "Author") {
id
name
director {
id
name
}
}
}`
params := &common.GraphQLParams{
Query: query,
}

result := params.ExecuteAsPost(t, alphaURL)
require.Nil(t, result.Errors)

expected := `{"myFavoriteMoviesPost":[{"id":"0x3","name":"Star Wars","director":
[{"id":"0x4","name":"George Lucas"}]},{"id":"0x5","name":"Star Trek","director":
[{"id":"0x6","name":"J.J. Abrams"}]}]}`
require.JSONEq(t, expected, string(result.Data))
}

func TestCustomQueryShouldForwardHeaders(t *testing.T) {
schema := customTypes + `
type Query {
Expand Down Expand Up @@ -1012,6 +1049,103 @@ func TestCustomFieldsShouldForwardHeaders(t *testing.T) {
require.Nilf(t, result.Errors, "%+v", result.Errors)
}

func TestCustomFieldsShouldSkipNonEmptyVariable(t *testing.T) {
schema := `
type User {
id: ID!
address:String
name: String
@custom(
http: {
url: "http://mock:8888/userName"
method: "GET"
body: "{uid: $id,address:$address}"
mode: SINGLE,
secretHeaders: ["GITHUB-API-TOKEN"]
}
)
age: Int! @search
}

# Dgraph.Secret GITHUB-API-TOKEN "some-api-token"
`
updateSchemaRequireNoGQLErrors(t, schema)
time.Sleep(2 * time.Second)

users := addUsers(t)
queryUser := `
query ($id: [ID!]){
queryUser(filter: {id: $id}, order: {asc: age}) {
name
age
}
}`
params := &common.GraphQLParams{
Query: queryUser,
Variables: map[string]interface{}{"id": []interface{}{
users[0].ID, users[1].ID, users[2].ID}},
}

result := params.ExecuteAsPost(t, alphaURL)
require.Nilf(t, result.Errors, "%+v", result.Errors)
}

func TestCustomFieldsShouldPassBody(t *testing.T) {
dg, err := testutil.DgraphClient(groupOnegRPC)
require.NoError(t, err)
testutil.DropAll(t, dg)
schema := `
type User {
id: String! @id @search(by: [hash, regexp])
address:String
name: String
@custom(
http: {
url: "http://mock:8888/userNameWithoutAddress"
method: "GET"
body: "{uid: $id,address:$address}"
mode: SINGLE,
secretHeaders: ["GITHUB-API-TOKEN"]
}
)
age: Int! @search
}
# Dgraph.Secret GITHUB-API-TOKEN "some-api-token"
`
updateSchemaRequireNoGQLErrors(t, schema)
time.Sleep(2 * time.Second)

params := &common.GraphQLParams{
Query: `mutation addUser {
addUser(input: [{ id:"0x5", age: 10 }]) {
user {
id
age
}
}
}`,
}

result := params.ExecuteAsPost(t, alphaURL)
common.RequireNoGQLErrors(t, result)

queryUser := `
query ($id: String!){
queryUser(filter: {id: {eq: $id}}) {
name
age
}
}`

params = &common.GraphQLParams{
Query: queryUser,
Variables: map[string]interface{}{"id": "0x5"},
}

result = params.ExecuteAsPost(t, alphaURL)
require.Nilf(t, result.Errors, "%+v", result.Errors)
}

func TestCustomFieldsShouldBeResolved(t *testing.T) {
// This test adds data, modifies the schema multiple times and fetches the data.
// It has the following modes.
Expand Down Expand Up @@ -1732,6 +1866,85 @@ func TestCustomPostMutation(t *testing.T) {
require.JSONEq(t, expected, string(result.Data))
}

func TestCustomPostMutationNullInBody(t *testing.T) {
schema := `type MovieDirector @remote {
id: ID!
name: String!
directed: [Movie]
}
type Movie @remote {
id: ID!
name: String
director: [MovieDirector]
}
input MovieDirectorInput {
id: ID
name: String
directed: [MovieInput]
}
input MovieInput {
id: ID
name: String
director: [MovieDirectorInput]
}
type Mutation {
createMyFavouriteMovies(input: [MovieInput!]): [Movie] @custom(http: {
url: "http://mock:8888/favMoviesCreateWithNullBody",
method: "POST",
body: "{ movies: $input}"
})
}`
updateSchemaRequireNoGQLErrors(t, schema)
time.Sleep(2 * time.Second)

params := &common.GraphQLParams{
Query: `
mutation createMovies($movs: [MovieInput!]) {
createMyFavouriteMovies(input: $movs) {
id
name
director {
id
name
}
}
}`,
Variables: map[string]interface{}{
"movs": []interface{}{
map[string]interface{}{
"name": "Mov1",
"director": []interface{}{map[string]interface{}{"name": "Dir1"}},
},
map[string]interface{}{"name": nil},
}},
}

result := params.ExecuteAsPost(t, alphaURL)
common.RequireNoGQLErrors(t, result)

expected := `
{
"createMyFavouriteMovies": [
{
"id": "0x1",
"name": "Mov1",
"director": [
{
"id": "0x2",
"name": "Dir1"
}
]
},
{
"id": "0x3",
"name": null,
"director": []
}
]
}`
require.JSONEq(t, expected, string(result.Data))
}

func TestCustomPatchMutation(t *testing.T) {
schema := customTypes + `
input MovieDirectorInput {
Expand Down
1 change: 1 addition & 0 deletions graphql/resolve/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -1793,6 +1793,7 @@ func (hr *httpResolver) rewriteAndExecute(ctx context.Context, field schema.Fiel
}
body = string(b)
}

b, err := makeRequest(hr.Client, hrc.Method, hrc.URL, body, hrc.ForwardHeaders)
if err != nil {
return emptyResult(externalRequestError(err, field))
Expand Down
Loading