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