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

graphql: Start collecting and returning errors from remote remote GraphQL endpoints. #5328

Merged
merged 7 commits into from
Apr 30, 2020
93 changes: 85 additions & 8 deletions graphql/e2e/custom_logic/cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -791,6 +791,38 @@ func gqlUserNameHandler(w http.ResponseWriter, r *http.Request) {
}`, userID)
}

func gqlUserNameWithErrorHandler(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}

if strings.Contains(string(b), "__schema") {
fmt.Fprint(w, introspectedSchemaForQuery("userName", "id"))
return
}

var req request
if err := json.Unmarshal(b, &req); err != nil {
return
}
userID := req.Variables["id"].(string)
fmt.Fprintf(w, `
{
"data": {
"userName": "uname-%s"
},
"errors": [
{
"message": "error-1 from username"
},
{
"message": "error-2 from username"
}
]
}`, userID)
}

func gqlCarHandler(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
Expand Down Expand Up @@ -978,6 +1010,19 @@ func gqlSchoolNamesHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, res)
}

func buildCarBatchOutput(b []byte, req request) []interface{} {
input := req.Variables["input"]
output := []interface{}{}
for _, i := range input.([]interface{}) {
im := i.(map[string]interface{})
id := im["id"].(string)
output = append(output, map[string]interface{}{
"name": "car-" + id,
})
}
return output
}

func gqlCarsHandler(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
Expand All @@ -993,20 +1038,50 @@ func gqlCarsHandler(w http.ResponseWriter, r *http.Request) {
if err := json.Unmarshal(b, &req); err != nil {
return
}
input := req.Variables["input"]
output := []interface{}{}
for _, i := range input.([]interface{}) {
im := i.(map[string]interface{})
id := im["id"].(string)
output = append(output, map[string]interface{}{
"name": "car-" + id,
})

output := buildCarBatchOutput(b, req)
response := map[string]interface{}{
"data": map[string]interface{}{
"cars": output,
},
}

b, err = json.Marshal(response)
if err != nil {
return
}
check2(fmt.Fprint(w, string(b)))
}

func gqlCarsWithErrorHandler(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
if err != nil {
return
}

if strings.Contains(string(b), "__schema") {
fmt.Fprint(w, generateIntrospectionResult(graphqlResponses["carsschema"].Schema))
return
}

var req request
if err := json.Unmarshal(b, &req); err != nil {
return
}

output := buildCarBatchOutput(b, req)
response := map[string]interface{}{
"data": map[string]interface{}{
"cars": output,
},
"errors": []map[string]interface{}{
map[string]interface{}{
"message": "error-1 from cars",
},
map[string]interface{}{
"message": "error-2 from cars",
},
},
}

b, err = json.Marshal(response)
Expand Down Expand Up @@ -1114,6 +1189,7 @@ func main() {

// for testing single mode
http.HandleFunc("/gqlUserName", gqlUserNameHandler)
http.HandleFunc("/gqlUserNameWithError", gqlUserNameWithErrorHandler)
http.HandleFunc("/gqlCar", gqlCarHandler)
http.HandleFunc("/gqlClass", gqlClassHandler)
http.HandleFunc("/gqlTeacherName", gqlTeacherNameHandler)
Expand All @@ -1124,6 +1200,7 @@ func main() {
http.HandleFunc("/getPostswithLike", getPostswithLike)
http.HandleFunc("/gqlUserNames", gqlUserNamesHandler)
http.HandleFunc("/gqlCars", gqlCarsHandler)
http.HandleFunc("/gqlCarsWithErrors", gqlCarsWithErrorHandler)
http.HandleFunc("/gqlClasses", gqlClassesHandler)
http.HandleFunc("/gqlTeacherNames", gqlTeacherNamesHandler)
http.HandleFunc("/gqlSchoolNames", gqlSchoolNamesHandler)
Expand Down
157 changes: 150 additions & 7 deletions graphql/e2e/custom_logic/custom_logic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,7 @@ type user struct {
Age int `json:"age,omitempty"`
}

func addUsers(t *testing.T, schools []*school) []*user {
func addUsersWithSchools(t *testing.T, schools []*school) []*user {
params := &common.GraphQLParams{
Query: `mutation addUser($s1: [SchoolRef], $s2: [SchoolRef], $s3: [SchoolRef]) {
addUser(input: [{ age: 10, schools: $s1 },
Expand Down Expand Up @@ -509,10 +509,41 @@ func addUsers(t *testing.T, schools []*school) []*user {
return res.AddUser.User
}

func addUsers(t *testing.T) []*user {
params := &common.GraphQLParams{
Query: `mutation addUser {
addUser(input: [{ age: 10 }, { age: 11 }, { age: 12 }]) {
user {
id
age
}
}
}`,
}

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

var res struct {
AddUser struct {
User []*user
}
}
err := json.Unmarshal([]byte(result.Data), &res)
require.NoError(t, err)

require.Equal(t, len(res.AddUser.User), 3)
// The order of mutation result is not the same as the input order, so we sort and return users here.
sort.Slice(res.AddUser.User, func(i, j int) bool {
return res.AddUser.User[i].Age < res.AddUser.User[j].Age
})
return res.AddUser.User
}

func verifyData(t *testing.T, users []*user, teachers []*teacher, schools []*school) {
queryUser := `
query {
queryUser(order: {asc: age}) {
query ($id: [ID!]){
queryUser(filter: {id: $id}, order: {asc: age}) {
name
age
cars {
Expand All @@ -533,6 +564,9 @@ func verifyData(t *testing.T, users []*user, teachers []*teacher, schools []*sch
}`
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)
Expand Down Expand Up @@ -788,7 +822,7 @@ func TestCustomFieldsShouldBeResolved(t *testing.T) {
// add some data
teachers := addTeachers(t)
schools := addSchools(t, teachers)
users := addUsers(t, schools)
users := addUsersWithSchools(t, schools)

// lets check batch mode first using REST endpoints.
t.Run("rest batch operation mode", func(t *testing.T) {
Expand Down Expand Up @@ -825,6 +859,115 @@ func TestCustomFieldsShouldBeResolved(t *testing.T) {
})
}

func TestCustomFieldResolutionShouldPropagateGraphQLErrors(t *testing.T) {
schema := `type Car @remote {
id: ID!
name: String!
}

type User {
id: ID!
name: String
@custom(
http: {
url: "http://mock:8888/gqlUserNameWithError"
method: "POST"
operation: "single"
graphql: "query { userName(id: $id) }"
}
)
age: Int! @search
cars: Car
@custom(
http: {
url: "http://mock:8888/gqlCarsWithErrors"
method: "POST"
operation: "batch"
graphql: "query { cars(input: [{ id: $id, age: $age}]) }"
}
)
}`
updateSchemaRequireNoGQLErrors(t, schema)
users := addUsers(t)
// Sleep so that schema update can come through in Alpha.
time.Sleep(time.Second)

queryUser := `
query ($id: [ID!]){
queryUser(filter: {id: $id}, order: {asc: age}) {
name
age
cars {
name
}
}
}`
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)
sort.Slice(result.Errors, func(i, j int) bool {
return result.Errors[i].Message < result.Errors[j].Message
})
require.Equal(t, x.GqlErrorList{
{
Message: "error-1 from cars",
},
{
Message: "error-1 from username",
},
{
Message: "error-1 from username",
},
{
Message: "error-1 from username",
},
{
Message: "error-2 from cars",
},
{
Message: "error-2 from username",
},
{
Message: "error-2 from username",
},
{
Message: "error-2 from username",
},
}, result.Errors)

expected := `{
"queryUser": [
{
"name": "uname-` + users[0].ID + `",
"age": 10,
"cars": {
"name": "car-` + users[0].ID + `"
}
},
{
"name": "uname-` + users[1].ID + `",
"age": 11,
"cars": {
"name": "car-` + users[1].ID + `"
}
},
{
"name": "uname-` + users[2].ID + `",
"age": 12,
"cars": {
"name": "car-` + users[2].ID + `"
}
}
]
}`

testutil.CompareJSON(t, expected, string(result.Data))
}

func TestForInvalidCustomQuery(t *testing.T) {
schema := customTypes + `
type Query {
Expand Down Expand Up @@ -1716,7 +1859,7 @@ func TestCustomGraphqlMutation2(t *testing.T) {
func TestForValidInputArgument(t *testing.T) {
schema := customTypes + `
type Query {
myCustom(yo: CountryInput!): [Country!]! @custom(http: {url: "http://mock:8888/validinpputfield", method: "POST",forwardHeaders: ["Content-Type"], graphql: "query{countries(filter: $yo)}"})
myCustom(yo: CountryInput!): [Country!]! @custom(http: {url: "http://mock:8888/validinpputfield", method: "POST",forwardHeaders: ["Content-Type"], graphql: "query{countries(filter: $yo)}"})
}
`
common.RequireNoGQLErrors(t, updateSchema(t, schema))
Expand Down Expand Up @@ -1750,7 +1893,7 @@ func TestForValidInputArgument(t *testing.T) {
func TestForInvalidInputObject(t *testing.T) {
schema := customTypes + `
type Query {
myCustom(yo: CountryInput!): [Country!]! @custom(http: {url: "http://mock:8888/invalidfield", method: "POST",forwardHeaders: ["Content-Type"], graphql: "query{countries(filter: $yo)}"})
myCustom(yo: CountryInput!): [Country!]! @custom(http: {url: "http://mock:8888/invalidfield", method: "POST",forwardHeaders: ["Content-Type"], graphql: "query{countries(filter: $yo)}"})
}
`
res := updateSchema(t, schema)
Expand All @@ -1760,7 +1903,7 @@ func TestForInvalidInputObject(t *testing.T) {
func TestForNestedInvalidInputObject(t *testing.T) {
schema := customTypes + `
type Query {
myCustom(yo: CountryInput!): [Country!]! @custom(http: {url: "http://mock:8888/nestedinvalid", method: "POST",forwardHeaders: ["Content-Type"], graphql: "query{countries(filter: $yo)}"})
myCustom(yo: CountryInput!): [Country!]! @custom(http: {url: "http://mock:8888/nestedinvalid", method: "POST",forwardHeaders: ["Content-Type"], graphql: "query{countries(filter: $yo)}"})
}
`
res := updateSchema(t, schema)
Expand Down
Loading