mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-08 20:47:43 +00:00
Smashing bugs in models by validating changes.
Who would have thought the what-changed tool would be the key to ensuring accuracy on the models.
This commit is contained in:
@@ -218,26 +218,26 @@ func TestNewSwaggerDocument_Paths(t *testing.T) {
|
|||||||
assert.Equal(t, "uploadFile", upload.Post.OperationId)
|
assert.Equal(t, "uploadFile", upload.Post.OperationId)
|
||||||
assert.Equal(t, "multipart/form-data", upload.Post.Consumes[0])
|
assert.Equal(t, "multipart/form-data", upload.Post.Consumes[0])
|
||||||
assert.Equal(t, "application/json", upload.Post.Produces[0])
|
assert.Equal(t, "application/json", upload.Post.Produces[0])
|
||||||
assert.Len(t, upload.Parameters, 3)
|
assert.Len(t, upload.Post.Parameters, 3)
|
||||||
assert.Equal(t, "petId", upload.Parameters[0].Name)
|
assert.Equal(t, "petId", upload.Post.Parameters[0].Name)
|
||||||
assert.Equal(t, "path", upload.Parameters[0].In)
|
assert.Equal(t, "path", upload.Post.Parameters[0].In)
|
||||||
assert.Equal(t, "ID of pet to update", upload.Parameters[0].Description)
|
assert.Equal(t, "ID of pet to update", upload.Post.Parameters[0].Description)
|
||||||
assert.True(t, *upload.Parameters[0].Required)
|
assert.True(t, *upload.Post.Parameters[0].Required)
|
||||||
assert.Equal(t, "integer", upload.Parameters[0].Type)
|
assert.Equal(t, "integer", upload.Post.Parameters[0].Type)
|
||||||
assert.Equal(t, "int64", upload.Parameters[0].Format)
|
assert.Equal(t, "int64", upload.Post.Parameters[0].Format)
|
||||||
assert.True(t, *upload.Parameters[0].ExclusiveMaximum)
|
assert.True(t, *upload.Post.Parameters[0].ExclusiveMaximum)
|
||||||
assert.True(t, *upload.Parameters[0].ExclusiveMinimum)
|
assert.True(t, *upload.Post.Parameters[0].ExclusiveMinimum)
|
||||||
assert.Equal(t, 2, *upload.Parameters[0].MaxLength)
|
assert.Equal(t, 2, *upload.Post.Parameters[0].MaxLength)
|
||||||
assert.Equal(t, 1, *upload.Parameters[0].MinLength)
|
assert.Equal(t, 1, *upload.Post.Parameters[0].MinLength)
|
||||||
assert.Equal(t, 1, *upload.Parameters[0].Minimum)
|
assert.Equal(t, 1, *upload.Post.Parameters[0].Minimum)
|
||||||
assert.Equal(t, 5, *upload.Parameters[0].Maximum)
|
assert.Equal(t, 5, *upload.Post.Parameters[0].Maximum)
|
||||||
assert.Equal(t, "hi!", upload.Parameters[0].Pattern)
|
assert.Equal(t, "hi!", upload.Post.Parameters[0].Pattern)
|
||||||
assert.Equal(t, 1, *upload.Parameters[0].MinItems)
|
assert.Equal(t, 1, *upload.Post.Parameters[0].MinItems)
|
||||||
assert.Equal(t, 20, *upload.Parameters[0].MaxItems)
|
assert.Equal(t, 20, *upload.Post.Parameters[0].MaxItems)
|
||||||
assert.True(t, *upload.Parameters[0].UniqueItems)
|
assert.True(t, *upload.Post.Parameters[0].UniqueItems)
|
||||||
assert.Len(t, upload.Parameters[0].Enum, 2)
|
assert.Len(t, upload.Post.Parameters[0].Enum, 2)
|
||||||
assert.Equal(t, "hello", upload.Parameters[0].Enum[0])
|
assert.Equal(t, "hello", upload.Post.Parameters[0].Enum[0])
|
||||||
def := upload.Parameters[0].Default.(map[string]interface{})
|
def := upload.Post.Parameters[0].Default.(map[string]interface{})
|
||||||
assert.Equal(t, "here", def["something"])
|
assert.Equal(t, "here", def["something"])
|
||||||
|
|
||||||
assert.Equal(t, "https://pb33f.io", upload.Post.ExternalDocs.URL)
|
assert.Equal(t, "https://pb33f.io", upload.Post.ExternalDocs.URL)
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ func ExtractArray[T Buildable[N], N any](label string, root *yaml.Node, idx *ind
|
|||||||
root.Content[1].Value)
|
root.Content[1].Value)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
_, ln, vn = utils.FindKeyNodeFull(label, root.Content)
|
_, ln, vn = utils.FindKeyNodeFullTop(label, root.Content)
|
||||||
if vn != nil {
|
if vn != nil {
|
||||||
if h, _, _ := utils.IsNodeRefValue(vn); h {
|
if h, _, _ := utils.IsNodeRefValue(vn); h {
|
||||||
ref, err := LocateRefNode(vn, idx)
|
ref, err := LocateRefNode(vn, idx)
|
||||||
|
|||||||
@@ -638,7 +638,7 @@ func TestExtractArray(t *testing.T) {
|
|||||||
var cNode yaml.Node
|
var cNode yaml.Node
|
||||||
_ = yaml.Unmarshal([]byte(yml), &cNode)
|
_ = yaml.Unmarshal([]byte(yml), &cNode)
|
||||||
|
|
||||||
things, _, _, err := ExtractArray[*pizza]("things", &cNode, idx)
|
things, _, _, err := ExtractArray[*pizza]("things", cNode.Content[0], idx)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, things)
|
assert.NotNil(t, things)
|
||||||
assert.Equal(t, "one", things[0].Value.Description.Value)
|
assert.Equal(t, "one", things[0].Value.Description.Value)
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*Swagger, []error) {
|
|||||||
doneChan := make(chan bool)
|
doneChan := make(chan bool)
|
||||||
errChan := make(chan error)
|
errChan := make(chan error)
|
||||||
for i := range extractionFuncs {
|
for i := range extractionFuncs {
|
||||||
go extractionFuncs[i](info.RootNode, &doc, idx, doneChan, errChan)
|
go extractionFuncs[i](info.RootNode.Content[0], &doc, idx, doneChan, errChan)
|
||||||
}
|
}
|
||||||
completedExtractions := 0
|
completedExtractions := 0
|
||||||
for completedExtractions < len(extractionFuncs) {
|
for completedExtractions < len(extractionFuncs) {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func CreateDocument(info *datamodel.SpecInfo) (*Document, []error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
||||||
_, ln, vn := utils.FindKeyNodeFull(base.InfoLabel, info.RootNode.Content)
|
_, ln, vn := utils.FindKeyNodeFullTop(base.InfoLabel, info.RootNode.Content[0].Content)
|
||||||
if vn != nil {
|
if vn != nil {
|
||||||
ir := base.Info{}
|
ir := base.Info{}
|
||||||
_ = low.BuildModel(vn, &ir)
|
_ = low.BuildModel(vn, &ir)
|
||||||
@@ -83,7 +83,7 @@ func extractInfo(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
||||||
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, info.RootNode, idx)
|
sec, ln, vn, err := low.ExtractArray[*base.SecurityRequirement](SecurityLabel, info.RootNode.Content[0], idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -96,7 +96,7 @@ func extractSecurity(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInd
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
||||||
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode, idx)
|
extDocs, dErr := low.ExtractObject[*base.ExternalDoc](base.ExternalDocsLabel, info.RootNode.Content[0], idx)
|
||||||
if dErr != nil {
|
if dErr != nil {
|
||||||
return dErr
|
return dErr
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ func extractExternalDocs(info *datamodel.SpecInfo, doc *Document, idx *index.Spe
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
||||||
_, ln, vn := utils.FindKeyNodeFull(ComponentsLabel, info.RootNode.Content)
|
_, ln, vn := utils.FindKeyNodeFullTop(ComponentsLabel, info.RootNode.Content[0].Content)
|
||||||
if vn != nil {
|
if vn != nil {
|
||||||
ir := Components{}
|
ir := Components{}
|
||||||
_ = low.BuildModel(vn, &ir)
|
_ = low.BuildModel(vn, &ir)
|
||||||
@@ -120,7 +120,7 @@ func extractComponents(info *datamodel.SpecInfo, doc *Document, idx *index.SpecI
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
||||||
_, ln, vn := utils.FindKeyNodeFull(ServersLabel, info.RootNode.Content)
|
_, ln, vn := utils.FindKeyNodeFull(ServersLabel, info.RootNode.Content[0].Content)
|
||||||
if vn != nil {
|
if vn != nil {
|
||||||
if utils.IsNodeArray(vn) {
|
if utils.IsNodeArray(vn) {
|
||||||
var servers []low.ValueReference[*Server]
|
var servers []low.ValueReference[*Server]
|
||||||
@@ -146,7 +146,7 @@ func extractServers(info *datamodel.SpecInfo, doc *Document, idx *index.SpecInde
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
||||||
_, ln, vn := utils.FindKeyNodeFull(base.TagsLabel, info.RootNode.Content)
|
_, ln, vn := utils.FindKeyNodeFull(base.TagsLabel, info.RootNode.Content[0].Content)
|
||||||
if vn != nil {
|
if vn != nil {
|
||||||
if utils.IsNodeArray(vn) {
|
if utils.IsNodeArray(vn) {
|
||||||
var tags []low.ValueReference[*base.Tag]
|
var tags []low.ValueReference[*base.Tag]
|
||||||
@@ -174,7 +174,7 @@ func extractTags(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
func extractPaths(info *datamodel.SpecInfo, doc *Document, idx *index.SpecIndex) error {
|
||||||
_, ln, vn := utils.FindKeyNodeFull(PathsLabel, info.RootNode.Content)
|
_, ln, vn := utils.FindKeyNodeFull(PathsLabel, info.RootNode.Content[0].Content)
|
||||||
if vn != nil {
|
if vn != nil {
|
||||||
ir := Paths{}
|
ir := Paths{}
|
||||||
err := ir.Build(vn, idx)
|
err := ir.Build(vn, idx)
|
||||||
|
|||||||
@@ -449,7 +449,8 @@ func TestCreateDocument_Components_Links(t *testing.T) {
|
|||||||
|
|
||||||
func TestCreateDocument_Doc_Security(t *testing.T) {
|
func TestCreateDocument_Doc_Security(t *testing.T) {
|
||||||
initTest()
|
initTest()
|
||||||
oAuth := doc.FindSecurityRequirement("OAuthScheme")
|
d := doc
|
||||||
|
oAuth := d.FindSecurityRequirement("OAuthScheme")
|
||||||
assert.Len(t, oAuth, 2)
|
assert.Len(t, oAuth, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,11 +96,10 @@ func (r *Response) Hash() [32]byte {
|
|||||||
if r.Description.Value != "" {
|
if r.Description.Value != "" {
|
||||||
f = append(f, r.Description.Value)
|
f = append(f, r.Description.Value)
|
||||||
}
|
}
|
||||||
|
|
||||||
keys := make([]string, len(r.Headers.Value))
|
keys := make([]string, len(r.Headers.Value))
|
||||||
z := 0
|
z := 0
|
||||||
for k := range r.Headers.Value {
|
for k := range r.Headers.Value {
|
||||||
keys[z] = low.GenerateHashString(r.Headers.Value[k].Value)
|
keys[z] = fmt.Sprintf("%s-%s", k.Value, low.GenerateHashString(r.Headers.Value[k].Value))
|
||||||
z++
|
z++
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
@@ -108,7 +107,7 @@ func (r *Response) Hash() [32]byte {
|
|||||||
keys = make([]string, len(r.Content.Value))
|
keys = make([]string, len(r.Content.Value))
|
||||||
z = 0
|
z = 0
|
||||||
for k := range r.Content.Value {
|
for k := range r.Content.Value {
|
||||||
keys[z] = low.GenerateHashString(r.Content.Value[k].Value)
|
keys[z] = fmt.Sprintf("%s-%s", k.Value, low.GenerateHashString(r.Content.Value[k].Value))
|
||||||
z++
|
z++
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
@@ -116,7 +115,7 @@ func (r *Response) Hash() [32]byte {
|
|||||||
keys = make([]string, len(r.Links.Value))
|
keys = make([]string, len(r.Links.Value))
|
||||||
z = 0
|
z = 0
|
||||||
for k := range r.Links.Value {
|
for k := range r.Links.Value {
|
||||||
keys[z] = low.GenerateHashString(r.Links.Value[k].Value)
|
keys[z] = fmt.Sprintf("%s-%s", k.Value, low.GenerateHashString(r.Links.Value[k].Value))
|
||||||
z++
|
z++
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ default:
|
|||||||
assert.Equal(t, "a link", link.Value.Description.Value)
|
assert.Equal(t, "a link", link.Value.Description.Value)
|
||||||
|
|
||||||
// check hash
|
// check hash
|
||||||
assert.Equal(t, "4ab807033ce9ca57ab551d8569cc11da8722c4ae75c003bc23b495ab756f468a",
|
assert.Equal(t, "c009b2046101bc03df802b4cf23f78176931137e6115bf7b445ca46856c06b51",
|
||||||
low.GenerateHashString(&n))
|
low.GenerateHashString(&n))
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -95,7 +95,7 @@ x-shoes: old`
|
|||||||
err = n.Build(idxNode.Content[0], idx)
|
err = n.Build(idxNode.Content[0], idx)
|
||||||
|
|
||||||
// check hash
|
// check hash
|
||||||
assert.Equal(t, "1b9161a7d31a9aa4580899f57092bcb6801b37045777bff28981bd2288c72b10",
|
assert.Equal(t, "54ab66e6cb8bd226940f421c2387e45215b84c946182435dfe2a3036043fa07c",
|
||||||
low.GenerateHashString(&n))
|
low.GenerateHashString(&n))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ func (r *Responses) Hash() [32]byte {
|
|||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
for k := range keys {
|
for k := range keys {
|
||||||
f = append(f, low.GenerateHashString(cMap[keys[k]]))
|
f = append(f, fmt.Sprintf("%s-%s", keys[k], low.GenerateHashString(cMap[keys[k]])))
|
||||||
}
|
}
|
||||||
if !r.Default.IsEmpty() {
|
if !r.Default.IsEmpty() {
|
||||||
f = append(f, low.GenerateHashString(r.Default.Value))
|
f = append(f, low.GenerateHashString(r.Default.Value))
|
||||||
|
|||||||
@@ -185,12 +185,10 @@ paths:
|
|||||||
/burgers/{burgerId}/dressings:
|
/burgers/{burgerId}/dressings:
|
||||||
get:
|
get:
|
||||||
operationId: listBurgerDressings
|
operationId: listBurgerDressings
|
||||||
tags:
|
|
||||||
- "Dressing"
|
|
||||||
summary: Get a list of all dressings available
|
summary: Get a list of all dressings available
|
||||||
description: Same as the summary, look up a tasty burger, by its ID - the burger identifier
|
description: Same as the summary, look up a tasty burger, by its ID - the burger identifier
|
||||||
parameters:
|
parameters:
|
||||||
- in: path
|
- in: query
|
||||||
name: burgerId
|
name: burgerId
|
||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
|
|||||||
@@ -621,3 +621,35 @@ trace:
|
|||||||
assert.Equal(t, 8, extChanges.TotalChanges())
|
assert.Equal(t, 8, extChanges.TotalChanges())
|
||||||
assert.Equal(t, 8, extChanges.TotalBreakingChanges())
|
assert.Equal(t, 8, extChanges.TotalBreakingChanges())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestComparePathItem_V3_ChangeParam(t *testing.T) {
|
||||||
|
|
||||||
|
left := `get:
|
||||||
|
operationId: listBurgerDressings
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: burgerId`
|
||||||
|
|
||||||
|
right := `get:
|
||||||
|
operationId: listBurgerDressings
|
||||||
|
parameters:
|
||||||
|
- in: head
|
||||||
|
name: burgerId`
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc v3.PathItem
|
||||||
|
var rDoc v3.PathItem
|
||||||
|
_ = low.BuildModel(lNode.Content[0], &lDoc)
|
||||||
|
_ = low.BuildModel(rNode.Content[0], &rDoc)
|
||||||
|
_ = lDoc.Build(lNode.Content[0], nil)
|
||||||
|
_ = rDoc.Build(rNode.Content[0], nil)
|
||||||
|
|
||||||
|
// compare.
|
||||||
|
extChanges := ComparePathItems(&lDoc, &rDoc)
|
||||||
|
assert.Equal(t, 1, extChanges.TotalChanges())
|
||||||
|
assert.Equal(t, 1, extChanges.TotalBreakingChanges())
|
||||||
|
}
|
||||||
|
|||||||
@@ -428,3 +428,34 @@ default:
|
|||||||
assert.Equal(t, 0, extChanges.TotalBreakingChanges())
|
assert.Equal(t, 0, extChanges.TotalBreakingChanges())
|
||||||
assert.Equal(t, v3.DescriptionLabel, extChanges.DefaultChanges.Changes[0].Property)
|
assert.Equal(t, v3.DescriptionLabel, extChanges.DefaultChanges.Changes[0].Property)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCompareResponses_V3_AddRemoveMediaType(t *testing.T) {
|
||||||
|
|
||||||
|
left := `200:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: int`
|
||||||
|
|
||||||
|
right := `200:
|
||||||
|
content:
|
||||||
|
application/xml:
|
||||||
|
schema:
|
||||||
|
type: int`
|
||||||
|
|
||||||
|
var lNode, rNode yaml.Node
|
||||||
|
_ = yaml.Unmarshal([]byte(left), &lNode)
|
||||||
|
_ = yaml.Unmarshal([]byte(right), &rNode)
|
||||||
|
|
||||||
|
// create low level objects
|
||||||
|
var lDoc v3.Responses
|
||||||
|
var rDoc v3.Responses
|
||||||
|
_ = low.BuildModel(lNode.Content[0], &lDoc)
|
||||||
|
_ = low.BuildModel(rNode.Content[0], &rDoc)
|
||||||
|
_ = lDoc.Build(lNode.Content[0], nil)
|
||||||
|
_ = rDoc.Build(rNode.Content[0], nil)
|
||||||
|
|
||||||
|
extChanges := CompareResponses(&lDoc, &rDoc)
|
||||||
|
assert.Equal(t, 2, extChanges.TotalChanges())
|
||||||
|
assert.Equal(t, 1, extChanges.TotalBreakingChanges())
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ func TestCompareOpenAPIDocuments(t *testing.T) {
|
|||||||
modDoc, _ := v3.CreateDocument(infoMod)
|
modDoc, _ := v3.CreateDocument(infoMod)
|
||||||
|
|
||||||
changes := CompareOpenAPIDocuments(origDoc, modDoc)
|
changes := CompareOpenAPIDocuments(origDoc, modDoc)
|
||||||
assert.Equal(t, 28, changes.TotalChanges())
|
assert.Equal(t, 30, changes.TotalChanges())
|
||||||
assert.Equal(t, 5, changes.TotalBreakingChanges())
|
assert.Equal(t, 6, changes.TotalBreakingChanges())
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user