github.com/ManabuSeki/goa-v1@v1.4.3/goagen/gen_client/cli_generator_test.go (about) 1 package genclient_test 2 3 import ( 4 "bytes" 5 "html/template" 6 "io/ioutil" 7 "os" 8 "path/filepath" 9 "strings" 10 11 "github.com/goadesign/goa/design" 12 "github.com/goadesign/goa/dslengine" 13 "github.com/goadesign/goa/goagen/codegen" 14 genclient "github.com/goadesign/goa/goagen/gen_client" 15 "github.com/goadesign/goa/version" 16 . "github.com/onsi/ginkgo" 17 . "github.com/onsi/gomega" 18 "github.com/onsi/gomega/gexec" 19 ) 20 21 var _ = Describe("Generate", func() { 22 var testgenPackagePath = filepath.FromSlash("github.com/goadesign/goa/goagen/gen_client/test_") 23 24 var outDir string 25 var files []string 26 var genErr error 27 28 BeforeEach(func() { 29 gopath := filepath.SplitList(os.Getenv("GOPATH"))[0] 30 outDir = filepath.Join(gopath, "src", testgenPackagePath) 31 err := os.MkdirAll(outDir, 0777) 32 Ω(err).ShouldNot(HaveOccurred()) 33 os.Args = []string{"goagen", "--out=" + outDir, "--design=foo", "--version=" + version.String()} 34 }) 35 36 JustBeforeEach(func() { 37 files, genErr = genclient.Generate() 38 }) 39 40 AfterEach(func() { 41 os.RemoveAll(outDir) 42 delete(codegen.Reserved, "client") 43 }) 44 45 Context("with a dummy API", func() { 46 BeforeEach(func() { 47 design.Design = &design.APIDefinition{ 48 Name: "testapi", 49 Title: "dummy API with no resource", 50 Description: "I told you it's dummy", 51 Consumes: design.DefaultEncoders, 52 } 53 }) 54 55 It("generates a dummy app", func() { 56 Ω(genErr).Should(BeNil()) 57 Ω(files).Should(HaveLen(8)) 58 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "testapi-cli", "main.go")) 59 content := string(c) 60 Ω(err).ShouldNot(HaveOccurred()) 61 Ω(len(strings.Split(content, "\n"))).Should(BeNumerically(">=", 16)) 62 _, err = gexec.Build(filepath.Join(testgenPackagePath, "tool", "testapi-cli")) 63 Ω(err).ShouldNot(HaveOccurred()) 64 }) 65 66 Context("generated commands.go", func() { 67 var commandHeader string 68 69 funcs := template.FuncMap{ 70 "sep": func() string { return string(os.PathSeparator) }, 71 } 72 73 BeforeEach(func() { 74 data := map[string]string{ 75 "outDir": outDir, 76 "design": "foo", 77 "tmpDir": testgenPackagePath, 78 "version": version.String(), 79 "title": "CLI Commands", 80 } 81 82 // Generate Headers 83 clientHeaderT, err := template.New("context").Funcs(funcs).Parse(clientHeaderTmpl) 84 Ω(err).ShouldNot(HaveOccurred()) 85 var b bytes.Buffer 86 err = clientHeaderT.Execute(&b, data) 87 Ω(err).ShouldNot(HaveOccurred()) 88 commandHeader = b.String() 89 }) 90 91 It("generates \"generated by\" header", func() { 92 Ω(genErr).Should(BeNil()) 93 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 94 content := string(c) 95 Ω(err).ShouldNot(HaveOccurred()) 96 Ω(content).Should(HavePrefix(commandHeader)) 97 }) 98 }) 99 }) 100 101 Context("with an action with two parameters", func() { 102 BeforeEach(func() { 103 codegen.TempCount = 0 104 design.Design = &design.APIDefinition{ 105 Name: "testapi", 106 Title: "dummy API with no resource", 107 Description: "I told you it's dummy", 108 Consumes: design.DefaultEncoders, 109 Resources: map[string]*design.ResourceDefinition{ 110 "foo": { 111 Name: "foo", 112 Actions: map[string]*design.ActionDefinition{ 113 "show": { 114 Name: "show", 115 Params: &design.AttributeDefinition{ 116 Type: design.Object{ 117 "nicID": &design.AttributeDefinition{Type: design.String}, 118 "ipAddress": &design.AttributeDefinition{Type: design.String}, 119 }, 120 }, 121 Routes: []*design.RouteDefinition{ 122 { 123 Verb: "GET", 124 Path: "/nics/:nicID/add/:ipAddress", 125 }, 126 }, 127 }, 128 }, 129 }, 130 }, 131 } 132 fooRes := design.Design.Resources["foo"] 133 showAct := fooRes.Actions["show"] 134 showAct.Parent = fooRes 135 showAct.Routes[0].Parent = showAct 136 }) 137 138 It("generate the correct command path formatting code", func() { 139 Ω(genErr).Should(BeNil()) 140 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 141 content := string(c) 142 Ω(err).ShouldNot(HaveOccurred()) 143 Ω(content).Should(ContainSubstring(`path = fmt.Sprintf("/nics/%v/add/%v", url.QueryEscape(cmd.NicID), url.QueryEscape(cmd.IPAddress)`)) 144 }) 145 }) 146 147 Context("with a resource name with underscores characters", func() { 148 BeforeEach(func() { 149 codegen.TempCount = 0 150 design.Design = &design.APIDefinition{ 151 Name: "testapi", 152 Title: "dummy API with no resource", 153 Description: "I told you it's dummy", 154 Consumes: design.DefaultEncoders, 155 Resources: map[string]*design.ResourceDefinition{ 156 "foo_test": { 157 Name: "foo_test", 158 Actions: map[string]*design.ActionDefinition{ 159 "show": { 160 Name: "show", 161 Params: &design.AttributeDefinition{ 162 Type: design.Object{ 163 "nicID": &design.AttributeDefinition{Type: design.String}, 164 "ipAddress": &design.AttributeDefinition{Type: design.String}, 165 }, 166 }, 167 Routes: []*design.RouteDefinition{ 168 { 169 Verb: "GET", 170 Path: "/nics/:nicID/add/:ipAddress", 171 }, 172 }, 173 }, 174 }, 175 }, 176 }, 177 } 178 fooRes := design.Design.Resources["foo_test"] 179 showAct := fooRes.Actions["show"] 180 showAct.Parent = fooRes 181 showAct.Routes[0].Parent = showAct 182 }) 183 184 It("generate the correct command path formatting code", func() { 185 Ω(genErr).Should(BeNil()) 186 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 187 content := string(c) 188 Ω(err).ShouldNot(HaveOccurred()) 189 Ω(content).Should(ContainSubstring("foo-test")) 190 }) 191 }) 192 193 Context("with an action with an integer parameter with no default value", func() { 194 BeforeEach(func() { 195 codegen.TempCount = 0 196 design.Design = &design.APIDefinition{ 197 Name: "testapi", 198 Title: "dummy API with no resource", 199 Description: "I told you it's dummy", 200 Consumes: design.DefaultEncoders, 201 Resources: map[string]*design.ResourceDefinition{ 202 "foo": { 203 Name: "foo", 204 Actions: map[string]*design.ActionDefinition{ 205 "show": { 206 Name: "show", 207 QueryParams: &design.AttributeDefinition{ 208 Type: design.Object{ 209 "param": &design.AttributeDefinition{Type: design.Integer}, 210 "time": &design.AttributeDefinition{Type: design.DateTime}, 211 "uuid": &design.AttributeDefinition{Type: design.UUID}, 212 "any": &design.AttributeDefinition{Type: design.Any}, 213 "bool": &design.AttributeDefinition{Type: design.Boolean}, 214 "number": &design.AttributeDefinition{Type: design.Number}, 215 "boolReq": &design.AttributeDefinition{Type: design.Boolean}, 216 "timeArray": &design.AttributeDefinition{Type: &design.Array{ElemType: &design.AttributeDefinition{Type: design.DateTime}}}, 217 "uuidArray": &design.AttributeDefinition{Type: &design.Array{ElemType: &design.AttributeDefinition{Type: design.UUID}}}, 218 "anyArray": &design.AttributeDefinition{Type: &design.Array{ElemType: &design.AttributeDefinition{Type: design.Any}}}, 219 "boolArray": &design.AttributeDefinition{Type: &design.Array{ElemType: &design.AttributeDefinition{Type: design.Boolean}}}, 220 "numberArray": &design.AttributeDefinition{Type: &design.Array{ElemType: &design.AttributeDefinition{Type: design.Number}}}, 221 }, 222 Validation: &dslengine.ValidationDefinition{Required: []string{"boolReq"}}, 223 }, 224 Routes: []*design.RouteDefinition{ 225 { 226 Verb: "GET", 227 Path: "", 228 }, 229 }, 230 }, 231 }, 232 }, 233 }, 234 } 235 fooRes := design.Design.Resources["foo"] 236 showAct := fooRes.Actions["show"] 237 showAct.Parent = fooRes 238 showAct.Routes[0].Parent = showAct 239 }) 240 241 It("generate the correct handling for special type DateTime", func() { 242 Ω(genErr).Should(BeNil()) 243 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 244 content := string(c) 245 Ω(err).ShouldNot(HaveOccurred()) 246 Ω(content).Should(ContainSubstring(", err = timeVal(cmd.Time)")) 247 Ω(content).Should(ContainSubstring(", tmp")) 248 Ω(content).Should(ContainSubstring("cc.Flags().StringVar(&cmd.Time, ")) 249 }) 250 It("generate the correct handling for special type DateTime Array", func() { 251 Ω(genErr).Should(BeNil()) 252 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 253 content := string(c) 254 Ω(err).ShouldNot(HaveOccurred()) 255 Ω(content).Should(ContainSubstring(", err = timeArray(cmd.TimeArray)")) 256 Ω(content).Should(ContainSubstring(", tmp")) 257 Ω(content).Should(ContainSubstring("cc.Flags().StringSliceVar(&cmd.TimeArray, ")) 258 }) 259 It("generate the correct handling for special type Number", func() { 260 Ω(genErr).Should(BeNil()) 261 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 262 content := string(c) 263 Ω(err).ShouldNot(HaveOccurred()) 264 Ω(content).Should(ContainSubstring(", err = float64Val(cmd.Number)")) 265 Ω(content).Should(ContainSubstring(", tmp")) 266 Ω(content).Should(ContainSubstring("cc.Flags().StringVar(&cmd.Number, ")) 267 }) 268 It("generate the correct handling for special type Number Array", func() { 269 Ω(genErr).Should(BeNil()) 270 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 271 content := string(c) 272 Ω(err).ShouldNot(HaveOccurred()) 273 Ω(content).Should(ContainSubstring(", err = float64Array(cmd.NumberArray)")) 274 Ω(content).Should(ContainSubstring(", tmp")) 275 Ω(content).Should(ContainSubstring("cc.Flags().StringSliceVar(&cmd.NumberArray, ")) 276 }) 277 It("generate the correct handling for special type Boolean", func() { 278 Ω(genErr).Should(BeNil()) 279 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 280 content := string(c) 281 Ω(err).ShouldNot(HaveOccurred()) 282 Ω(content).Should(ContainSubstring(", err = boolVal(cmd.Bool)")) 283 Ω(content).Should(ContainSubstring(", tmp")) 284 Ω(content).Should(ContainSubstring("cc.Flags().StringVar(&cmd.Bool, ")) 285 }) 286 It("generate the correct handling for special type Boolean Array", func() { 287 Ω(genErr).Should(BeNil()) 288 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 289 content := string(c) 290 Ω(err).ShouldNot(HaveOccurred()) 291 Ω(content).Should(ContainSubstring(", err = boolArray(cmd.BoolArray)")) 292 Ω(content).Should(ContainSubstring(", tmp")) 293 Ω(content).Should(ContainSubstring("cc.Flags().StringSliceVar(&cmd.BoolArray, ")) 294 }) 295 It("generate the correct handling for special type required Boolean", func() { 296 Ω(genErr).Should(BeNil()) 297 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 298 content := string(c) 299 Ω(err).ShouldNot(HaveOccurred()) 300 Ω(content).Should(ContainSubstring(", err = boolVal(cmd.BoolReq)")) 301 Ω(content).Should(ContainSubstring(", *tmp")) 302 Ω(content).Should(ContainSubstring("cc.Flags().StringVar(&cmd.BoolReq, ")) 303 }) 304 It("generate the correct handling for special type UUID", func() { 305 Ω(genErr).Should(BeNil()) 306 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 307 content := string(c) 308 Ω(err).ShouldNot(HaveOccurred()) 309 Ω(content).Should(ContainSubstring(", err = uuidVal(cmd.UUID)")) 310 Ω(content).Should(ContainSubstring(", tmp")) 311 Ω(content).Should(ContainSubstring("cc.Flags().StringVar(&cmd.UUID, ")) 312 }) 313 It("generate the correct handling for special type UUID Array", func() { 314 Ω(genErr).Should(BeNil()) 315 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 316 content := string(c) 317 Ω(err).ShouldNot(HaveOccurred()) 318 Ω(content).Should(ContainSubstring(", err = uuidArray(cmd.UUIDArray)")) 319 Ω(content).Should(ContainSubstring(", tmp")) 320 Ω(content).Should(ContainSubstring("cc.Flags().StringSliceVar(&cmd.UUIDArray, ")) 321 }) 322 It("generate the correct handling for special type Any", func() { 323 Ω(genErr).Should(BeNil()) 324 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 325 content := string(c) 326 Ω(err).ShouldNot(HaveOccurred()) 327 Ω(content).Should(ContainSubstring(", err = jsonVal(cmd.Any)")) 328 Ω(content).Should(ContainSubstring(", tmp")) 329 Ω(content).Should(ContainSubstring("cc.Flags().StringVar(&cmd.Any, ")) 330 }) 331 It("generate the correct handling for special type Any Array", func() { 332 Ω(genErr).Should(BeNil()) 333 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 334 content := string(c) 335 Ω(err).ShouldNot(HaveOccurred()) 336 Ω(content).Should(ContainSubstring(", err = jsonArray(cmd.AnyArray)")) 337 Ω(content).Should(ContainSubstring(", tmp")) 338 Ω(content).Should(ContainSubstring("cc.Flags().StringSliceVar(&cmd.AnyArray, ")) 339 }) 340 341 It("generates the correct command flag initialization code", func() { 342 Ω(genErr).Should(BeNil()) 343 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 344 content := string(c) 345 Ω(err).ShouldNot(HaveOccurred()) 346 Ω(len(strings.Split(content, "\n"))).Should(BeNumerically(">=", 3)) 347 Ω(content).Should(ContainSubstring("var param int")) 348 Ω(content).Should(ContainSubstring("var time_ string")) 349 Ω(content).Should(ContainSubstring(".Flags()")) 350 _, err = gexec.Build(filepath.Join(testgenPackagePath, "tool", "testapi-cli")) 351 Ω(err).ShouldNot(HaveOccurred()) 352 353 }) 354 355 Context("with an action with a multiline description", func() { 356 const multiline = "multi\nline" 357 358 BeforeEach(func() { 359 design.Design.Resources["foo"].Actions["show"].Description = multiline 360 }) 361 362 It("properly escapes the multi-line string used in the short description", func() { 363 Ω(genErr).Should(BeNil()) 364 Ω(files).Should(HaveLen(9)) 365 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 366 content := string(c) 367 Ω(err).ShouldNot(HaveOccurred()) 368 Ω(content).Should(ContainSubstring(multiline)) 369 }) 370 }) 371 372 Context("with an action with a description containing backticks", func() { 373 const pre = "pre" 374 const post = "post" 375 376 BeforeEach(func() { 377 design.Design.Resources["foo"].Actions["show"].Description = pre + "`" + post 378 }) 379 380 It("properly escapes the multi-line string used in the short description", func() { 381 Ω(genErr).Should(BeNil()) 382 Ω(files).Should(HaveLen(9)) 383 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 384 content := string(c) 385 Ω(err).ShouldNot(HaveOccurred()) 386 Ω(content).Should(ContainSubstring(pre + "` + \"`\" + `" + post)) 387 }) 388 }) 389 }) 390 Context("with an action with a special typed UUID path param", func() { 391 BeforeEach(func() { 392 codegen.TempCount = 0 393 design.Design = &design.APIDefinition{ 394 Name: "testapi", 395 Title: "dummy API with no resource", 396 Description: "I told you it's dummy", 397 Consumes: design.DefaultEncoders, 398 Resources: map[string]*design.ResourceDefinition{ 399 "foo": { 400 Name: "foo", 401 Actions: map[string]*design.ActionDefinition{ 402 "show": { 403 Name: "show", 404 Params: &design.AttributeDefinition{ 405 Type: design.Object{ 406 "id": &design.AttributeDefinition{Type: design.UUID}, 407 }, 408 Validation: &dslengine.ValidationDefinition{Required: []string{"id"}}, 409 }, 410 Routes: []*design.RouteDefinition{ 411 { 412 Verb: "GET", 413 Path: "resource/:id", 414 }, 415 }, 416 }, 417 }, 418 }, 419 }, 420 } 421 fooRes := design.Design.Resources["foo"] 422 showAct := fooRes.Actions["show"] 423 showAct.Parent = fooRes 424 showAct.Routes[0].Parent = showAct 425 }) 426 427 It("generates direct access to Command field when resolving path", func() { 428 Ω(genErr).Should(BeNil()) 429 Ω(files).Should(HaveLen(9)) 430 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "cli", "commands.go")) 431 content := string(c) 432 Ω(err).ShouldNot(HaveOccurred()) 433 Ω(content).Should(ContainSubstring("path = fmt.Sprintf(\"/resource/%v\", cmd.ID)")) 434 }) 435 }) 436 437 Context("with an action with security configured", func() { 438 BeforeEach(func() { 439 codegen.TempCount = 0 440 securitySchemeDef := &design.SecuritySchemeDefinition{ 441 SchemeName: "jwt-1", 442 Kind: design.JWTSecurityKind, 443 } 444 design.Design = &design.APIDefinition{ 445 Name: "testapi", 446 Title: "dummy API with no resource", 447 Description: "I told you it's dummy", 448 Consumes: design.DefaultEncoders, 449 SecuritySchemes: []*design.SecuritySchemeDefinition{ 450 securitySchemeDef, 451 }, 452 Resources: map[string]*design.ResourceDefinition{ 453 "foo": { 454 Name: "foo", 455 Actions: map[string]*design.ActionDefinition{ 456 "show": { 457 Name: "show", 458 QueryParams: &design.AttributeDefinition{ 459 Type: design.Object{ 460 "param": &design.AttributeDefinition{Type: design.Integer}, 461 "time": &design.AttributeDefinition{Type: design.DateTime}, 462 }, 463 }, 464 Routes: []*design.RouteDefinition{ 465 { 466 Verb: "GET", 467 Path: "", 468 }, 469 }, 470 Security: &design.SecurityDefinition{ 471 Scheme: securitySchemeDef, 472 }, 473 }, 474 }, 475 }, 476 }, 477 } 478 fooRes := design.Design.Resources["foo"] 479 showAct := fooRes.Actions["show"] 480 showAct.Parent = fooRes 481 showAct.Routes[0].Parent = showAct 482 }) 483 484 It("generates registers the signer flags from main", func() { 485 Ω(genErr).Should(BeNil()) 486 Ω(files).Should(HaveLen(9)) 487 c, err := ioutil.ReadFile(filepath.Join(outDir, "tool", "testapi-cli", "main.go")) 488 content := string(c) 489 Ω(err).ShouldNot(HaveOccurred()) 490 Ω(content).Should(ContainSubstring("jwt1Signer := newJWT1Signer()")) 491 Ω(content).Should(ContainSubstring("c.SetJWT1Signer(jwt1Signer)")) 492 }) 493 }) 494 })