github.com/josephspurrier/go-swagger@v0.2.1-0.20221129144919-1f672a142a00/generator/generate_test.go (about)

     1  package generator
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"log"
     7  	"os"
     8  	"path"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strings"
    12  	"testing"
    13  
    14  	"github.com/stretchr/testify/assert"
    15  	"github.com/stretchr/testify/require"
    16  )
    17  
    18  func TestGenerateAndTest(t *testing.T) {
    19  	defer discardOutput()()
    20  
    21  	cwd := testCwd(t)
    22  	const root = "generated"
    23  	defer func() {
    24  		_ = os.RemoveAll(filepath.Join(cwd, root))
    25  	}()
    26  
    27  	t.Run("server build", func(t *testing.T) {
    28  		for name, cas := range generateFixtures(t) {
    29  			thisCas := cas
    30  			thisName := name
    31  
    32  			t.Run(thisName, func(t *testing.T) {
    33  				t.Parallel()
    34  
    35  				log.SetOutput(io.Discard)
    36  				defer thisCas.warnFailed(t)
    37  
    38  				// default opts
    39  				opts := testGenOpts()
    40  
    41  				// create directory layout, defer clean
    42  				defer thisCas.prepareTarget(t, thisName, "server_test", root, opts)()
    43  
    44  				// preparation before generation
    45  				if thisCas.prepare != nil {
    46  					thisCas.prepare(t, opts)
    47  				}
    48  
    49  				t.Logf("generating test server at: %s, from %s", opts.Target, opts.Spec)
    50  
    51  				err := GenerateServer("", nil, nil, opts)
    52  				if thisCas.wantError {
    53  					require.Errorf(t, err, "expected an error for server build fixture: %s", opts.Spec)
    54  				} else {
    55  					require.NoError(t, err, "unexpected error for server build fixture: %s", opts.Spec)
    56  				}
    57  
    58  				// verify
    59  				if thisCas.verify != nil {
    60  					thisCas.verify(t, opts.Target)
    61  				}
    62  
    63  				// fixture-specific clean
    64  				if thisCas.clean != nil {
    65  					thisCas.clean()
    66  				}
    67  			})
    68  		}
    69  	})
    70  }
    71  
    72  type generateFixture struct {
    73  	name      string
    74  	spec      string
    75  	target    string
    76  	wantError bool
    77  	prepare   func(testing.TB, *GenOpts)
    78  	verify    func(testing.TB, string)
    79  	clean     func()
    80  }
    81  
    82  func (f generateFixture) base(t testing.TB, root string) (string, func()) {
    83  	// base generation target
    84  	cwd := testCwd(t)
    85  
    86  	base := filepath.Join(cwd, root)
    87  	require.NoErrorf(t, os.MkdirAll(base, 0700), "error in test creating target dir")
    88  
    89  	generated, err := os.MkdirTemp(base, "generated")
    90  	require.NoErrorf(t, err, "error in test creating temp dir")
    91  
    92  	return generated, func() {
    93  		_ = os.RemoveAll(generated)
    94  	}
    95  }
    96  
    97  func (f generateFixture) prepareTarget(t testing.TB, name, base, root string, opts *GenOpts) func() {
    98  	if name == "" {
    99  		name = f.name
   100  	}
   101  
   102  	spec := filepath.FromSlash(f.spec)
   103  	opts.Spec = spec
   104  
   105  	generated, clean := f.base(t, root)
   106  
   107  	if f.target == "" {
   108  		opts.Target = filepath.Join(generated, opts.LanguageOpts.ManglePackageName(name, base))
   109  	} else {
   110  		opts.Target = filepath.Join(generated, filepath.Base(f.target))
   111  	}
   112  
   113  	require.NoErrorf(t, os.MkdirAll(opts.Target, 0700), "error in test creating target dir")
   114  
   115  	return clean
   116  }
   117  
   118  func (f generateFixture) warnFailed(t testing.TB) func() {
   119  	return func() {
   120  		if t.Failed() {
   121  			t.Log("ERROR: generation failed")
   122  		}
   123  	}
   124  }
   125  
   126  func generateFixtures(t testing.TB) map[string]generateFixture {
   127  	return map[string]generateFixture{
   128  		"issue 1943": {
   129  			spec:   "../fixtures/bugs/1943/fixture-1943.yaml",
   130  			target: "../fixtures/bugs/1943",
   131  			prepare: func(_ testing.TB, opts *GenOpts) {
   132  				input, err := os.ReadFile("../fixtures/bugs/1943/datarace_test.go")
   133  				require.NoError(t, err)
   134  
   135  				// rewrite imports for the relocated test program
   136  				cwd := testCwd(t)
   137  				rebased := bytes.ReplaceAll(
   138  					input,
   139  					[]byte("/fixtures/bugs/1943"),
   140  					[]byte(filepath.ToSlash(strings.TrimPrefix(opts.Target, filepath.Dir(cwd)))),
   141  				)
   142  
   143  				require.NoError(t, os.WriteFile(filepath.Join(opts.Target, "datarace_test.go"), rebased, 0600))
   144  				opts.ExcludeSpec = false
   145  			},
   146  			verify: func(t testing.TB, target string) {
   147  				if runtime.GOOS == "windows" {
   148  					// don't run race tests on Appveyor CI
   149  					t.Logf("warn: race test skipped on windows")
   150  					return
   151  				}
   152  
   153  				const packages = "./..."
   154  				testPrg := "datarace_test.go"
   155  
   156  				goExecInDir(t, target, "get", packages)
   157  
   158  				t.Log("running data race test on generated server")
   159  				goExecInDir(t, target, "test", "-v", "-race", testPrg)
   160  			},
   161  		},
   162  		"packages_mangling": {
   163  			spec: "../fixtures/bugs/2111/fixture-2111.yaml",
   164  			prepare: func(_ testing.TB, opts *GenOpts) {
   165  				opts.IncludeMain = true
   166  			},
   167  			verify: func(t testing.TB, target string) {
   168  				require.True(t, fileExists(target, defaultServerTarget))
   169  				assert.True(t, fileExists(filepath.Join(target, "cmd", "unsafe-tag-names-server"), "main.go"))
   170  
   171  				srvTarget := filepath.Join(target, defaultServerTarget)
   172  				opsTarget := filepath.Join(srvTarget, defaultOperationsTarget)
   173  				require.True(t, fileExists(opsTarget, ""))
   174  
   175  				for _, fileOrDir := range []string{
   176  					"abc_linux", "abc_test",
   177  					"api",
   178  					"custom",
   179  					"hash_tag_donuts",
   180  					"nr123abc", "nr_at_donuts", "plus_donuts",
   181  					"strfmt",
   182  					"forced",
   183  					"gtl",
   184  					"nr12nasty",
   185  					"override",
   186  					"get_notag.go",
   187  					"operationsops",
   188  				} {
   189  					assert.True(t, fileExists(opsTarget, fileOrDir))
   190  				}
   191  
   192  				buf, err := os.ReadFile(filepath.Join(srvTarget, "configure_unsafe_tag_names.go"))
   193  				require.NoError(t, err)
   194  
   195  				code := string(buf)
   196  
   197  				// assert imports, with deconfliction
   198  				cwd := testCwd(t)
   199  				base := path.Join("github.com", "go-swagger", "go-swagger",
   200  					filepath.ToSlash(strings.TrimPrefix(target, filepath.Dir(cwd))),
   201  				)
   202  
   203  				baseImport := path.Join(base, `restapi/operations`)
   204  				assertImports(t, baseImport, code)
   205  
   206  				assertInCode(t, `api.APIGetConflictHandler = apiops.GetConflictHandlerFunc(`, code)
   207  				assertInCode(t, `api.StrfmtGetAnotherConflictHandler = strfmtops.GetAnotherConflictHandlerFunc(`, code)
   208  				assertInCode(t, `api.GetNotagHandler = operations.GetNotagHandlerFunc(`, code)
   209  
   210  				buf2, err := os.ReadFile(filepath.Join(opsTarget, "unsafe_tag_names_api.go"))
   211  				require.NoError(t, err)
   212  
   213  				api := string(buf2)
   214  				assertImports(t, baseImport, api)
   215  
   216  				assertInCode(t, `APIGetConflictHandler: apiops.GetConflictHandlerFunc(func(params apiops.GetConflictParams) middleware.Responder {`, api)
   217  				assertInCode(t, `StrfmtGetAnotherConflictHandler: strfmtops.GetAnotherConflictHandlerFunc(func(params strfmtops.GetAnotherConflictParams) middleware.Responder {`, api)
   218  				assertInCode(t, `GetNotagHandler: GetNotagHandlerFunc(func(params GetNotagParams) middleware.Responder {`, api)
   219  
   220  				assertInCode(t, `OverrideDeleteTestOverrideHandler override.DeleteTestOverrideHandler`, api)
   221  				assertInCode(t, `StrfmtGetAnotherConflictHandler strfmtops.GetAnotherConflictHandler`, api)
   222  				assertInCode(t, `APIGetConflictHandler apiops.GetConflictHandler`, api)
   223  				assertInCode(t, `CustomGetCustomHandler custom.GetCustomHandler`, api)
   224  				assertInCode(t, `AbcLinuxGetMultipleHandler abc_linux.GetMultipleHandler`, api)
   225  				assertInCode(t, `GetNotagHandler GetNotagHandler`, api)
   226  				assertInCode(t, `AbcLinuxGetOtherReservedHandler abc_linux.GetOtherReservedHandler`, api)
   227  				assertInCode(t, `PlusDonutsGetOtherUnsafeHandler plus_donuts.GetOtherUnsafeHandler`, api)
   228  				assertInCode(t, `AbcTestGetReservedHandler abc_test.GetReservedHandler`, api)
   229  				assertInCode(t, `GtlGetTestOverrideHandler gtl.GetTestOverrideHandler`, api)
   230  				assertInCode(t, `HashTagDonutsGetUnsafeHandler hash_tag_donuts.GetUnsafeHandler`, api)
   231  				assertInCode(t, `NrAtDonutsGetYetAnotherUnsafeHandler nr_at_donuts.GetYetAnotherUnsafeHandler`, api)
   232  				assertInCode(t, `ForcedPostTestOverrideHandler forced.PostTestOverrideHandler`, api)
   233  				assertInCode(t, `Nr12nastyPutTestOverrideHandler nr12nasty.PutTestOverrideHandler`, api)
   234  				assertInCode(t, `Nr123abcTestIDHandler nr123abc.TestIDHandler`, api)
   235  			},
   236  		},
   237  		"packages_flattening": {
   238  			spec: "../fixtures/bugs/2111/fixture-2111.yaml",
   239  			prepare: func(_ testing.TB, opts *GenOpts) {
   240  				opts.SkipTagPackages = true
   241  			},
   242  			verify: func(t testing.TB, target string) {
   243  				require.True(t, fileExists(target, defaultServerTarget))
   244  
   245  				srvTarget := filepath.Join(target, defaultServerTarget)
   246  				opsTarget := filepath.Join(srvTarget, defaultOperationsTarget)
   247  				require.True(t, fileExists(opsTarget, ""))
   248  
   249  				for _, fileOrDir := range []string{
   250  					"abc_linux", "abc_test",
   251  					"api",
   252  					"custom",
   253  					"hash_tag_donuts",
   254  					"nr123abc", "nr_at_donuts", "plus_donuts",
   255  					"strfmt",
   256  					"forced",
   257  					"gtl",
   258  					"nr12nasty",
   259  					"override",
   260  					"operationsops",
   261  				} {
   262  					assert.Falsef(t, fileExists(opsTarget, fileOrDir), "did not expect %s in %s", fileOrDir, opsTarget)
   263  				}
   264  
   265  				assert.Truef(t, fileExists(opsTarget, "get_notag.go"), "expected %s in %s", "get_notag.go", opsTarget)
   266  
   267  				buf, err := os.ReadFile(filepath.Join(srvTarget, "configure_unsafe_tag_names.go"))
   268  				require.NoError(t, err)
   269  				code := string(buf)
   270  
   271  				cwd := testCwd(t)
   272  				base := path.Join("github.com", "go-swagger", "go-swagger",
   273  					filepath.ToSlash(strings.TrimPrefix(target, filepath.Dir(cwd))),
   274  				)
   275  
   276  				baseImport := path.Join(base, `restapi/operations`)
   277  				assertRegexpInCode(t, baseImport, code)
   278  
   279  				assertInCode(t, `api.GetConflictHandler = operations.GetConflictHandlerFunc(`, code)
   280  				assertInCode(t, `api.GetAnotherConflictHandler = operations.GetAnotherConflictHandlerFunc(`, code)
   281  				assertInCode(t, `api.GetNotagHandler = operations.GetNotagHandlerFunc(`, code)
   282  
   283  				buf2, err := os.ReadFile(filepath.Join(opsTarget, "unsafe_tag_names_api.go"))
   284  				require.NoError(t, err)
   285  				api := string(buf2)
   286  
   287  				assertInCode(t, `GetConflictHandler: GetConflictHandlerFunc(func(params GetConflictParams) middleware.Responder {`, api)
   288  				assertInCode(t, `GetAnotherConflictHandler: GetAnotherConflictHandlerFunc(func(params GetAnotherConflictParams) middleware.Responder {`, api)
   289  				assertInCode(t, `NotagHandler: GetNotagHandlerFunc(func(params GetNotagParams) middleware.Responder {`, api)
   290  
   291  				assertInCode(t, `DeleteTestOverrideHandler DeleteTestOverrideHandler`, api)
   292  				assertInCode(t, `GetAnotherConflictHandler GetAnotherConflictHandler`, api)
   293  				assertInCode(t, `GetConflictHandler GetConflictHandler`, api)
   294  				assertInCode(t, `GetCustomHandler GetCustomHandler`, api)
   295  				assertInCode(t, `GetMultipleHandler GetMultipleHandler`, api)
   296  				assertInCode(t, `GetNotagHandler GetNotagHandler`, api)
   297  				assertInCode(t, `GetOtherReservedHandler GetOtherReservedHandler`, api)
   298  				assertInCode(t, `GetOtherUnsafeHandler GetOtherUnsafeHandler`, api)
   299  				assertInCode(t, `GetReservedHandler GetReservedHandler`, api)
   300  				assertInCode(t, `GetTestOverrideHandler GetTestOverrideHandler`, api)
   301  				assertInCode(t, `GetUnsafeHandler GetUnsafeHandler`, api)
   302  				assertInCode(t, `GetYetAnotherUnsafeHandler GetYetAnotherUnsafeHandler`, api)
   303  				assertInCode(t, `PostTestOverrideHandler PostTestOverrideHandler`, api)
   304  				assertInCode(t, `PutTestOverrideHandler PutTestOverrideHandler`, api)
   305  				assertInCode(t, `TestIDHandler TestIDHandler`, api)
   306  			},
   307  		},
   308  		"main_package": {
   309  			spec: "../fixtures/bugs/2111/fixture-2111.yaml",
   310  			prepare: func(_ testing.TB, opts *GenOpts) {
   311  				opts.IncludeMain = true
   312  				opts.MainPackage = "custom-api"
   313  				opts.SkipTagPackages = true
   314  			},
   315  			verify: func(t testing.TB, target string) {
   316  				assert.True(t, fileExists(filepath.Join(target, "cmd", "custom-api"), "main.go"))
   317  			},
   318  		},
   319  		"external_model": {
   320  			spec: "../fixtures/bugs/1897/fixture-1897.yaml",
   321  			prepare: func(t testing.TB, opts *GenOpts) {
   322  				modelOpts := *opts
   323  				modelOpts.AcceptDefinitionsOnly = true
   324  				modelOpts.Spec = "../fixtures/bugs/1897/model.yaml"
   325  				modelOpts.ModelPackage = "external"
   326  				modelOpts.Target = filepath.Dir(modelOpts.Spec)
   327  
   328  				require.NoError(t, GenerateModels(nil, &modelOpts))
   329  
   330  				t.Logf("generated external model")
   331  				require.True(t, fileExists(modelOpts.Target, "external"))
   332  				require.True(t, fileExists(modelOpts.Target, filepath.Join("external", "error.go")))
   333  
   334  				opts.IncludeMain = true
   335  			},
   336  			verify: func(t testing.TB, target string) {
   337  				location := filepath.Join(target, "cmd", "repro1897-server")
   338  				require.True(t, fileExists("", location))
   339  
   340  				t.Log("building generated server")
   341  				goExecInDir(t, location, "build")
   342  			},
   343  			clean: func() {
   344  				// remove generated external models
   345  				_ = os.RemoveAll(filepath.Join("..", "fixtures", "bugs", "1897", "external"))
   346  			},
   347  		},
   348  		"external_models_hints": {
   349  			spec:   "../fixtures/enhancements/2224/fixture-2224.yaml",
   350  			target: "2224-hints",
   351  			prepare: func(t testing.TB, opts *GenOpts) {
   352  				modelOpts := *opts
   353  				modelOpts.AcceptDefinitionsOnly = true
   354  				modelOpts.Spec = "../fixtures/enhancements/2224/fixture-2224-models.yaml"
   355  				modelOpts.ModelPackage = "external"
   356  				modelOpts.Target = filepath.Dir(modelOpts.Spec)
   357  
   358  				require.NoError(t, GenerateModels(nil, &modelOpts))
   359  
   360  				t.Logf("generated external model")
   361  				require.True(t, fileExists(modelOpts.Target, "external"))
   362  
   363  				for _, model := range []string{
   364  					"access_point.go", "base.go",
   365  					"hotspot.go", "hotspot_type.go",
   366  					"incorrect.go", "json_message.go",
   367  					"json_object.go", "json_object_with_alias.go",
   368  					"object_with_embedded.go", "object_with_externals.go",
   369  					"raw.go", "request.go",
   370  					"request_pointer.go", "time_as_object.go", "time.go",
   371  				} {
   372  					require.True(t, fileExists(modelOpts.Target, filepath.Join("external", model)))
   373  				}
   374  
   375  				opts.IncludeMain = true
   376  			},
   377  			verify: func(t testing.TB, target string) {
   378  				// generated models (not external)
   379  				require.True(t, fileExists(target, "models"))
   380  				for _, model := range []string{"error.go", "external_with_embed.go"} {
   381  					require.True(t, fileExists(target, filepath.Join("models", model)))
   382  				}
   383  
   384  				location := filepath.Join(target, "cmd", "external-types-with-hints-server")
   385  				require.True(t, fileExists("", location))
   386  
   387  				t.Log("building generated server")
   388  				goExecInDir(t, location, "build")
   389  			},
   390  			clean: func() {
   391  				// remove generated external models
   392  				_ = os.RemoveAll(filepath.Join("..", "fixtures", "enhancements", "2224", "external"))
   393  			},
   394  		},
   395  		"conflict_name_api_issue_2405_1": {
   396  			spec:   "../examples/todo-list/swagger.yml",
   397  			target: "2405-1",
   398  			prepare: func(_ testing.TB, opts *GenOpts) {
   399  				opts.ServerPackage = "api"
   400  				opts.IncludeMain = true
   401  			},
   402  			verify: func(t testing.TB, target string) {
   403  				location := filepath.Join(target, "cmd", "simple-to-do-list-api-server")
   404  				require.True(t, fileExists("", location))
   405  
   406  				t.Log("building generated server")
   407  				goExecInDir(t, location, "build")
   408  			},
   409  		},
   410  		"conflict_name_api_issue_2405_2": {
   411  			spec:   "../examples/todo-list/swagger.yml",
   412  			target: "2405-2",
   413  			prepare: func(_ testing.TB, opts *GenOpts) {
   414  				opts.ServerPackage = "loads"
   415  				opts.IncludeMain = true
   416  			},
   417  			verify: func(t testing.TB, target string) {
   418  				location := filepath.Join(target, "cmd", "simple-to-do-list-api-server")
   419  				require.True(t, fileExists("", location))
   420  
   421  				t.Log("building generated server")
   422  				goExecInDir(t, location, "build")
   423  			},
   424  		},
   425  		"conflict_name_api_issue_2405_3": {
   426  			spec:   "../fixtures/bugs/2405/fixture-2405.yaml",
   427  			target: "2405-3",
   428  			prepare: func(_ testing.TB, opts *GenOpts) {
   429  				opts.ServerPackage = "server"
   430  				opts.APIPackage = "api"
   431  				opts.IncludeMain = true
   432  			},
   433  			verify: func(t testing.TB, target string) {
   434  				location := filepath.Join(target, "cmd", "simple-to-do-list-api-server")
   435  				require.True(t, fileExists("", location))
   436  
   437  				t.Log("building generated server")
   438  				goExecInDir(t, location, "build")
   439  			},
   440  		},
   441  		"ext_types_issue_2385": {
   442  			spec:   "../fixtures/bugs/2385/fixture-2385.yaml",
   443  			target: "2385",
   444  			prepare: func(t testing.TB, opts *GenOpts) {
   445  				opts.MainPackage = "nrcodegen-server"
   446  				opts.IncludeMain = true
   447  				location := filepath.Join(opts.Target, "models")
   448  
   449  				// add some custom model to the generated models
   450  				addModelsToLocation(t, location, "my_type.go")
   451  			},
   452  			verify: func(_ testing.TB, target string) {
   453  				location := filepath.Join(target, "cmd", "nrcodegen-server")
   454  				require.True(t, fileExists("", location))
   455  
   456  				t.Log("building generated server")
   457  				goExecInDir(t, location, "build")
   458  
   459  				location = filepath.Join(target, "models")
   460  
   461  				t.Log("building generated models")
   462  				goExecInDir(t, location, "build")
   463  			},
   464  		},
   465  		"ext_types_full_example": {
   466  			spec:   "../examples/external-types/example-external-types.yaml",
   467  			target: "external-full",
   468  			prepare: func(_ testing.TB, opts *GenOpts) {
   469  				opts.MainPackage = "nrcodegen-server"
   470  				opts.IncludeMain = true
   471  				opts.ValidateSpec = false // the spec contains AdditionalItems
   472  				location := filepath.Join(opts.Target, "models")
   473  
   474  				// add some custom model to the generated models
   475  				addModelsToLocation(t, location, "my_type.go")
   476  			},
   477  			verify: func(t testing.TB, target string) {
   478  				location := filepath.Join(target, "cmd", "nrcodegen-server")
   479  				require.True(t, fileExists("", location))
   480  
   481  				t.Log("building generated server")
   482  				goExecInDir(t, location, "build")
   483  
   484  				location = filepath.Join(target, "models")
   485  
   486  				t.Log("building generated models")
   487  				goExecInDir(t, location, "build")
   488  			},
   489  		},
   490  	}
   491  }
   492  
   493  func addModelsToLocation(t testing.TB, location, file string) {
   494  	// writes some external model to a file to supplement codegen
   495  	// (test external types)
   496  	require.NoError(t, os.MkdirAll(location, 0700))
   497  
   498  	require.NoError(t, os.WriteFile(filepath.Join(location, file), []byte(`
   499  package models
   500  
   501  import (
   502    "context"
   503    "io"
   504    "github.com/go-openapi/strfmt"
   505  )
   506  
   507  // MyType ...
   508  type MyType string
   509  
   510  // Validate MyType
   511  func (MyType) Validate(strfmt.Registry) error { return nil }
   512  func (MyType) ContextValidate(context.Context, strfmt.Registry) error { return nil }
   513  
   514  // MyInteger ...
   515  type MyInteger int
   516  
   517  // Validate MyInteger
   518  func (MyInteger) Validate(strfmt.Registry) error { return nil }
   519  func (MyInteger) ContextValidate(context.Context, strfmt.Registry) error { return nil }
   520  
   521  // MyString ...
   522  type MyString string
   523  
   524  // Validate MyString
   525  func (MyString) Validate(strfmt.Registry) error { return nil }
   526  func (MyString) ContextValidate(context.Context, strfmt.Registry) error { return nil }
   527  
   528  // MyOtherType ...
   529  type MyOtherType struct{}
   530  
   531  // Validate MyOtherType
   532  func (MyOtherType) Validate(strfmt.Registry) error { return nil }
   533  func (MyOtherType) ContextValidate(context.Context, strfmt.Registry) error { return nil }
   534  
   535  // MyStreamer ...
   536  type MyStreamer io.Reader
   537  `),
   538  		os.ModePerm))
   539  }