diff --git a/generate/generate_test.go b/generate/generate_test.go new file mode 100644 index 0000000..6d28746 --- /dev/null +++ b/generate/generate_test.go @@ -0,0 +1,85 @@ +package generate + +import ( + "errors" + "fmt" + "go/format" + "io/ioutil" + "os" + "path/filepath" + "strings" + "testing" +) + +const dataDir = "testdata" + +func readFile(t *testing.T, filename string, allowNotExist bool) string { + t.Helper() + data, err := ioutil.ReadFile(filepath.Join(dataDir, filename)) + if err != nil { + if allowNotExist && errors.Is(err, os.ErrNotExist) { + return "" + } + t.Fatal(err) + } + return string(data) +} + +func gofmt(src string) (string, error) { + src = strings.TrimSpace(src) + formatted, err := format.Source([]byte(src)) + if err != nil { + return src, fmt.Errorf("go parse error: %w", err) + } + return string(formatted), nil +} + +func TestGenerate(t *testing.T) { + // This test uses the schema, queries, and expected-output in ./testdata. + // The schema is in schema.graphql. The queries are in TestName.graphql; + // the test asserts that the output of the generator for that query is + // matches TestName.graphql.go. To update the expected output, run the + // tests with UPDATE_SNAPSHOTS=1 (they will still fail, but also do the + // update, so you can see which updates were made). + update := (os.Getenv("UPDATE_SNAPSHOTS") == "1") + + files, err := ioutil.ReadDir(dataDir) + if err != nil { + t.Fatal(err) + } + + for _, file := range files { + graphqlFilename := file.Name() + if graphqlFilename == "schema.graphql" || !strings.HasSuffix(graphqlFilename, ".graphql") { + continue + } + goFilename := graphqlFilename + ".go" + + t.Run(graphqlFilename, func(t *testing.T) { + expectedGoCode, err := gofmt(readFile(t, goFilename, update)) + if err != nil { + t.Fatal(err) + } + + goCode, err := Generate(&Config{ + Schema: filepath.Join("testdata", "schema.graphql"), + Queries: filepath.Join("testdata", graphqlFilename), + Package: "test", + }) + if err != nil { + t.Fatal(err) + } + + if string(goCode) != expectedGoCode { + t.Errorf("got:\n%v\nwant:\n%v\n", string(goCode), expectedGoCode) + if update { + t.Log("Updating testdata dir to match") + err = ioutil.WriteFile(filepath.Join(dataDir, goFilename), goCode, 0644) + if err != nil { + t.Errorf("Unable to update testdata dir: %v", err) + } + } + } + }) + } +} diff --git a/generate/testdata/InputObject.graphql b/generate/testdata/InputObject.graphql new file mode 100644 index 0000000..770dbfa --- /dev/null +++ b/generate/testdata/InputObject.graphql @@ -0,0 +1,5 @@ +query InputObjectQuery($query: UserQueryInput) { + user(query: $query) { + id + } +} diff --git a/generate/testdata/QueryWithInput.graphql.go b/generate/testdata/InputObject.graphql.go similarity index 57% rename from generate/testdata/QueryWithInput.graphql.go rename to generate/testdata/InputObject.graphql.go index 64dd84a..be88014 100644 --- a/generate/testdata/QueryWithInput.graphql.go +++ b/generate/testdata/InputObject.graphql.go @@ -8,7 +8,7 @@ import ( "github.com/Khan/genql/graphql" ) -type QueryWithInputResponse struct { +type InputObjectQueryResponse struct { User *User `json:"user"` } @@ -24,20 +24,21 @@ type User struct { } type UserQueryInput struct { - Email *string `json:"email"` - Name *string `json:"name"` - Id *string `json:"id"` - Role *Role `json:"role"` + Email *string `json:"email"` + Name *string `json:"name"` + Id *string `json:"id"` + Role *Role `json:"role"` + Names []*string `json:"names"` } -func QueryWithInput(client *graphql.Client, query *UserQueryInput) (*QueryWithInputResponse, error) { +func InputObjectQuery(client *graphql.Client, query *UserQueryInput) (*InputObjectQueryResponse, error) { variables := map[string]interface{}{ "query": query, } - var retval QueryWithInputResponse + var retval InputObjectQueryResponse err := client.MakeRequest(context.Background(), ` -query QueryWithInput ($query: UserQueryInput) { +query InputObjectQuery ($query: UserQueryInput) { user(query: $query) { id } diff --git a/generate/testdata/ListInput.graphql b/generate/testdata/ListInput.graphql new file mode 100644 index 0000000..2459c0a --- /dev/null +++ b/generate/testdata/ListInput.graphql @@ -0,0 +1,5 @@ +query ListInputQuery($names: [String]) { + user(query: {names: $names}) { + id + } +} diff --git a/generate/testdata/ListInput.graphql.go b/generate/testdata/ListInput.graphql.go new file mode 100644 index 0000000..c13c2be --- /dev/null +++ b/generate/testdata/ListInput.graphql.go @@ -0,0 +1,33 @@ +package test + +// Code generated by github.com/Khan/genql, DO NOT EDIT. + +import ( + "context" + + "github.com/Khan/genql/graphql" +) + +type ListInputQueryResponse struct { + User *User `json:"user"` +} + +type User struct { + Id string `json:"id"` +} + +func ListInputQuery(client *graphql.Client, names []*string) (*ListInputQueryResponse, error) { + variables := map[string]interface{}{ + "names": names, + } + + var retval ListInputQueryResponse + err := client.MakeRequest(context.Background(), ` +query ListInputQuery ($names: [String]) { + user(query: {names:$names}) { + id + } +} +`, &retval, variables) + return &retval, err +} diff --git a/generate/testdata/QueryWithInput.graphql b/generate/testdata/QueryWithInput.graphql deleted file mode 100644 index dbc26f2..0000000 --- a/generate/testdata/QueryWithInput.graphql +++ /dev/null @@ -1,5 +0,0 @@ -query QueryWithInput($query: UserQueryInput) { - user(query: $query) { - id - } -} diff --git a/generate/testdata/SimpleInput.graphql b/generate/testdata/SimpleInput.graphql new file mode 100644 index 0000000..3af5a86 --- /dev/null +++ b/generate/testdata/SimpleInput.graphql @@ -0,0 +1,5 @@ +query SimpleInputQuery($name: String!) { + user(query: {name: $name}) { + id + } +} diff --git a/generate/testdata/SimpleInput.graphql.go b/generate/testdata/SimpleInput.graphql.go new file mode 100644 index 0000000..59345b5 --- /dev/null +++ b/generate/testdata/SimpleInput.graphql.go @@ -0,0 +1,33 @@ +package test + +// Code generated by github.com/Khan/genql, DO NOT EDIT. + +import ( + "context" + + "github.com/Khan/genql/graphql" +) + +type SimpleInputQueryResponse struct { + User *User `json:"user"` +} + +type User struct { + Id string `json:"id"` +} + +func SimpleInputQuery(client *graphql.Client, name string) (*SimpleInputQueryResponse, error) { + variables := map[string]interface{}{ + "name": name, + } + + var retval SimpleInputQueryResponse + err := client.MakeRequest(context.Background(), ` +query SimpleInputQuery ($name: String!) { + user(query: {name:$name}) { + id + } +} +`, &retval, variables) + return &retval, err +} diff --git a/generate/testdata/schema.graphql b/generate/testdata/schema.graphql index a3b1e9f..f697e04 100644 --- a/generate/testdata/schema.graphql +++ b/generate/testdata/schema.graphql @@ -8,6 +8,7 @@ input UserQueryInput { name: String id: ID role: Role + names: [String] } type AuthMethod { diff --git a/generate/types_test.go b/generate/types_test.go deleted file mode 100644 index 8dde7ad..0000000 --- a/generate/types_test.go +++ /dev/null @@ -1,186 +0,0 @@ -package generate - -import ( - "errors" - "fmt" - "go/format" - "io/ioutil" - "os" - "path/filepath" - "sort" - "strings" - "testing" - - "github.com/vektah/gqlparser" - "github.com/vektah/gqlparser/ast" -) - -const dataDir = "testdata" - -func readFile(t *testing.T, filename string, allowNotExist bool) string { - t.Helper() - data, err := ioutil.ReadFile(filepath.Join(dataDir, filename)) - if err != nil { - if allowNotExist && errors.Is(err, os.ErrNotExist) { - return "" - } - t.Fatal(err) - } - return string(data) -} - -func gofmt(src string) (string, error) { - src = strings.TrimSpace(src) - formatted, err := format.Source([]byte(src)) - if err != nil { - return src, fmt.Errorf("go parse error: %w", err) - } - return string(formatted), nil -} - -func TestTypeForOperation(t *testing.T) { - // This test uses the schema, queries, and expected-output in ./testdata. - // The schema is in schema.graphql. The queries are in TestName.graphql; - // the test asserts that such queries, when run through the type-generator, - // produce the types in TestName.go (the name of the overall response type - // will be Response). - // - // Change update on the next line to true to update all the expected output - // files to match current output. - // TODO(benkraft): Make this a flag or something. - update := false - - files, err := ioutil.ReadDir(dataDir) - if err != nil { - t.Fatal(err) - } - - for _, file := range files { - graphqlFilename := file.Name() - if graphqlFilename == "schema.graphql" || !strings.HasSuffix(graphqlFilename, ".graphql") { - continue - } - goFilename := graphqlFilename + ".go" - - t.Run(graphqlFilename, func(t *testing.T) { - expectedGoCode, err := gofmt(readFile(t, goFilename, update)) - if err != nil { - t.Fatal(err) - } - - goCode, err := Generate(&Config{ - Schema: filepath.Join("testdata", "schema.graphql"), - Queries: filepath.Join("testdata", graphqlFilename), - Package: "test", - }) - if err != nil { - t.Fatal(err) - } - - if string(goCode) != expectedGoCode { - t.Errorf("got:\n%v\nwant:\n%v\n", string(goCode), expectedGoCode) - if update { - t.Log("Updating testdata dir to match") - err = ioutil.WriteFile(filepath.Join(dataDir, goFilename), goCode, 0644) - if err != nil { - t.Errorf("Unable to update testdata dir: %v", err) - } - } - } - }) - } - - if update { - // This is an error to ensure we don't commit update := true - t.Error("Updated testdata dir") - } -} - -// TODO(benkraft): Figure out how to do this with testdata-files -func TestTypeForInputType(t *testing.T) { - tests := []struct { - name string - graphQLType string - expectedGoType string - otherTypes []string - }{{ - `RequiredBuiltin`, - `String!`, - `string`, - nil, - }, { - `ListOfBuiltin`, - `[String]`, - `[]*string`, - nil, - }, { - `DefinedType`, - `UserQueryInput`, - `*UserQueryInput`, - []string{ - `type Role string - const ( - StudentRole Role = "STUDENT" - TeacherRole Role = "TEACHER" - )`, - `type UserQueryInput struct { - Email *string ` + "`json:\"email\"`" + ` - Name *string ` + "`json:\"name\"`" + ` - Id *string ` + "`json:\"id\"`" + ` - Role *Role ` + "`json:\"role\"`" + ` - }`, - }, - }} - - schemaText := readFile(t, "schema.graphql", false) - - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - sort.Strings(test.otherTypes) // To match generator.Types() - expectedGoCode := fmt.Sprintf( - "type Input %s\n\n%s", test.expectedGoType, - strings.Join(test.otherTypes, "\n\n")) - expectedGoCode, err := gofmt(expectedGoCode) - if err != nil { - t.Fatal(err) - } - - extraSchemaText := fmt.Sprintf( - "extend type Query { testQuery(var: %s): User }", test.graphQLType) - schema, graphqlError := gqlparser.LoadSchema( - &ast.Source{Name: "test schema", Input: schemaText}, - &ast.Source{Name: "test schema extension", Input: extraSchemaText}, - ) - if graphqlError != nil { - t.Fatal(graphqlError) - } - - operation := fmt.Sprintf( - "query($var: %s) { testQuery(var: $var) { id } }", test.graphQLType) - queryDoc, graphqlListError := gqlparser.LoadQuery(schema, operation) - if graphqlListError != nil { - t.Fatal(graphqlListError) - } - - g := newGenerator(&Config{Package: "test_package"}, schema) - goType, err := g.getTypeForInputType( - queryDoc.Operations[0].VariableDefinitions[0].Type) - if err != nil { - t.Error(err) - } - - goCode := fmt.Sprintf("type Input %s\n\n%s", goType, g.Types()) - - // gofmt before comparing. - goCode, err = gofmt(goCode) - if err != nil { - t.Error(err) - } - - if goCode != expectedGoCode { - t.Errorf("got:\n%v\nwant:\n%v\n", goCode, expectedGoCode) - } - }) - } -}