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 }