package resolver import ( "errors" "fmt" "io/ioutil" "net/url" "testing" "github.com/pb33f/libopenapi/index" "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" ) func TestNewResolver(t *testing.T) { assert.Nil(t, NewResolver(nil)) } func Benchmark_ResolveDocumentStripe(b *testing.B) { stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") for n := 0; n < b.N; n++ { var rootNode yaml.Node yaml.Unmarshal(stripe, &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) resolver.Resolve() } } func TestResolver_ResolveComponents_CircularSpec(t *testing.T) { circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml") var rootNode yaml.Node yaml.Unmarshal(circular, &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) assert.NotNil(t, resolver) circ := resolver.Resolve() assert.Len(t, circ, 3) _, err := yaml.Marshal(resolver.resolvedRoot) assert.NoError(t, err) } func TestResolver_CheckForCircularReferences(t *testing.T) { circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml") var rootNode yaml.Node yaml.Unmarshal(circular, &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) assert.NotNil(t, resolver) circ := resolver.CheckForCircularReferences() assert.Len(t, circ, 3) assert.Len(t, resolver.GetResolvingErrors(), 3) assert.Len(t, resolver.GetCircularErrors(), 3) _, err := yaml.Marshal(resolver.resolvedRoot) assert.NoError(t, err) } func TestResolver_CheckForCircularReferences_DigitalOcean(t *testing.T) { circular, _ := ioutil.ReadFile("../test_specs/digitalocean.yaml") var rootNode yaml.Node yaml.Unmarshal(circular, &rootNode) baseURL, _ := url.Parse("https://raw.githubusercontent.com/digitalocean/openapi/main/specification") index := index.NewSpecIndexWithConfig(&rootNode, &index.SpecIndexConfig{ AllowRemoteLookup: true, AllowFileLookup: true, BaseURL: baseURL, }) resolver := NewResolver(index) assert.NotNil(t, resolver) circ := resolver.CheckForCircularReferences() assert.Len(t, circ, 0) assert.Len(t, resolver.GetResolvingErrors(), 0) assert.Len(t, resolver.GetCircularErrors(), 0) _, err := yaml.Marshal(resolver.resolvedRoot) assert.NoError(t, err) } func TestResolver_CircularReferencesRequiredValid(t *testing.T) { circular, _ := ioutil.ReadFile("../test_specs/swagger-valid-recursive-model.yaml") var rootNode yaml.Node yaml.Unmarshal(circular, &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) assert.NotNil(t, resolver) circ := resolver.CheckForCircularReferences() assert.Len(t, circ, 0) _, err := yaml.Marshal(resolver.resolvedRoot) assert.NoError(t, err) } func TestResolver_CircularReferencesRequiredInvalid(t *testing.T) { circular, _ := ioutil.ReadFile("../test_specs/swagger-invalid-recursive-model.yaml") var rootNode yaml.Node yaml.Unmarshal(circular, &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) assert.NotNil(t, resolver) circ := resolver.CheckForCircularReferences() assert.Len(t, circ, 2) _, err := yaml.Marshal(resolver.resolvedRoot) assert.NoError(t, err) } func TestResolver_DeepJourney(t *testing.T) { var journey []*index.Reference for f := 0; f < 200; f++ { journey = append(journey, nil) } index := index.NewSpecIndex(nil) resolver := NewResolver(index) assert.Nil(t, resolver.extractRelatives(nil, nil, journey, false)) } func TestResolver_ResolveComponents_Stripe(t *testing.T) { stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") var rootNode yaml.Node yaml.Unmarshal(stripe, &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) assert.NotNil(t, resolver) circ := resolver.Resolve() assert.Len(t, circ, 3) assert.Len(t, resolver.GetNonPolymorphicCircularErrors(), 3) assert.Len(t, resolver.GetPolymorphicCircularErrors(), 0) } func TestResolver_ResolveComponents_BurgerShop(t *testing.T) { mixedref, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml") var rootNode yaml.Node yaml.Unmarshal(mixedref, &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) assert.NotNil(t, resolver) circ := resolver.Resolve() assert.Len(t, circ, 0) } func TestResolver_ResolveComponents_PolyNonCircRef(t *testing.T) { yml := `paths: /hey: get: responses: "200": $ref: '#/components/schemas/crackers' components: schemas: cheese: description: cheese anyOf: items: $ref: '#/components/schemas/crackers' crackers: description: crackers allOf: - $ref: '#/components/schemas/tea' tea: description: tea` var rootNode yaml.Node yaml.Unmarshal([]byte(yml), &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) assert.NotNil(t, resolver) circ := resolver.CheckForCircularReferences() assert.Len(t, circ, 0) } func TestResolver_ResolveComponents_PolyCircRef(t *testing.T) { yml := `openapi: 3.1.0 components: schemas: cheese: description: cheese anyOf: - $ref: '#/components/schemas/crackers' crackers: description: crackers anyOf: - $ref: '#/components/schemas/cheese' tea: description: tea` var rootNode yaml.Node yaml.Unmarshal([]byte(yml), &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) assert.NotNil(t, resolver) _ = resolver.CheckForCircularReferences() resolver.circularReferences[0].IsInfiniteLoop = true // override assert.Len(t, index.GetCircularReferences(), 1) assert.Len(t, resolver.GetPolymorphicCircularErrors(), 1) assert.Equal(t, 2, index.GetCircularReferences()[0].LoopIndex) } func TestResolver_ResolveComponents_Missing(t *testing.T) { yml := `paths: /hey: get: responses: "200": $ref: '#/components/schemas/crackers' components: schemas: cheese: description: cheese properties: thang: $ref: '#/components/schemas/crackers' crackers: description: crackers properties: butter: $ref: 'go home, I am drunk'` var rootNode yaml.Node yaml.Unmarshal([]byte(yml), &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) assert.NotNil(t, resolver) err := resolver.Resolve() assert.Len(t, err, 1) assert.Equal(t, "cannot resolve reference `go home, I am drunk`, it's missing: $go home, I am drunk [18:11]", err[0].Error()) } func TestResolver_ResolveComponents_MixedRef(t *testing.T) { mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml") var rootNode yaml.Node yaml.Unmarshal(mixedref, &rootNode) b := index.CreateOpenAPIIndexConfig() idx := index.NewSpecIndexWithConfig(&rootNode, b) resolver := NewResolver(idx) assert.NotNil(t, resolver) circ := resolver.Resolve() assert.Len(t, circ, 0) assert.Equal(t, 5, resolver.GetIndexesVisited()) // in v0.8.2 a new check was added when indexing, to prevent re-indexing the same file multiple times. assert.Equal(t, 191, resolver.GetRelativesSeen()) assert.Equal(t, 35, resolver.GetJourneysTaken()) assert.Equal(t, 62, resolver.GetReferenceVisited()) } func TestResolver_ResolveComponents_k8s(t *testing.T) { k8s, _ := ioutil.ReadFile("../test_specs/k8s.json") var rootNode yaml.Node yaml.Unmarshal(k8s, &rootNode) index := index.NewSpecIndex(&rootNode) resolver := NewResolver(index) assert.NotNil(t, resolver) circ := resolver.Resolve() assert.Len(t, circ, 0) } // Example of how to resolve the Stripe OpenAPI specification, and check for circular reference errors func ExampleNewResolver() { // create a yaml.Node reference as a root node. var rootNode yaml.Node // load in the Stripe OpenAPI spec (lots of polymorphic complexity in here) stripeBytes, _ := ioutil.ReadFile("../test_specs/stripe.yaml") // unmarshal bytes into our rootNode. _ = yaml.Unmarshal(stripeBytes, &rootNode) // create a new spec index (resolver depends on it) indexConfig := index.CreateClosedAPIIndexConfig() index := index.NewSpecIndexWithConfig(&rootNode, indexConfig) // create a new resolver using the index. resolver := NewResolver(index) // resolve the document, if there are circular reference errors, they are returned/ // WARNING: this is a destructive action and the rootNode will be PERMANENTLY altered and cannot be unresolved circularErrors := resolver.Resolve() // The Stripe API has a bunch of circular reference problems, mainly from polymorphism. // So let's print them out. // fmt.Printf("There are %d circular reference errors, %d of them are polymorphic errors, %d are not", len(circularErrors), len(resolver.GetPolymorphicCircularErrors()), len(resolver.GetNonPolymorphicCircularErrors())) // Output: There are 3 circular reference errors, 0 of them are polymorphic errors, 3 are not } func ExampleResolvingError() { re := ResolvingError{ ErrorRef: errors.New("Je suis une erreur"), Node: &yaml.Node{ Line: 5, Column: 21, }, Path: "#/definitions/JeSuisUneErreur", CircularReference: &index.CircularReferenceResult{}, } fmt.Printf("%s", re.Error()) // Output: Je suis une erreur: #/definitions/JeSuisUneErreur [5:21] }