Files
libopenapi/document_test.go
Dave Shanley cecee1ca73 Working through coverage
Fixing whack a mole issues as code is fully tested.
2023-03-26 06:10:31 -04:00

436 lines
12 KiB
Go

// Copyright 2022 Princess B33f Heavy Industries / Dave Shanley
// SPDX-License-Identifier: MIT
package libopenapi
import (
"fmt"
"github.com/pb33f/libopenapi/datamodel/high/base"
"github.com/pb33f/libopenapi/what-changed/model"
"github.com/stretchr/testify/assert"
"io/ioutil"
"os"
"testing"
)
func TestLoadDocument_Simple_V2(t *testing.T) {
yml := `swagger: 2.0.1`
doc, err := NewDocument([]byte(yml))
assert.NoError(t, err)
assert.Equal(t, "2.0.1", doc.GetVersion())
v2Doc, docErr := doc.BuildV2Model()
assert.Len(t, docErr, 0)
assert.NotNil(t, v2Doc)
assert.NotNil(t, doc.GetSpecInfo())
fmt.Print()
}
func TestLoadDocument_Simple_V2_Error(t *testing.T) {
yml := `swagger: 2.0`
doc, err := NewDocument([]byte(yml))
assert.NoError(t, err)
v2Doc, docErr := doc.BuildV3Model()
assert.Len(t, docErr, 1)
assert.Nil(t, v2Doc)
}
func TestLoadDocument_Simple_V2_Error_BadSpec(t *testing.T) {
yml := `swagger: 2.0
definitions:
thing:
$ref: bork`
doc, err := NewDocument([]byte(yml))
assert.NoError(t, err)
v2Doc, docErr := doc.BuildV2Model()
assert.Len(t, docErr, 2)
assert.Nil(t, v2Doc)
}
func TestLoadDocument_Simple_V3_Error(t *testing.T) {
yml := `openapi: 3.0.1`
doc, err := NewDocument([]byte(yml))
assert.NoError(t, err)
v2Doc, docErr := doc.BuildV2Model()
assert.Len(t, docErr, 1)
assert.Nil(t, v2Doc)
}
func TestLoadDocument_Error_V2NoSpec(t *testing.T) {
doc := new(document) // not how this should be instantiated.
_, err := doc.BuildV2Model()
assert.Len(t, err, 1)
}
func TestLoadDocument_Error_V3NoSpec(t *testing.T) {
doc := new(document) // not how this should be instantiated.
_, err := doc.BuildV3Model()
assert.Len(t, err, 1)
}
func TestLoadDocument_Empty(t *testing.T) {
yml := ``
_, err := NewDocument([]byte(yml))
assert.Error(t, err)
}
func TestLoadDocument_Simple_V3(t *testing.T) {
yml := `openapi: 3.0.1`
doc, err := NewDocument([]byte(yml))
assert.NoError(t, err)
assert.Equal(t, "3.0.1", doc.GetVersion())
v3Doc, docErr := doc.BuildV3Model()
assert.Len(t, docErr, 0)
assert.NotNil(t, v3Doc)
}
func TestLoadDocument_Simple_V3_Error_BadSpec(t *testing.T) {
yml := `openapi: 3.0
paths:
"/some":
$ref: bork`
doc, err := NewDocument([]byte(yml))
assert.NoError(t, err)
v3Doc, docErr := doc.BuildV3Model()
assert.Len(t, docErr, 2)
assert.Nil(t, v3Doc)
}
func TestDocument_Serialize_Error(t *testing.T) {
doc := new(document) // not how this should be instantiated.
_, err := doc.Serialize()
assert.Error(t, err)
}
func TestDocument_Serialize(t *testing.T) {
yml := `openapi: 3.0
info:
title: The magic API
`
doc, _ := NewDocument([]byte(yml))
serial, err := doc.Serialize()
assert.NoError(t, err)
assert.Equal(t, yml, string(serial))
}
func TestDocument_Serialize_Modified(t *testing.T) {
yml := `openapi: 3.0
info:
title: The magic API
`
ymlModified := `openapi: 3.0
info:
title: The magic API - but now, altered!
`
doc, _ := NewDocument([]byte(yml))
v3Doc, _ := doc.BuildV3Model()
v3Doc.Model.Info.GoLow().Title.Mutate("The magic API - but now, altered!")
serial, err := doc.Serialize()
assert.NoError(t, err)
assert.Equal(t, ymlModified, string(serial))
}
func TestDocument_RenderAndReload_ChangeCheck_Burgershop(t *testing.T) {
bs, _ := os.ReadFile("test_specs/burgershop.openapi.yaml")
doc, _ := NewDocument(bs)
doc.BuildV3Model()
rend, newDoc, _, _ := doc.RenderAndReload()
// compare documents
compReport, errs := CompareDocuments(doc, newDoc)
// should noth be nil.
assert.Nil(t, errs)
assert.NotNil(t, rend)
assert.Nil(t, compReport)
}
func TestDocument_RenderAndReload_ChangeCheck_Stripe(t *testing.T) {
bs, _ := os.ReadFile("test_specs/stripe.yaml")
doc, _ := NewDocument(bs)
doc.BuildV3Model()
_, newDoc, _, _ := doc.RenderAndReload()
// compare documents
compReport, errs := CompareDocuments(doc, newDoc)
// get flat list of changes.
flatChanges := compReport.GetAllChanges()
// remove everything that is a description change (stripe has a lot of those from having 519 empty descriptions)
var filtered []*model.Change
for i := range flatChanges {
if flatChanges[i].Property != "description" {
filtered = append(filtered, flatChanges[i])
}
}
assert.Nil(t, errs)
tc := compReport.TotalChanges()
bc := compReport.TotalBreakingChanges()
assert.Equal(t, 0, bc)
assert.Equal(t, 519, tc)
// there should be no other changes than the 519 descriptions.
assert.Equal(t, 0, len(filtered))
}
func TestDocument_RenderAndReload(t *testing.T) {
// load an OpenAPI 3 specification from bytes
petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json")
// create a new document from specification bytes
doc, err := NewDocument(petstore)
// if anything went wrong, an error is thrown
if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err))
}
// because we know this is a v3 spec, we can build a ready to go model from it.
m, _ := doc.BuildV3Model()
// mutate the model
h := m.Model
h.Paths.PathItems["/pet/findByStatus"].Get.OperationId = "findACakeInABakery"
h.Paths.PathItems["/pet/findByStatus"].Get.Responses.Codes["400"].Description = "a nice bucket of mice"
h.Paths.PathItems["/pet/findByTags"].Get.Tags =
append(h.Paths.PathItems["/pet/findByTags"].Get.Tags, "gurgle", "giggle")
h.Paths.PathItems["/pet/{petId}"].Delete.Security = append(h.Paths.PathItems["/pet/{petId}"].Delete.Security,
&base.SecurityRequirement{Requirements: map[string][]string{
"pizza-and-cake": {"read:abook", "write:asong"},
}})
h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example = "I am a teapot, filled with love."
h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl = "https://pb33f.io"
bytes, _, newDocModel, e := doc.RenderAndReload()
assert.Nil(t, e)
assert.NotNil(t, bytes)
h = newDocModel.Model
assert.Equal(t, "findACakeInABakery", h.Paths.PathItems["/pet/findByStatus"].Get.OperationId)
assert.Equal(t, "a nice bucket of mice",
h.Paths.PathItems["/pet/findByStatus"].Get.Responses.Codes["400"].Description)
assert.Len(t, h.Paths.PathItems["/pet/findByTags"].Get.Tags, 3)
assert.Len(t, h.Paths.PathItems["/pet/findByTags"].Get.Tags, 3)
yu := h.Paths.PathItems["/pet/{petId}"].Delete.Security
assert.Equal(t, "read:abook", yu[len(yu)-1].Requirements["pizza-and-cake"][0])
assert.Equal(t, "I am a teapot, filled with love.",
h.Components.Schemas["Order"].Schema().Properties["status"].Schema().Example)
assert.Equal(t, "https://pb33f.io",
h.Components.SecuritySchemes["petstore_auth"].Flows.Implicit.AuthorizationUrl)
}
func TestDocument_RenderAndReload_Swagger(t *testing.T) {
petstore, _ := ioutil.ReadFile("test_specs/petstorev2.json")
doc, _ := NewDocument(petstore)
doc.BuildV2Model()
doc.BuildV2Model()
_, _, _, e := doc.RenderAndReload()
assert.Len(t, e, 1)
assert.Equal(t, "this method only supports OpenAPI 3 documents, not Swagger", e[0].Error())
}
func TestDocument_BuildModelPreBuild(t *testing.T) {
petstore, _ := ioutil.ReadFile("test_specs/petstorev3.json")
doc, _ := NewDocument(petstore)
doc.BuildV3Model()
doc.BuildV3Model()
_, _, _, e := doc.RenderAndReload()
assert.Len(t, e, 0)
}
func TestDocument_BuildModelCircular(t *testing.T) {
petstore, _ := ioutil.ReadFile("test_specs/circular-tests.yaml")
doc, _ := NewDocument(petstore)
m, e := doc.BuildV3Model()
assert.NotNil(t, m)
assert.Len(t, e, 3)
}
func TestDocument_BuildModelBad(t *testing.T) {
petstore, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml")
doc, _ := NewDocument(petstore)
m, e := doc.BuildV3Model()
assert.Nil(t, m)
assert.Len(t, e, 9)
}
func TestDocument_Serialize_JSON_Modified(t *testing.T) {
json := `{ 'openapi': '3.0',
'info': {
'title': 'The magic API'
}
}
`
jsonModified := `{"info":{"title":"The magic API - but now, altered!"},"openapi":"3.0"}`
doc, _ := NewDocument([]byte(json))
v3Doc, _ := doc.BuildV3Model()
// eventually this will be encapsulated up high.
// mutation does not replace low model, eventually pointers will be used.
newTitle := v3Doc.Model.Info.GoLow().Title.Mutate("The magic API - but now, altered!")
v3Doc.Model.Info.GoLow().Title = newTitle
assert.Equal(t, "The magic API - but now, altered!", v3Doc.Model.Info.GoLow().Title.Value)
serial, err := doc.Serialize()
assert.NoError(t, err)
assert.Equal(t, jsonModified, string(serial))
}
func TestExtractReference(t *testing.T) {
var data = `
openapi: "3.1"
components:
parameters:
Param1:
description: "I am a param"
paths:
/something:
get:
parameters:
- $ref: '#/components/parameters/Param1'`
doc, err := NewDocument([]byte(data))
if err != nil {
panic(err)
}
result, errs := doc.BuildV3Model()
if len(errs) > 0 {
panic(errs)
}
// extract operation.
operation := result.Model.Paths.PathItems["/something"].Get
// print it out.
fmt.Printf("param1: %s, is reference? %t, original reference %s",
operation.Parameters[0].Description, operation.GoLow().Parameters.Value[0].IsReference(),
operation.GoLow().Parameters.Value[0].Reference)
}
func TestDocument_BuildModel_CompareDocsV3_LeftError(t *testing.T) {
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml")
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml")
originalDoc, _ := NewDocument(burgerShopOriginal)
updatedDoc, _ := NewDocument(burgerShopUpdated)
changes, errors := CompareDocuments(originalDoc, updatedDoc)
assert.Len(t, errors, 9)
assert.Nil(t, changes)
}
func TestDocument_BuildModel_CompareDocsV3_RightError(t *testing.T) {
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/badref-burgershop.openapi.yaml")
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/burgershop.openapi-modified.yaml")
originalDoc, _ := NewDocument(burgerShopOriginal)
updatedDoc, _ := NewDocument(burgerShopUpdated)
changes, errors := CompareDocuments(updatedDoc, originalDoc)
assert.Len(t, errors, 9)
assert.Nil(t, changes)
}
func TestDocument_BuildModel_CompareDocsV2_Error(t *testing.T) {
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/petstorev2-badref.json")
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/petstorev2-badref.json")
originalDoc, _ := NewDocument(burgerShopOriginal)
updatedDoc, _ := NewDocument(burgerShopUpdated)
changes, errors := CompareDocuments(updatedDoc, originalDoc)
assert.Len(t, errors, 2)
assert.Nil(t, changes)
}
func TestDocument_BuildModel_CompareDocsV2V3Mix_Error(t *testing.T) {
burgerShopOriginal, _ := ioutil.ReadFile("test_specs/petstorev2.json")
burgerShopUpdated, _ := ioutil.ReadFile("test_specs/petstorev3.json")
originalDoc, _ := NewDocument(burgerShopOriginal)
updatedDoc, _ := NewDocument(burgerShopUpdated)
changes, errors := CompareDocuments(updatedDoc, originalDoc)
assert.Len(t, errors, 1)
assert.Nil(t, changes)
}
func TestSchemaRefIsFollowed(t *testing.T) {
petstore, _ := ioutil.ReadFile("test_specs/ref-followed.yaml")
// create a new document from specification bytes
document, err := NewDocument(petstore)
// if anything went wrong, an error is thrown
if err != nil {
panic(fmt.Sprintf("cannot create new document: %e", err))
}
// because we know this is a v3 spec, we can build a ready to go model from it.
v3Model, errors := document.BuildV3Model()
// if anything went wrong when building the v3 model, a slice of errors will be returned
if len(errors) > 0 {
for i := range errors {
fmt.Printf("error: %e\n", errors[i])
}
panic(fmt.Sprintf("cannot create v3 model from document: %d errors reported", len(errors)))
}
// get a count of the number of paths and schemas.
schemas := v3Model.Model.Components.Schemas
assert.Equal(t, 4, len(schemas))
fp := schemas["FP"]
fbsref := schemas["FBSRef"]
assert.Equal(t, fp.Schema().Pattern, fbsref.Schema().Pattern)
assert.Equal(t, fp.Schema().Example, fbsref.Schema().Example)
byte := schemas["Byte"]
uint64 := schemas["UInt64"]
assert.Equal(t, uint64.Schema().Format, byte.Schema().Format)
assert.Equal(t, uint64.Schema().Type, byte.Schema().Type)
assert.Equal(t, uint64.Schema().Nullable, byte.Schema().Nullable)
assert.Equal(t, uint64.Schema().Example, byte.Schema().Example)
assert.Equal(t, uint64.Schema().Minimum, byte.Schema().Minimum)
}