github.com/senomas/gqlgen@v0.17.11-0.20220626120754-9aee61b0716a/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/99designs/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 two args (string, 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{},
   136  						},
   137  					},
   138  					{
   139  						ArgumentDefinition: &ast2.ArgumentDefinition{
   140  							Name: "test2",
   141  						},
   142  						TypeReference: &config.TypeReference{
   143  							GO: types.Typ[types.String],
   144  						},
   145  					},
   146  				},
   147  			},
   148  			Expected: `ctx, ` + `
   149  				func () interface{} {
   150  					if fc.Args["test"] == nil {
   151  						return nil
   152  					}
   153  					return fc.Args["test"].(interface{})
   154  				}(), fc.Args["test2"].(string)`,
   155  		},
   156  		{
   157  			Name: "Resolver field that isn't root object with single int argument",
   158  			Field: Field{
   159  				Object: &Object{
   160  					Root: false,
   161  				},
   162  				IsResolver: true,
   163  				Args: []*FieldArgument{
   164  					{
   165  						ArgumentDefinition: &ast2.ArgumentDefinition{
   166  							Name: "test",
   167  						},
   168  						TypeReference: &config.TypeReference{
   169  							GO: types.Typ[types.Int],
   170  						},
   171  					},
   172  				},
   173  			},
   174  			Expected: `rctx, obj, fc.Args["test"].(int)`,
   175  		},
   176  	}
   177  
   178  	for _, tc := range tt {
   179  		t.Run(tc.Name, func(t *testing.T) {
   180  			require.Equal(t, tc.CallArgs(), tc.Expected)
   181  		})
   182  	}
   183  }