github.com/niko0xdev/gqlgen@v0.17.55-0.20240120102243-2ecff98c3e37/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/stretchr/testify/require" 12 ast2 "github.com/vektah/gqlparser/v2/ast" 13 14 "github.com/niko0xdev/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 // test setup to parse the types 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 }