github.com/unionj-cloud/go-doudou@v1.3.8-0.20221011095552-0088008e5b31/cmd/internal/svc/codegen/httpintegrationtesting.go (about)

     1  package codegen
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"fmt"
     7  	"github.com/rbretecher/go-postman-collection"
     8  	"github.com/unionj-cloud/go-doudou/cmd/internal/astutils"
     9  	"github.com/unionj-cloud/go-doudou/toolkit/zlogger"
    10  	"github.com/unionj-cloud/go-doudou/version"
    11  	"go/ast"
    12  	"go/parser"
    13  	"go/token"
    14  	"io/ioutil"
    15  	"os"
    16  	"path/filepath"
    17  	"strings"
    18  	"text/template"
    19  )
    20  
    21  var appendIntegrationTestingTmpl = `
    22  {{- range $response := .Responses }}
    23  
    24  func Test_{{$response.Name | cleanName}}(t *testing.T) {
    25  	apitest.New("{{$response.Name}}").
    26  		Handler(router).
    27  		{{$response.OriginalRequest.Method | toString | capital}}("{{ $response.OriginalRequest.URL.Path | toEndpoint }}").
    28  {{- range $header := $response.OriginalRequest.Header }}
    29  {{- if not $header.Disabled }}
    30  		Header("{{$header.Key}}", "{{$header.Value}}").
    31  {{- end }}
    32  {{- end }}
    33  {{- if $response.OriginalRequest.URL.Query }}
    34  {{- range $query := $response.OriginalRequest.URL.Query }}
    35  {{- if not (index $query "disabled") }}
    36  		Query("{{index $query "key"}}", "{{index $query "value"}}").
    37  {{- end }}
    38  {{- end }}
    39  {{- end }}
    40  {{- if $response.OriginalRequest.Body }}
    41  {{- if eq $response.OriginalRequest.Body.Mode "raw" }}
    42  		JSON(` + "`" + `{{$response.OriginalRequest.Body.Raw}}` + "`" + `).
    43  {{- else if eq $response.OriginalRequest.Body.Mode "urlencoded" }}
    44  {{- range $query := $response.OriginalRequest.Body.URLEncoded }}
    45  {{- if not (index $query "disabled") }}
    46  		FormData("{{index $query "key"}}", "{{index $query "value"}}").
    47  {{- end }}
    48  {{- end }}
    49  {{- else if eq $response.OriginalRequest.Body.Mode "formdata" }}
    50  {{- range $query := $response.OriginalRequest.Body.FormData }}
    51  {{- if not (index $query "disabled") }}
    52  {{- if eq (index $query "type") "file" }}
    53  		MultipartFile("{{index $query "key"}}", "{{index $query "src"}}").
    54  {{- else }}
    55  		MultipartFormData("{{index $query "key"}}", "{{index $query "value"}}").
    56  {{- end }}
    57  {{- end }}
    58  {{- end }}
    59  {{- end }}
    60  {{- end }}
    61  		Expect(t).
    62  {{- if $response.Body }}
    63  		Body(` + "`" + `{{$response.Body}}` + "`" + `).
    64  {{- end }}
    65  		Status({{$response.Code}}).
    66  		End()
    67  }
    68  {{- end }}
    69  `
    70  
    71  var integrationTestingImportTmpl = `
    72  	"github.com/gorilla/mux"
    73  	"github.com/joho/godotenv"
    74  	"github.com/rs/zerolog"
    75  	"github.com/unionj-cloud/go-doudou/toolkit/zlogger"
    76  	"github.com/steinfletcher/apitest"
    77  	jsonpath "github.com/steinfletcher/apitest-jsonpath"
    78  	"net/http"
    79  	{{.ServiceAlias}} "{{.ServicePackage}}"
    80  	"{{.ServicePackage}}/config"
    81  	"{{.ServicePackage}}/db"
    82  	"{{.ServicePackage}}/transport/httpsrv"
    83  	"testing"
    84  `
    85  
    86  var initIntegrationTestingTmpl = `/**
    87  * Generated by go-doudou {{.Version}}.
    88  * You can edit it as your need.
    89  */
    90  package integrationtest_test
    91  
    92  import ()
    93  
    94  var (
    95  	router *mux.Router
    96  )
    97  
    98  func TestMain(m *testing.M) {
    99  	_ = godotenv.Load("{{.DotenvPath}}")
   100  	conf := config.LoadFromEnv()
   101  	conn, err := db.NewDb(conf.DbConf)
   102  	if err != nil {
   103  		panic(err)
   104  	}
   105  	defer func() {
   106  		if conn == nil {
   107  			return
   108  		}
   109  		if err := conn.Close(); err == nil {
   110  			zlogger.Info().Msg("Database connection is closed")
   111  		} else {
   112  			zlogger.Warn().Msg("Failed to close database connection")
   113  		}
   114  	}()
   115  	svc := {{.ServiceAlias}}.New{{.SvcName}}(conf, conn)
   116  	handler := httpsrv.New{{.SvcName}}Handler(svc)
   117  	router = mux.NewRouter()
   118  	for _, item := range httpsrv.Routes(handler) {
   119  		router.
   120  			Methods(item.Method).
   121  			Path(item.Pattern).
   122  			Name(item.Name).
   123  			Handler(item.HandlerFunc)
   124  	}
   125  	m.Run()
   126  }
   127  ` + appendIntegrationTestingTmpl
   128  
   129  func toEndpoint(input []string) string {
   130  	return "/" + strings.Join(input, "/")
   131  }
   132  
   133  func toString(input postman.Method) string {
   134  	return string(input)
   135  }
   136  
   137  func capital(input string) string {
   138  	return strings.Title(strings.ToLower(input))
   139  }
   140  
   141  func GenHttpIntegrationTesting(dir string, ic astutils.InterfaceCollector, postmanCollectionPath, dotenvPath string) {
   142  	var (
   143  		err                error
   144  		modfile            string
   145  		modName            string
   146  		firstLine          string
   147  		testFile           string
   148  		f                  *os.File
   149  		modf               *os.File
   150  		tpl                *template.Template
   151  		buf                bytes.Buffer
   152  		fi                 os.FileInfo
   153  		tmpl               string
   154  		importBuf          bytes.Buffer
   155  		integrationTestDir string
   156  		responses          []*postman.Response
   157  	)
   158  	integrationTestDir = filepath.Join(dir, "integrationtest")
   159  	if err = os.MkdirAll(integrationTestDir, os.ModePerm); err != nil {
   160  		panic(err)
   161  	}
   162  	testFile = filepath.Join(integrationTestDir, "integration_test.go")
   163  	responses = notGenerated(integrationTestDir, postmanCollectionPath)
   164  	fi, _ = os.Stat(testFile)
   165  	if fi != nil {
   166  		zlogger.Warn().Msg("New content will be append to integration_test.go file")
   167  		if f, err = os.OpenFile(testFile, os.O_APPEND, os.ModePerm); err != nil {
   168  			panic(err)
   169  		}
   170  		defer f.Close()
   171  		tmpl = appendIntegrationTestingTmpl
   172  	} else {
   173  		if f, err = os.Create(testFile); err != nil {
   174  			panic(err)
   175  		}
   176  		defer f.Close()
   177  		tmpl = initIntegrationTestingTmpl
   178  	}
   179  	modfile = filepath.Join(dir, "go.mod")
   180  	if modf, err = os.Open(modfile); err != nil {
   181  		panic(err)
   182  	}
   183  	reader := bufio.NewReader(modf)
   184  	firstLine, _ = reader.ReadString('\n')
   185  	modName = strings.TrimSpace(strings.TrimPrefix(firstLine, "module"))
   186  
   187  	funcMap := make(map[string]interface{})
   188  	funcMap["toEndpoint"] = toEndpoint
   189  	funcMap["toString"] = toString
   190  	funcMap["capital"] = capital
   191  	funcMap["cleanName"] = cleanName
   192  	if tpl, err = template.New("integration_test.go.tmpl").Funcs(funcMap).Parse(tmpl); err != nil {
   193  		panic(err)
   194  	}
   195  	absDotenv, _ := filepath.Abs(dotenvPath)
   196  	absDir, _ := filepath.Abs(integrationTestDir)
   197  	relDotenv, _ := filepath.Rel(absDir, absDotenv)
   198  	if err = tpl.Execute(&buf, struct {
   199  		ServicePackage string
   200  		ServiceAlias   string
   201  		Version        string
   202  		DotenvPath     string
   203  		SvcName        string
   204  		Responses      []*postman.Response
   205  	}{
   206  		ServicePackage: modName,
   207  		ServiceAlias:   ic.Package.Name,
   208  		Version:        version.Release,
   209  		DotenvPath:     relDotenv,
   210  		SvcName:        ic.Interfaces[0].Name,
   211  		Responses:      responses,
   212  	}); err != nil {
   213  		panic(err)
   214  	}
   215  	original, err := ioutil.ReadAll(f)
   216  	if err != nil {
   217  		panic(err)
   218  	}
   219  	original = append(original, buf.Bytes()...)
   220  	if tpl, err = template.New("testimportimpl.go.tmpl").Parse(integrationTestingImportTmpl); err != nil {
   221  		panic(err)
   222  	}
   223  	if err = tpl.Execute(&importBuf, struct {
   224  		ServicePackage string
   225  		ServiceAlias   string
   226  	}{
   227  		ServicePackage: modName,
   228  		ServiceAlias:   ic.Package.Name,
   229  	}); err != nil {
   230  		panic(err)
   231  	}
   232  	original = astutils.AppendImportStatements(original, importBuf.Bytes())
   233  	astutils.FixImport(original, testFile)
   234  }
   235  
   236  func notGenerated(dir, postmanCollectionPath string) []*postman.Response {
   237  	file, err := os.Open(postmanCollectionPath)
   238  	if err != nil {
   239  		panic(err)
   240  	}
   241  	defer file.Close()
   242  	c, err := postman.ParseCollection(file)
   243  	if err != nil {
   244  		panic(err)
   245  	}
   246  	responses := flattenResponses(c.Items)
   247  	var files []string
   248  	err = filepath.Walk(dir, astutils.Visit(&files))
   249  	if err != nil {
   250  		panic(err)
   251  	}
   252  	sc := astutils.NewStaticMethodCollector(astutils.ExprString)
   253  	for _, file := range files {
   254  		root, err := parser.ParseFile(token.NewFileSet(), file, nil, parser.ParseComments)
   255  		if err != nil {
   256  			panic(err)
   257  		}
   258  		ast.Walk(sc, root)
   259  	}
   260  	methodStore := make(map[string]struct{})
   261  	for _, item := range sc.Methods {
   262  		methodStore[item.Name] = struct{}{}
   263  	}
   264  	var result []*postman.Response
   265  	for _, item := range responses {
   266  		methodName := fmt.Sprintf("Test_%s", cleanName(item.Name))
   267  		if _, exists := methodStore[methodName]; !exists {
   268  			result = append(result, item)
   269  		}
   270  	}
   271  	return result
   272  }
   273  
   274  func flattenResponses(items []*postman.Items) []*postman.Response {
   275  	var result []*postman.Response
   276  	for _, item := range items {
   277  		if len(item.Items) > 0 {
   278  			result = append(result, flattenResponses(item.Items)...)
   279  		} else {
   280  			for _, resp := range item.Responses {
   281  				if resp.Name != "Untitled Example" && resp.Name != "response" {
   282  					result = append(result, resp)
   283  				}
   284  			}
   285  		}
   286  	}
   287  	return result
   288  }
   289  
   290  func cleanName(name string) string {
   291  	name = strings.ReplaceAll(strings.ReplaceAll(name, "{", ""), "}", "")
   292  	name = strings.ReplaceAll(strings.Trim(name, "/"), "/", "_")
   293  	return name
   294  }