diff --git a/datamodel/low/base/schema_proxy.go b/datamodel/low/base/schema_proxy.go index 15b225d..d715cab 100644 --- a/datamodel/low/base/schema_proxy.go +++ b/datamodel/low/base/schema_proxy.go @@ -4,6 +4,7 @@ package base import ( + "crypto/sha256" "github.com/pb33f/libopenapi/datamodel/low" "github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/utils" @@ -120,6 +121,13 @@ func (sp *SchemaProxy) Hash() [32]byte { if sp.rendered != nil { return sp.rendered.Hash() } else { - return sp.Schema().Hash() + if !sp.isReference { + // only resolve this proxy if it's not a ref. + sch := sp.Schema() + sp.rendered = sch + return sch.Hash() + } } + // hash reference value only, do not resolve! + return sha256.Sum256([]byte(sp.referenceLookup)) } diff --git a/datamodel/low/v3/components.go b/datamodel/low/v3/components.go index 31e878b..9f7fd55 100644 --- a/datamodel/low/v3/components.go +++ b/datamodel/low/v3/components.go @@ -39,7 +39,6 @@ func (co *Components) FindExtension(ext string) *low.ValueReference[any] { // FindSchema attempts to locate a SchemaProxy from 'schemas' with a specific name func (co *Components) FindSchema(schema string) *low.ValueReference[*base.SchemaProxy] { return low.FindItemInMap[*base.SchemaProxy](schema, co.Schemas.Value) - } // FindResponse attempts to locate a Response from 'responses' with a specific name @@ -169,8 +168,22 @@ func extractComponentValues[T low.Buildable[N], N any](label string, root *yaml. eChan := make(chan error) var buildComponent = func(label *yaml.Node, value *yaml.Node, c chan componentBuildResult[T], ec chan<- error) { var n T = new(N) + + // if this is a reference, extract it (although components with references is an antipattern) + // If you're building components as references... pls... stop, this code should not need to be here. + // TODO: check circular crazy on this. It may explode + var err error + if h, _, _ := utils.IsNodeRefValue(value); h && label.Value != SchemasLabel { + value, err = low.LocateRefNode(value, idx) + } + if err != nil { + ec <- err + return + } + + // build. _ = low.BuildModel(value, n) - err := n.Build(value, idx) + err = n.Build(value, idx) if err != nil { ec <- err return diff --git a/what-changed/model/comparison_functions.go b/what-changed/model/comparison_functions.go index 7325559..529525f 100644 --- a/what-changed/model/comparison_functions.go +++ b/what-changed/model/comparison_functions.go @@ -224,7 +224,9 @@ func CheckMapForChanges[T any, R any](expLeft, expRight map[low.KeyReference[str return } // run comparison. + chLock.Lock() expChanges[k] = compareFunc(p[k].Value, h[k].Value) + chLock.Unlock() doneChan <- true } diff --git a/what-changed/model/components.go b/what-changed/model/components.go index d49b8a1..b3f050f 100644 --- a/what-changed/model/components.go +++ b/what-changed/model/components.go @@ -5,8 +5,8 @@ package model import ( "github.com/pb33f/libopenapi/datamodel/low" - v2 "github.com/pb33f/libopenapi/datamodel/low/v2" - v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/datamodel/low/v3" "reflect" ) @@ -77,55 +77,55 @@ func CompareComponents(l, r any) *ComponentsChanges { comparisons := 0 // run as fast as we can, thread all the things. - if !lComponents.Schemas.IsEmpty() || rComponents.Schemas.IsEmpty() { + if !lComponents.Schemas.IsEmpty() || !rComponents.Schemas.IsEmpty() { comparisons++ go runComparison(lComponents.Schemas.Value, rComponents.Schemas.Value, &changes, v3.SchemasLabel, CompareSchemas, doneChan) } - if !lComponents.Responses.IsEmpty() || rComponents.Responses.IsEmpty() { + if !lComponents.Responses.IsEmpty() || !rComponents.Responses.IsEmpty() { comparisons++ go runComparison(lComponents.Responses.Value, rComponents.Responses.Value, &changes, v3.ResponsesLabel, CompareResponseV3, doneChan) } - if !lComponents.Parameters.IsEmpty() || rComponents.Parameters.IsEmpty() { + if !lComponents.Parameters.IsEmpty() || !rComponents.Parameters.IsEmpty() { comparisons++ go runComparison(lComponents.Parameters.Value, rComponents.Parameters.Value, &changes, v3.ParametersLabel, CompareParametersV3, doneChan) } - if !lComponents.Examples.IsEmpty() || rComponents.Examples.IsEmpty() { + if !lComponents.Examples.IsEmpty() || !rComponents.Examples.IsEmpty() { comparisons++ go runComparison(lComponents.Examples.Value, rComponents.Examples.Value, &changes, v3.ExamplesLabel, CompareExamples, doneChan) } - if !lComponents.RequestBodies.IsEmpty() || rComponents.RequestBodies.IsEmpty() { + if !lComponents.RequestBodies.IsEmpty() || !rComponents.RequestBodies.IsEmpty() { comparisons++ go runComparison(lComponents.RequestBodies.Value, rComponents.RequestBodies.Value, &changes, v3.RequestBodiesLabel, CompareRequestBodies, doneChan) } - if !lComponents.Headers.IsEmpty() || rComponents.Headers.IsEmpty() { + if !lComponents.Headers.IsEmpty() || !rComponents.Headers.IsEmpty() { comparisons++ go runComparison(lComponents.Headers.Value, rComponents.Headers.Value, &changes, v3.HeadersLabel, CompareHeadersV3, doneChan) } - if !lComponents.SecuritySchemes.IsEmpty() || rComponents.SecuritySchemes.IsEmpty() { + if !lComponents.SecuritySchemes.IsEmpty() || !rComponents.SecuritySchemes.IsEmpty() { comparisons++ go runComparison(lComponents.SecuritySchemes.Value, rComponents.SecuritySchemes.Value, &changes, v3.SecuritySchemesLabel, CompareSecuritySchemesV3, doneChan) } - if !lComponents.Links.IsEmpty() || rComponents.Links.IsEmpty() { + if !lComponents.Links.IsEmpty() || !rComponents.Links.IsEmpty() { comparisons++ go runComparison(lComponents.Links.Value, rComponents.Links.Value, &changes, v3.LinksLabel, CompareLinks, doneChan) } - if !lComponents.Callbacks.IsEmpty() || rComponents.Callbacks.IsEmpty() { + if !lComponents.Callbacks.IsEmpty() || !rComponents.Callbacks.IsEmpty() { comparisons++ go runComparison(lComponents.Callbacks.Value, rComponents.Callbacks.Value, &changes, v3.CallbacksLabel, CompareCallback, doneChan) @@ -246,9 +246,6 @@ func (c *ComponentsChanges) TotalBreakingChanges() int { for k := range c.ParameterChanges { v += c.ParameterChanges[k].TotalBreakingChanges() } - for k := range c.ExamplesChanges { - v += c.ExamplesChanges[k].TotalBreakingChanges() - } for k := range c.RequestBodyChanges { v += c.RequestBodyChanges[k].TotalBreakingChanges() } @@ -264,8 +261,5 @@ func (c *ComponentsChanges) TotalBreakingChanges() int { for k := range c.CallbackChanges { v += c.CallbackChanges[k].TotalBreakingChanges() } - if c.ExtensionChanges != nil { - v += c.ExtensionChanges.TotalBreakingChanges() - } return v } diff --git a/what-changed/model/components_test.go b/what-changed/model/components_test.go index 7d74069..55a0b5a 100644 --- a/what-changed/model/components_test.go +++ b/what-changed/model/components_test.go @@ -5,8 +5,9 @@ package model import ( "github.com/pb33f/libopenapi/datamodel/low" - v2 "github.com/pb33f/libopenapi/datamodel/low/v2" - v3low "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/pb33f/libopenapi/index" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" "testing" @@ -227,7 +228,7 @@ param3: assert.Equal(t, Modified, extChanges.ParameterChanges["param1"].Changes[0].ChangeType) assert.Equal(t, "WIDE AWAKE", extChanges.ParameterChanges["param1"].Changes[0].New) assert.Equal(t, "KINDA SNOOZ", extChanges.ParameterChanges["param3"].Changes[0].New) - assert.Equal(t, v3low.NameLabel, extChanges.ParameterChanges["param1"].Changes[0].Property) + assert.Equal(t, v3.NameLabel, extChanges.ParameterChanges["param1"].Changes[0].Property) } @@ -366,7 +367,7 @@ resp2: assert.Equal(t, 1, extChanges.TotalChanges()) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, 1, extChanges.ResponsesChanges["resp2"].TotalChanges()) - assert.Equal(t, v3low.DescriptionLabel, extChanges.ResponsesChanges["resp2"].Changes[0].Property) + assert.Equal(t, v3.DescriptionLabel, extChanges.ResponsesChanges["resp2"].Changes[0].Property) } func TestCompareComponents_Swagger_Responses_Added(t *testing.T) { @@ -499,5 +500,856 @@ scheme2: assert.Equal(t, 1, extChanges.TotalChanges()) assert.Equal(t, 0, extChanges.TotalBreakingChanges()) assert.Equal(t, 1, extChanges.SecuritySchemeChanges["scheme2"].TotalChanges()) - assert.Equal(t, v3low.DescriptionLabel, extChanges.SecuritySchemeChanges["scheme2"].Changes[0].Property) + assert.Equal(t, v3.DescriptionLabel, extChanges.SecuritySchemeChanges["scheme2"].Changes[0].Property) +} + +func TestCompareComponents_OpenAPI_Schemas_Equal(t *testing.T) { + + left := ` +schemas: + coffee: + description: tasty + tv: + description: mostly boring.` + + right := `schemas: + coffee: + description: tasty + tv: + description: mostly boring.` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Schemas_Refs_FullBuild(t *testing.T) { + + left := `components: + schemas: + coffee: + description: tasty + tv: + $ref: '#/components/schemas/coffee'` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + + _ = low.BuildModel(lNode.Content[0], &lDoc) + _ = low.BuildModel(rNode.Content[0], &rDoc) + + idx := index.NewSpecIndex(&lNode) + + _ = lDoc.Build(lNode.Content[0], idx) + _ = rDoc.Build(rNode.Content[0], idx) + + // compare. + extChanges := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Schemas_Modify(t *testing.T) { + + left := ` +schemas: + coffee: + description: tasty + tv: + description: mostly boring.` + + right := `schemas: + coffee: + description: tasty + tv: + description: mostly boring, except when it is sci-fi` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, 1, extChanges.SchemaChanges["tv"].TotalChanges()) + assert.Equal(t, v3.DescriptionLabel, extChanges.SchemaChanges["tv"].Changes[0].Property) +} + +func TestCompareComponents_OpenAPI_Schemas_Add(t *testing.T) { + + left := ` +schemas: + coffee: + description: tasty + tv: + description: mostly boring.` + + right := `schemas: + coffee: + description: tasty + tv: + description: mostly boring. + herbs: + description: need a massive slowdown` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, "herbs", extChanges.Changes[0].New) +} + +func TestCompareComponents_OpenAPI_Schemas_Remove(t *testing.T) { + + left := ` +schemas: + coffee: + description: tasty + tv: + description: mostly boring.` + + right := `schemas: + coffee: + description: tasty + tv: + description: mostly boring. + herbs: + description: need a massive slowdown` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, "herbs", extChanges.Changes[0].Original) +} + +func TestCompareComponents_OpenAPI_Responses_Equal(t *testing.T) { + + left := ` +responses: + niceResponse: + description: hello + badResponse: + description: go away please` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Responses_FullBuild(t *testing.T) { + left := `components: + responses: + coffee: + description: tasty + tv: + $ref: '#/components/responses/coffee'` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + + _ = low.BuildModel(lNode.Content[0], &lDoc) + _ = low.BuildModel(rNode.Content[0], &rDoc) + + idx := index.NewSpecIndex(&lNode) + + _ = lDoc.Build(lNode.Content[0], idx) + _ = rDoc.Build(rNode.Content[0], idx) + + // compare. + extChanges := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Responses_FullBuild_IdenticalRef(t *testing.T) { + left := `components: + responses: + coffee: + description: tasty + tv: + $ref: '#/components/responses/coffee'` + + right := `components: + responses: + coffee: + $ref: '#/components/responses/tv' + tv: + description: tasty` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + + _ = low.BuildModel(lNode.Content[0], &lDoc) + _ = low.BuildModel(rNode.Content[0], &rDoc) + + idx := index.NewSpecIndex(&lNode) + idx2 := index.NewSpecIndex(&rNode) + + _ = lDoc.Build(lNode.Content[0], idx) + _ = rDoc.Build(rNode.Content[0], idx2) + + // compare. + extChanges := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Responses_Modify(t *testing.T) { + + left := `responses: + niceResponse: + description: hello + badResponse: + description: go away please` + + right := `responses: + niceResponse: + description: hello my matey + badResponse: + description: go away please, now!` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&rDoc, &lDoc) + assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) +} + +func TestCompareComponents_OpenAPI_Responses_Add(t *testing.T) { + + left := `responses: + niceResponse: + description: hello + badResponse: + description: go away please!` + + right := `responses: + niceResponse: + description: hello + badResponse: + description: go away please! + indifferent: + description: stay, or go, who cares?` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, "indifferent", extChanges.Changes[0].New) +} + +func TestCompareComponents_OpenAPI_Responses_Remove(t *testing.T) { + + left := `responses: + niceResponse: + description: hello + badResponse: + description: go away please!` + + right := `responses: + niceResponse: + description: hello + badResponse: + description: go away please! + indifferent: + description: stay, or go, who cares?` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, "indifferent", extChanges.Changes[0].Original) +} + +func TestCompareComponents_OpenAPI_Parameters_Equal(t *testing.T) { + + left := `parameters: + param1: + name: a parameter + param2: + name: another param` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Parameters_Modified(t *testing.T) { + + left := `parameters: + param1: + name: a parameter + param2: + name: another param` + + right := `parameters: + param1: + name: a parameter modified + param2: + name: another param but modified` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Equal(t, 2, extChanges.TotalBreakingChanges()) +} + +func TestCompareComponents_OpenAPI_Parameters_Added(t *testing.T) { + + left := `parameters: + param1: + name: a parameter + param2: + name: another param` + + right := `parameters: + param1: + name: a parameter + param2: + name: another param + param3: + name: do you like code?` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, "param3", extChanges.Changes[0].New) +} + +func TestCompareComponents_OpenAPI_Parameters_Removed(t *testing.T) { + + left := `parameters: + param1: + name: a parameter + param2: + name: another param` + + right := `parameters: + param1: + name: a parameter + param2: + name: another param + param3: + name: do you like code?` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, "param3", extChanges.Changes[0].Original) +} + +func TestCompareComponents_OpenAPI_Examples_Equal(t *testing.T) { + + left := `examples: + example1: + description: an example + example2: + description: another example` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Examples_Modified(t *testing.T) { + + left := `examples: + example1: + description: an example + example2: + description: another example` + + right := `examples: + example1: + description: change me + example2: + description: grow me` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) +} + +func TestCompareComponents_OpenAPI_RequestBodies_Equal(t *testing.T) { + + left := `requestBodies: + body1: + description: a request + body2: + description: another request` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_RequestBodies_Modified(t *testing.T) { + + left := `requestBodies: + body1: + description: a request + body2: + description: another request` + + right := `requestBodies: + body1: + description: a request but changed + body2: + description: another request, also changed` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) +} + +func TestCompareComponents_OpenAPI_Headers_Equal(t *testing.T) { + + left := `headers: + header1: + description: a header + header2: + description: another header` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Headers_Modified(t *testing.T) { + + left := `headers: + header1: + description: a header + header2: + description: another header` + + right := `headers: + header1: + description: a header but different + header2: + description: another header, also different` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) +} + +func TestCompareComponents_OpenAPI_SecuritySchemes_Equal(t *testing.T) { + + left := `securitySchemes: + scheme1: + description: a scheme + scheme2: + description: another scheme` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Links_Equal(t *testing.T) { + + left := `links: + link1: + operationId: link1 + link2: + operationId: link2` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Links_Modified(t *testing.T) { + + left := `links: + link1: + operationId: link1 + link2: + operationId: link2` + + right := `links: + link1: + operationId: somethingFresh + link2: + operationId: somethingNew` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 2, extChanges.TotalChanges()) + assert.Equal(t, 2, extChanges.TotalBreakingChanges()) +} + +func TestCompareComponents_OpenAPI_Callbacks_Equal(t *testing.T) { + + left := `callbacks: + link1: + url: https://pb33f.io + link2: + url: https://pb33f.io` + + right := left + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareComponents_OpenAPI_Callbacks_Modified(t *testing.T) { + + left := `callbacks: + link1: + '{$request.query.queryUrl}': + post: + description: a nice callback + link2: + '{$pizza.cake.burgers}': + post: + description: pizza and cake, and burgers.` + + right := `callbacks: + link1: + '{$request.query.queryUrl}': + post: + description: a nice callback, but changed + link2: + '{$pizza.cake.burgers}': + get: + description: pizza and cake, and burgers, and ketchup.` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) +} + +func TestCompareComponents_OpenAPI_Extensions_Modified(t *testing.T) { + + left := `x-components: are done"` + + right := `x-components: I hope` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.Components + var rDoc v3.Components + _ = 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 := CompareComponents(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) } diff --git a/what-changed/model/schema_test.go b/what-changed/model/schema_test.go index 6bc6300..03be331 100644 --- a/what-changed/model/schema_test.go +++ b/what-changed/model/schema_test.go @@ -199,17 +199,21 @@ func TestCompareSchemas_RefIgnore(t *testing.T) { func TestCompareSchemas_RefChanged(t *testing.T) { left := `components: schemas: - Yo: + Woah: type: int + Yo: + type: string OK: - $ref: '#/components/schemas/No'` + $ref: '#/components/schemas/Woah'` right := `components: schemas: - Yo: + Woah: type: int + Yo: + type: string OK: - $ref: '#/components/schemas/Woah'` + $ref: '#/components/schemas/Yo'` leftDoc, rightDoc := test_BuildDoc(left, right) @@ -221,7 +225,7 @@ func TestCompareSchemas_RefChanged(t *testing.T) { assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) assert.Equal(t, Modified, changes.Changes[0].ChangeType) - assert.Equal(t, "#/components/schemas/Woah", changes.Changes[0].New) + assert.Equal(t, "string", changes.Changes[0].New) } func TestCompareSchemas_RefToInline(t *testing.T) { @@ -230,7 +234,7 @@ func TestCompareSchemas_RefToInline(t *testing.T) { Yo: type: int OK: - $ref: '#/components/schemas/No'` + $ref: '#/components/schemas/Yo'` right := `components: schemas: @@ -249,8 +253,8 @@ func TestCompareSchemas_RefToInline(t *testing.T) { assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) assert.Equal(t, Modified, changes.Changes[0].ChangeType) - assert.Equal(t, v3.RefLabel, changes.Changes[0].Property) - assert.Equal(t, "#/components/schemas/No", changes.Changes[0].Original) + assert.Equal(t, v3.TypeLabel, changes.Changes[0].Property) + assert.Equal(t, "int", changes.Changes[0].Original) } @@ -260,7 +264,7 @@ func TestCompareSchemas_InlineToRef(t *testing.T) { Yo: type: int OK: - $ref: '#/components/schemas/No'` + $ref: '#/components/schemas/Yo'` right := `components: schemas: @@ -279,8 +283,8 @@ func TestCompareSchemas_InlineToRef(t *testing.T) { assert.NotNil(t, changes) assert.Len(t, changes.Changes, 1) assert.Equal(t, Modified, changes.Changes[0].ChangeType) - assert.Equal(t, v3.RefLabel, changes.Changes[0].Property) - assert.Equal(t, "#/components/schemas/No", changes.Changes[0].New) + assert.Equal(t, v3.TypeLabel, changes.Changes[0].Property) + assert.Equal(t, "int", changes.Changes[0].New) }