diff --git a/cmd/server_test.go b/cmd/server_test.go index dbe5fcd..131e6ac 100644 --- a/cmd/server_test.go +++ b/cmd/server_test.go @@ -74,7 +74,7 @@ func TestPrintProto(t *testing.T) { name: "mock server, normal", args: []string{"server", "--mock-config=testdata/invalid-api.yaml", "-p=0", "--http-port=0"}, verify: func(t *testing.T, buffer *bytes.Buffer, err error) { - assert.NoError(t, err) + assert.Error(t, err) }, }} for _, tt := range tests { diff --git a/docs/api-testing-mock-schema.json b/docs/api-testing-mock-schema.json new file mode 100644 index 0000000..d67f308 --- /dev/null +++ b/docs/api-testing-mock-schema.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Mock Server Schema", + "type": "object", + "properties": { + "objects": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "initCount": {"type": "integer"}, + "sample": {"type": "string"}, + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "kind": {"type": "string"} + }, + "required": ["name", "kind"] + } + } + }, + "required": ["name"] + } + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "request": { + "type": "object", + "properties": { + "path": {"type": "string"}, + "method": {"type": "string"}, + "header": { + "type": "object", + "additionalProperties": {"type": "string"} + }, + "body": {"type": "string"} + }, + "required": ["path"] + }, + "response": { + "type": "object", + "properties": { + "encoder": {"type": "string"}, + "body": {"type": "string"}, + "header": { + "type": "object", + "additionalProperties": {"type": "string"} + }, + "statusCode": {"type": "integer"}, + "bodyData": {"type": "string", "contentEncoding": "base64"} + } + }, + "param": { + "type": "object", + "additionalProperties": {"type": "string"} + } + }, + "required": ["name", "request", "response"] + } + }, + "webhooks": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": {"type": "string"}, + "timer": {"type": "string"}, + "request": { + "type": "object", + "properties": { + "path": {"type": "string"}, + "method": {"type": "string"}, + "header": { + "type": "object", + "additionalProperties": {"type": "string"} + }, + "body": {"type": "string"} + }, + "required": ["path"] + } + }, + "required": ["name", "timer", "request"] + } + } + } +} diff --git a/docs/constants.go b/docs/constants.go index 3e37d80..380390e 100644 --- a/docs/constants.go +++ b/docs/constants.go @@ -1,6 +1,46 @@ +/* +Copyright 2024 API Testing Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ package docs -import _ "embed" +import ( + _ "embed" + "fmt" + yamlconv "github.com/ghodss/yaml" + "github.com/xeipuuv/gojsonschema" +) //go:embed api-testing-schema.json var Schema string + +//go:embed api-testing-mock-schema.json +var MockSchema string + +func Validate(data []byte, schema string) (err error) { + // convert YAML to JSON + var jsonData []byte + if jsonData, err = yamlconv.YAMLToJSON(data); err == nil { + schemaLoader := gojsonschema.NewStringLoader(schema) + documentLoader := gojsonschema.NewBytesLoader(jsonData) + + var result *gojsonschema.Result + if result, err = gojsonschema.Validate(schemaLoader, documentLoader); err == nil { + if !result.Valid() { + err = fmt.Errorf("%v", result.Errors()) + } + } + } + return +} diff --git a/pkg/mock/in_memory_test.go b/pkg/mock/in_memory_test.go index 6417987..d9ea791 100644 --- a/pkg/mock/in_memory_test.go +++ b/pkg/mock/in_memory_test.go @@ -179,7 +179,7 @@ func TestInMemoryServer(t *testing.T) { server := NewInMemoryServer(0) err := server.Start(NewInMemoryReader(`webhooks: - timer: 1s`), "/") - assert.NoError(t, err) + assert.Error(t, err) }) t.Run("invalid webhook payload", func(t *testing.T) { @@ -189,7 +189,7 @@ func TestInMemoryServer(t *testing.T) { timer: 1ms request: body: "{{.fake"`), "/") - assert.NoError(t, err) + assert.Error(t, err) }) t.Run("invalid webhook api template", func(t *testing.T) { diff --git a/pkg/mock/reader.go b/pkg/mock/reader.go index c736254..18dab6d 100644 --- a/pkg/mock/reader.go +++ b/pkg/mock/reader.go @@ -16,6 +16,8 @@ limitations under the License. package mock import ( + "errors" + "github.com/linuxsuren/api-testing/docs" "os" "gopkg.in/yaml.v3" @@ -41,8 +43,7 @@ func NewLocalFileReader(file string) Reader { func (r *localFileReader) Parse() (server *Server, err error) { if r.data, err = os.ReadFile(r.file); err == nil { - server = &Server{} - err = yaml.Unmarshal(r.data, server) + server, err = validateAndParse(r.data) } return } @@ -67,8 +68,7 @@ func NewInMemoryReader(config string) ReaderAndWriter { } func (r *inMemoryReader) Parse() (server *Server, err error) { - server = &Server{} - err = yaml.Unmarshal(r.data, server) + server, err = validateAndParse(r.data) return } @@ -79,3 +79,12 @@ func (r *inMemoryReader) GetData() []byte { func (r *inMemoryReader) Write(data []byte) { r.data = data } + +func validateAndParse(data []byte) (server *Server, err error) { + server = &Server{} + if len(data) > 0 { + err = yaml.Unmarshal(data, server) + err = errors.Join(err, docs.Validate(data, docs.MockSchema)) + } + return +} diff --git a/pkg/mock/types.go b/pkg/mock/types.go index 58dec61..d88248d 100644 --- a/pkg/mock/types.go +++ b/pkg/mock/types.go @@ -16,47 +16,47 @@ limitations under the License. package mock type Object struct { - Name string `yaml:"name"` - InitCount *int `yaml:"initCount"` - Sample string `yaml:"sample"` - Fields []Field `yaml:"fields"` + Name string `yaml:"name" json:"name"` + InitCount *int `yaml:"initCount" json:"initCount"` + Sample string `yaml:"sample" json:"sample"` + Fields []Field `yaml:"fields" json:"fields"` } type Field struct { - Name string `yaml:"name"` - Kind string `yaml:"kind"` + Name string `yaml:"name" json:"name"` + Kind string `yaml:"kind" json:"kind"` } type Item struct { - Name string `yaml:"name"` - Request Request `yaml:"request"` - Response Response `yaml:"response"` + Name string `yaml:"name" json:"name"` + Request Request `yaml:"request" json:"request"` + Response Response `yaml:"response" json:"response"` Param map[string]string } type Request struct { - Path string `yaml:"path"` - Method string `yaml:"method"` - Header map[string]string `yaml:"header"` - Body string `yaml:"body"` + Path string `yaml:"path" json:"path"` + Method string `yaml:"method" json:"method"` + Header map[string]string `yaml:"header" json:"header"` + Body string `yaml:"body" json:"body"` } type Response struct { - Encoder string `yaml:"encoder"` - Body string `yaml:"body"` - Header map[string]string `yaml:"header"` - StatusCode int `yaml:"statusCode"` + Encoder string `yaml:"encoder" json:"encoder"` + Body string `yaml:"body" json:"body"` + Header map[string]string `yaml:"header" json:"header"` + StatusCode int `yaml:"statusCode" json:"statusCode"` BodyData []byte } type Webhook struct { - Name string `yaml:"name"` - Timer string `yaml:"timer"` - Request Request `yaml:"request"` + Name string `yaml:"name" json:"name"` + Timer string `yaml:"timer" json:"timer"` + Request Request `yaml:"request" json:"request"` } type Server struct { - Objects []Object `yaml:"objects"` - Items []Item `yaml:"items"` - Webhooks []Webhook `yaml:"webhooks"` + Objects []Object `yaml:"objects" json:"objects"` + Items []Item `yaml:"items" json:"items"` + Webhooks []Webhook `yaml:"webhooks" json:"webhooks"` } diff --git a/pkg/testing/parser.go b/pkg/testing/parser.go index 3868343..2c9fe50 100644 --- a/pkg/testing/parser.go +++ b/pkg/testing/parser.go @@ -16,22 +16,21 @@ limitations under the License. package testing import ( - "bytes" - "fmt" - "io" - "mime/multipart" - "net/http" - "net/url" - "os" - "path" - "strings" + "bytes" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "os" + "path" + "strings" - yamlconv "github.com/ghodss/yaml" - "github.com/linuxsuren/api-testing/docs" - "github.com/linuxsuren/api-testing/pkg/render" - "github.com/linuxsuren/api-testing/pkg/util" - "github.com/xeipuuv/gojsonschema" - "gopkg.in/yaml.v3" + "github.com/linuxsuren/api-testing/docs" + "github.com/linuxsuren/api-testing/pkg/render" + "github.com/linuxsuren/api-testing/pkg/util" + "gopkg.in/yaml.v3" ) const ( @@ -41,23 +40,8 @@ const ( // Parse parses a file and returns the test suite func Parse(data []byte) (testSuite *TestSuite, err error) { testSuite, err = ParseFromData(data) - // schema validation - if err == nil { - // convert YAML to JSON - var jsonData []byte - if jsonData, err = yamlconv.YAMLToJSON(data); err == nil { - schemaLoader := gojsonschema.NewStringLoader(docs.Schema) - documentLoader := gojsonschema.NewBytesLoader(jsonData) - - var result *gojsonschema.Result - if result, err = gojsonschema.Validate(schemaLoader, documentLoader); err == nil { - if !result.Valid() { - err = fmt.Errorf("%v", result.Errors()) - } - } - } - } + err = errors.Join(err, docs.Validate(data, docs.Schema)) return }