mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-06 04:20:11 +00:00
however, more hardening is required, Asana is getting upset with changes found when rendering, so that's up next. Then, we will need to harden against few others.
469 lines
13 KiB
Go
469 lines
13 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_ChangeCheck_Asana(t *testing.T) {
|
|
//
|
|
// bs, _ := os.ReadFile("test_specs/asana.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)
|
|
}
|
|
|