diff --git a/datamodel/low/v2/security_scheme_test.go b/datamodel/low/v2/security_scheme_test.go index 0d63ab6..3cd1697 100644 --- a/datamodel/low/v2/security_scheme_test.go +++ b/datamodel/low/v2/security_scheme_test.go @@ -11,7 +11,7 @@ import ( "testing" ) -func TestSecurityScheme_Build(t *testing.T) { +func TestSecurityScheme_Build_Borked(t *testing.T) { yml := `scopes: $ref: break` @@ -29,3 +29,24 @@ func TestSecurityScheme_Build(t *testing.T) { assert.Error(t, err) } + +func TestSecurityScheme_Build_Scopes(t *testing.T) { + + yml := `scopes: + some:thing: here + something: there` + + var idxNode yaml.Node + mErr := yaml.Unmarshal([]byte(yml), &idxNode) + assert.NoError(t, mErr) + idx := index.NewSpecIndex(&idxNode) + + var n SecurityScheme + err := low.BuildModel(&idxNode, &n) + assert.NoError(t, err) + + err = n.Build(idxNode.Content[0], idx) + assert.NoError(t, err) + assert.Len(t, n.Scopes.Value.Values, 2) + +} diff --git a/datamodel/low/v3/constants.go b/datamodel/low/v3/constants.go index 2718ce6..25d0c3f 100644 --- a/datamodel/low/v3/constants.go +++ b/datamodel/low/v3/constants.go @@ -102,4 +102,12 @@ const ( ContentTypeLabel = "contentType" SecurityDefinitionLabel = "securityDefinition" Scopes = "scopes" + AuthorizationUrlLabel = "authorizationUrl" + TokenUrlLabel = "tokenUrl" + RefreshUrlLabel = "refreshUrl" + FlowLabel = "flow" + FlowsLabel = "flows" + SchemeLabel = "scheme" + OpenIdConnectUrlLabel = "openIdConnectUrl" + ScopesLabel = "scopes" ) diff --git a/what-changed/model/oauth_flows.go b/what-changed/model/oauth_flows.go new file mode 100644 index 0000000..b11c5f9 --- /dev/null +++ b/what-changed/model/oauth_flows.go @@ -0,0 +1,215 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package model + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + v3 "github.com/pb33f/libopenapi/datamodel/low/v3" +) + +type OAuthFlowsChanges struct { + PropertyChanges + ImplicitChanges *OAuthFlowChanges + PasswordChanges *OAuthFlowChanges + ClientCredentialsChanges *OAuthFlowChanges + AuthorizationCodeChanges *OAuthFlowChanges + ExtensionChanges *ExtensionChanges +} + +func (o *OAuthFlowsChanges) TotalChanges() int { + c := o.PropertyChanges.TotalChanges() + if o.ImplicitChanges != nil { + c += o.ImplicitChanges.TotalChanges() + } + if o.PasswordChanges != nil { + c += o.PasswordChanges.TotalChanges() + } + if o.ClientCredentialsChanges != nil { + c += o.ClientCredentialsChanges.TotalChanges() + } + if o.AuthorizationCodeChanges != nil { + c += o.AuthorizationCodeChanges.TotalChanges() + } + if o.ExtensionChanges != nil { + c += o.ExtensionChanges.TotalChanges() + } + return c +} + +func (o *OAuthFlowsChanges) TotalBreakingChanges() int { + c := o.PropertyChanges.TotalBreakingChanges() + if o.ImplicitChanges != nil { + c += o.ImplicitChanges.TotalBreakingChanges() + } + if o.PasswordChanges != nil { + c += o.PasswordChanges.TotalBreakingChanges() + } + if o.ClientCredentialsChanges != nil { + c += o.ClientCredentialsChanges.TotalBreakingChanges() + } + if o.AuthorizationCodeChanges != nil { + c += o.AuthorizationCodeChanges.TotalBreakingChanges() + } + return c +} + +func CompareOAuthFlows(l, r *v3.OAuthFlows) *OAuthFlowsChanges { + if low.AreEqual(l, r) { + return nil + } + + oa := new(OAuthFlowsChanges) + var changes []*Change + + // client credentials + if !l.ClientCredentials.IsEmpty() && !r.ClientCredentials.IsEmpty() { + oa.ClientCredentialsChanges = CompareOAuthFlow(l.ClientCredentials.Value, r.ClientCredentials.Value) + } + if !l.ClientCredentials.IsEmpty() && r.ClientCredentials.IsEmpty() { + CreateChange(&changes, ObjectRemoved, v3.ClientCredentialsLabel, + l.ClientCredentials.ValueNode, nil, true, + l.ClientCredentials.Value, nil) + } + if l.ClientCredentials.IsEmpty() && !r.ClientCredentials.IsEmpty() { + CreateChange(&changes, ObjectAdded, v3.ClientCredentialsLabel, + nil, r.ClientCredentials.ValueNode, false, + nil, r.ClientCredentials.Value) + } + + // implicit + if !l.Implicit.IsEmpty() && !r.Implicit.IsEmpty() { + oa.ImplicitChanges = CompareOAuthFlow(l.Implicit.Value, r.Implicit.Value) + } + if !l.Implicit.IsEmpty() && r.Implicit.IsEmpty() { + CreateChange(&changes, ObjectRemoved, v3.ImplicitLabel, + l.Implicit.ValueNode, nil, true, + l.Implicit.Value, nil) + } + if l.Implicit.IsEmpty() && !r.Implicit.IsEmpty() { + CreateChange(&changes, ObjectAdded, v3.ImplicitLabel, + nil, r.Implicit.ValueNode, false, + nil, r.Implicit.Value) + } + + // password + if !l.Password.IsEmpty() && !r.Password.IsEmpty() { + oa.PasswordChanges = CompareOAuthFlow(l.Password.Value, r.Password.Value) + } + if !l.Password.IsEmpty() && r.Password.IsEmpty() { + CreateChange(&changes, ObjectRemoved, v3.PasswordLabel, + l.Password.ValueNode, nil, true, + l.Password.Value, nil) + } + if l.Password.IsEmpty() && !r.Password.IsEmpty() { + CreateChange(&changes, ObjectAdded, v3.PasswordLabel, + nil, r.Password.ValueNode, false, + nil, r.Password.Value) + } + + // auth code + if !l.AuthorizationCode.IsEmpty() && !r.AuthorizationCode.IsEmpty() { + oa.AuthorizationCodeChanges = CompareOAuthFlow(l.AuthorizationCode.Value, r.AuthorizationCode.Value) + } + if !l.AuthorizationCode.IsEmpty() && r.AuthorizationCode.IsEmpty() { + CreateChange(&changes, ObjectRemoved, v3.AuthorizationCodeLabel, + l.AuthorizationCode.ValueNode, nil, true, + l.AuthorizationCode.Value, nil) + } + if l.AuthorizationCode.IsEmpty() && !r.AuthorizationCode.IsEmpty() { + CreateChange(&changes, ObjectAdded, v3.AuthorizationCodeLabel, + nil, r.AuthorizationCode.ValueNode, false, + nil, r.AuthorizationCode.Value) + } + oa.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions) + oa.Changes = changes + return oa +} + +type OAuthFlowChanges struct { + PropertyChanges + ExtensionChanges *ExtensionChanges +} + +func (o *OAuthFlowChanges) TotalChanges() int { + c := o.PropertyChanges.TotalChanges() + if o.ExtensionChanges != nil { + c += o.ExtensionChanges.TotalChanges() + } + return c +} + +func (o *OAuthFlowChanges) TotalBreakingChanges() int { + return o.PropertyChanges.TotalBreakingChanges() +} + +func CompareOAuthFlow(l, r *v3.OAuthFlow) *OAuthFlowChanges { + if low.AreEqual(l, r) { + return nil + } + + var changes []*Change + var props []*PropertyCheck + + // authorization url + props = append(props, &PropertyCheck{ + LeftNode: l.AuthorizationUrl.ValueNode, + RightNode: r.AuthorizationUrl.ValueNode, + Label: v3.AuthorizationUrlLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + // token url + props = append(props, &PropertyCheck{ + LeftNode: l.TokenUrl.ValueNode, + RightNode: r.TokenUrl.ValueNode, + Label: v3.TokenUrlLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + // refresh url + props = append(props, &PropertyCheck{ + LeftNode: l.RefreshUrl.ValueNode, + RightNode: r.RefreshUrl.ValueNode, + Label: v3.RefreshUrlLabel, + Changes: &changes, + Breaking: true, + Original: l, + New: r, + }) + + CheckProperties(props) + + for v := range l.Scopes.Value { + if r != nil && r.FindScope(v.Value) == nil { + CreateChange(&changes, ObjectRemoved, v3.Scopes, + l.Scopes.Value[v].ValueNode, nil, true, + v.Value, nil) + continue + } + if r != nil && r.FindScope(v.Value) != nil { + if l.Scopes.Value[v].Value != r.FindScope(v.Value).Value { + CreateChange(&changes, Modified, v3.Scopes, + l.Scopes.Value[v].ValueNode, r.FindScope(v.Value).ValueNode, true, + l.Scopes.Value[v].Value, r.FindScope(v.Value).Value) + } + } + } + for v := range r.Scopes.Value { + if l != nil && l.FindScope(v.Value) == nil { + CreateChange(&changes, ObjectAdded, v3.Scopes, + nil, r.Scopes.Value[v].ValueNode, false, + nil, v.Value) + } + } + oa := new(OAuthFlowChanges) + oa.Changes = changes + oa.ExtensionChanges = CompareExtensions(l.Extensions, r.Extensions) + return oa +} diff --git a/what-changed/model/oauth_flows_test.go b/what-changed/model/oauth_flows_test.go new file mode 100644 index 0000000..3df6664 --- /dev/null +++ b/what-changed/model/oauth_flows_test.go @@ -0,0 +1,329 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package model + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + v3 "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "testing" +) + +func TestCompareOAuthFlow(t *testing.T) { + + left := `authorizationUrl: cheese +tokenUrl: biscuits +refreshUrl: cake +scopes: + riff: raff` + + right := `authorizationUrl: cheese +tokenUrl: biscuits +refreshUrl: cake +scopes: + riff: raff` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.OAuthFlow + var rDoc v3.OAuthFlow + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareOAuthFlow(&lDoc, &rDoc) + assert.Nil(t, extChanges) + +} + +func TestCompareOAuthFlow_Modified(t *testing.T) { + + left := `authorizationUrl: toast +tokenUrl: biscuits +refreshUrl: roast +scopes: + riff: raff +x-burgers: nice` + + right := `authorizationUrl: cheese +tokenUrl: biscuits +refreshUrl: cake +scopes: + riff: raff +x-burgers: crispy` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.OAuthFlow + var rDoc v3.OAuthFlow + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareOAuthFlow(&lDoc, &rDoc) + assert.Equal(t, 3, extChanges.TotalChanges()) + assert.Equal(t, 2, extChanges.TotalBreakingChanges()) +} + +func TestCompareOAuthFlow_AddScope(t *testing.T) { + + left := `authorizationUrl: toast +tokenUrl: biscuits +refreshUrl: roast +scopes: + riff: raff +x-burgers: nice` + + right := `authorizationUrl: toast +tokenUrl: biscuits +refreshUrl: roast +scopes: + riff: raff + tiff: taff +x-burgers: nice` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.OAuthFlow + var rDoc v3.OAuthFlow + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareOAuthFlow(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, "taff", extChanges.Changes[0].New) + assert.Equal(t, "tiff", extChanges.Changes[0].NewObject) +} + +func TestCompareOAuthFlow_RemoveScope(t *testing.T) { + + left := `authorizationUrl: toast +tokenUrl: biscuits +refreshUrl: roast +scopes: + riff: raff +x-burgers: nice` + + right := `authorizationUrl: toast +tokenUrl: biscuits +refreshUrl: roast +scopes: + riff: raff + tiff: taff +x-burgers: nice` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.OAuthFlow + var rDoc v3.OAuthFlow + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareOAuthFlow(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, "taff", extChanges.Changes[0].Original) + assert.Equal(t, "tiff", extChanges.Changes[0].OriginalObject) +} + +func TestCompareOAuthFlow_ModifyScope(t *testing.T) { + + left := `authorizationUrl: toast +tokenUrl: biscuits +refreshUrl: roast +scopes: + riff: ruffles +x-burgers: nice` + + right := `authorizationUrl: toast +tokenUrl: biscuits +refreshUrl: roast +scopes: + riff: raff +x-burgers: nice` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.OAuthFlow + var rDoc v3.OAuthFlow + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareOAuthFlow(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, "raff", extChanges.Changes[0].New) + assert.Equal(t, "raff", extChanges.Changes[0].NewObject) + assert.Equal(t, "ruffles", extChanges.Changes[0].Original) + assert.Equal(t, "ruffles", extChanges.Changes[0].OriginalObject) +} + +func TestCompareOAuthFlows(t *testing.T) { + left := `implicit: + authorizationUrl: cheese +password: + authorizationUrl: cake +clientCredentials: + authorizationUrl: chicken +authorizationCode: + authorizationUrl: chalk +x-coke: cola` + + right := `implicit: + authorizationUrl: cheese +password: + authorizationUrl: cake +clientCredentials: + authorizationUrl: chicken +authorizationCode: + authorizationUrl: chalk +x-coke: cola` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.OAuthFlows + var rDoc v3.OAuthFlows + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareOAuthFlows(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareOAuthFlows_AddEverything(t *testing.T) { + left := `x-coke: cola` + + right := `implicit: + authorizationUrl: cheese +password: + authorizationUrl: cake +clientCredentials: + authorizationUrl: chicken +authorizationCode: + authorizationUrl: chalk +x-coke: cola` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.OAuthFlows + var rDoc v3.OAuthFlows + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareOAuthFlows(&lDoc, &rDoc) + assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) +} + +func TestCompareOAuthFlows_RemoveEverything(t *testing.T) { + left := `x-coke: cola` + + right := `implicit: + authorizationUrl: cheese +password: + authorizationUrl: cake +clientCredentials: + authorizationUrl: chicken +authorizationCode: + authorizationUrl: chalk +x-coke: cola` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.OAuthFlows + var rDoc v3.OAuthFlows + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareOAuthFlows(&rDoc, &lDoc) + assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Equal(t, 4, extChanges.TotalBreakingChanges()) +} + +func TestCompareOAuthFlows_ModifyEverything(t *testing.T) { + left := `implicit: + authorizationUrl: cheese +password: + authorizationUrl: cake +clientCredentials: + authorizationUrl: chicken +authorizationCode: + authorizationUrl: chalk +x-coke: cola` + + right := `implicit: + authorizationUrl: herbs +password: + authorizationUrl: coffee +clientCredentials: + authorizationUrl: tea +authorizationCode: + authorizationUrl: pasta +x-coke: cherry` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.OAuthFlows + var rDoc v3.OAuthFlows + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareOAuthFlows(&lDoc, &rDoc) + assert.Equal(t, 5, extChanges.TotalChanges()) + assert.Equal(t, 4, extChanges.TotalBreakingChanges()) +} diff --git a/what-changed/model/security_scheme.go b/what-changed/model/security_scheme.go new file mode 100644 index 0000000..347422e --- /dev/null +++ b/what-changed/model/security_scheme.go @@ -0,0 +1,148 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package model + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/datamodel/low/v3" + "reflect" +) + +type SecuritySchemeChanges struct { + PropertyChanges + ExtensionChanges *ExtensionChanges + // v3 + OAuthFlowChanges *OAuthFlowsChanges + // v2 + ScopesChanges *ScopesChanges +} + +func (ss *SecuritySchemeChanges) TotalChanges() int { + c := ss.PropertyChanges.TotalChanges() + if ss.OAuthFlowChanges != nil { + c += ss.OAuthFlowChanges.TotalChanges() + } + if ss.ScopesChanges != nil { + c += ss.ScopesChanges.TotalChanges() + } + if ss.ExtensionChanges != nil { + c += ss.ExtensionChanges.TotalChanges() + } + return c +} + +func (ss *SecuritySchemeChanges) TotalBreakingChanges() int { + c := ss.PropertyChanges.TotalBreakingChanges() + if ss.OAuthFlowChanges != nil { + c += ss.OAuthFlowChanges.TotalBreakingChanges() + } + if ss.ScopesChanges != nil { + c += ss.ScopesChanges.TotalBreakingChanges() + } + return c +} + +func CompareSecuritySchemes(l, r any) *SecuritySchemeChanges { + + var props []*PropertyCheck + var changes []*Change + + sc := new(SecuritySchemeChanges) + if reflect.TypeOf(&v2.SecurityScheme{}) == reflect.TypeOf(l) && + reflect.TypeOf(&v2.SecurityScheme{}) == reflect.TypeOf(r) { + + lSS := l.(*v2.SecurityScheme) + rSS := r.(*v2.SecurityScheme) + + if low.AreEqual(lSS, rSS) { + return nil + } + addPropertyCheck(&props, lSS.Type.ValueNode, rSS.Type.ValueNode, + lSS.Type.Value, rSS.Type.Value, &changes, v3.TypeLabel, true) + + addPropertyCheck(&props, lSS.Description.ValueNode, rSS.Description.ValueNode, + lSS.Description.Value, rSS.Description.Value, &changes, v3.DescriptionLabel, false) + + addPropertyCheck(&props, lSS.Name.ValueNode, rSS.Name.ValueNode, + lSS.Name.Value, rSS.Name.Value, &changes, v3.NameLabel, true) + + addPropertyCheck(&props, lSS.In.ValueNode, rSS.In.ValueNode, + lSS.In.Value, rSS.In.Value, &changes, v3.InLabel, true) + + addPropertyCheck(&props, lSS.Flow.ValueNode, rSS.Flow.ValueNode, + lSS.Flow.Value, rSS.Flow.Value, &changes, v3.FlowLabel, true) + + addPropertyCheck(&props, lSS.AuthorizationUrl.ValueNode, rSS.AuthorizationUrl.ValueNode, + lSS.AuthorizationUrl.Value, rSS.AuthorizationUrl.Value, &changes, v3.AuthorizationUrlLabel, true) + + addPropertyCheck(&props, lSS.TokenUrl.ValueNode, rSS.TokenUrl.ValueNode, + lSS.TokenUrl.Value, rSS.TokenUrl.Value, &changes, v3.TokenUrlLabel, true) + + if !lSS.Scopes.IsEmpty() && !rSS.Scopes.IsEmpty() { + if !low.AreEqual(lSS.Scopes.Value, rSS.Scopes.Value) { + sc.ScopesChanges = CompareScopes(lSS.Scopes.Value, rSS.Scopes.Value) + } + } + if lSS.Scopes.IsEmpty() && !rSS.Scopes.IsEmpty() { + CreateChange(&changes, ObjectAdded, v3.ScopesLabel, + nil, rSS.Scopes.ValueNode, false, nil, rSS.Scopes.Value) + } + if !lSS.Scopes.IsEmpty() && rSS.Scopes.IsEmpty() { + CreateChange(&changes, ObjectRemoved, v3.ScopesLabel, + lSS.Scopes.ValueNode, nil, true, lSS.Scopes.Value, nil) + } + + sc.ExtensionChanges = CompareExtensions(lSS.Extensions, rSS.Extensions) + } + + if reflect.TypeOf(&v3.SecurityScheme{}) == reflect.TypeOf(l) && + reflect.TypeOf(&v3.SecurityScheme{}) == reflect.TypeOf(r) { + + lSS := l.(*v3.SecurityScheme) + rSS := r.(*v3.SecurityScheme) + + if low.AreEqual(lSS, rSS) { + return nil + } + addPropertyCheck(&props, lSS.Type.ValueNode, rSS.Type.ValueNode, + lSS.Type.Value, rSS.Type.Value, &changes, v3.TypeLabel, true) + + addPropertyCheck(&props, lSS.Description.ValueNode, rSS.Description.ValueNode, + lSS.Description.Value, rSS.Description.Value, &changes, v3.DescriptionLabel, false) + + addPropertyCheck(&props, lSS.Name.ValueNode, rSS.Name.ValueNode, + lSS.Name.Value, rSS.Name.Value, &changes, v3.NameLabel, true) + + addPropertyCheck(&props, lSS.In.ValueNode, rSS.In.ValueNode, + lSS.In.Value, rSS.In.Value, &changes, v3.InLabel, true) + + addPropertyCheck(&props, lSS.Scheme.ValueNode, rSS.Scheme.ValueNode, + lSS.Scheme.Value, rSS.Scheme.Value, &changes, v3.SchemeLabel, true) + + addPropertyCheck(&props, lSS.BearerFormat.ValueNode, rSS.BearerFormat.ValueNode, + lSS.BearerFormat.Value, rSS.BearerFormat.Value, &changes, v3.SchemeLabel, false) + + addPropertyCheck(&props, lSS.OpenIdConnectUrl.ValueNode, rSS.OpenIdConnectUrl.ValueNode, + lSS.OpenIdConnectUrl.Value, rSS.OpenIdConnectUrl.Value, &changes, v3.OpenIdConnectUrlLabel, false) + + if !lSS.Flows.IsEmpty() && !rSS.Flows.IsEmpty() { + if !low.AreEqual(lSS.Flows.Value, rSS.Flows.Value) { + sc.OAuthFlowChanges = CompareOAuthFlows(lSS.Flows.Value, rSS.Flows.Value) + } + } + if lSS.Flows.IsEmpty() && !rSS.Flows.IsEmpty() { + CreateChange(&changes, ObjectAdded, v3.FlowsLabel, + nil, rSS.Flows.ValueNode, false, nil, rSS.Flows.Value) + } + if !lSS.Flows.IsEmpty() && rSS.Flows.IsEmpty() { + CreateChange(&changes, ObjectRemoved, v3.ScopesLabel, + lSS.Flows.ValueNode, nil, true, lSS.Flows.Value, nil) + } + sc.ExtensionChanges = CompareExtensions(lSS.Extensions, rSS.Extensions) + } + CheckProperties(props) + sc.Changes = changes + return sc +} diff --git a/what-changed/model/security_scheme_test.go b/what-changed/model/security_scheme_test.go new file mode 100644 index 0000000..708c202 --- /dev/null +++ b/what-changed/model/security_scheme_test.go @@ -0,0 +1,322 @@ +// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley +// SPDX-License-Identifier: MIT + +package model + +import ( + "github.com/pb33f/libopenapi/datamodel/low" + "github.com/pb33f/libopenapi/datamodel/low/v2" + "github.com/pb33f/libopenapi/datamodel/low/v3" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "testing" +) + +func TestCompareSecuritySchemes_v2(t *testing.T) { + + left := `type: string +description: a thing +flow: heavy +authorizationUrl: https://somewheremagicandnotreal.com +tokenUrl: https://amadeupplacefilledwithendlesstimeandbeer.com +x-beer: tasty` + + right := `type: string +description: a thing +flow: heavy +authorizationUrl: https://somewheremagicandnotreal.com +tokenUrl: https://amadeupplacefilledwithendlesstimeandbeer.com +x-beer: tasty` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityScheme + var rDoc v2.SecurityScheme + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecuritySchemes(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareSecuritySchemes_v2_ModifyProps(t *testing.T) { + + left := `type: int +description: who cares if this changes? +flow: very heavy +x-beer: tasty` + + right := `type: string +description: a thing +flow: heavy +x-beer: very tasty` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityScheme + var rDoc v2.SecurityScheme + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecuritySchemes(&lDoc, &rDoc) + assert.Equal(t, 4, extChanges.TotalChanges()) + assert.Equal(t, 2, extChanges.TotalBreakingChanges()) + assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) + assert.Equal(t, Modified, extChanges.Changes[1].ChangeType) + assert.Equal(t, Modified, extChanges.Changes[2].ChangeType) + assert.Equal(t, Modified, extChanges.ExtensionChanges.Changes[0].ChangeType) +} + +func TestCompareSecuritySchemes_v2_AddScope(t *testing.T) { + + left := `description: I am a thing` + + right := `description: I am a thing +scopes: + pizza:pie + lemon:sky` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityScheme + var rDoc v2.SecurityScheme + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecuritySchemes(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) + assert.Equal(t, v3.ScopesLabel, extChanges.Changes[0].Property) +} + +func TestCompareSecuritySchemes_v2_RemoveScope(t *testing.T) { + + left := `description: I am a thing` + + right := `description: I am a thing +scopes: + pizza:pie + lemon:sky` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityScheme + var rDoc v2.SecurityScheme + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecuritySchemes(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) + assert.Equal(t, v3.ScopesLabel, extChanges.Changes[0].Property) +} + +func TestCompareSecuritySchemes_v2_ModifyScope(t *testing.T) { + + left := `scopes: + pizza: pie` + + right := `scopes: + pizza: pie + lemon: sky` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v2.SecurityScheme + var rDoc v2.SecurityScheme + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecuritySchemes(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectAdded, extChanges.ScopesChanges.Changes[0].ChangeType) + assert.Equal(t, v3.ScopesLabel, extChanges.ScopesChanges.Changes[0].Property) +} + +func TestCompareSecuritySchemes_v3(t *testing.T) { + + left := `type: string +description: a thing +scheme: fishy +bearerFormat: golden +x-beer: tasty` + + right := `x-beer: tasty +type: string +bearerFormat: golden +scheme: fishy +description: a thing` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.SecurityScheme + var rDoc v3.SecurityScheme + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecuritySchemes(&lDoc, &rDoc) + assert.Nil(t, extChanges) +} + +func TestCompareSecuritySchemes_v3_ModifyProps(t *testing.T) { + + left := `type: string +description: a thing +scheme: fishy +bearerFormat: golden +x-beer: tasty` + + right := `type: int +description: a thing that can change without breaking +scheme: smokey +bearerFormat: amber +x-beer: cool` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.SecurityScheme + var rDoc v3.SecurityScheme + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecuritySchemes(&lDoc, &rDoc) + assert.Equal(t, 5, extChanges.TotalChanges()) + assert.Equal(t, 2, extChanges.TotalBreakingChanges()) + assert.Equal(t, Modified, extChanges.Changes[0].ChangeType) + assert.Equal(t, Modified, extChanges.Changes[1].ChangeType) + assert.Equal(t, Modified, extChanges.Changes[2].ChangeType) + assert.Equal(t, Modified, extChanges.Changes[3].ChangeType) + assert.Equal(t, Modified, extChanges.ExtensionChanges.Changes[0].ChangeType) +} + +func TestCompareSecuritySchemes_v3_AddFlows(t *testing.T) { + + left := `type: oauth` + + right := `type: oauth +flows: + implicit: + tokenUrl: https://magichappyclappyland.com` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.SecurityScheme + var rDoc v3.SecurityScheme + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecuritySchemes(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 0, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectAdded, extChanges.Changes[0].ChangeType) +} + +func TestCompareSecuritySchemes_v3_RemoveFlows(t *testing.T) { + + left := `type: oauth` + + right := `type: oauth +flows: + implicit: + tokenUrl: https://magichappyclappyland.com` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.SecurityScheme + var rDoc v3.SecurityScheme + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecuritySchemes(&rDoc, &lDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, ObjectRemoved, extChanges.Changes[0].ChangeType) +} + +func TestCompareSecuritySchemes_v3_ModifyFlows(t *testing.T) { + + left := `type: oauth +flows: + implicit: + tokenUrl: https://magichappyclappyland.com` + + right := `type: oauth +flows: + implicit: + tokenUrl: https://chickennuggetsandchickensoup.com` + + var lNode, rNode yaml.Node + _ = yaml.Unmarshal([]byte(left), &lNode) + _ = yaml.Unmarshal([]byte(right), &rNode) + + // create low level objects + var lDoc v3.SecurityScheme + var rDoc v3.SecurityScheme + _ = low.BuildModel(&lNode, &lDoc) + _ = low.BuildModel(&rNode, &rDoc) + _ = lDoc.Build(lNode.Content[0], nil) + _ = rDoc.Build(rNode.Content[0], nil) + + // compare + extChanges := CompareSecuritySchemes(&lDoc, &rDoc) + assert.Equal(t, 1, extChanges.TotalChanges()) + assert.Equal(t, 1, extChanges.TotalBreakingChanges()) + assert.Equal(t, Modified, extChanges.OAuthFlowChanges.ImplicitChanges.Changes[0].ChangeType) +}