github.com/Desuuuu/genqlient@v0.5.3/generate/generate_test.go (about)

     1  package generate
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"os/exec"
     7  	"path/filepath"
     8  	"strings"
     9  	"testing"
    10  
    11  	"github.com/Desuuuu/genqlient/internal/testutil"
    12  	"gopkg.in/yaml.v2"
    13  )
    14  
    15  const (
    16  	dataDir   = "testdata/queries"
    17  	errorsDir = "testdata/errors"
    18  )
    19  
    20  // buildGoFile returns an error if the given Go code is not valid.
    21  //
    22  // namePrefix is used for the temp-file, and is just for debugging.
    23  func buildGoFile(namePrefix string, content []byte) error {
    24  	// We need to put this within the current module, rather than in
    25  	// /tmp, so that it can access internal/testutil.
    26  	f, err := os.CreateTemp("./testdata/tmp", namePrefix+"_*.go")
    27  	if err != nil {
    28  		return err
    29  	}
    30  	defer func() {
    31  		f.Close()
    32  		os.Remove(f.Name())
    33  	}()
    34  
    35  	_, err = f.Write(content)
    36  	if err != nil {
    37  		return err
    38  	}
    39  
    40  	cmd := exec.Command("go", "build", f.Name())
    41  	cmd.Stdout = os.Stdout
    42  	cmd.Stderr = os.Stderr
    43  	err = cmd.Run()
    44  	if err != nil {
    45  		return fmt.Errorf("generated code does not compile: %w", err)
    46  	}
    47  	return nil
    48  }
    49  
    50  // TestGenerate is a snapshot-based test of code-generation.
    51  //
    52  // This file just has the test runner; the actual data is all in
    53  // testdata/queries.  Specifically, the schema used for all the queries is in
    54  // schema.graphql; the queries themselves are in TestName.graphql.  The test
    55  // asserts that running genqlient on that query produces the generated code in
    56  // the snapshot-file TestName.graphql.go.
    57  //
    58  // To update the snapshots (if the code-generator has changed), run the test
    59  // with `UPDATE_SNAPSHOTS=1`; it will fail the tests and print any diffs, but
    60  // update the snapshots.  Make sure to check that the output is sensible; the
    61  // snapshots don't even get compiled!
    62  func TestGenerate(t *testing.T) {
    63  	files, err := os.ReadDir(dataDir)
    64  	if err != nil {
    65  		t.Fatal(err)
    66  	}
    67  
    68  	for _, file := range files {
    69  		sourceFilename := file.Name()
    70  		if sourceFilename == "schema.graphql" || !strings.HasSuffix(sourceFilename, ".graphql") {
    71  			continue
    72  		}
    73  		goFilename := sourceFilename + ".go"
    74  		queriesFilename := sourceFilename + ".json"
    75  
    76  		t.Run(sourceFilename, func(t *testing.T) {
    77  			generated, err := Generate(&Config{
    78  				Schema:           []string{filepath.Join(dataDir, "schema.graphql")},
    79  				Operations:       []string{filepath.Join(dataDir, sourceFilename)},
    80  				Package:          "test",
    81  				Generated:        goFilename,
    82  				ExportOperations: queriesFilename,
    83  				ContextType:      "-",
    84  				Bindings: map[string]*TypeBinding{
    85  					"ID":       {Type: "github.com/Desuuuu/genqlient/internal/testutil.ID"},
    86  					"DateTime": {Type: "time.Time"},
    87  					"Date": {
    88  						Type:        "time.Time",
    89  						Marshaler:   "github.com/Desuuuu/genqlient/internal/testutil.MarshalDate",
    90  						Unmarshaler: "github.com/Desuuuu/genqlient/internal/testutil.UnmarshalDate",
    91  					},
    92  					"Junk":        {Type: "interface{}"},
    93  					"ComplexJunk": {Type: "[]map[string]*[]*map[string]interface{}"},
    94  					"Pokemon": {
    95  						Type:              "github.com/Desuuuu/genqlient/internal/testutil.Pokemon",
    96  						ExpectExactFields: "{ species level }",
    97  					},
    98  					"PokemonInput": {Type: "github.com/Desuuuu/genqlient/internal/testutil.Pokemon"},
    99  				},
   100  				AllowBrokenFeatures: true,
   101  			})
   102  			if err != nil {
   103  				t.Fatal(err)
   104  			}
   105  
   106  			for filename, content := range generated {
   107  				t.Run(filename, func(t *testing.T) {
   108  					testutil.Cupaloy.SnapshotT(t, string(content))
   109  				})
   110  			}
   111  
   112  			t.Run("Build", func(t *testing.T) {
   113  				if testing.Short() {
   114  					t.Skip("skipping build due to -short")
   115  				}
   116  
   117  				err := buildGoFile(sourceFilename, generated[goFilename])
   118  				if err != nil {
   119  					t.Error(err)
   120  				}
   121  			})
   122  		})
   123  	}
   124  }
   125  
   126  func getDefaultConfig(t *testing.T) *Config {
   127  	// Parse the config that `genqlient --init` generates, to make sure that
   128  	// works.
   129  	var config Config
   130  	b, err := os.ReadFile("default_genqlient.yaml")
   131  	if err != nil {
   132  		t.Fatal(err)
   133  	}
   134  
   135  	err = yaml.UnmarshalStrict(b, &config)
   136  	if err != nil {
   137  		t.Fatal(err)
   138  	}
   139  	return &config
   140  }
   141  
   142  // TestGenerateWithConfig tests several configuration options that affect
   143  // generated code but don't require particular query structures to test.
   144  //
   145  // It runs a simple query from TestGenerate with several different genqlient
   146  // configurations.  It uses snapshots, just like TestGenerate.
   147  func TestGenerateWithConfig(t *testing.T) {
   148  	tests := []struct {
   149  		name    string
   150  		baseDir string  // relative to dataDir
   151  		config  *Config // omits Schema and Operations, set below.
   152  	}{
   153  		{"DefaultConfig", "", getDefaultConfig(t)},
   154  		{"Subpackage", "", &Config{
   155  			Generated: "mypkg/myfile.go",
   156  		}},
   157  		{"SubpackageConfig", "mypkg", &Config{
   158  			Generated: "myfile.go", // (relative to genqlient.yaml)
   159  		}},
   160  		{"PackageName", "", &Config{
   161  			Generated: "myfile.go",
   162  			Package:   "mypkg",
   163  		}},
   164  		{"ExportOperations", "", &Config{
   165  			Generated:        "generated.go",
   166  			ExportOperations: "operations.json",
   167  		}},
   168  		{"CustomContext", "", &Config{
   169  			Generated:   "generated.go",
   170  			ContextType: "github.com/Desuuuu/genqlient/internal/testutil.MyContext",
   171  		}},
   172  		{"StructReferences", "", &Config{
   173  			StructReferences: true,
   174  			Generated:        "generated-structrefs.go",
   175  		}},
   176  		{"OptionalPointers", "", &Config{
   177  			OptionalPointers: true,
   178  			Generated:        "generated-optionalpointers.go",
   179  		}},
   180  		{"NoContext", "", &Config{
   181  			Generated:   "generated.go",
   182  			ContextType: "-",
   183  		}},
   184  		{"ClientGetter", "", &Config{
   185  			Generated:    "generated.go",
   186  			ClientGetter: "github.com/Desuuuu/genqlient/internal/testutil.GetClientFromContext",
   187  		}},
   188  		{"ClientGetterCustomContext", "", &Config{
   189  			Generated:    "generated.go",
   190  			ClientGetter: "github.com/Desuuuu/genqlient/internal/testutil.GetClientFromMyContext",
   191  			ContextType:  "github.com/Desuuuu/genqlient/internal/testutil.MyContext",
   192  		}},
   193  		{"ClientGetterNoContext", "", &Config{
   194  			Generated:    "generated.go",
   195  			ClientGetter: "github.com/Desuuuu/genqlient/internal/testutil.GetClientFromNowhere",
   196  			ContextType:  "-",
   197  		}},
   198  		{"Extensions", "", &Config{
   199  			Generated:  "generated.go",
   200  			Extensions: true,
   201  		}},
   202  	}
   203  
   204  	sourceFilename := "SimpleQuery.graphql"
   205  
   206  	for _, test := range tests {
   207  		config := test.config
   208  		baseDir := filepath.Join(dataDir, test.baseDir)
   209  		t.Run(test.name, func(t *testing.T) {
   210  			err := config.ValidateAndFillDefaults(baseDir)
   211  			config.Schema = []string{filepath.Join(dataDir, "schema.graphql")}
   212  			config.Operations = []string{filepath.Join(dataDir, sourceFilename)}
   213  			if err != nil {
   214  				t.Fatal(err)
   215  			}
   216  			generated, err := Generate(config)
   217  			if err != nil {
   218  				t.Fatal(err)
   219  			}
   220  
   221  			for filename, content := range generated {
   222  				t.Run(filename, func(t *testing.T) {
   223  					testutil.Cupaloy.SnapshotT(t, string(content))
   224  				})
   225  			}
   226  
   227  			t.Run("Build", func(t *testing.T) {
   228  				if testing.Short() {
   229  					t.Skip("skipping build due to -short")
   230  				}
   231  
   232  				err := buildGoFile(sourceFilename,
   233  					generated[config.Generated])
   234  				if err != nil {
   235  					t.Error(err)
   236  				}
   237  			})
   238  		})
   239  	}
   240  }
   241  
   242  // TestGenerate is a snapshot-based test of error text.
   243  //
   244  // For each .go or .graphql file in testdata/errors, and corresponding
   245  // .schema.graphql file, it asserts that the given query returns an error, and
   246  // that that error's string-text matches the snapshot.  The snapshotting is
   247  // useful to ensure we don't accidentally make the text less readable, drop the
   248  // line numbers, etc.  We include both .go and .graphql tests, to make sure the
   249  // line numbers work in both cases.
   250  func TestGenerateErrors(t *testing.T) {
   251  	files, err := os.ReadDir(errorsDir)
   252  	if err != nil {
   253  		t.Fatal(err)
   254  	}
   255  
   256  	for _, file := range files {
   257  		sourceFilename := file.Name()
   258  		if !strings.HasSuffix(sourceFilename, ".graphql") &&
   259  			!strings.HasSuffix(sourceFilename, ".go") ||
   260  			strings.HasSuffix(sourceFilename, ".schema.graphql") {
   261  			continue
   262  		}
   263  
   264  		baseFilename := strings.TrimSuffix(sourceFilename, filepath.Ext(sourceFilename))
   265  		schemaFilename := baseFilename + ".schema.graphql"
   266  		testFilename := strings.ReplaceAll(sourceFilename, ".", "/")
   267  
   268  		t.Run(testFilename, func(t *testing.T) {
   269  			_, err := Generate(&Config{
   270  				Schema:      []string{filepath.Join(errorsDir, schemaFilename)},
   271  				Operations:  []string{filepath.Join(errorsDir, sourceFilename)},
   272  				Package:     "test",
   273  				Generated:   os.DevNull,
   274  				ContextType: "context.Context",
   275  				Bindings: map[string]*TypeBinding{
   276  					"ValidScalar":   {Type: "string"},
   277  					"InvalidScalar": {Type: "bogus"},
   278  					"Pokemon": {
   279  						Type:              "github.com/Desuuuu/genqlient/internal/testutil.Pokemon",
   280  						ExpectExactFields: "{ species level }",
   281  					},
   282  				},
   283  				AllowBrokenFeatures: true,
   284  			})
   285  			if err == nil {
   286  				t.Fatal("expected an error")
   287  			}
   288  
   289  			testutil.Cupaloy.SnapshotT(t, err.Error())
   290  		})
   291  	}
   292  }