feat: Resolver tests for valid and invalid recursive models

This commit is contained in:
Benjamin Nolan (TwoWholeWorms)
2023-01-05 14:07:39 +01:00
committed by Dave Shanley
parent 5f92de63a4
commit e8a954d5ae
5 changed files with 270 additions and 25 deletions

View File

@@ -2,11 +2,12 @@ package resolver
import ( import (
"fmt" "fmt"
"io/ioutil"
"testing"
"github.com/pb33f/libopenapi/index" "github.com/pb33f/libopenapi/index"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
"io/ioutil"
"testing"
) )
func TestNewResolver(t *testing.T) { func TestNewResolver(t *testing.T) {
@@ -25,7 +26,6 @@ func Benchmark_ResolveDocumentStripe(b *testing.B) {
} }
func TestResolver_ResolveComponents_CircularSpec(t *testing.T) { func TestResolver_ResolveComponents_CircularSpec(t *testing.T) {
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml") circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(circular, &rootNode) yaml.Unmarshal(circular, &rootNode)
@@ -43,7 +43,6 @@ func TestResolver_ResolveComponents_CircularSpec(t *testing.T) {
} }
func TestResolver_CheckForCircularReferences(t *testing.T) { func TestResolver_CheckForCircularReferences(t *testing.T) {
circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml") circular, _ := ioutil.ReadFile("../test_specs/circular-tests.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(circular, &rootNode) yaml.Unmarshal(circular, &rootNode)
@@ -62,8 +61,41 @@ func TestResolver_CheckForCircularReferences(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestResolver_DeepJourney(t *testing.T) { 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 var journey []*index.Reference
for f := 0; f < 200; f++ { for f := 0; f < 200; f++ {
journey = append(journey, nil) journey = append(journey, nil)
@@ -71,11 +103,9 @@ func TestResolver_DeepJourney(t *testing.T) {
index := index.NewSpecIndex(nil) index := index.NewSpecIndex(nil)
resolver := NewResolver(index) resolver := NewResolver(index)
assert.Nil(t, resolver.extractRelatives(nil, nil, journey, false)) assert.Nil(t, resolver.extractRelatives(nil, nil, journey, false))
} }
func TestResolver_ResolveComponents_Stripe(t *testing.T) { func TestResolver_ResolveComponents_Stripe(t *testing.T) {
stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml") stripe, _ := ioutil.ReadFile("../test_specs/stripe.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(stripe, &rootNode) yaml.Unmarshal(stripe, &rootNode)
@@ -86,15 +116,13 @@ func TestResolver_ResolveComponents_Stripe(t *testing.T) {
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 23) assert.Len(t, circ, 3)
assert.Len(t, resolver.GetNonPolymorphicCircularErrors(), 3) assert.Len(t, resolver.GetNonPolymorphicCircularErrors(), 3)
assert.Len(t, resolver.GetPolymorphicCircularErrors(), 20) assert.Len(t, resolver.GetPolymorphicCircularErrors(), 20)
} }
func TestResolver_ResolveComponents_BurgerShop(t *testing.T) { func TestResolver_ResolveComponents_BurgerShop(t *testing.T) {
mixedref, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml") mixedref, _ := ioutil.ReadFile("../test_specs/burgershop.openapi.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(mixedref, &rootNode) yaml.Unmarshal(mixedref, &rootNode)
@@ -106,11 +134,9 @@ func TestResolver_ResolveComponents_BurgerShop(t *testing.T) {
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 0) assert.Len(t, circ, 0)
} }
func TestResolver_ResolveComponents_PolyNonCircRef(t *testing.T) { func TestResolver_ResolveComponents_PolyNonCircRef(t *testing.T) {
yml := `paths: yml := `paths:
/hey: /hey:
get: get:
@@ -141,11 +167,9 @@ components:
circ := resolver.CheckForCircularReferences() circ := resolver.CheckForCircularReferences()
assert.Len(t, circ, 0) assert.Len(t, circ, 0)
} }
func TestResolver_ResolveComponents_MixedRef(t *testing.T) { func TestResolver_ResolveComponents_MixedRef(t *testing.T) {
mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml") mixedref, _ := ioutil.ReadFile("../test_specs/mixedref-burgershop.openapi.yaml")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(mixedref, &rootNode) yaml.Unmarshal(mixedref, &rootNode)
@@ -157,11 +181,9 @@ func TestResolver_ResolveComponents_MixedRef(t *testing.T) {
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 10) assert.Len(t, circ, 10)
} }
func TestResolver_ResolveComponents_k8s(t *testing.T) { func TestResolver_ResolveComponents_k8s(t *testing.T) {
k8s, _ := ioutil.ReadFile("../test_specs/k8s.json") k8s, _ := ioutil.ReadFile("../test_specs/k8s.json")
var rootNode yaml.Node var rootNode yaml.Node
yaml.Unmarshal(k8s, &rootNode) yaml.Unmarshal(k8s, &rootNode)
@@ -172,12 +194,11 @@ func TestResolver_ResolveComponents_k8s(t *testing.T) {
assert.NotNil(t, resolver) assert.NotNil(t, resolver)
circ := resolver.Resolve() circ := resolver.Resolve()
assert.Len(t, circ, 1) assert.Len(t, circ, 0)
} }
// Example of how to resolve the Stripe OpenAPI specification, and check for circular reference errors // Example of how to resolve the Stripe OpenAPI specification, and check for circular reference errors
func ExampleNewResolver() { func ExampleNewResolver() {
// create a yaml.Node reference as a root node. // create a yaml.Node reference as a root node.
var rootNode yaml.Node var rootNode yaml.Node
@@ -203,5 +224,4 @@ func ExampleNewResolver() {
fmt.Printf("There are %d circular reference errors, %d of them are polymorphic errors, %d are not", 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())) len(circularErrors), len(resolver.GetPolymorphicCircularErrors()), len(resolver.GetNonPolymorphicCircularErrors()))
// Output: There are 23 circular reference errors, 20 of them are polymorphic errors, 3 are not // Output: There are 23 circular reference errors, 20 of them are polymorphic errors, 3 are not
} }

View File

@@ -14,6 +14,8 @@ components:
properties: properties:
things: things:
"$ref": "#/components/schemas/Two" "$ref": "#/components/schemas/Two"
required:
- things
Two: Two:
description: "test two" description: "test two"
properties: properties:
@@ -21,6 +23,9 @@ components:
"$ref": "#/components/schemas/One" "$ref": "#/components/schemas/One"
anyOf: anyOf:
- "$ref": "#/components/schemas/Four" - "$ref": "#/components/schemas/Four"
required:
- testThing
- anyOf
Three: Three:
description: "test three" description: "test three"
properties: properties:
@@ -30,30 +35,40 @@ components:
"$ref": "#/components/schemas/Seven" "$ref": "#/components/schemas/Seven"
yester: yester:
"$ref": "#/components/schemas/Seven" "$ref": "#/components/schemas/Seven"
required:
- tester
- bester
- yester
Four: Four:
description: "test four" description: "test four"
properties: properties:
lemons: lemons:
"$ref": "#/components/schemas/Nine" "$ref": "#/components/schemas/Nine"
required:
- lemons
Five: Five:
properties: properties:
rice: rice:
"$ref": "#/components/schemas/Six" "$ref": "#/components/schemas/Six"
required:
- rice
Six: Six:
properties: properties:
mints: mints:
"$ref": "#/components/schemas/Nine" "$ref": "#/components/schemas/Nine"
required:
- mints
Seven: Seven:
properties: properties:
wow: wow:
"$ref": "#/components/schemas/Three" "$ref": "#/components/schemas/Three"
required:
- wow
Nine: Nine:
description: done. description: done.
Ten: Ten:
properties: properties:
yeah: yeah:
"$ref": "#/components/schemas/Ten" "$ref": "#/components/schemas/Ten"
required:
- yeah

View File

@@ -12,11 +12,15 @@ definitions:
properties: properties:
things: things:
"$ref": "#/definitions/Two" "$ref": "#/definitions/Two"
required:
- things
Two: Two:
description: "test two" description: "test two"
properties: properties:
testThing: testThing:
"$ref": "#/definitions/One" "$ref": "#/definitions/One"
required:
- testThing
Three: Three:
description: "test three" description: "test three"
properties: properties:
@@ -26,26 +30,40 @@ definitions:
"$ref": "#/definitions/Seven" "$ref": "#/definitions/Seven"
yester: yester:
"$ref": "#/definitions/Seven" "$ref": "#/definitions/Seven"
required:
- tester
- bester
- yester
Four: Four:
description: "test four" description: "test four"
properties: properties:
lemons: lemons:
"$ref": "#/definitions/Nine" "$ref": "#/definitions/Nine"
required:
- lemons
Five: Five:
properties: properties:
rice: rice:
"$ref": "#/definitions/Six" "$ref": "#/definitions/Six"
required:
- rice
Six: Six:
properties: properties:
mints: mints:
"$ref": "#/definitions/Nine" "$ref": "#/definitions/Nine"
required:
- mints
Seven: Seven:
properties: properties:
wow: wow:
"$ref": "#/definitions/Three" "$ref": "#/definitions/Three"
required:
- wow
Nine: Nine:
description: done. description: done.
Ten: Ten:
properties: properties:
yeah: yeah:
"$ref": "#/definitions/Ten" "$ref": "#/definitions/Ten"
required:
- yeah

View File

@@ -0,0 +1,99 @@
schemes:
- https
securityDefinitions:
api_token:
type: apiKey
in: header
name: Swagger-Token
definitions:
BaseModel:
description: BaseModel is the top-level definition in this example
type: object
properties:
directChildren:
description: A nested array of direct recursive models.
type: array
items:
$ref: '#/definitions/DirectRecursiveModel'
indirectChildren:
description: A nested array of indirect recursive models.
type: array
items:
$ref: '#/definitions/IndirectRecursiveModelOne'
additionalProperties: false
required:
- directChildren
- indirectChildren
DirectRecursiveModel:
description: DirectRecursiveModel is a nested model which can optionally contain itself.
type: object
properties:
children:
description: A nested array of direct recursive models.
type: array
items:
$ref: '#/definitions/DirectRecursiveModel'
additionalProperties: false
required:
- children
IndirectRecursiveModelOne:
description: IndirectRecursiveModelOne is a nested model which can optionally contain IndirectRecursiveModelTwo.
type: object
properties:
children:
description: A nested array of indirect recursive models.
type: array
items:
$ref: '#/definitions/IndirectRecursiveModelTwo'
additionalProperties: false
required:
- children
IndirectRecursiveModelTwo:
description: IndirectRecursiveModelTwo is a nested model which can optionally contain IndirectRecursiveModelOne.
type: object
properties:
children:
description: A nested array of indirect recursive models.
type: array
items:
$ref: '#/definitions/IndirectRecursiveModelOne'
additionalProperties: false
required:
- children
security:
- api_token: []
produces:
- application/json
paths:
/baseModels:
post:
parameters:
- in: body
name: BaseModel
description: ''
required: true
schema:
$ref: '#/definitions/BaseModel'
responses:
'201':
schema:
$ref: '#/definitions/BaseModel'
description: Resource
'400':
description: Schema mismatch
'404':
description: Resource does not exist
'422':
description: Unprocessable
operationId: createBaseModel
description: Create BaseModel allows you to create a new BaseModel
summary: Create BaseModel
consumes:
- application/json
host: api.app.example.com
info:
title: Invalid Recursive Definition Example
version: '1.0'
description: This example contains recursive model definitions which are invalid because their "children", "directChildren", and "indirectChildren" properties are all marked as required.
swagger: '2.0'
basePath: /

View File

@@ -0,0 +1,93 @@
schemes:
- https
securityDefinitions:
api_token:
type: apiKey
in: header
name: Swagger-Token
definitions:
BaseModel:
description: BaseModel is the top-level definition in this example
type: object
properties:
directChildren:
description: A nested array of direct recursive models.
type: array
items:
$ref: '#/definitions/DirectRecursiveModel'
indirectChildren:
description: A nested array of indirect recursive models.
type: array
items:
$ref: '#/definitions/IndirectRecursiveModelOne'
additionalProperties: false
required:
- directChildren
- indirectChildren
DirectRecursiveModel:
description: DirectRecursiveModel is a nested model which can optionally contain itself.
type: object
properties:
children:
description: A nested array of direct recursive models.
type: array
items:
$ref: '#/definitions/DirectRecursiveModel'
additionalProperties: false
IndirectRecursiveModelOne:
description: IndirectRecursiveModelOne is a nested model which can optionally contain IndirectRecursiveModelTwo.
type: object
properties:
children:
description: A nested array of indirect recursive models.
type: array
items:
$ref: '#/definitions/IndirectRecursiveModelTwo'
additionalProperties: false
IndirectRecursiveModelTwo:
description: IndirectRecursiveModelTwo is a nested model which can optionally contain IndirectRecursiveModelOne.
type: object
properties:
children:
description: A nested array of indirect recursive models.
type: array
items:
$ref: '#/definitions/IndirectRecursiveModelOne'
additionalProperties: false
security:
- api_token: []
produces:
- application/json
paths:
/baseModels:
post:
parameters:
- in: body
name: BaseModel
description: ''
required: true
schema:
$ref: '#/definitions/BaseModel'
responses:
'201':
schema:
$ref: '#/definitions/BaseModel'
description: Resource
'400':
description: Schema mismatch
'404':
description: Resource does not exist
'422':
description: Unprocessable
operationId: createBaseModel
description: Create BaseModel allows you to create a new BaseModel
summary: Create BaseModel
consumes:
- application/json
host: api.app.example.com
info:
title: Valid Recursive Definition Example
version: '1.0'
description: This example contains a recursive model definition which is valid because DirectRecursiveModel's "children" property is optional.
swagger: '2.0'
basePath: /