diff --git a/datamodel/low/base/info_test.go b/datamodel/low/base/info_test.go index 286a4dc..adc7378 100644 --- a/datamodel/low/base/info_test.go +++ b/datamodel/low/base/info_test.go @@ -34,7 +34,7 @@ x-cli-name: pizza cli` err := low.BuildModel(idxNode.Content[0], &n) assert.NoError(t, err) - err = n.Build(nil,idxNode.Content[0], idx) + err = n.Build(nil, idxNode.Content[0], idx) assert.NoError(t, err) assert.Equal(t, "pizza", n.Title.Value) diff --git a/document.go b/document.go index 66e8036..d99e6ab 100644 --- a/document.go +++ b/document.go @@ -72,6 +72,21 @@ type Document interface { // it's too old, so it should be motivation to upgrade to OpenAPI 3. RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], []error) + // Render will render the high level model as it currently exists (including any mutations, additions + // and removals to and from any object in the tree). Unlike RenderAndReload, Render will simply print the state + // of the model as it currently exists, and will not re-load the model into memory. It means that the low-level and + // the high-level models will be out of sync, and the index will only be useful for the original document. + // + // Why use this instead of RenderAndReload? + // + // The simple answer is that RenderAndReload is a destructive operation, and will re-build the entire model from + // scratch using the new bytes, which is desirable if you want to make changes to the high level model and then + // 'reload' the model into memory, so that line numbers and column numbers are correct and the index is accurate. + // However, if you don't care about the low-level model, and you're not using the index, and you just want to + // print the state of the model as it currently exists, then Render() is the method to use. + // **IMPORTANT** This method only supports OpenAPI Documents. + Render() ([]byte, error) + // Serialize will re-render a Document back into a []byte slice. If any modifications have been made to the // underlying data model using low level APIs, then those changes will be reflected in the serialized output. // @@ -168,14 +183,32 @@ func (d *document) Serialize() ([]byte, error) { } func (d *document) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Document], []error) { + + newBytes, rerr := d.Render() + if rerr != nil { + return nil, nil, nil, []error{rerr} + } + + newDoc, err := NewDocumentWithConfiguration(newBytes, d.config) + if err != nil { + return newBytes, newDoc, nil, []error{err} + } + // build the model. + model, errs := newDoc.BuildV3Model() + if errs != nil { + return newBytes, newDoc, model, errs + } + // this document is now dead, long live the new document! + return newBytes, newDoc, model, nil +} + +func (d *document) Render() ([]byte, error) { if d.highSwaggerModel != nil && d.highOpenAPI3Model == nil { - return nil, nil, nil, []error{errors.New("this method only supports OpenAPI 3 documents, not Swagger")} + return nil, errors.New("this method only supports OpenAPI 3 documents, not Swagger") } var newBytes []byte - // render the model as the correct type based on the source. - // https://github.com/pb33f/libopenapi/issues/105 if d.info.SpecFileType == datamodel.JSONFileType { jsonIndent := " " i := d.info.OriginalIndentation @@ -190,17 +223,7 @@ func (d *document) RenderAndReload() ([]byte, Document, *DocumentModel[v3high.Do newBytes = d.highOpenAPI3Model.Model.RenderWithIndention(d.info.OriginalIndentation) } - newDoc, err := NewDocumentWithConfiguration(newBytes, d.config) - if err != nil { - return newBytes, newDoc, nil, []error{err} - } - // build the model. - model, errs := newDoc.BuildV3Model() - if errs != nil { - return newBytes, newDoc, model, errs - } - // this document is now dead, long live the new document! - return newBytes, newDoc, model, nil + return newBytes, nil } func (d *document) BuildV2Model() (*DocumentModel[v2high.Swagger], []error) { diff --git a/document_test.go b/document_test.go index 0998e05..9c7d3c5 100644 --- a/document_test.go +++ b/document_test.go @@ -8,7 +8,6 @@ import ( "github.com/pb33f/libopenapi/datamodel/high/base" "github.com/pb33f/libopenapi/what-changed/model" "github.com/stretchr/testify/assert" - "io/ioutil" "os" "strings" "testing" @@ -230,7 +229,7 @@ func TestDocument_RenderAndReload_ChangeCheck_Asana(t *testing.T) { func TestDocument_RenderAndReload(t *testing.T) { // load an OpenAPI 3 specification from bytes - petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json") + petstore, _ := os.ReadFile("test_specs/petstorev3.json") // create a new document from specification bytes doc, err := NewDocument(petstore) @@ -276,10 +275,101 @@ func TestDocument_RenderAndReload(t *testing.T) { assert.Equal(t, "https://pb33f.io", h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl) +} + +func TestDocument_Render(t *testing.T) { + + // load an OpenAPI 3 specification from bytes + petstore, _ := os.ReadFile("test_specs/petstorev3.json") + + // create a new document from specification bytes + doc, err := NewDocument(petstore) + + // if anything went wrong, an error is thrown + if err != nil { + panic(fmt.Sprintf("cannot create new document: %e", err)) + } + + // because we know this is a v3 spec, we can build a ready to go model from it. + m, _ := doc.BuildV3Model() + + // mutate the model + h := m.Model + h.Paths.PathItems["/pet/findByStatus"].Get.OperationId = "findACakeInABakery" + h.Paths.PathItems["/pet/findByStatus"].Get.Responses.Codes["400"].Description = "a nice bucket of mice" + h.Paths.PathItems["/pet/findByTags"].Get.Tags = + append(h.Paths.PathItems["/pet/findByTags"].Get.Tags, "gurgle", "giggle") + + h.Paths.PathItems["/pet/{petId}"].Delete.Security = append(h.Paths.PathItems["/pet/{petId}"].Delete.Security, + &base.SecurityRequirement{Requirements: map[string][]string{ + "pizza-and-cake": {"read:abook", "write:asong"}, + }}) + + h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example = "I am a teapot, filled with love." + h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl = "https://pb33f.io" + + bytes, e := doc.Render() + assert.NoError(t, e) + assert.NotNil(t, bytes) + + newDoc, docErr := NewDocument(bytes) + + assert.NoError(t, docErr) + + newDocModel, docErrs := newDoc.BuildV3Model() + assert.Len(t, docErrs, 0) + + h = newDocModel.Model + assert.Equal(t, "findACakeInABakery", h.Paths.PathItems["/pet/findByStatus"].Get.OperationId) + assert.Equal(t, "a nice bucket of mice", + h.Paths.PathItems["/pet/findByStatus"].Get.Responses.Codes["400"].Description) + assert.Len(t, h.Paths.PathItems["/pet/findByTags"].Get.Tags, 3) + + assert.Len(t, h.Paths.PathItems["/pet/findByTags"].Get.Tags, 3) + yu := h.Paths.PathItems["/pet/{petId}"].Delete.Security + assert.Equal(t, "read:abook", yu[len(yu)-1].Requirements["pizza-and-cake"][0]) + assert.Equal(t, "I am a teapot, filled with love.", + h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example) + + assert.Equal(t, "https://pb33f.io", + h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl) +} + +func TestDocument_RenderWithLargeIndention(t *testing.T) { + + json := `{ + "openapi": "3.0" +}` + doc, _ := NewDocument([]byte(json)) + + doc.BuildV3Model() + bytes, _ := doc.Render() + assert.Equal(t, json, string(bytes)) } + +func TestDocument_Render_ChangeCheck_Burgershop(t *testing.T) { + + bs, _ := os.ReadFile("test_specs/burgershop.openapi.yaml") + doc, _ := NewDocument(bs) + doc.BuildV3Model() + + rend, _ := doc.Render() + + newDoc, _ := NewDocument(rend) + + // compare documents + compReport, errs := CompareDocuments(doc, newDoc) + + // should noth be nil. + assert.Nil(t, errs) + assert.NotNil(t, rend) + assert.Nil(t, compReport) + +} + func TestDocument_RenderAndReload_Swagger(t *testing.T) { - petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json") + petstore, _ := os.ReadFile("test_specs/petstorev2.json") doc, _ := NewDocument(petstore) doc.BuildV2Model() doc.BuildV2Model() @@ -289,8 +379,19 @@ func TestDocument_RenderAndReload_Swagger(t *testing.T) { } +func TestDocument_Render_Swagger(t *testing.T) { + petstore, _ := os.ReadFile("test_specs/petstorev2.json") + doc, _ := NewDocument(petstore) + doc.BuildV2Model() + doc.BuildV2Model() + _, e := doc.Render() + assert.Error(t, e) + assert.Equal(t, "this method only supports OpenAPI 3 documents, not Swagger", e.Error()) + +} + func TestDocument_BuildModelPreBuild(t *testing.T) { - petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json") + petstore, _ := os.ReadFile("test_specs/petstorev3.json") doc, e := NewDocument(petstore) assert.NoError(t, e) doc.BuildV3Model() @@ -314,7 +415,7 @@ func TestDocument_AnyDocWithConfig(t *testing.T) { } func TestDocument_BuildModelCircular(t *testing.T) { - petstore, _ := ioutil.ReadFile("test_specs/circular-tests.yaml") + petstore, _ := os.ReadFile("test_specs/circular-tests.yaml") doc, _ := NewDocument(petstore) m, e := doc.BuildV3Model() assert.NotNil(t, m) @@ -322,7 +423,7 @@ func TestDocument_BuildModelCircular(t *testing.T) { } func TestDocument_BuildModelBad(t *testing.T) { - petstore, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml") + petstore, _ := os.ReadFile("test_specs/badref-burgershop.openapi.yaml") doc, _ := NewDocument(petstore) m, e := doc.BuildV3Model() assert.Nil(t, m) @@ -387,8 +488,8 @@ paths: } func TestDocument_BuildModel_CompareDocsV3_LeftError(t *testing.T) { - burgerShopOriginal, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml") - burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml") + burgerShopOriginal, _ := os.ReadFile("test_specs/badref-burgershop.openapi.yaml") + burgerShopUpdated, _ := os.ReadFile("test_specs/burgershop.openapi-modified.yaml") originalDoc, _ := NewDocument(burgerShopOriginal) updatedDoc, _ := NewDocument(burgerShopUpdated) changes, errors := CompareDocuments(originalDoc, updatedDoc) @@ -398,8 +499,8 @@ func TestDocument_BuildModel_CompareDocsV3_LeftError(t *testing.T) { func TestDocument_BuildModel_CompareDocsV3_RightError(t *testing.T) { - burgerShopOriginal, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml") - burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml") + burgerShopOriginal, _ := os.ReadFile("test_specs/badref-burgershop.openapi.yaml") + burgerShopUpdated, _ := os.ReadFile("test_specs/burgershop.openapi-modified.yaml") originalDoc, _ := NewDocument(burgerShopOriginal) updatedDoc, _ := NewDocument(burgerShopUpdated) changes, errors := CompareDocuments(updatedDoc, originalDoc) @@ -410,8 +511,8 @@ func TestDocument_BuildModel_CompareDocsV3_RightError(t *testing.T) { func TestDocument_BuildModel_CompareDocsV2_Error(t *testing.T) { - burgerShopOriginal, _ := ioutil.ReadFile("test_specs/petstorev2-badref.json") - burgerShopUpdated, _ := ioutil.ReadFile("test_specs/petstorev2-badref.json") + burgerShopOriginal, _ := os.ReadFile("test_specs/petstorev2-badref.json") + burgerShopUpdated, _ := os.ReadFile("test_specs/petstorev2-badref.json") originalDoc, _ := NewDocument(burgerShopOriginal) updatedDoc, _ := NewDocument(burgerShopUpdated) changes, errors := CompareDocuments(updatedDoc, originalDoc) @@ -422,8 +523,8 @@ func TestDocument_BuildModel_CompareDocsV2_Error(t *testing.T) { func TestDocument_BuildModel_CompareDocsV2V3Mix_Error(t *testing.T) { - burgerShopOriginal, _ := ioutil.ReadFile("test_specs/petstorev2.json") - burgerShopUpdated, _ := ioutil.ReadFile("test_specs/petstorev3.json") + burgerShopOriginal, _ := os.ReadFile("test_specs/petstorev2.json") + burgerShopUpdated, _ := os.ReadFile("test_specs/petstorev3.json") originalDoc, _ := NewDocument(burgerShopOriginal) updatedDoc, _ := NewDocument(burgerShopUpdated) changes, errors := CompareDocuments(updatedDoc, originalDoc) @@ -433,7 +534,7 @@ func TestDocument_BuildModel_CompareDocsV2V3Mix_Error(t *testing.T) { } func TestSchemaRefIsFollowed(t *testing.T) { - petstore, _ := ioutil.ReadFile("test_specs/ref-followed.yaml") + petstore, _ := os.ReadFile("test_specs/ref-followed.yaml") // create a new document from specification bytes document, err := NewDocument(petstore) diff --git a/index/find_component.go b/index/find_component.go index dc6746a..e3664cf 100644 --- a/index/find_component.go +++ b/index/find_component.go @@ -4,86 +4,86 @@ package index import ( - "fmt" - "io" - "net/http" - "net/url" - "os" - "path/filepath" - "strings" - "time" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" + "time" - "github.com/pb33f/libopenapi/utils" - "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" - "gopkg.in/yaml.v3" + "github.com/pb33f/libopenapi/utils" + "github.com/vmware-labs/yaml-jsonpath/pkg/yamlpath" + "gopkg.in/yaml.v3" ) // FindComponent will locate a component by its reference, returns nil if nothing is found. // This method will recurse through remote, local and file references. For each new external reference // a new index will be created. These indexes can then be traversed recursively. func (index *SpecIndex) FindComponent(componentId string, parent *yaml.Node) *Reference { - if index.root == nil { - return nil - } + if index.root == nil { + return nil + } - remoteLookup := func(id string) (*yaml.Node, *yaml.Node, error) { - if index.config.AllowRemoteLookup { - return index.lookupRemoteReference(id) - } else { - return nil, nil, fmt.Errorf("remote lookups are not permitted, " + - "please set AllowRemoteLookup to true in the configuration") - } - } + remoteLookup := func(id string) (*yaml.Node, *yaml.Node, error) { + if index.config.AllowRemoteLookup { + return index.lookupRemoteReference(id) + } else { + return nil, nil, fmt.Errorf("remote lookups are not permitted, " + + "please set AllowRemoteLookup to true in the configuration") + } + } - fileLookup := func(id string) (*yaml.Node, *yaml.Node, error) { - if index.config.AllowFileLookup { - return index.lookupFileReference(id) - } else { - return nil, nil, fmt.Errorf("local lookups are not permitted, " + - "please set AllowFileLookup to true in the configuration") - } - } + fileLookup := func(id string) (*yaml.Node, *yaml.Node, error) { + if index.config.AllowFileLookup { + return index.lookupFileReference(id) + } else { + return nil, nil, fmt.Errorf("local lookups are not permitted, " + + "please set AllowFileLookup to true in the configuration") + } + } - switch DetermineReferenceResolveType(componentId) { - case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case. - return index.FindComponentInRoot(componentId) + switch DetermineReferenceResolveType(componentId) { + case LocalResolve: // ideally, every single ref in every single spec is local. however, this is not the case. + return index.FindComponentInRoot(componentId) - case HttpResolve: - uri := strings.Split(componentId, "#") - if len(uri) >= 2 { - return index.performExternalLookup(uri, componentId, remoteLookup, parent) - } - if len(uri) == 1 { - // if there is no reference, second segment is empty / has no name - // this means there is no component to look-up and the entire file should be pulled in. - // to stop all the other code from breaking (that is expecting a component), let's just post-pend - // a hash to the end of the componentId and ensure the uri slice is as expected. - // described in https://github.com/pb33f/libopenapi/issues/37 - componentId = fmt.Sprintf("%s#", componentId) - uri = append(uri, "") - return index.performExternalLookup(uri, componentId, remoteLookup, parent) - } + case HttpResolve: + uri := strings.Split(componentId, "#") + if len(uri) >= 2 { + return index.performExternalLookup(uri, componentId, remoteLookup, parent) + } + if len(uri) == 1 { + // if there is no reference, second segment is empty / has no name + // this means there is no component to look-up and the entire file should be pulled in. + // to stop all the other code from breaking (that is expecting a component), let's just post-pend + // a hash to the end of the componentId and ensure the uri slice is as expected. + // described in https://github.com/pb33f/libopenapi/issues/37 + componentId = fmt.Sprintf("%s#", componentId) + uri = append(uri, "") + return index.performExternalLookup(uri, componentId, remoteLookup, parent) + } - case FileResolve: - uri := strings.Split(componentId, "#") - if len(uri) == 2 { - return index.performExternalLookup(uri, componentId, fileLookup, parent) - } - if len(uri) == 1 { - // if there is no reference, second segment is empty / has no name - // this means there is no component to look-up and the entire file should be pulled in. - // to stop all the other code from breaking (that is expecting a component), let's just post-pend - // a hash to the end of the componentId and ensure the uri slice is as expected. - // described in https://github.com/pb33f/libopenapi/issues/37 - // - // ^^ this same issue was re-reported in file based lookups in vacuum. - // more info here: https://github.com/daveshanley/vacuum/issues/225 - componentId = fmt.Sprintf("%s#", componentId) - uri = append(uri, "") - return index.performExternalLookup(uri, componentId, fileLookup, parent) - } - } - return nil + case FileResolve: + uri := strings.Split(componentId, "#") + if len(uri) == 2 { + return index.performExternalLookup(uri, componentId, fileLookup, parent) + } + if len(uri) == 1 { + // if there is no reference, second segment is empty / has no name + // this means there is no component to look-up and the entire file should be pulled in. + // to stop all the other code from breaking (that is expecting a component), let's just post-pend + // a hash to the end of the componentId and ensure the uri slice is as expected. + // described in https://github.com/pb33f/libopenapi/issues/37 + // + // ^^ this same issue was re-reported in file based lookups in vacuum. + // more info here: https://github.com/daveshanley/vacuum/issues/225 + componentId = fmt.Sprintf("%s#", componentId) + uri = append(uri, "") + return index.performExternalLookup(uri, componentId, fileLookup, parent) + } + } + return nil } var httpClient = &http.Client{Timeout: time.Duration(60) * time.Second} @@ -91,367 +91,367 @@ var httpClient = &http.Client{Timeout: time.Duration(60) * time.Second} type RemoteURLHandler = func(url string) (*http.Response, error) func getRemoteDoc(g RemoteURLHandler, u string, d chan []byte, e chan error) { - resp, err := g(u) - if err != nil { - e <- err - close(e) - close(d) - return - } - var body []byte - body, _ = io.ReadAll(resp.Body) - d <- body - close(e) - close(d) + resp, err := g(u) + if err != nil { + e <- err + close(e) + close(d) + return + } + var body []byte + body, _ = io.ReadAll(resp.Body) + d <- body + close(e) + close(d) } func (index *SpecIndex) lookupRemoteReference(ref string) (*yaml.Node, *yaml.Node, error) { - // split string to remove file reference - uri := strings.Split(ref, "#") + // split string to remove file reference + uri := strings.Split(ref, "#") - // have we already seen this remote source? - var parsedRemoteDocument *yaml.Node - alreadySeen, foundDocument := index.CheckForSeenRemoteSource(uri[0]) + // have we already seen this remote source? + var parsedRemoteDocument *yaml.Node + alreadySeen, foundDocument := index.CheckForSeenRemoteSource(uri[0]) - if alreadySeen { - parsedRemoteDocument = foundDocument - } else { + if alreadySeen { + parsedRemoteDocument = foundDocument + } else { - d := make(chan bool) - var body []byte - var err error + d := make(chan bool) + var body []byte + var err error - go func(uri string) { - bc := make(chan []byte) - ec := make(chan error) - var getter RemoteURLHandler = httpClient.Get - if index.config != nil && index.config.RemoteURLHandler != nil { - getter = index.config.RemoteURLHandler - } + go func(uri string) { + bc := make(chan []byte) + ec := make(chan error) + var getter RemoteURLHandler = httpClient.Get + if index.config != nil && index.config.RemoteURLHandler != nil { + getter = index.config.RemoteURLHandler + } - // if we have a remote handler, use it instead of the default. - if index.config != nil && index.config.FSHandler != nil { - go func() { - remoteFS := index.config.FSHandler - remoteFile, rErr := remoteFS.Open(uri) - if rErr != nil { - e := fmt.Errorf("unable to open remote file: %s", rErr) - ec <- e - return - } - b, ioErr := io.ReadAll(remoteFile) - if ioErr != nil { - e := fmt.Errorf("unable to read remote file bytes: %s", ioErr) - ec <- e - return - } - bc <- b - }() - } else { - go getRemoteDoc(getter, uri, bc, ec) - } - select { - case v := <-bc: - body = v - break - case er := <-ec: - err = er - break - } - if len(body) > 0 { - var remoteDoc yaml.Node - er := yaml.Unmarshal(body, &remoteDoc) - if er != nil { - err = er - d <- true - return - } - parsedRemoteDocument = &remoteDoc - if index.config != nil { - index.config.seenRemoteSources.Store(uri, &remoteDoc) - } - } - d <- true - }(uri[0]) + // if we have a remote handler, use it instead of the default. + if index.config != nil && index.config.FSHandler != nil { + go func() { + remoteFS := index.config.FSHandler + remoteFile, rErr := remoteFS.Open(uri) + if rErr != nil { + e := fmt.Errorf("unable to open remote file: %s", rErr) + ec <- e + return + } + b, ioErr := io.ReadAll(remoteFile) + if ioErr != nil { + e := fmt.Errorf("unable to read remote file bytes: %s", ioErr) + ec <- e + return + } + bc <- b + }() + } else { + go getRemoteDoc(getter, uri, bc, ec) + } + select { + case v := <-bc: + body = v + break + case er := <-ec: + err = er + break + } + if len(body) > 0 { + var remoteDoc yaml.Node + er := yaml.Unmarshal(body, &remoteDoc) + if er != nil { + err = er + d <- true + return + } + parsedRemoteDocument = &remoteDoc + if index.config != nil { + index.config.seenRemoteSources.Store(uri, &remoteDoc) + } + } + d <- true + }(uri[0]) - // wait for double go fun. - <-d - if err != nil { - // no bueno. - return nil, nil, err - } - } + // wait for double go fun. + <-d + if err != nil { + // no bueno. + return nil, nil, err + } + } - // lookup item from reference by using a path query. - var query string - if len(uri) >= 2 { - query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) - } else { - query = "$" - } - - query, err := url.PathUnescape(query) - if err != nil { - return nil, nil, err - } + // lookup item from reference by using a path query. + var query string + if len(uri) >= 2 { + query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) + } else { + query = "$" + } - // remove any URL encoding - query = strings.Replace(query, "~1", "./", 1) - query = strings.ReplaceAll(query, "~1", "/") + query, err := url.PathUnescape(query) + if err != nil { + return nil, nil, err + } - path, err := yamlpath.NewPath(query) - if err != nil { - return nil, nil, err - } - result, _ := path.Find(parsedRemoteDocument) - if len(result) == 1 { - return result[0], parsedRemoteDocument, nil - } - return nil, nil, nil + // remove any URL encoding + query = strings.Replace(query, "~1", "./", 1) + query = strings.ReplaceAll(query, "~1", "/") + + path, err := yamlpath.NewPath(query) + if err != nil { + return nil, nil, err + } + result, _ := path.Find(parsedRemoteDocument) + if len(result) == 1 { + return result[0], parsedRemoteDocument, nil + } + return nil, nil, nil } func (index *SpecIndex) lookupFileReference(ref string) (*yaml.Node, *yaml.Node, error) { - // split string to remove file reference - uri := strings.Split(ref, "#") - file := strings.ReplaceAll(uri[0], "file:", "") - filePath := filepath.Dir(file) - fileName := filepath.Base(file) + // split string to remove file reference + uri := strings.Split(ref, "#") + file := strings.ReplaceAll(uri[0], "file:", "") + filePath := filepath.Dir(file) + fileName := filepath.Base(file) - var parsedRemoteDocument *yaml.Node + var parsedRemoteDocument *yaml.Node - if index.seenRemoteSources[file] != nil { - parsedRemoteDocument = index.seenRemoteSources[file] - } else { + if index.seenRemoteSources[file] != nil { + parsedRemoteDocument = index.seenRemoteSources[file] + } else { - base := index.config.BasePath - fileToRead := filepath.Join(base, filePath, fileName) - var body []byte - var err error + base := index.config.BasePath + fileToRead := filepath.Join(base, filePath, fileName) + var body []byte + var err error - // if we have an FS handler, use it instead of the default behavior - if index.config != nil && index.config.FSHandler != nil { - remoteFS := index.config.FSHandler - remoteFile, rErr := remoteFS.Open(fileToRead) - if rErr != nil { - e := fmt.Errorf("unable to open file: %s", rErr) - return nil, nil, e - } - body, err = io.ReadAll(remoteFile) - if err != nil { - e := fmt.Errorf("unable to read file bytes: %s", err) - return nil, nil, e - } + // if we have an FS handler, use it instead of the default behavior + if index.config != nil && index.config.FSHandler != nil { + remoteFS := index.config.FSHandler + remoteFile, rErr := remoteFS.Open(fileToRead) + if rErr != nil { + e := fmt.Errorf("unable to open file: %s", rErr) + return nil, nil, e + } + body, err = io.ReadAll(remoteFile) + if err != nil { + e := fmt.Errorf("unable to read file bytes: %s", err) + return nil, nil, e + } - } else { + } else { - // try and read the file off the local file system, if it fails - // check for a baseURL and then ask our remote lookup function to go try and get it. - body, err = os.ReadFile(fileToRead) + // try and read the file off the local file system, if it fails + // check for a baseURL and then ask our remote lookup function to go try and get it. + body, err = os.ReadFile(fileToRead) - if err != nil { + if err != nil { - // if we have a baseURL, then we can try and get the file from there. - if index.config != nil && index.config.BaseURL != nil { + // if we have a baseURL, then we can try and get the file from there. + if index.config != nil && index.config.BaseURL != nil { - u := index.config.BaseURL - remoteRef := GenerateCleanSpecConfigBaseURL(u, ref, true) - a, b, e := index.lookupRemoteReference(remoteRef) - if e != nil { - // give up, we can't find the file, not locally, not remotely. It's toast. - return nil, nil, e - } - return a, b, nil + u := index.config.BaseURL + remoteRef := GenerateCleanSpecConfigBaseURL(u, ref, true) + a, b, e := index.lookupRemoteReference(remoteRef) + if e != nil { + // give up, we can't find the file, not locally, not remotely. It's toast. + return nil, nil, e + } + return a, b, nil - } else { - // no baseURL? then we can't do anything, give up. - return nil, nil, err - } - } - } - var remoteDoc yaml.Node - err = yaml.Unmarshal(body, &remoteDoc) - if err != nil { - return nil, nil, err - } - parsedRemoteDocument = &remoteDoc - if index.seenLocalSources != nil { - index.sourceLock.Lock() - index.seenLocalSources[file] = &remoteDoc - index.sourceLock.Unlock() - } - } + } else { + // no baseURL? then we can't do anything, give up. + return nil, nil, err + } + } + } + var remoteDoc yaml.Node + err = yaml.Unmarshal(body, &remoteDoc) + if err != nil { + return nil, nil, err + } + parsedRemoteDocument = &remoteDoc + if index.seenLocalSources != nil { + index.sourceLock.Lock() + index.seenLocalSources[file] = &remoteDoc + index.sourceLock.Unlock() + } + } - // lookup item from reference by using a path query. - var query string - if len(uri) >= 2 { - query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) - } else { - query = "$" - } + // lookup item from reference by using a path query. + var query string + if len(uri) >= 2 { + query = fmt.Sprintf("$%s", strings.ReplaceAll(uri[1], "/", ".")) + } else { + query = "$" + } - query, err := url.PathUnescape(query) - if err != nil { - return nil, nil, err - } + query, err := url.PathUnescape(query) + if err != nil { + return nil, nil, err + } - // remove any URL encoding - query = strings.Replace(query, "~1", "./", 1) - query = strings.ReplaceAll(query, "~1", "/") + // remove any URL encoding + query = strings.Replace(query, "~1", "./", 1) + query = strings.ReplaceAll(query, "~1", "/") - path, err := yamlpath.NewPath(query) - if err != nil { - return nil, nil, err - } - result, _ := path.Find(parsedRemoteDocument) - if len(result) == 1 { - return result[0], parsedRemoteDocument, nil - } + path, err := yamlpath.NewPath(query) + if err != nil { + return nil, nil, err + } + result, _ := path.Find(parsedRemoteDocument) + if len(result) == 1 { + return result[0], parsedRemoteDocument, nil + } - return nil, parsedRemoteDocument, nil + return nil, parsedRemoteDocument, nil } func (index *SpecIndex) FindComponentInRoot(componentId string) *Reference { - if index.root != nil { + if index.root != nil { - // check component for url encoding. - if strings.Contains(componentId, "%") { - // decode the url. - componentId, _ = url.QueryUnescape(componentId) - } + // check component for url encoding. + if strings.Contains(componentId, "%") { + // decode the url. + componentId, _ = url.QueryUnescape(componentId) + } - name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId) - path, err := yamlpath.NewPath(friendlySearch) - if path == nil || err != nil { - return nil // no component found - } - res, _ := path.Find(index.root) + name, friendlySearch := utils.ConvertComponentIdIntoFriendlyPathSearch(componentId) + path, err := yamlpath.NewPath(friendlySearch) + if path == nil || err != nil { + return nil // no component found + } + res, _ := path.Find(index.root) - if len(res) == 1 { - resNode := res[0] - if res[0].Kind == yaml.DocumentNode { - resNode = res[0].Content[0] - } - ref := &Reference{ - Definition: componentId, - Name: name, - Node: resNode, - Path: friendlySearch, - RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}), - } + if len(res) == 1 { + resNode := res[0] + if res[0].Kind == yaml.DocumentNode { + resNode = res[0].Content[0] + } + ref := &Reference{ + Definition: componentId, + Name: name, + Node: resNode, + Path: friendlySearch, + RequiredRefProperties: index.extractDefinitionRequiredRefProperties(res[0], map[string][]string{}), + } - return ref - } - } - return nil + return ref + } + } + return nil } func (index *SpecIndex) performExternalLookup(uri []string, componentId string, - lookupFunction ExternalLookupFunction, parent *yaml.Node) *Reference { - if len(uri) > 0 { - index.externalLock.RLock() - externalSpecIndex := index.externalSpecIndex[uri[0]] - index.externalLock.RUnlock() + lookupFunction ExternalLookupFunction, parent *yaml.Node) *Reference { + if len(uri) > 0 { + index.externalLock.RLock() + externalSpecIndex := index.externalSpecIndex[uri[0]] + index.externalLock.RUnlock() - if externalSpecIndex == nil { - _, newRoot, err := lookupFunction(componentId) - if err != nil { - indexError := &IndexingError{ - Err: err, - Node: parent, - Path: componentId, - } - index.errorLock.Lock() - index.refErrors = append(index.refErrors, indexError) - index.errorLock.Unlock() - return nil - } + if externalSpecIndex == nil { + _, newRoot, err := lookupFunction(componentId) + if err != nil { + indexError := &IndexingError{ + Err: err, + Node: parent, + Path: componentId, + } + index.errorLock.Lock() + index.refErrors = append(index.refErrors, indexError) + index.errorLock.Unlock() + return nil + } - // cool, cool, lets index this spec also. This is a recursive action and will keep going - // until all remote references have been found. - var bp *url.URL - var bd string + // cool, cool, lets index this spec also. This is a recursive action and will keep going + // until all remote references have been found. + var bp *url.URL + var bd string - if index.config.BaseURL != nil { - bp = index.config.BaseURL - } - if index.config.BasePath != "" { - bd = index.config.BasePath - } + if index.config.BaseURL != nil { + bp = index.config.BaseURL + } + if index.config.BasePath != "" { + bd = index.config.BasePath + } - var path, newBasePath string - var newUrl *url.URL + var path, newBasePath string + var newUrl *url.URL - if bp != nil { - path = GenerateCleanSpecConfigBaseURL(bp, uri[0], false) - newUrl, _ = url.Parse(path) - newBasePath = filepath.Dir(filepath.Join(index.config.BasePath, filepath.Dir(newUrl.Path))) - } - if bd != "" { - if len(uri[0]) > 0 { - // if there is no base url defined, but we can know we have been requested remotely, - // set the base url to the remote url base path. - // first check if the first param is actually a URL - io, er := url.ParseRequestURI(uri[0]) - if er != nil { - newBasePath = filepath.Dir(filepath.Join(bd, uri[0])) - } else { - if newUrl == nil || newUrl.String() != io.String() { - newUrl, _ = url.Parse(fmt.Sprintf("%s://%s%s", io.Scheme, io.Host, filepath.Dir(io.Path))) - } - newBasePath = filepath.Dir(filepath.Join(bd, uri[1])) - } - } else { - newBasePath = filepath.Dir(filepath.Join(bd, uri[0])) - } - } + if bp != nil { + path = GenerateCleanSpecConfigBaseURL(bp, uri[0], false) + newUrl, _ = url.Parse(path) + newBasePath = filepath.Dir(filepath.Join(index.config.BasePath, filepath.Dir(newUrl.Path))) + } + if bd != "" { + if len(uri[0]) > 0 { + // if there is no base url defined, but we can know we have been requested remotely, + // set the base url to the remote url base path. + // first check if the first param is actually a URL + io, er := url.ParseRequestURI(uri[0]) + if er != nil { + newBasePath = filepath.Dir(filepath.Join(bd, uri[0])) + } else { + if newUrl == nil || newUrl.String() != io.String() { + newUrl, _ = url.Parse(fmt.Sprintf("%s://%s%s", io.Scheme, io.Host, filepath.Dir(io.Path))) + } + newBasePath = filepath.Dir(filepath.Join(bd, uri[1])) + } + } else { + newBasePath = filepath.Dir(filepath.Join(bd, uri[0])) + } + } - if newUrl != nil || newBasePath != "" { - newConfig := &SpecIndexConfig{ - BaseURL: newUrl, - BasePath: newBasePath, - AllowRemoteLookup: index.config.AllowRemoteLookup, - AllowFileLookup: index.config.AllowFileLookup, - ParentIndex: index, - seenRemoteSources: index.config.seenRemoteSources, - remoteLock: index.config.remoteLock, - uri: uri, - } + if newUrl != nil || newBasePath != "" { + newConfig := &SpecIndexConfig{ + BaseURL: newUrl, + BasePath: newBasePath, + AllowRemoteLookup: index.config.AllowRemoteLookup, + AllowFileLookup: index.config.AllowFileLookup, + ParentIndex: index, + seenRemoteSources: index.config.seenRemoteSources, + remoteLock: index.config.remoteLock, + uri: uri, + } - var newIndex *SpecIndex - seen := index.SearchAncestryForSeenURI(uri[0]) - if seen == nil { + var newIndex *SpecIndex + seen := index.SearchAncestryForSeenURI(uri[0]) + if seen == nil { - newIndex = NewSpecIndexWithConfig(newRoot, newConfig) - index.refLock.Lock() - index.externalLock.Lock() - index.externalSpecIndex[uri[0]] = newIndex - index.externalLock.Unlock() - newIndex.relativePath = path - newIndex.parentIndex = index - index.AddChild(newIndex) - index.refLock.Unlock() - externalSpecIndex = newIndex - } else { - externalSpecIndex = seen - } - } - } + newIndex = NewSpecIndexWithConfig(newRoot, newConfig) + index.refLock.Lock() + index.externalLock.Lock() + index.externalSpecIndex[uri[0]] = newIndex + index.externalLock.Unlock() + newIndex.relativePath = path + newIndex.parentIndex = index + index.AddChild(newIndex) + index.refLock.Unlock() + externalSpecIndex = newIndex + } else { + externalSpecIndex = seen + } + } + } - if externalSpecIndex != nil { - foundRef := externalSpecIndex.FindComponentInRoot(uri[1]) - if foundRef != nil { - nameSegs := strings.Split(uri[1], "/") - ref := &Reference{ - Definition: componentId, - Name: nameSegs[len(nameSegs)-1], - Node: foundRef.Node, - IsRemote: true, - RemoteLocation: componentId, - Path: foundRef.Path, - } - return ref - } - } - } - return nil + if externalSpecIndex != nil { + foundRef := externalSpecIndex.FindComponentInRoot(uri[1]) + if foundRef != nil { + nameSegs := strings.Split(uri[1], "/") + ref := &Reference{ + Definition: componentId, + Name: nameSegs[len(nameSegs)-1], + Node: foundRef.Node, + IsRemote: true, + RemoteLocation: componentId, + Path: foundRef.Path, + } + return ref + } + } + } + return nil } diff --git a/renderer/mock_generator_examples_test.go b/renderer/mock_generator_examples_test.go index 04ca2cb..b9acc5a 100644 --- a/renderer/mock_generator_examples_test.go +++ b/renderer/mock_generator_examples_test.go @@ -4,125 +4,125 @@ package renderer import ( - "fmt" - "github.com/pb33f/libopenapi" - "os" + "fmt" + "github.com/pb33f/libopenapi" + "os" ) func ExampleMockGenerator_generateBurgerMock_yaml() { - // create a new YAML mock generator - mg := NewMockGenerator(YAML) + // create a new YAML mock generator + mg := NewMockGenerator(YAML) - burgerShop, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml") + burgerShop, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml") - // create a new document from specification and build a v3 model. - document, _ := libopenapi.NewDocument(burgerShop) - v3Model, _ := document.BuildV3Model() + // create a new document from specification and build a v3 model. + document, _ := libopenapi.NewDocument(burgerShop) + v3Model, _ := document.BuildV3Model() - // create a mock of the Burger model - burgerModel := v3Model.Model.Components.Schemas["Burger"] - burger := burgerModel.Schema() - mock, err := mg.GenerateMock(burger, "") + // create a mock of the Burger model + burgerModel := v3Model.Model.Components.Schemas["Burger"] + burger := burgerModel.Schema() + mock, err := mg.GenerateMock(burger, "") - if err != nil { - panic(err) - } - fmt.Println(string(mock)) - // Output: name: Big Mac - //numPatties: 2 + if err != nil { + panic(err) + } + fmt.Println(string(mock)) + // Output: name: Big Mac + //numPatties: 2 } func ExampleMockGenerator_generateFriesMock_json() { - // create a new YAML mock generator - mg := NewMockGenerator(JSON) + // create a new YAML mock generator + mg := NewMockGenerator(JSON) - burgerShop, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml") + burgerShop, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml") - // create a new document from specification and build a v3 model. - document, _ := libopenapi.NewDocument(burgerShop) - v3Model, _ := document.BuildV3Model() + // create a new document from specification and build a v3 model. + document, _ := libopenapi.NewDocument(burgerShop) + v3Model, _ := document.BuildV3Model() - // create a mock of the Fries model - friesModel := v3Model.Model.Components.Schemas["Fries"] - fries := friesModel.Schema() - mock, err := mg.GenerateMock(fries, "") + // create a mock of the Fries model + friesModel := v3Model.Model.Components.Schemas["Fries"] + fries := friesModel.Schema() + mock, err := mg.GenerateMock(fries, "") - if err != nil { - panic(err) - } - fmt.Println(string(mock)) - // Output: {"favoriteDrink":{"drinkType":"coke","size":"M"},"potatoShape":"Crispy Shoestring"} + if err != nil { + panic(err) + } + fmt.Println(string(mock)) + // Output: {"favoriteDrink":{"drinkType":"coke","size":"M"},"potatoShape":"Crispy Shoestring"} } func ExampleMockGenerator_generateRequestMock_json() { - // create a new YAML mock generator - mg := NewMockGenerator(JSON) + // create a new YAML mock generator + mg := NewMockGenerator(JSON) - burgerShop, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml") + burgerShop, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml") - // create a new document from specification and build a v3 model. - document, _ := libopenapi.NewDocument(burgerShop) - v3Model, _ := document.BuildV3Model() + // create a new document from specification and build a v3 model. + document, _ := libopenapi.NewDocument(burgerShop) + v3Model, _ := document.BuildV3Model() - // create a mock of the burger request model, extracted from the operation directly. - burgerRequestModel := v3Model.Model.Paths.PathItems["/burgers"].Post.RequestBody.Content["application/json"] + // create a mock of the burger request model, extracted from the operation directly. + burgerRequestModel := v3Model.Model.Paths.PathItems["/burgers"].Post.RequestBody.Content["application/json"] - // use the 'cakeBurger' example to generate a mock - mock, err := mg.GenerateMock(burgerRequestModel, "cakeBurger") + // use the 'cakeBurger' example to generate a mock + mock, err := mg.GenerateMock(burgerRequestModel, "cakeBurger") - if err != nil { - panic(err) - } - fmt.Println(string(mock)) - // Output: {"name":"Chocolate Cake Burger","numPatties":5} + if err != nil { + panic(err) + } + fmt.Println(string(mock)) + // Output: {"name":"Chocolate Cake Burger","numPatties":5} } func ExampleMockGenerator_generateResponseMock_json() { - mg := NewMockGenerator(JSON) - // create a new YAML mock generator + mg := NewMockGenerator(JSON) + // create a new YAML mock generator - burgerShop, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml") + burgerShop, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml") - // create a new document from specification and build a v3 model. - document, _ := libopenapi.NewDocument(burgerShop) - v3Model, _ := document.BuildV3Model() + // create a new document from specification and build a v3 model. + document, _ := libopenapi.NewDocument(burgerShop) + v3Model, _ := document.BuildV3Model() - // create a mock of the burger response model, extracted from the operation directly. - burgerResponseModel := v3Model.Model.Paths.PathItems["/burgers"].Post.Responses.Codes["200"].Content["application/json"] + // create a mock of the burger response model, extracted from the operation directly. + burgerResponseModel := v3Model.Model.Paths.PathItems["/burgers"].Post.Responses.Codes["200"].Content["application/json"] - // use the 'filetOFish' example to generate a mock - mock, err := mg.GenerateMock(burgerResponseModel, "filetOFish") + // use the 'filetOFish' example to generate a mock + mock, err := mg.GenerateMock(burgerResponseModel, "filetOFish") - if err != nil { - panic(err) - } - fmt.Println(string(mock)) - // Output: {"name":"Filet-O-Fish","numPatties":1} + if err != nil { + panic(err) + } + fmt.Println(string(mock)) + // Output: {"name":"Filet-O-Fish","numPatties":1} } func ExampleMockGenerator_generatePolymorphicMock_json() { - mg := NewMockGenerator(JSON) - // create a new YAML mock generator + mg := NewMockGenerator(JSON) + // create a new YAML mock generator - burgerShop, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml") + burgerShop, _ := os.ReadFile("../test_specs/burgershop.openapi.yaml") - // create a new document from specification and build a v3 model. - document, _ := libopenapi.NewDocument(burgerShop) - v3Model, _ := document.BuildV3Model() + // create a new document from specification and build a v3 model. + document, _ := libopenapi.NewDocument(burgerShop) + v3Model, _ := document.BuildV3Model() - // create a mock of the SomePayload component, which uses polymorphism (incorrectly) - payloadModel := v3Model.Model.Components.Schemas["SomePayload"] - payload := payloadModel.Schema() - mock, err := mg.GenerateMock(payload, "") + // create a mock of the SomePayload component, which uses polymorphism (incorrectly) + payloadModel := v3Model.Model.Components.Schemas["SomePayload"] + payload := payloadModel.Schema() + mock, err := mg.GenerateMock(payload, "") - if err != nil { - panic(err) - } - fmt.Println(string(mock)) - // Output: {"drinkType":"coke","size":"M"} + if err != nil { + panic(err) + } + fmt.Println(string(mock)) + // Output: {"drinkType":"coke","size":"M"} } diff --git a/renderer/mock_generator_test.go b/renderer/mock_generator_test.go index 80d63fc..028095c 100644 --- a/renderer/mock_generator_test.go +++ b/renderer/mock_generator_test.go @@ -4,26 +4,26 @@ package renderer import ( - "encoding/json" - highbase "github.com/pb33f/libopenapi/datamodel/high/base" - "github.com/pb33f/libopenapi/datamodel/low" - lowbase "github.com/pb33f/libopenapi/datamodel/low/base" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" - "strings" - "testing" + "encoding/json" + highbase "github.com/pb33f/libopenapi/datamodel/high/base" + "github.com/pb33f/libopenapi/datamodel/low" + lowbase "github.com/pb33f/libopenapi/datamodel/low/base" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "strings" + "testing" ) type fakeMockable struct { - Schema *highbase.SchemaProxy - Example any - Examples map[string]*highbase.Example + Schema *highbase.SchemaProxy + Example any + Examples map[string]*highbase.Example } type fakeMockableButWithASchemaNotAProxy struct { - Schema *highbase.Schema - Example any - Examples map[string]*highbase.Example + Schema *highbase.Schema + Example any + Examples map[string]*highbase.Example } var simpleFakeMockSchema = `type: string @@ -40,240 +40,240 @@ properties: maximum: 400` func createFakeMock(mock string, values map[string]any, example any) *fakeMockable { - var root yaml.Node - _ = yaml.Unmarshal([]byte(mock), &root) - var lowProxy lowbase.SchemaProxy - _ = lowProxy.Build(&root, root.Content[0], nil) - lowRef := low.NodeReference[*lowbase.SchemaProxy]{ - Value: &lowProxy, - } - highSchema := highbase.NewSchemaProxy(&lowRef) - examples := make(map[string]*highbase.Example) + var root yaml.Node + _ = yaml.Unmarshal([]byte(mock), &root) + var lowProxy lowbase.SchemaProxy + _ = lowProxy.Build(&root, root.Content[0], nil) + lowRef := low.NodeReference[*lowbase.SchemaProxy]{ + Value: &lowProxy, + } + highSchema := highbase.NewSchemaProxy(&lowRef) + examples := make(map[string]*highbase.Example) - for k, v := range values { - examples[k] = &highbase.Example{ - Value: v, - } - } - return &fakeMockable{ - Schema: highSchema, - Example: example, - Examples: examples, - } + for k, v := range values { + examples[k] = &highbase.Example{ + Value: v, + } + } + return &fakeMockable{ + Schema: highSchema, + Example: example, + Examples: examples, + } } func createFakeMockWithoutProxy(mock string, values map[string]any, example any) *fakeMockableButWithASchemaNotAProxy { - var root yaml.Node - _ = yaml.Unmarshal([]byte(mock), &root) - var lowProxy lowbase.SchemaProxy - _ = lowProxy.Build(&root, root.Content[0], nil) - lowRef := low.NodeReference[*lowbase.SchemaProxy]{ - Value: &lowProxy, - } - highSchema := highbase.NewSchemaProxy(&lowRef) - examples := make(map[string]*highbase.Example) + var root yaml.Node + _ = yaml.Unmarshal([]byte(mock), &root) + var lowProxy lowbase.SchemaProxy + _ = lowProxy.Build(&root, root.Content[0], nil) + lowRef := low.NodeReference[*lowbase.SchemaProxy]{ + Value: &lowProxy, + } + highSchema := highbase.NewSchemaProxy(&lowRef) + examples := make(map[string]*highbase.Example) - for k, v := range values { - examples[k] = &highbase.Example{ - Value: v, - } - } - return &fakeMockableButWithASchemaNotAProxy{ - Schema: highSchema.Schema(), - Example: example, - Examples: examples, - } + for k, v := range values { + examples[k] = &highbase.Example{ + Value: v, + } + } + return &fakeMockableButWithASchemaNotAProxy{ + Schema: highSchema.Schema(), + Example: example, + Examples: examples, + } } func TestNewMockGenerator(t *testing.T) { - mg := NewMockGenerator(JSON) - assert.NotNil(t, mg) + mg := NewMockGenerator(JSON) + assert.NotNil(t, mg) } func TestNewMockGeneratorWithDictionary(t *testing.T) { - mg := NewMockGeneratorWithDictionary("", JSON) - assert.NotNil(t, mg) + mg := NewMockGeneratorWithDictionary("", JSON) + assert.NotNil(t, mg) } func TestMockGenerator_GenerateJSONMock_BadObject(t *testing.T) { - type NotMockable struct { - pizza string - } + type NotMockable struct { + pizza string + } - mg := NewMockGenerator(JSON) - mock, err := mg.GenerateMock(&NotMockable{}, "") - assert.Error(t, err) - assert.Nil(t, mock) + mg := NewMockGenerator(JSON) + mock, err := mg.GenerateMock(&NotMockable{}, "") + assert.Error(t, err) + assert.Nil(t, mock) } func TestMockGenerator_GenerateJSONMock_EmptyObject(t *testing.T) { - mg := NewMockGenerator(JSON) - mock, err := mg.GenerateMock(&fakeMockable{}, "") - assert.NoError(t, err) - assert.Nil(t, mock) + mg := NewMockGenerator(JSON) + mock, err := mg.GenerateMock(&fakeMockable{}, "") + assert.NoError(t, err) + assert.Nil(t, mock) } func TestMockGenerator_GenerateJSONMock_SuppliedExample_JSON(t *testing.T) { - fakeExample := map[string]any{ - "fish-and-chips": "cod-and-chips-twice", - } - fake := createFakeMock(simpleFakeMockSchema, nil, fakeExample) - mg := NewMockGenerator(JSON) - mock, err := mg.GenerateMock(fake, "") - assert.NoError(t, err) - assert.Equal(t, "{\"fish-and-chips\":\"cod-and-chips-twice\"}", string(mock)) + fakeExample := map[string]any{ + "fish-and-chips": "cod-and-chips-twice", + } + fake := createFakeMock(simpleFakeMockSchema, nil, fakeExample) + mg := NewMockGenerator(JSON) + mock, err := mg.GenerateMock(fake, "") + assert.NoError(t, err) + assert.Equal(t, "{\"fish-and-chips\":\"cod-and-chips-twice\"}", string(mock)) } func TestMockGenerator_GenerateJSONMock_SuppliedExample_YAML(t *testing.T) { - fakeExample := map[string]any{ - "fish-and-chips": "cod-and-chips-twice", - } - fake := createFakeMock(simpleFakeMockSchema, nil, fakeExample) - mg := NewMockGenerator(YAML) - mock, err := mg.GenerateMock(fake, "") - assert.NoError(t, err) - assert.Equal(t, "fish-and-chips: cod-and-chips-twice", strings.TrimSpace(string(mock))) + fakeExample := map[string]any{ + "fish-and-chips": "cod-and-chips-twice", + } + fake := createFakeMock(simpleFakeMockSchema, nil, fakeExample) + mg := NewMockGenerator(YAML) + mock, err := mg.GenerateMock(fake, "") + assert.NoError(t, err) + assert.Equal(t, "fish-and-chips: cod-and-chips-twice", strings.TrimSpace(string(mock))) } func TestMockGenerator_GenerateJSONMock_MultiExamples_NoName_JSON(t *testing.T) { - fakeExample := map[string]any{ - "exampleOne": map[string]any{ - "fish-and-chips": "cod-and-chips-twice", - }, - "exampleTwo": map[string]any{ - "rice-and-peas": "brown-or-white-rice", - }, - } - fake := createFakeMock(simpleFakeMockSchema, fakeExample, nil) - mg := NewMockGenerator(JSON) - mock, err := mg.GenerateMock(fake, "JimmyJammyJimJams") // does not exist - assert.NoError(t, err) - assert.NotEmpty(t, string(mock)) + fakeExample := map[string]any{ + "exampleOne": map[string]any{ + "fish-and-chips": "cod-and-chips-twice", + }, + "exampleTwo": map[string]any{ + "rice-and-peas": "brown-or-white-rice", + }, + } + fake := createFakeMock(simpleFakeMockSchema, fakeExample, nil) + mg := NewMockGenerator(JSON) + mock, err := mg.GenerateMock(fake, "JimmyJammyJimJams") // does not exist + assert.NoError(t, err) + assert.NotEmpty(t, string(mock)) } func TestMockGenerator_GenerateJSONMock_MultiExamples_JSON(t *testing.T) { - fakeExample := map[string]any{ - "exampleOne": map[string]any{ - "fish-and-chips": "cod-and-chips-twice", - }, - "exampleTwo": map[string]any{ - "rice-and-peas": "brown-or-white-rice", - }, - } - fake := createFakeMock(simpleFakeMockSchema, fakeExample, nil) - mg := NewMockGenerator(JSON) - mock, err := mg.GenerateMock(fake, "exampleTwo") - assert.NoError(t, err) - assert.Equal(t, "{\"rice-and-peas\":\"brown-or-white-rice\"}", string(mock)) + fakeExample := map[string]any{ + "exampleOne": map[string]any{ + "fish-and-chips": "cod-and-chips-twice", + }, + "exampleTwo": map[string]any{ + "rice-and-peas": "brown-or-white-rice", + }, + } + fake := createFakeMock(simpleFakeMockSchema, fakeExample, nil) + mg := NewMockGenerator(JSON) + mock, err := mg.GenerateMock(fake, "exampleTwo") + assert.NoError(t, err) + assert.Equal(t, "{\"rice-and-peas\":\"brown-or-white-rice\"}", string(mock)) } func TestMockGenerator_GenerateJSONMock_MultiExamples_PrettyJSON(t *testing.T) { - fakeExample := map[string]any{ - "exampleOne": map[string]any{ - "fish-and-chips": "cod-and-chips-twice", - }, - "exampleTwo": map[string]any{ - "rice-and-peas": "brown-or-white-rice", - "peas": "buttery", - }, - } - fake := createFakeMock(simpleFakeMockSchema, fakeExample, nil) - mg := NewMockGenerator(JSON) - mg.SetPretty() - mock, err := mg.GenerateMock(fake, "exampleTwo") - assert.NoError(t, err) - assert.Equal(t, "{\n \"peas\": \"buttery\",\n \"rice-and-peas\": \"brown-or-white-rice\"\n}", string(mock)) + fakeExample := map[string]any{ + "exampleOne": map[string]any{ + "fish-and-chips": "cod-and-chips-twice", + }, + "exampleTwo": map[string]any{ + "rice-and-peas": "brown-or-white-rice", + "peas": "buttery", + }, + } + fake := createFakeMock(simpleFakeMockSchema, fakeExample, nil) + mg := NewMockGenerator(JSON) + mg.SetPretty() + mock, err := mg.GenerateMock(fake, "exampleTwo") + assert.NoError(t, err) + assert.Equal(t, "{\n \"peas\": \"buttery\",\n \"rice-and-peas\": \"brown-or-white-rice\"\n}", string(mock)) } func TestMockGenerator_GenerateJSONMock_MultiExamples_YAML(t *testing.T) { - fakeExample := map[string]any{ - "exampleOne": map[string]any{ - "fish-and-chips": "cod-and-chips-twice", - }, - "exampleTwo": map[string]any{ - "rice-and-peas": "brown-or-white-rice", - }, - } - fake := createFakeMock(simpleFakeMockSchema, fakeExample, nil) - mg := NewMockGenerator(YAML) - mock, err := mg.GenerateMock(fake, "exampleTwo") - assert.NoError(t, err) - assert.Equal(t, "rice-and-peas: brown-or-white-rice", strings.TrimSpace(string(mock))) + fakeExample := map[string]any{ + "exampleOne": map[string]any{ + "fish-and-chips": "cod-and-chips-twice", + }, + "exampleTwo": map[string]any{ + "rice-and-peas": "brown-or-white-rice", + }, + } + fake := createFakeMock(simpleFakeMockSchema, fakeExample, nil) + mg := NewMockGenerator(YAML) + mock, err := mg.GenerateMock(fake, "exampleTwo") + assert.NoError(t, err) + assert.Equal(t, "rice-and-peas: brown-or-white-rice", strings.TrimSpace(string(mock))) } func TestMockGenerator_GenerateJSONMock_NoExamples_JSON(t *testing.T) { - fake := createFakeMock(simpleFakeMockSchema, nil, nil) - mg := NewMockGenerator(JSON) - mock, err := mg.GenerateMock(fake, "") - assert.NoError(t, err) - assert.Equal(t, "magic-herbs", string(mock)) + fake := createFakeMock(simpleFakeMockSchema, nil, nil) + mg := NewMockGenerator(JSON) + mock, err := mg.GenerateMock(fake, "") + assert.NoError(t, err) + assert.Equal(t, "magic-herbs", string(mock)) } func TestMockGenerator_GenerateJSONMock_NoExamples_YAML(t *testing.T) { - fake := createFakeMock(simpleFakeMockSchema, nil, nil) - mg := NewMockGenerator(YAML) - mock, err := mg.GenerateMock(fake, "") - assert.NoError(t, err) - assert.Equal(t, "magic-herbs", string(mock)) + fake := createFakeMock(simpleFakeMockSchema, nil, nil) + mg := NewMockGenerator(YAML) + mock, err := mg.GenerateMock(fake, "") + assert.NoError(t, err) + assert.Equal(t, "magic-herbs", string(mock)) } func TestMockGenerator_GenerateJSONMock_Object_NoExamples_JSON(t *testing.T) { - fake := createFakeMock(objectFakeMockSchema, nil, nil) - mg := NewMockGenerator(JSON) - mock, err := mg.GenerateMock(fake, "") - assert.NoError(t, err) + fake := createFakeMock(objectFakeMockSchema, nil, nil) + mg := NewMockGenerator(JSON) + mock, err := mg.GenerateMock(fake, "") + assert.NoError(t, err) - // re-serialize back into a map and check the values - var m map[string]any - err = json.Unmarshal(mock, &m) - assert.NoError(t, err) + // re-serialize back into a map and check the values + var m map[string]any + err = json.Unmarshal(mock, &m) + assert.NoError(t, err) - assert.Len(t, m, 2) - assert.GreaterOrEqual(t, len(m["coffee"].(string)), 6) - assert.GreaterOrEqual(t, m["herbs"].(float64), float64(350)) - assert.LessOrEqual(t, m["herbs"].(float64), float64(400)) + assert.Len(t, m, 2) + assert.GreaterOrEqual(t, len(m["coffee"].(string)), 6) + assert.GreaterOrEqual(t, m["herbs"].(float64), float64(350)) + assert.LessOrEqual(t, m["herbs"].(float64), float64(400)) } func TestMockGenerator_GenerateJSONMock_Object_NoExamples_YAML(t *testing.T) { - fake := createFakeMock(objectFakeMockSchema, nil, nil) - mg := NewMockGenerator(YAML) - mock, err := mg.GenerateMock(fake, "") - assert.NoError(t, err) + fake := createFakeMock(objectFakeMockSchema, nil, nil) + mg := NewMockGenerator(YAML) + mock, err := mg.GenerateMock(fake, "") + assert.NoError(t, err) - // re-serialize back into a map and check the values - var m map[string]any - err = yaml.Unmarshal(mock, &m) - assert.NoError(t, err) + // re-serialize back into a map and check the values + var m map[string]any + err = yaml.Unmarshal(mock, &m) + assert.NoError(t, err) - assert.Len(t, m, 2) - assert.GreaterOrEqual(t, len(m["coffee"].(string)), 6) - assert.GreaterOrEqual(t, m["herbs"].(int), 350) - assert.LessOrEqual(t, m["herbs"].(int), 400) + assert.Len(t, m, 2) + assert.GreaterOrEqual(t, len(m["coffee"].(string)), 6) + assert.GreaterOrEqual(t, m["herbs"].(int), 350) + assert.LessOrEqual(t, m["herbs"].(int), 400) } // should result in the exact same output as the above test func TestMockGenerator_GenerateJSONMock_Object_RawSchema(t *testing.T) { - fake := createFakeMockWithoutProxy(objectFakeMockSchema, nil, nil) + fake := createFakeMockWithoutProxy(objectFakeMockSchema, nil, nil) - mg := NewMockGenerator(YAML) - mock, err := mg.GenerateMock(fake, "") - assert.NoError(t, err) + mg := NewMockGenerator(YAML) + mock, err := mg.GenerateMock(fake, "") + assert.NoError(t, err) - // re-serialize back into a map and check the values - var m map[string]any - err = yaml.Unmarshal(mock, &m) - assert.NoError(t, err) + // re-serialize back into a map and check the values + var m map[string]any + err = yaml.Unmarshal(mock, &m) + assert.NoError(t, err) - assert.Len(t, m, 2) - assert.GreaterOrEqual(t, len(m["coffee"].(string)), 6) - assert.GreaterOrEqual(t, m["herbs"].(int), 350) - assert.LessOrEqual(t, m["herbs"].(int), 400) + assert.Len(t, m, 2) + assert.GreaterOrEqual(t, len(m["coffee"].(string)), 6) + assert.GreaterOrEqual(t, m["herbs"].(int), 350) + assert.LessOrEqual(t, m["herbs"].(int), 400) }