mirror of
https://github.com/LukeHagar/libopenapi.git
synced 2025-12-09 12:37:49 +00:00
fix: continued moving everything to orderedmaps plus cleaned up most the tests
This commit is contained in:
@@ -13,9 +13,11 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
const Example = "Example"
|
||||
const Examples = "Examples"
|
||||
const Schema = "Schema"
|
||||
const (
|
||||
Example = "Example"
|
||||
Examples = "Examples"
|
||||
Schema = "Schema"
|
||||
)
|
||||
|
||||
type MockType int
|
||||
|
||||
@@ -27,7 +29,7 @@ const (
|
||||
// MockGenerator is used to generate mocks for high-level mockable structs or *base.Schema pointers.
|
||||
// The mock generator will attempt to generate a mock from a struct using the following fields:
|
||||
// - Example: any type, this is the default example to use if no examples are present.
|
||||
// - Examples: orderedmap.Map[string, *base.Example], this is a map of examples keyed by name.
|
||||
// - Examples: *orderedmap.Map[string, *base.Example], this is a map of examples keyed by name.
|
||||
// - Schema: *base.SchemaProxy, this is the schema to use if no examples are present.
|
||||
//
|
||||
// The mock generator will attempt to generate a mock from a *base.Schema pointer.
|
||||
@@ -61,7 +63,7 @@ func (mg *MockGenerator) SetPretty() {
|
||||
|
||||
// GenerateMock generates a mock for a given high-level mockable struct. The mockable struct must contain the following fields:
|
||||
// Example: any type, this is the default example to use if no examples are present.
|
||||
// Examples: orderedmap.Map[string, *base.Example], this is a map of examples keyed by name.
|
||||
// Examples: *orderedmap.Map[string, *base.Example], this is a map of examples keyed by name.
|
||||
// Schema: *base.SchemaProxy, this is the schema to use if no examples are present.
|
||||
// The name parameter is optional, if provided, the mock generator will attempt to find an example with the given name.
|
||||
// If no name is provided, the first example will be used.
|
||||
@@ -89,10 +91,21 @@ func (mg *MockGenerator) GenerateMock(mock any, name string) ([]byte, error) {
|
||||
}
|
||||
|
||||
// if the value has an example, try and render it out as is.
|
||||
exampleValue := v.FieldByName(Example).Interface()
|
||||
if exampleValue != nil {
|
||||
// try and serialize the example value
|
||||
return mg.renderMock(exampleValue), nil
|
||||
f := v.FieldByName(Example)
|
||||
if !f.IsNil() {
|
||||
// Pointer/Interface Shenanigans
|
||||
ex := f.Interface()
|
||||
if y, ok := ex.(*yaml.Node); ok {
|
||||
if y != nil {
|
||||
ex = y
|
||||
} else {
|
||||
ex = nil
|
||||
}
|
||||
}
|
||||
if ex != nil {
|
||||
// try and serialize the example value
|
||||
return mg.renderMock(ex), nil
|
||||
}
|
||||
}
|
||||
|
||||
// if there is no example, but there are multi-examples.
|
||||
@@ -100,8 +113,8 @@ func (mg *MockGenerator) GenerateMock(mock any, name string) ([]byte, error) {
|
||||
examplesValue := examples.Interface()
|
||||
if examplesValue != nil && !examples.IsNil() {
|
||||
|
||||
// cast examples to orderedmap.Map[string, any]
|
||||
examplesMap := examplesValue.(orderedmap.Map[string, *highbase.Example])
|
||||
// cast examples to *orderedmap.Map[string, *highbase.Example]
|
||||
examplesMap := examplesValue.(*orderedmap.Map[string, *highbase.Example])
|
||||
|
||||
// if the name is not empty, try and find the example by name
|
||||
for pair := orderedmap.First(examplesMap); pair != nil; pair = pair.Next() {
|
||||
@@ -157,6 +170,11 @@ func (mg *MockGenerator) renderMock(v any) []byte {
|
||||
|
||||
func (mg *MockGenerator) renderMockJSON(v any) []byte {
|
||||
var data []byte
|
||||
|
||||
if y, ok := v.(*yaml.Node); ok {
|
||||
_ = y.Decode(&v)
|
||||
}
|
||||
|
||||
// determine the type, render properly.
|
||||
switch reflect.ValueOf(v).Kind() {
|
||||
case reflect.Map, reflect.Slice, reflect.Array, reflect.Struct, reflect.Ptr:
|
||||
|
||||
@@ -13,6 +13,7 @@ import (
|
||||
"github.com/pb33f/libopenapi/datamodel/low"
|
||||
lowbase "github.com/pb33f/libopenapi/datamodel/low/base"
|
||||
"github.com/pb33f/libopenapi/orderedmap"
|
||||
"github.com/pb33f/libopenapi/utils"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
@@ -20,13 +21,13 @@ import (
|
||||
type fakeMockable struct {
|
||||
Schema *highbase.SchemaProxy
|
||||
Example any
|
||||
Examples orderedmap.Map[string, *highbase.Example]
|
||||
Examples *orderedmap.Map[string, *highbase.Example]
|
||||
}
|
||||
|
||||
type fakeMockableButWithASchemaNotAProxy struct {
|
||||
Schema *highbase.Schema
|
||||
Example any
|
||||
Examples orderedmap.Map[string, *highbase.Example]
|
||||
Examples *orderedmap.Map[string, *highbase.Example]
|
||||
}
|
||||
|
||||
var simpleFakeMockSchema = `type: string
|
||||
@@ -55,7 +56,7 @@ func createFakeMock(mock string, values map[string]any, example any) *fakeMockab
|
||||
|
||||
for k, v := range values {
|
||||
examples.Set(k, &highbase.Example{
|
||||
Value: v,
|
||||
Value: utils.CreateYamlNode(v),
|
||||
})
|
||||
}
|
||||
return &fakeMockable{
|
||||
@@ -78,7 +79,7 @@ func createFakeMockWithoutProxy(mock string, values map[string]any, example any)
|
||||
|
||||
for k, v := range values {
|
||||
examples.Set(k, &highbase.Example{
|
||||
Value: v,
|
||||
Value: utils.CreateYamlNode(v),
|
||||
})
|
||||
}
|
||||
return &fakeMockableButWithASchemaNotAProxy{
|
||||
@@ -110,7 +111,6 @@ func TestMockGenerator_GenerateJSONMock_BadObject(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMockGenerator_GenerateJSONMock_EmptyObject(t *testing.T) {
|
||||
|
||||
mg := NewMockGenerator(JSON)
|
||||
mock, err := mg.GenerateMock(&fakeMockable{}, "")
|
||||
assert.NoError(t, err)
|
||||
@@ -118,7 +118,6 @@ func TestMockGenerator_GenerateJSONMock_EmptyObject(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMockGenerator_GenerateJSONMock_SuppliedExample_JSON(t *testing.T) {
|
||||
|
||||
fakeExample := map[string]any{
|
||||
"fish-and-chips": "cod-and-chips-twice",
|
||||
}
|
||||
@@ -130,7 +129,6 @@ func TestMockGenerator_GenerateJSONMock_SuppliedExample_JSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMockGenerator_GenerateJSONMock_SuppliedExample_YAML(t *testing.T) {
|
||||
|
||||
fakeExample := map[string]any{
|
||||
"fish-and-chips": "cod-and-chips-twice",
|
||||
}
|
||||
@@ -208,7 +206,6 @@ func TestMockGenerator_GenerateJSONMock_MultiExamples_YAML(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMockGenerator_GenerateJSONMock_NoExamples_JSON(t *testing.T) {
|
||||
|
||||
fake := createFakeMock(simpleFakeMockSchema, nil, nil)
|
||||
mg := NewMockGenerator(JSON)
|
||||
mock, err := mg.GenerateMock(fake, "")
|
||||
@@ -217,16 +214,14 @@ func TestMockGenerator_GenerateJSONMock_NoExamples_JSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMockGenerator_GenerateJSONMock_NoExamples_YAML(t *testing.T) {
|
||||
|
||||
fake := createFakeMock(simpleFakeMockSchema, nil, nil)
|
||||
mg := NewMockGenerator(YAML)
|
||||
mock, err := mg.GenerateMock(fake, "")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "magic-herbs", string(mock))
|
||||
assert.Equal(t, "magic-herbs", strings.TrimSpace(string(mock)))
|
||||
}
|
||||
|
||||
func TestMockGenerator_GenerateJSONMock_Object_NoExamples_JSON(t *testing.T) {
|
||||
|
||||
fake := createFakeMock(objectFakeMockSchema, nil, nil)
|
||||
mg := NewMockGenerator(JSON)
|
||||
mock, err := mg.GenerateMock(fake, "")
|
||||
@@ -244,7 +239,6 @@ func TestMockGenerator_GenerateJSONMock_Object_NoExamples_JSON(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMockGenerator_GenerateJSONMock_Object_NoExamples_YAML(t *testing.T) {
|
||||
|
||||
fake := createFakeMock(objectFakeMockSchema, nil, nil)
|
||||
mg := NewMockGenerator(YAML)
|
||||
mock, err := mg.GenerateMock(fake, "")
|
||||
@@ -263,7 +257,6 @@ func TestMockGenerator_GenerateJSONMock_Object_NoExamples_YAML(t *testing.T) {
|
||||
|
||||
// should result in the exact same output as the above test
|
||||
func TestMockGenerator_GenerateJSONMock_Object_RawSchema(t *testing.T) {
|
||||
|
||||
fake := createFakeMockWithoutProxy(objectFakeMockSchema, nil, nil)
|
||||
|
||||
mg := NewMockGenerator(YAML)
|
||||
|
||||
@@ -19,33 +19,35 @@ import (
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
const rootType = "rootType"
|
||||
const stringType = "string"
|
||||
const numberType = "number"
|
||||
const integerType = "integer"
|
||||
const booleanType = "boolean"
|
||||
const objectType = "object"
|
||||
const arrayType = "array"
|
||||
const int32Type = "int32"
|
||||
const floatType = "float"
|
||||
const doubleType = "double"
|
||||
const byteType = "byte"
|
||||
const binaryType = "binary"
|
||||
const passwordType = "password"
|
||||
const dateType = "date"
|
||||
const dateTimeType = "date-time"
|
||||
const timeType = "time"
|
||||
const emailType = "email"
|
||||
const hostnameType = "hostname"
|
||||
const ipv4Type = "ipv4"
|
||||
const ipv6Type = "ipv6"
|
||||
const uriType = "uri"
|
||||
const uriReferenceType = "uri-reference"
|
||||
const uuidType = "uuid"
|
||||
const allOfType = "allOf"
|
||||
const anyOfType = "anyOf"
|
||||
const oneOfType = "oneOf"
|
||||
const itemsType = "items"
|
||||
const (
|
||||
rootType = "rootType"
|
||||
stringType = "string"
|
||||
numberType = "number"
|
||||
integerType = "integer"
|
||||
booleanType = "boolean"
|
||||
objectType = "object"
|
||||
arrayType = "array"
|
||||
int32Type = "int32"
|
||||
floatType = "float"
|
||||
doubleType = "double"
|
||||
byteType = "byte"
|
||||
binaryType = "binary"
|
||||
passwordType = "password"
|
||||
dateType = "date"
|
||||
dateTimeType = "date-time"
|
||||
timeType = "time"
|
||||
emailType = "email"
|
||||
hostnameType = "hostname"
|
||||
ipv4Type = "ipv4"
|
||||
ipv6Type = "ipv6"
|
||||
uriType = "uri"
|
||||
uriReferenceType = "uri-reference"
|
||||
uuidType = "uuid"
|
||||
allOfType = "allOf"
|
||||
anyOfType = "anyOf"
|
||||
oneOfType = "oneOf"
|
||||
itemsType = "items"
|
||||
)
|
||||
|
||||
// used to generate random words if there is no dictionary applied.
|
||||
const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
@@ -97,10 +99,12 @@ func (wr *SchemaRenderer) DisableRequiredCheck() {
|
||||
// DiveIntoSchema will dive into a schema and inject values from examples into a map. If there are no examples in
|
||||
// the schema, then the renderer will attempt to generate a value based on the schema type, format and pattern.
|
||||
func (wr *SchemaRenderer) DiveIntoSchema(schema *base.Schema, key string, structure map[string]any, depth int) {
|
||||
|
||||
// got an example? use it, we're done here.
|
||||
if schema.Example != nil {
|
||||
structure[key] = schema.Example
|
||||
var example any
|
||||
_ = schema.Example.Decode(&example)
|
||||
|
||||
structure[key] = example
|
||||
return
|
||||
}
|
||||
|
||||
@@ -114,7 +118,12 @@ func (wr *SchemaRenderer) DiveIntoSchema(schema *base.Schema, key string, struct
|
||||
if slices.Contains(schema.Type, stringType) {
|
||||
// check for an enum, if there is one, then pick a random value from it.
|
||||
if schema.Enum != nil && len(schema.Enum) > 0 {
|
||||
structure[key] = schema.Enum[rand.Int()%len(schema.Enum)]
|
||||
enum := schema.Enum[rand.Int()%len(schema.Enum)]
|
||||
|
||||
var example any
|
||||
_ = enum.Decode(&example)
|
||||
|
||||
structure[key] = example
|
||||
} else {
|
||||
|
||||
// generate a random value based on the schema format, pattern and length values.
|
||||
@@ -187,7 +196,12 @@ func (wr *SchemaRenderer) DiveIntoSchema(schema *base.Schema, key string, struct
|
||||
if slices.Contains(schema.Type, numberType) || slices.Contains(schema.Type, integerType) {
|
||||
|
||||
if schema.Enum != nil && len(schema.Enum) > 0 {
|
||||
structure[key] = schema.Enum[rand.Int()%len(schema.Enum)]
|
||||
enum := schema.Enum[rand.Int()%len(schema.Enum)]
|
||||
|
||||
var example any
|
||||
_ = enum.Decode(&example)
|
||||
|
||||
structure[key] = example
|
||||
} else {
|
||||
|
||||
var minimum int64 = 1
|
||||
@@ -303,7 +317,6 @@ func (wr *SchemaRenderer) DiveIntoSchema(schema *base.Schema, key string, struct
|
||||
// an array needs an items schema
|
||||
itemsSchema := schema.Items
|
||||
if itemsSchema != nil {
|
||||
|
||||
// otherwise the items value is a schema, so we need to dive into it
|
||||
if itemsSchema.IsA() {
|
||||
|
||||
@@ -326,7 +339,6 @@ func (wr *SchemaRenderer) DiveIntoSchema(schema *base.Schema, key string, struct
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func readFile(file io.Reader) []string {
|
||||
@@ -350,7 +362,6 @@ func ReadDictionary(dictionaryLocation string) []string {
|
||||
// to prevent a stack overflow, the maximum depth is 100 (anything more than this is probably a bug).
|
||||
// set the values to 0 to return the first word returned, essentially ignore the min and max values.
|
||||
func (wr *SchemaRenderer) RandomWord(min, max int64, depth int) string {
|
||||
|
||||
// break out if we've gone too deep
|
||||
if depth > 100 {
|
||||
return fmt.Sprintf("no-word-found-%d-%d", min, max)
|
||||
|
||||
@@ -38,7 +38,6 @@ properties:
|
||||
}
|
||||
|
||||
func createSchemaRenderer() *SchemaRenderer {
|
||||
|
||||
osDict := "/usr/share/dict/words"
|
||||
if _, err := os.Stat(osDict); err != nil {
|
||||
osDict = ""
|
||||
@@ -68,7 +67,6 @@ func getSchema(schema []byte) *highbase.Schema {
|
||||
}
|
||||
|
||||
func TestRenderExample_StringWithExample(t *testing.T) {
|
||||
|
||||
testObject := `type: string
|
||||
example: dog`
|
||||
|
||||
@@ -79,11 +77,9 @@ example: dog`
|
||||
wr.DiveIntoSchema(compiled, "pb33f", journeyMap, 0)
|
||||
|
||||
assert.Equal(t, journeyMap["pb33f"], "dog")
|
||||
|
||||
}
|
||||
|
||||
func TestRenderExample_StringWithNoExample(t *testing.T) {
|
||||
|
||||
testObject := `type: string`
|
||||
|
||||
compiled := getSchema([]byte(testObject))
|
||||
@@ -95,11 +91,9 @@ func TestRenderExample_StringWithNoExample(t *testing.T) {
|
||||
assert.NotNil(t, journeyMap["pb33f"])
|
||||
assert.GreaterOrEqual(t, len(journeyMap["pb33f"].(string)), 3)
|
||||
assert.LessOrEqual(t, len(journeyMap["pb33f"].(string)), 10)
|
||||
|
||||
}
|
||||
|
||||
func TestRenderExample_StringWithNoExample_Format_Datetime(t *testing.T) {
|
||||
|
||||
testObject := `type: string
|
||||
format: date-time`
|
||||
|
||||
@@ -115,7 +109,6 @@ format: date-time`
|
||||
}
|
||||
|
||||
func TestRenderExample_StringWithNoExample_Pattern_Email(t *testing.T) {
|
||||
|
||||
testObject := `type: string
|
||||
pattern: "^[a-z]{5,10}@[a-z]{5,10}\\.(com|net|org)$"` // an email address
|
||||
|
||||
@@ -134,7 +127,6 @@ pattern: "^[a-z]{5,10}@[a-z]{5,10}\\.(com|net|org)$"` // an email address
|
||||
}
|
||||
|
||||
func TestRenderExample_StringWithNoExample_Pattern_PhoneNumber(t *testing.T) {
|
||||
|
||||
testObject := `type: string
|
||||
pattern: "^\\([0-9]{3}\\)-[0-9]{3}-[0-9]{4}$"` // a phone number
|
||||
|
||||
@@ -152,7 +144,6 @@ pattern: "^\\([0-9]{3}\\)-[0-9]{3}-[0-9]{4}$"` // a phone number
|
||||
}
|
||||
|
||||
func TestRenderExample_StringWithNoExample_Format_Date(t *testing.T) {
|
||||
|
||||
testObject := `type: string
|
||||
format: date`
|
||||
|
||||
@@ -390,7 +381,6 @@ minLength: 3`
|
||||
assert.NotNil(t, journeyMap["pb33f"])
|
||||
assert.LessOrEqual(t, len(journeyMap["pb33f"].(string)), 8)
|
||||
assert.GreaterOrEqual(t, len(journeyMap["pb33f"].(string)), 3)
|
||||
|
||||
}
|
||||
|
||||
func TestRenderExample_NumberNoExample_Default(t *testing.T) {
|
||||
@@ -771,7 +761,6 @@ properties:
|
||||
assert.Equal(t, journeyMap["pb33f"].(map[string]interface{})["price"].(float64), 19.99)
|
||||
assert.Equal(t, journeyMap["pb33f"].(map[string]interface{})["category"].(string), "shirts")
|
||||
assert.Equal(t, journeyMap["pb33f"].(map[string]interface{})["image"].(string), "https://pb33f.io/images/t-shirt.png")
|
||||
|
||||
}
|
||||
|
||||
func TestRenderExample_TestGiftshopProduct_UsingTopLevelExample(t *testing.T) {
|
||||
@@ -839,7 +828,6 @@ example:
|
||||
|
||||
assert.Equal(t, journeyMap["pb33f"].(map[string]interface{})["category"].(string), "not-a-category")
|
||||
assert.Equal(t, journeyMap["pb33f"].(map[string]interface{})["image"].(string), "not-an-image")
|
||||
|
||||
}
|
||||
|
||||
func TestRenderExample_TestGiftshopProduct_UsingNoExamples(t *testing.T) {
|
||||
@@ -889,7 +877,6 @@ properties:
|
||||
assert.NotEmpty(t, journeyMap["pb33f"].(map[string]interface{})["price"].(float32))
|
||||
assert.NotEmpty(t, journeyMap["pb33f"].(map[string]interface{})["category"].(string))
|
||||
assert.NotEmpty(t, journeyMap["pb33f"].(map[string]interface{})["image"].(string))
|
||||
|
||||
}
|
||||
|
||||
func TestRenderExample_Test_MultiPolymorphic(t *testing.T) {
|
||||
@@ -931,7 +918,7 @@ properties:
|
||||
burger := journeyMap["pb33f"].(map[string]interface{})["burger"].(map[string]interface{})
|
||||
assert.NotNil(t, burger)
|
||||
assert.NotEmpty(t, burger["name"].(string))
|
||||
assert.NotZero(t, burger["weight"].(int64))
|
||||
assert.NotZero(t, burger["weight"].(int))
|
||||
assert.NotEmpty(t, burger["patty"].(string))
|
||||
assert.True(t, burger["frozen"].(bool))
|
||||
}
|
||||
@@ -1083,7 +1070,6 @@ func (errReader) Read(p []byte) (n int, err error) {
|
||||
}
|
||||
|
||||
func TestReadDictionary_BadReader(t *testing.T) {
|
||||
|
||||
words := readFile(errReader(0))
|
||||
assert.LessOrEqual(t, len(words), 0)
|
||||
}
|
||||
@@ -1113,14 +1099,13 @@ func TestWordRenderer_RandomWordMinMaxZero(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestRenderSchema_NestedDeep(t *testing.T) {
|
||||
|
||||
deepNest := createNestedStructure()
|
||||
journeyMap := make(map[string]any)
|
||||
wr := createSchemaRenderer()
|
||||
wr.DiveIntoSchema(deepNest.Schema(), "pb33f", journeyMap, 0)
|
||||
|
||||
assert.NotNil(t, journeyMap["pb33f"])
|
||||
var journeyLevel = 0
|
||||
journeyLevel := 0
|
||||
var dive func(mapNode map[string]any, level int)
|
||||
// count the levels to validate the recursion hard limit.
|
||||
dive = func(mapNode map[string]any, level int) {
|
||||
@@ -1144,7 +1129,6 @@ func TestCreateRendererUsingDictionary(t *testing.T) {
|
||||
}
|
||||
|
||||
func createNestedStructure() *highbase.SchemaProxy {
|
||||
|
||||
schema := `type: [object]
|
||||
properties:
|
||||
name:
|
||||
|
||||
Reference in New Issue
Block a user