1 package codegen
2
3 import (
4 "go/ast"
5 "go/importer"
6 "go/parser"
7 "go/token"
8 "go/types"
9 "testing"
10
11 "github.com/stretchr/testify/require"
12 ast2 "github.com/vektah/gqlparser/v2/ast"
13
14 "github.com/99designs/gqlgen/codegen/config"
15 )
16
17 func TestFindField(t *testing.T) {
18 input := `
19 package test
20
21 type Std struct {
22 Name string
23 Value int
24 }
25 type Anon struct {
26 Name string
27 Tags
28 }
29 type Tags struct {
30 Bar string ` + "`" + `gqlgen:"foo"` + "`" + `
31 Foo int ` + "`" + `gqlgen:"bar"` + "`" + `
32 }
33 type Amb struct {
34 Bar string ` + "`" + `gqlgen:"foo"` + "`" + `
35 Foo int ` + "`" + `gqlgen:"foo"` + "`" + `
36 }
37 type Embed struct {
38 Std
39 Test string
40 }
41 `
42 scope, err := parseScope(input, "test")
43 require.NoError(t, err)
44
45 std := scope.Lookup("Std").Type().(*types.Named)
46 anon := scope.Lookup("Anon").Type().(*types.Named)
47 tags := scope.Lookup("Tags").Type().(*types.Named)
48 amb := scope.Lookup("Amb").Type().(*types.Named)
49 embed := scope.Lookup("Embed").Type().(*types.Named)
50
51 tests := []struct {
52 Name string
53 Named *types.Named
54 Field string
55 Tag string
56 Expected string
57 ShouldError bool
58 }{
59 {"Finds a field by name with no tag", std, "name", "", "Name", false},
60 {"Finds a field by name when passed tag but tag not used", std, "name", "gqlgen", "Name", false},
61 {"Ignores tags when not passed a tag", tags, "foo", "", "Foo", false},
62 {"Picks field with tag over field name when passed a tag", tags, "foo", "gqlgen", "Bar", false},
63 {"Errors when ambigious", amb, "foo", "gqlgen", "", true},
64 {"Finds a field that is in embedded struct", anon, "bar", "", "Bar", false},
65 {"Finds field that is not in embedded struct", embed, "test", "", "Test", false},
66 }
67
68 for _, tt := range tests {
69 b := builder{Config: &config.Config{StructTag: tt.Tag}}
70 target, err := b.findBindTarget(tt.Named, tt.Field)
71 if tt.ShouldError {
72 require.Nil(t, target, tt.Name)
73 require.Error(t, err, tt.Name)
74 } else {
75 require.NoError(t, err, tt.Name)
76 require.Equal(t, tt.Expected, target.Name(), tt.Name)
77 }
78 }
79 }
80
81 func parseScope(input interface{}, packageName string) (*types.Scope, error) {
82
83 fset := token.NewFileSet()
84 f, err := parser.ParseFile(fset, "test.go", input, 0)
85 if err != nil {
86 return nil, err
87 }
88
89 conf := types.Config{Importer: importer.Default()}
90 pkg, err := conf.Check(packageName, fset, []*ast.File{f}, nil)
91 if err != nil {
92 return nil, err
93 }
94
95 return pkg.Scope(), nil
96 }
97
98 func TestEqualFieldName(t *testing.T) {
99 tt := []struct {
100 Name string
101 Source string
102 Target string
103 Expected bool
104 }{
105 {Name: "words with same case", Source: "test", Target: "test", Expected: true},
106 {Name: "words different case", Source: "test", Target: "tEsT", Expected: true},
107 {Name: "different words", Source: "foo", Target: "bar", Expected: false},
108 {Name: "separated with underscore", Source: "the_test", Target: "TheTest", Expected: true},
109 {Name: "empty values", Source: "", Target: "", Expected: true},
110 }
111
112 for _, tc := range tt {
113 t.Run(tc.Name, func(t *testing.T) {
114 result := equalFieldName(tc.Source, tc.Target)
115 require.Equal(t, tc.Expected, result)
116 })
117 }
118 }
119
120 func TestField_CallArgs(t *testing.T) {
121 tt := []struct {
122 Name string
123 Field
124 Expected string
125 }{
126 {
127 Name: "Field with method that has context, and three args (string, interface, named interface)",
128 Field: Field{
129 MethodHasContext: true,
130 Args: []*FieldArgument{
131 {
132 ArgumentDefinition: &ast2.ArgumentDefinition{
133 Name: "test",
134 },
135 TypeReference: &config.TypeReference{
136 GO: (&types.Interface{}).Complete(),
137 },
138 },
139 {
140 ArgumentDefinition: &ast2.ArgumentDefinition{
141 Name: "test2",
142 },
143 TypeReference: &config.TypeReference{
144 GO: types.NewNamed(
145 types.NewTypeName(token.NoPos, nil, "TestInterface", nil),
146 (&types.Interface{}).Complete(),
147 nil,
148 ),
149 },
150 },
151 {
152 ArgumentDefinition: &ast2.ArgumentDefinition{
153 Name: "test3",
154 },
155 TypeReference: &config.TypeReference{
156 GO: types.Typ[types.String],
157 },
158 },
159 },
160 },
161 Expected: `ctx, ` + `
162 func () interface{} {
163 if fc.Args["test"] == nil {
164 return nil
165 }
166 return fc.Args["test"].(interface{})
167 }(), fc.Args["test2"].(TestInterface), fc.Args["test3"].(string)`,
168 },
169 {
170 Name: "Resolver field that isn't root object with single int argument",
171 Field: Field{
172 Object: &Object{
173 Root: false,
174 },
175 IsResolver: true,
176 Args: []*FieldArgument{
177 {
178 ArgumentDefinition: &ast2.ArgumentDefinition{
179 Name: "test",
180 },
181 TypeReference: &config.TypeReference{
182 GO: types.Typ[types.Int],
183 },
184 },
185 },
186 },
187 Expected: `rctx, obj, fc.Args["test"].(int)`,
188 },
189 }
190
191 for _, tc := range tt {
192 t.Run(tc.Name, func(t *testing.T) {
193 require.Equal(t, tc.CallArgs(), tc.Expected)
194 })
195 }
196 }
197
View as plain text