github.com/shogo82148/goa-v1@v1.6.2/design/validation_test.go (about)

     1  package design_test
     2  
     3  import (
     4  	"go/build"
     5  	"os"
     6  	"path"
     7  
     8  	. "github.com/onsi/ginkgo"
     9  	. "github.com/onsi/gomega"
    10  	"github.com/shogo82148/goa-v1/design"
    11  	"github.com/shogo82148/goa-v1/design/apidsl"
    12  	"github.com/shogo82148/goa-v1/dslengine"
    13  )
    14  
    15  var _ = Describe("Validation", func() {
    16  	Context("with a type attribute", func() {
    17  		const attName = "attName"
    18  		var dsl func()
    19  
    20  		var att *design.AttributeDefinition
    21  
    22  		JustBeforeEach(func() {
    23  			dslengine.Reset()
    24  			apidsl.Type("bar", func() {
    25  				dsl()
    26  			})
    27  			dslengine.Run()
    28  			if dslengine.Errors == nil {
    29  				Ω(design.Design.Types).ShouldNot(BeNil())
    30  				Ω(design.Design.Types).Should(HaveKey("bar"))
    31  				Ω(design.Design.Types["bar"]).ShouldNot(BeNil())
    32  				Ω(design.Design.Types["bar"].Type).Should(BeAssignableToTypeOf(design.Object{}))
    33  				o := design.Design.Types["bar"].Type.(design.Object)
    34  				Ω(o).Should(HaveKey(attName))
    35  				att = o[attName]
    36  			}
    37  		})
    38  
    39  		Context("with a valid enum validation", func() {
    40  			BeforeEach(func() {
    41  				dsl = func() {
    42  					apidsl.Attribute(attName, design.String, func() {
    43  						apidsl.Enum("red", "blue")
    44  					})
    45  				}
    46  			})
    47  
    48  			It("records the validation", func() {
    49  				Ω(dslengine.Errors).ShouldNot(HaveOccurred())
    50  				Ω(att.Validation).ShouldNot(BeNil())
    51  				Ω(att.Validation.Values).Should(Equal([]interface{}{"red", "blue"}))
    52  			})
    53  		})
    54  
    55  		Context("with an incompatible enum validation type", func() {
    56  			BeforeEach(func() {
    57  				dsl = func() {
    58  					apidsl.Attribute(attName, design.Integer, func() {
    59  						apidsl.Enum(1, "blue")
    60  					})
    61  				}
    62  			})
    63  
    64  			It("produces an error", func() {
    65  				Ω(dslengine.Errors).Should(HaveOccurred())
    66  			})
    67  		})
    68  
    69  		Context("with a default value that doesn't exist in enum", func() {
    70  			BeforeEach(func() {
    71  				dsl = func() {
    72  					apidsl.Attribute(attName, design.Integer, func() {
    73  						apidsl.Enum(1, 2, 3)
    74  						apidsl.Default(4)
    75  					})
    76  				}
    77  			})
    78  			It("produces an error", func() {
    79  				Ω(dslengine.Errors).Should(HaveOccurred())
    80  				Ω(dslengine.Errors.Error()).Should(Equal(
    81  					`type "bar": field attName - default value 4 is not one of the accepted values: []interface {}{1, 2, 3}`))
    82  			})
    83  		})
    84  
    85  		Context("with a valid format validation", func() {
    86  			BeforeEach(func() {
    87  				dsl = func() {
    88  					apidsl.Attribute(attName, design.String, func() {
    89  						apidsl.Format("email")
    90  					})
    91  				}
    92  			})
    93  
    94  			It("records the validation", func() {
    95  				Ω(dslengine.Errors).ShouldNot(HaveOccurred())
    96  				Ω(att.Validation).ShouldNot(BeNil())
    97  				Ω(att.Validation.Format).Should(Equal("email"))
    98  			})
    99  		})
   100  
   101  		Context("with an invalid format validation", func() {
   102  			BeforeEach(func() {
   103  				dsl = func() {
   104  					apidsl.Attribute(attName, design.String, func() {
   105  						apidsl.Format("emailz")
   106  					})
   107  				}
   108  			})
   109  
   110  			It("produces an error", func() {
   111  				Ω(dslengine.Errors).Should(HaveOccurred())
   112  			})
   113  		})
   114  
   115  		Context("with a valid pattern validation", func() {
   116  			BeforeEach(func() {
   117  				dsl = func() {
   118  					apidsl.Attribute(attName, design.String, func() {
   119  						apidsl.Pattern("^foo$")
   120  					})
   121  				}
   122  			})
   123  
   124  			It("records the validation", func() {
   125  				Ω(dslengine.Errors).ShouldNot(HaveOccurred())
   126  				Ω(att.Validation).ShouldNot(BeNil())
   127  				Ω(att.Validation.Pattern).Should(Equal("^foo$"))
   128  			})
   129  		})
   130  
   131  		Context("with an invalid pattern validation", func() {
   132  			BeforeEach(func() {
   133  				dsl = func() {
   134  					apidsl.Attribute(attName, design.String, func() {
   135  						apidsl.Pattern("[invalid")
   136  					})
   137  				}
   138  			})
   139  
   140  			It("produces an error", func() {
   141  				Ω(dslengine.Errors).Should(HaveOccurred())
   142  			})
   143  		})
   144  
   145  		Context("with an invalid format validation type", func() {
   146  			BeforeEach(func() {
   147  				dsl = func() {
   148  					apidsl.Attribute(attName, design.Integer, func() {
   149  						apidsl.Format("email")
   150  					})
   151  				}
   152  			})
   153  
   154  			It("produces an error", func() {
   155  				Ω(dslengine.Errors).Should(HaveOccurred())
   156  			})
   157  		})
   158  
   159  		Context("with a valid min value validation", func() {
   160  			BeforeEach(func() {
   161  				dsl = func() {
   162  					apidsl.Attribute(attName, design.Integer, func() {
   163  						apidsl.Minimum(2)
   164  					})
   165  				}
   166  			})
   167  
   168  			It("records the validation", func() {
   169  				Ω(dslengine.Errors).ShouldNot(HaveOccurred())
   170  				Ω(att.Validation).ShouldNot(BeNil())
   171  				Ω(*att.Validation.Minimum).Should(Equal(2.0))
   172  			})
   173  		})
   174  
   175  		Context("with an invalid min value validation", func() {
   176  			BeforeEach(func() {
   177  				dsl = func() {
   178  					apidsl.Attribute(attName, design.String, func() {
   179  						apidsl.Minimum(2)
   180  					})
   181  				}
   182  			})
   183  
   184  			It("produces an error", func() {
   185  				Ω(dslengine.Errors).Should(HaveOccurred())
   186  			})
   187  		})
   188  
   189  		Context("with a valid max value validation", func() {
   190  			BeforeEach(func() {
   191  				dsl = func() {
   192  					apidsl.Attribute(attName, design.Integer, func() {
   193  						apidsl.Maximum(2)
   194  					})
   195  				}
   196  			})
   197  
   198  			It("records the validation", func() {
   199  				Ω(dslengine.Errors).ShouldNot(HaveOccurred())
   200  				Ω(att.Validation).ShouldNot(BeNil())
   201  				Ω(*att.Validation.Maximum).Should(Equal(2.0))
   202  			})
   203  		})
   204  
   205  		Context("with an invalid max value validation", func() {
   206  			BeforeEach(func() {
   207  				dsl = func() {
   208  					apidsl.Attribute(attName, design.String, func() {
   209  						apidsl.Maximum(2)
   210  					})
   211  				}
   212  			})
   213  
   214  			It("produces an error", func() {
   215  				Ω(dslengine.Errors).Should(HaveOccurred())
   216  			})
   217  		})
   218  
   219  		Context("with a valid min length validation", func() {
   220  			BeforeEach(func() {
   221  				dsl = func() {
   222  					apidsl.Attribute(attName, apidsl.ArrayOf(design.Integer), func() {
   223  						apidsl.MinLength(2)
   224  					})
   225  				}
   226  			})
   227  
   228  			It("records the validation", func() {
   229  				Ω(dslengine.Errors).ShouldNot(HaveOccurred())
   230  				Ω(att.Validation).ShouldNot(BeNil())
   231  				Ω(*att.Validation.MinLength).Should(Equal(2))
   232  			})
   233  		})
   234  
   235  		Context("with an invalid min length validation", func() {
   236  			BeforeEach(func() {
   237  				dsl = func() {
   238  					apidsl.Attribute(attName, design.Integer, func() {
   239  						apidsl.MinLength(2)
   240  					})
   241  				}
   242  			})
   243  
   244  			It("produces an error", func() {
   245  				Ω(dslengine.Errors).Should(HaveOccurred())
   246  			})
   247  		})
   248  
   249  		Context("with a valid max length validation", func() {
   250  			BeforeEach(func() {
   251  				dsl = func() {
   252  					apidsl.Attribute(attName, design.String, func() {
   253  						apidsl.MaxLength(2)
   254  					})
   255  				}
   256  			})
   257  
   258  			It("records the validation", func() {
   259  				Ω(dslengine.Errors).ShouldNot(HaveOccurred())
   260  				Ω(att.Validation).ShouldNot(BeNil())
   261  				Ω(*att.Validation.MaxLength).Should(Equal(2))
   262  			})
   263  		})
   264  
   265  		Context("with an invalid max length validation", func() {
   266  			BeforeEach(func() {
   267  				dsl = func() {
   268  					apidsl.Attribute(attName, design.Integer, func() {
   269  						apidsl.MaxLength(2)
   270  					})
   271  				}
   272  			})
   273  
   274  			It("produces an error", func() {
   275  				Ω(dslengine.Errors).Should(HaveOccurred())
   276  			})
   277  		})
   278  
   279  		Context("with a required field validation", func() {
   280  			BeforeEach(func() {
   281  				dsl = func() {
   282  					apidsl.Attribute(attName, design.String)
   283  					apidsl.Required(attName)
   284  				}
   285  			})
   286  
   287  			It("records the validation", func() {
   288  				Ω(dslengine.Errors).ShouldNot(HaveOccurred())
   289  				Ω(design.Design.Types["bar"].Validation).ShouldNot(BeNil())
   290  				Ω(design.Design.Types["bar"].Validation.Required).Should(Equal([]string{attName}))
   291  			})
   292  		})
   293  	})
   294  
   295  	Context("actions with different http methods", func() {
   296  		It("should be valid because methods are different", func() {
   297  			dslengine.Reset()
   298  
   299  			apidsl.Resource("one", func() {
   300  				apidsl.Action("first", func() {
   301  					apidsl.Routing(apidsl.GET("/:first"))
   302  				})
   303  				apidsl.Action("second", func() {
   304  					apidsl.Routing(apidsl.DELETE("/:second"))
   305  				})
   306  			})
   307  
   308  			dslengine.Run()
   309  
   310  			Ω(dslengine.Errors).ShouldNot(HaveOccurred())
   311  		})
   312  	})
   313  
   314  	Context("with an action", func() {
   315  		var dsl func()
   316  
   317  		JustBeforeEach(func() {
   318  			dslengine.Reset()
   319  			apidsl.Resource("foo", func() {
   320  				apidsl.Action("bar", func() {
   321  					apidsl.Routing(apidsl.GET("/buz"))
   322  					dsl()
   323  				})
   324  			})
   325  			dslengine.Run()
   326  		})
   327  
   328  		Context("which has a file type param", func() {
   329  			BeforeEach(func() {
   330  				dsl = func() {
   331  					apidsl.Params(func() {
   332  						apidsl.Param("file", design.File)
   333  					})
   334  				}
   335  			})
   336  
   337  			It("produces an error", func() {
   338  				Ω(dslengine.Errors.Error()).Should(Equal(
   339  					`resource "foo" action "bar": Param file has an invalid type, action params cannot be a file`,
   340  				))
   341  			})
   342  		})
   343  
   344  		Context("which has a file array type param", func() {
   345  			BeforeEach(func() {
   346  				dsl = func() {
   347  					apidsl.Params(func() {
   348  						apidsl.Param("file_array", apidsl.ArrayOf(design.File))
   349  					})
   350  				}
   351  			})
   352  
   353  			It("produces an error", func() {
   354  				Ω(dslengine.Errors.Error()).Should(Equal(
   355  					`resource "foo" action "bar": Param file_array has an invalid type, action params cannot be a file array`,
   356  				))
   357  			})
   358  		})
   359  
   360  		Context("which has a payload contains a file", func() {
   361  			dslengine.Reset()
   362  			var payload = apidsl.Type("qux", func() {
   363  				apidsl.Attribute("file", design.File)
   364  				apidsl.Required("file")
   365  			})
   366  			dslengine.Run()
   367  
   368  			BeforeEach(func() {
   369  				dsl = func() {
   370  					apidsl.Payload(payload)
   371  				}
   372  			})
   373  
   374  			It("produces an error", func() {
   375  				Ω(dslengine.Errors.Error()).Should(Equal(
   376  					`resource "foo" action "bar": Payload qux contains an invalid type, action payloads cannot contain a file`,
   377  				))
   378  			})
   379  
   380  			Context("and multipart form", func() {
   381  				BeforeEach(func() {
   382  					dsl = func() {
   383  						apidsl.Payload(payload)
   384  						apidsl.MultipartForm()
   385  					}
   386  				})
   387  
   388  				It("produces no error", func() {
   389  					Ω(dslengine.Errors).ShouldNot(HaveOccurred())
   390  				})
   391  			})
   392  		})
   393  
   394  		Context("which has a response contains a file", func() {
   395  			BeforeEach(func() {
   396  				dslengine.Reset()
   397  				var response = apidsl.MediaType("application/vnd.goa.example", func() {
   398  					apidsl.TypeName("quux")
   399  					apidsl.Attributes(func() {
   400  						apidsl.Attribute("file", design.File)
   401  						apidsl.Required("file")
   402  					})
   403  					apidsl.View("default", func() {
   404  						apidsl.Attribute("file")
   405  					})
   406  				})
   407  				dslengine.Run()
   408  				dsl = func() {
   409  					apidsl.Response(design.OK, response)
   410  				}
   411  			})
   412  
   413  			It("produces an error", func() {
   414  				Ω(dslengine.Errors.Error()).Should(Equal(
   415  					`resource "foo" action "bar": Response OK contains an invalid type, action responses cannot contain a file`,
   416  				))
   417  			})
   418  		})
   419  	})
   420  
   421  	Describe("EncoderDefinition", func() {
   422  		var (
   423  			enc           *design.EncodingDefinition
   424  			oldGoPath     string
   425  			oldWorkingDir string
   426  			cellarPath    string
   427  		)
   428  
   429  		BeforeEach(func() {
   430  			enc = &design.EncodingDefinition{MIMETypes: []string{"application/foo"}, Encoder: true, PackagePath: "github.com/shogo82148/goa-v1/encoding/foo"}
   431  			oldGoPath = build.Default.GOPATH
   432  
   433  			var err error
   434  			oldWorkingDir, err = os.Getwd()
   435  			Ω(err).ShouldNot(HaveOccurred())
   436  
   437  			cellarPath = path.Join(oldWorkingDir, "tmp_gopath/src/github.com/shogo82148/goa-v1_fake_cellar")
   438  			Ω(os.MkdirAll(cellarPath, 0777)).ShouldNot(HaveOccurred())
   439  		})
   440  
   441  		JustBeforeEach(func() {
   442  			build.Default.GOPATH = path.Join(oldWorkingDir, "tmp_gopath")
   443  			Ω(os.Chdir(cellarPath)).ShouldNot(HaveOccurred())
   444  		})
   445  
   446  		AfterEach(func() {
   447  			build.Default.GOPATH = oldGoPath
   448  			os.Chdir(oldWorkingDir)
   449  			Ω(os.RemoveAll("tmp_gopath")).ShouldNot(HaveOccurred())
   450  		})
   451  
   452  		Context("with package is not found", func() {
   453  			It("returns a validation error", func() {
   454  				Ω(len(enc.Validate().Errors)).Should(Equal(1))
   455  				Ω(enc.Validate().Errors[0].Error()).Should(MatchRegexp("^invalid Go package path"))
   456  			})
   457  		})
   458  
   459  		// FIXME @shogo82148
   460  		// Context("with package in gopath", func() {
   461  		// 	BeforeEach(func() {
   462  		// 		packagePath := path.Join(cellarPath, "../goa/encoding/foo")
   463  
   464  		// 		Ω(os.MkdirAll(packagePath, 0777)).ShouldNot(HaveOccurred())
   465  		// 		Ω(ioutil.WriteFile(path.Join(packagePath, "encoding.go"), []byte("package foo"), 0777)).ShouldNot(HaveOccurred())
   466  		// 	})
   467  
   468  		// 	It("validates EncoderDefinition", func() {
   469  		// 		Ω(enc.Validate().Errors).Should(BeNil())
   470  		// 	})
   471  		// })
   472  
   473  		// Context("with package in vendor", func() {
   474  		// 	BeforeEach(func() {
   475  		// 		packagePath := path.Join(cellarPath, "vendor/github.com/shogo82148/goa-v1/encoding/foo")
   476  
   477  		// 		Ω(os.MkdirAll(packagePath, 0777)).ShouldNot(HaveOccurred())
   478  		// 		Ω(ioutil.WriteFile(path.Join(packagePath, "encoding.go"), []byte("package foo"), 0777)).ShouldNot(HaveOccurred())
   479  		// 	})
   480  
   481  		// 	It("validates EncoderDefinition", func() {
   482  		// 		Ω(enc.Validate().Errors).Should(BeNil())
   483  		// 	})
   484  		// })
   485  	})
   486  })