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  }