Skip to content

Commit

Permalink
Add GraphQL Matchers for tests using httpexpect (#13)
Browse files Browse the repository at this point in the history
  • Loading branch information
felipemfp authored Jul 29, 2019
1 parent b961441 commit ad93b65
Show file tree
Hide file tree
Showing 10 changed files with 1,029 additions and 0 deletions.
58 changes: 58 additions & 0 deletions gqlerrors/extensions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package gqlerrors

import (
"fmt"

"github.com/mitchellh/mapstructure"
"gopkg.in/gavv/httpexpect.v1"
)

type FormattedError struct {
Message string `json:"message"`
Extensions map[string]interface{} `json:"extensions,omitempty"`
}

type graphQLError struct {
Data map[string]interface{} `json:"data"`
Errors []*FormattedError `json:"errors"`
}

// decode an objects
func (g *graphQLError) decode(input interface{}) error {
decoder, err := mapstructure.NewDecoder(&mapstructure.DecoderConfig{
TagName: "json",
Result: g,
})

if err != nil {
return err
}

return decoder.Decode(input)
}

func prepareFromJSON(mutateOrQuery string, actual interface{}) (*graphQLError, error) {
data, ok := actual.(*httpexpect.Object)
if !ok {
return nil, fmt.Errorf("`actual` is not an json object")
}

// Decoding GraphQL error
var graphQLError graphQLError
err := graphQLError.decode(data.Raw())
if err != nil {
return nil, err
}

if len(graphQLError.Errors) == 0 {
return nil, fmt.Errorf("expected an error is not `%s`", actual)
}

for key := range graphQLError.Data {
if key != mutateOrQuery {
return nil, fmt.Errorf("expected mutate or query name [%s] not is equal [%s]", key, mutateOrQuery)
}
}

return &graphQLError, nil
}
61 changes: 61 additions & 0 deletions gqlerrors/extensions_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package gqlerrors

import (
"fmt"

"github.com/lab259/errors/v2"
"github.com/onsi/gomega/format"
)

type errWithGraphQLCodeMatcher struct {
Code interface{}
MutateOrQuery string
}

func (matcher *errWithGraphQLCodeMatcher) Match(actual interface{}) (bool, error) {
graphQLError, err := prepareFromJSON(matcher.MutateOrQuery, actual)
if err != nil {
return false, err
}

for _, v := range graphQLError.Errors {
code, ok := v.Extensions["code"].(string)
if !ok {
return false, fmt.Errorf("couldn't have key `code` %q", v.Extensions)
}

switch matcher.Code.(type) {
case errors.Option:
c := matcher.Code.(errors.Option)
cError := c(nil).Error()
if code != cError {
return false, fmt.Errorf("code [%s] not equal [%s]", cError, code)

}
case string:
if code != matcher.Code {
return false, fmt.Errorf("code [%s] not equal [%s]", matcher.Code, code)
}
case nil:
return false, fmt.Errorf("the code cannot be null")
}

}

return true, nil
}

func (matcher *errWithGraphQLCodeMatcher) FailureMessage(actual interface{}) string {
return format.Message(actual, "to have any code equal field", matcher.Code)
}

func (matcher *errWithGraphQLCodeMatcher) NegatedFailureMessage(actual interface{}) string {
return format.Message(actual, "to have any code equal field", matcher.Code)
}

func ErrWithGraphQLCode(mutateOrQueryName string, code interface{}) *errWithGraphQLCodeMatcher {
return &errWithGraphQLCodeMatcher{
Code: code,
MutateOrQuery: mutateOrQueryName,
}
}
180 changes: 180 additions & 0 deletions gqlerrors/extensions_code_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
package gqlerrors_test

import (
"github.com/lab259/errors/v2"
"github.com/lab259/errors/v2/gqlerrors"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
"gopkg.in/gavv/httpexpect.v1"
)

var _ = Describe("GraphQL Extensions Code", func() {
It("should check the return code", func() {
jsonData := httpexpect.NewObject(&httpGomegaFail{}, map[string]interface{}{
"data": map[string]interface{}{"mutate": nil},
"errors": []map[string]interface{}{
{
"extensions": map[string]interface{}{
"code": "validation",
},
},
},
})

a := gqlerrors.ErrWithGraphQLCode("mutate", "validation")
ok, err := a.Match(jsonData)
Expect(ok).To(BeTrue())
Expect(err).ToNot(HaveOccurred())
})

It("should fail check the return code when input not object", func() {
a := gqlerrors.ErrWithGraphQLCode("mutate", "validate")
ok, err := a.Match("it is not an object of the type `*httpexpect.Object`")
Expect(ok).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("`actual` is not an json object"))
})

It("should fail when not matcher extension error", func() {
m := gqlerrors.ErrWithGraphQLCode("mutate", "validate")

ok, err := m.Match(&httpexpect.Object{})
Expect(ok).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("expected an error is not `&{{<nil> %!s(bool=false)} map[]}`"))
})

It("should fail checking the return code when mutate not matcher", func() {
jsonData := httpexpect.NewObject(&httpGomegaFail{}, map[string]interface{}{
"data": map[string]interface{}{"mutate": nil},
"errors": []map[string]interface{}{
{
"extensions": map[string]interface{}{
"code": "validation",
},
},
},
})

code := errors.Code("invalid")
a := gqlerrors.ErrWithGraphQLCode("mutateInvalid", code)
ok, err := a.Match(jsonData)
Expect(ok).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("expected mutate or query name [mutate] not is equal [mutateInvalid]"))
})

It("should fail checking the return when error not contains code", func() {
jsonData := httpexpect.NewObject(&httpGomegaFail{}, map[string]interface{}{
"data": map[string]interface{}{"mutate": nil},
"errors": []map[string]interface{}{
{
"extensions": map[string]interface{}{
"module": "accounts",
},
},
},
})

a := gqlerrors.ErrWithGraphQLCode("mutate", "users")
ok, err := a.Match(jsonData)
Expect(ok).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("couldn't have key `code` map[\"module\":\"accounts\"]"))
})

It("should fail to decode when error not matcher", func() {
jsonData := httpexpect.NewObject(&httpGomegaFail{}, map[string]interface{}{
"data": map[string]interface{}{"mutate": nil},
"errors": map[string]interface{}{
"extensions": map[string]interface{}{
"code": "invalid-account-id",
},
},
})

code := errors.Code("invalid-account-id")
a := gqlerrors.ErrWithGraphQLCode("mutate", code)
ok, err := a.Match(jsonData)
Expect(ok).To(BeFalse())
Expect(err).To(HaveOccurred())
})

It("should fail checking the return when error not matcher code option", func() {
jsonData := httpexpect.NewObject(&httpGomegaFail{}, map[string]interface{}{
"data": map[string]interface{}{"mutate": nil},
"errors": []map[string]interface{}{
{
"extensions": map[string]interface{}{
"code": "validation",
},
},
},
})

op := errors.Code("new-code")
a := gqlerrors.ErrWithGraphQLCode("mutate", op)
ok, err := a.Match(jsonData)
Expect(ok).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("code [new-code] not equal [validation]"))
})

It("should fail checking the return when error not matcher code option nil", func() {
jsonData := httpexpect.NewObject(&httpGomegaFail{}, map[string]interface{}{
"data": map[string]interface{}{"mutate": nil},
"errors": []map[string]interface{}{
{
"extensions": map[string]interface{}{
"code": "validation",
},
},
},
})

a := gqlerrors.ErrWithGraphQLCode("mutate", nil)
ok, err := a.Match(jsonData)
Expect(ok).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("the code cannot be null"))
})

It("should fail checking the return when error not matcher code text", func() {
jsonData := httpexpect.NewObject(&httpGomegaFail{}, map[string]interface{}{
"data": map[string]interface{}{"mutate": nil},
"errors": []map[string]interface{}{
{
"extensions": map[string]interface{}{
"code": "validation",
},
},
},
})

a := gqlerrors.ErrWithGraphQLCode("mutate", "graphql")
ok, err := a.Match(jsonData)
Expect(ok).To(BeFalse())
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(Equal("code [graphql] not equal [validation]"))
})

It("should checking failure message", func() {
mutate := "mutate"
code := "graphql"
a := gqlerrors.ErrWithGraphQLCode(mutate, code)
failureMessage := a.FailureMessage("mutate")
fMessage := format.Message(mutate, "to have any code equal field", code)
Expect(failureMessage).To(Equal(fMessage))
})

It("should checking negative failure message", func() {
mutate := "mutate"
code := "graphql"
a := gqlerrors.ErrWithGraphQLCode(mutate, code)
failureMessage := a.NegatedFailureMessage("mutate")
fMessage := format.Message(mutate, "to have any code equal field", code)
Expect(failureMessage).To(Equal(fMessage))
})

})
81 changes: 81 additions & 0 deletions gqlerrors/extensions_message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package gqlerrors

import (
"fmt"
"strings"

"github.com/lab259/errors/v2"
"github.com/onsi/gomega/format"
)

type errWithGraphQLMessageMatcher struct {
Message interface{}
MutateOrQuery string
}

func (matcher *errWithGraphQLMessageMatcher) Match(actual interface{}) (bool, error) {
graphQLError, err := prepareFromJSON(matcher.MutateOrQuery, actual)
if err != nil {
return false, err
}

for _, v := range graphQLError.Errors {
if extMessage, ok := v.Extensions["message"].(string); ok {
switch matcher.Message.(type) {
case errors.Option:
op := matcher.Message.(errors.Option)
message := op(nil).Error()
return matcher.messageOption(extMessage, message)
case string:
message := matcher.Message.(string)
return matcher.messageOption(extMessage, message)
}
}

if v.Message != "" {
switch matcher.Message.(type) {
case errors.Option:
op := matcher.Message.(errors.Option)
message := op(nil).Error()
return matcher.messageOption(v.Message, message)
case string:
message := matcher.Message.(string)
return matcher.messageOption(v.Message, message)
case error:
message := matcher.Message.(error)
return matcher.messageOption(v.Message, message.Error())
}
}
}

return false, fmt.Errorf("the field message not found")
}

func (matcher *errWithGraphQLMessageMatcher) FailureMessage(actual interface{}) string {
return format.Message(actual, "to have any message equal field", matcher.Message)
}

func (matcher *errWithGraphQLMessageMatcher) NegatedFailureMessage(actual interface{}) string {
return format.Message(actual, "to have any message equal field", matcher.Message)
}

func ErrWithGraphQLMessage(mutateOrQueryName string, message interface{}) *errWithGraphQLMessageMatcher {
return &errWithGraphQLMessageMatcher{
Message: message,
MutateOrQuery: mutateOrQueryName,
}
}

func (matcher *errWithGraphQLMessageMatcher) messageOption(source, expected string) (bool, error) {
ok := strings.Contains(source, expected)
if !ok {
message := matcher.Message
switch matcher.Message.(type) {
case errors.Option:
op := matcher.Message.(errors.Option)
message = op(nil).Error()
}
return false, fmt.Errorf("message [%s] not equal [%s]", message, source)
}
return true, nil
}
Loading

0 comments on commit ad93b65

Please sign in to comment.