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:
Dave Shanley
2022-11-19 10:32:19 -05:00
parent 22ca3ced63
commit 97a03315d6
14 changed files with 297 additions and 236 deletions

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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)

View File

@@ -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)
} }

View File

@@ -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)

View File

@@ -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))
} }

View File

@@ -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))

View File

@@ -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

View File

@@ -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())
}

View File

@@ -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())
}

View File

@@ -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())
} }