package analysis import ( "path/filepath" "strings" "testing" "github.com/go-openapi/analysis/internal/antest" "github.com/go-openapi/analysis/internal/flatten/operations" "github.com/go-openapi/analysis/internal/flatten/sortref" "github.com/go-openapi/jsonpointer" "github.com/go-openapi/spec" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestName_FromRef(t *testing.T) { t.Parallel() values := []struct{ Source, Expected string }{ {"#/definitions/errorModel", "errorModel"}, {"http://somewhere.com/definitions/errorModel", "errorModel"}, {"http://somewhere.com/definitions/errorModel.json", "errorModel"}, {"/definitions/errorModel", "errorModel"}, {"/definitions/errorModel.json", "errorModel"}, {"http://somewhere.com", "somewhereCom"}, {"#", ""}, } for _, v := range values { assert.Equal(t, v.Expected, nameFromRef(spec.MustCreateRef(v.Source), &FlattenOpts{})) } } func TestName_FromRefMangle(t *testing.T) { t.Parallel() values := []struct{ Source, Expected, ExpectedKeepName string }{ {"#/definitions/ErrorModel", "errorModel", "ErrorModel"}, {"#/definitions/Error_Model", "errorModel", "Error_Model"}, {"http://somewhere.com/definitions/errorModel", "errorModel", "errorModel"}, {"http://somewhere.com/definitions/ErrorModel.json", "errorModel", "ErrorModel"}, {"/definitions/ErrorModel", "errorModel", "ErrorModel"}, {"/definitions/ErrorModel.json", "errorModel", "ErrorModel"}, {"http://somewhere.com", "somewhereCom", "somewhere com"}, {"#", "", ""}, } for _, v := range values { assert.Equal(t, v.Expected, nameFromRef(spec.MustCreateRef(v.Source), &FlattenOpts{})) assert.Equal(t, v.ExpectedKeepName, nameFromRef(spec.MustCreateRef(v.Source), &FlattenOpts{KeepNames: true})) } } func TestName_Definition(t *testing.T) { values := []struct { Source, Expected string Definitions spec.Definitions }{ {"#/definitions/errorModel", "errorModel", map[string]spec.Schema(nil)}, {"http://somewhere.com/definitions/errorModel", "errorModel", map[string]spec.Schema(nil)}, {"#/definitions/errorModel", "errorModel", map[string]spec.Schema{"apples": *spec.StringProperty()}}, {"#/definitions/errorModel", "errorModelOAIGen", map[string]spec.Schema{"errorModel": *spec.StringProperty()}}, {"#/definitions/errorModel", "errorModelOAIGen1", map[string]spec.Schema{"errorModel": *spec.StringProperty(), "errorModelOAIGen": *spec.StringProperty()}}, {"#", "oaiGen", nil}, } for _, v := range values { u, _ := uniqifyName(v.Definitions, nameFromRef(spec.MustCreateRef(v.Source), &FlattenOpts{})) assert.Equal(t, v.Expected, u) } } func TestName_SplitKey(t *testing.T) { type KeyFlag uint64 const ( isOperation KeyFlag = 1 << iota isDefinition isSharedOperationParam isOperationParam isOperationResponse isDefaultResponse isStatusCodeResponse ) values := []struct { Key string Flags KeyFlag PathItemRef spec.Ref PathRef spec.Ref Name string }{ { "#/paths/~1some~1where~1{id}/parameters/1/schema", isOperation | isSharedOperationParam, spec.Ref{}, spec.MustCreateRef("#/paths/~1some~1where~1{id}"), "", }, { "#/paths/~1some~1where~1{id}/get/parameters/2/schema", isOperation | isOperationParam, spec.MustCreateRef("#/paths/~1some~1where~1{id}/GET"), spec.MustCreateRef("#/paths/~1some~1where~1{id}"), "", }, { "#/paths/~1some~1where~1{id}/get/responses/default/schema", isOperation | isOperationResponse | isDefaultResponse, spec.MustCreateRef("#/paths/~1some~1where~1{id}/GET"), spec.MustCreateRef("#/paths/~1some~1where~1{id}"), "Default", }, { "#/paths/~1some~1where~1{id}/get/responses/200/schema", isOperation | isOperationResponse | isStatusCodeResponse, spec.MustCreateRef("#/paths/~1some~1where~1{id}/GET"), spec.MustCreateRef("#/paths/~1some~1where~1{id}"), "OK", }, { "#/definitions/namedAgain", isDefinition, spec.Ref{}, spec.Ref{}, "namedAgain", }, { "#/definitions/datedRecords/items/1", isDefinition, spec.Ref{}, spec.Ref{}, "datedRecords", }, { "#/definitions/datedRecords/items/1", isDefinition, spec.Ref{}, spec.Ref{}, "datedRecords", }, { "#/definitions/datedTaggedRecords/items/1", isDefinition, spec.Ref{}, spec.Ref{}, "datedTaggedRecords", }, { "#/definitions/datedTaggedRecords/additionalItems", isDefinition, spec.Ref{}, spec.Ref{}, "datedTaggedRecords", }, { "#/definitions/otherRecords/items", isDefinition, spec.Ref{}, spec.Ref{}, "otherRecords", }, { "#/definitions/tags/additionalProperties", isDefinition, spec.Ref{}, spec.Ref{}, "tags", }, { "#/definitions/namedThing/properties/name", isDefinition, spec.Ref{}, spec.Ref{}, "namedThing", }, } for i, v := range values { parts := sortref.KeyParts(v.Key) pref := parts.PathRef() piref := parts.PathItemRef() assert.Equal(t, v.PathRef.String(), pref.String(), "pathRef: %s at %d", v.Key, i) assert.Equal(t, v.PathItemRef.String(), piref.String(), "pathItemRef: %s at %d", v.Key, i) if v.Flags&isOperation != 0 { assert.True(t, parts.IsOperation(), "isOperation: %s at %d", v.Key, i) } else { assert.False(t, parts.IsOperation(), "isOperation: %s at %d", v.Key, i) } if v.Flags&isDefinition != 0 { assert.True(t, parts.IsDefinition(), "isDefinition: %s at %d", v.Key, i) assert.Equal(t, v.Name, parts.DefinitionName(), "definition name: %s at %d", v.Key, i) } else { assert.False(t, parts.IsDefinition(), "isDefinition: %s at %d", v.Key, i) if v.Name != "" { assert.Equal(t, v.Name, parts.ResponseName(), "response name: %s at %d", v.Key, i) } } if v.Flags&isOperationParam != 0 { assert.True(t, parts.IsOperationParam(), "isOperationParam: %s at %d", v.Key, i) } else { assert.False(t, parts.IsOperationParam(), "isOperationParam: %s at %d", v.Key, i) } if v.Flags&isSharedOperationParam != 0 { assert.True(t, parts.IsSharedOperationParam(), "isSharedOperationParam: %s at %d", v.Key, i) } else { assert.False(t, parts.IsSharedOperationParam(), "isSharedOperationParam: %s at %d", v.Key, i) } if v.Flags&isOperationResponse != 0 { assert.True(t, parts.IsOperationResponse(), "isOperationResponse: %s at %d", v.Key, i) } else { assert.False(t, parts.IsOperationResponse(), "isOperationResponse: %s at %d", v.Key, i) } if v.Flags&isDefaultResponse != 0 { assert.True(t, parts.IsDefaultResponse(), "isDefaultResponse: %s at %d", v.Key, i) } else { assert.False(t, parts.IsDefaultResponse(), "isDefaultResponse: %s at %d", v.Key, i) } if v.Flags&isStatusCodeResponse != 0 { assert.True(t, parts.IsStatusCodeResponse(), "isStatusCodeResponse: %s at %d", v.Key, i) } else { assert.False(t, parts.IsStatusCodeResponse(), "isStatusCodeResponse: %s at %d", v.Key, i) } } } func TestName_NamesFromKey(t *testing.T) { bp := filepath.Join("fixtures", "inline_schemas.yml") sp := antest.LoadOrFail(t, bp) values := []struct { Key string Names []string }{ {"#/paths/~1some~1where~1{id}/parameters/1/schema", []string{"GetSomeWhereID params body", "PostSomeWhereID params body"}}, {"#/paths/~1some~1where~1{id}/get/parameters/2/schema", []string{"GetSomeWhereID params body"}}, {"#/paths/~1some~1where~1{id}/get/responses/default/schema", []string{"GetSomeWhereID Default body"}}, {"#/paths/~1some~1where~1{id}/get/responses/200/schema", []string{"GetSomeWhereID OK body"}}, {"#/definitions/namedAgain", []string{"namedAgain"}}, {"#/definitions/datedTag/allOf/1", []string{"datedTag allOf 1"}}, {"#/definitions/datedRecords/items/1", []string{"datedRecords tuple 1"}}, {"#/definitions/datedTaggedRecords/items/1", []string{"datedTaggedRecords tuple 1"}}, {"#/definitions/datedTaggedRecords/additionalItems", []string{"datedTaggedRecords tuple additionalItems"}}, {"#/definitions/otherRecords/items", []string{"otherRecords items"}}, {"#/definitions/tags/additionalProperties", []string{"tags additionalProperties"}}, {"#/definitions/namedThing/properties/name", []string{"namedThing name"}}, } for i, v := range values { ptr, err := jsonpointer.New(definitionPtr(v.Key)[1:]) require.NoError(t, err) vv, _, err := ptr.Get(sp) require.NoError(t, err) switch tv := vv.(type) { case *spec.Schema: aschema, err := Schema(SchemaOpts{Schema: tv, Root: sp, BasePath: bp}) require.NoError(t, err) names := namesFromKey(sortref.KeyParts(v.Key), aschema, operations.AllOpRefsByRef(New(sp), nil)) assert.Equal(t, v.Names, names, "for %s at %d", v.Key, i) case spec.Schema: aschema, err := Schema(SchemaOpts{Schema: &tv, Root: sp, BasePath: bp}) require.NoError(t, err) names := namesFromKey(sortref.KeyParts(v.Key), aschema, operations.AllOpRefsByRef(New(sp), nil)) assert.Equal(t, v.Names, names, "for %s at %d", v.Key, i) default: assert.Fail(t, "unknown type", "got %T", vv) } } } func TestName_BuildNameWithReservedKeyWord(t *testing.T) { s := sortref.SplitKey([]string{"definitions", "fullview", "properties", "properties"}) startIdx := 2 segments := []string{"fullview"} newName := s.BuildName(segments, startIdx, partAdder(nil)) assert.Equal(t, "fullview properties", newName) s = sortref.SplitKey([]string{"definitions", "fullview", "properties", "properties", "properties", "properties", "properties", "properties"}) newName = s.BuildName(segments, startIdx, partAdder(nil)) assert.Equal(t, "fullview"+strings.Repeat(" properties", 3), newName) } func TestName_InlinedSchemas(t *testing.T) { values := []struct { Key string Location string Ref spec.Ref }{ {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/2/properties/name", "#/definitions/getSomeWhereIdParamsBodyRecordItems2/properties/name", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2Name"), }, {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/1", "#/definitions/getSomeWhereIdParamsBodyRecord/items/1", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems1"), }, {"#/paths/~1some~1where~1{id}/get/parameters/2/schema/properties/record/items/2", "#/definitions/getSomeWhereIdParamsBodyRecord/items/2", spec.MustCreateRef("#/definitions/getSomeWhereIdParamsBodyRecordItems2"), }, {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/2/properties/name", "#/definitions/getSomeWhereIdOKBodyRecordItems2/properties/name", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems2Name"), }, {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/1", "#/definitions/getSomeWhereIdOKBodyRecord/items/1", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems1"), }, {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record/items/2", "#/definitions/getSomeWhereIdOKBodyRecord/items/2", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecordItems2"), }, {"#/paths/~1some~1where~1{id}/get/responses/200/schema/properties/record", "#/definitions/getSomeWhereIdOKBody/properties/record", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBodyRecord"), }, {"#/paths/~1some~1where~1{id}/get/responses/200/schema", "#/paths/~1some~1where~1{id}/get/responses/200/schema", spec.MustCreateRef("#/definitions/getSomeWhereIdOKBody"), }, {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/2/properties/name", "#/definitions/getSomeWhereIdDefaultBodyRecordItems2/properties/name", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems2Name"), }, {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/1", "#/definitions/getSomeWhereIdDefaultBodyRecord/items/1", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems1"), }, {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record/items/2", "#/definitions/getSomeWhereIdDefaultBodyRecord/items/2", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecordItems2"), }, {"#/paths/~1some~1where~1{id}/get/responses/default/schema/properties/record", "#/definitions/getSomeWhereIdDefaultBody/properties/record", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBodyRecord"), }, {"#/paths/~1some~1where~1{id}/get/responses/default/schema", "#/paths/~1some~1where~1{id}/get/responses/default/schema", spec.MustCreateRef("#/definitions/getSomeWhereIdDefaultBody"), }, // maps: // {"#/definitions/nestedThing/properties/record/items/2/allOf/1/additionalProperties", // "#/definitions/nestedThingRecordItems2AllOf1/additionalProperties", // spec.MustCreateRef("#/definitions/nestedThingRecordItems2AllOf1AdditionalProperties"), // }, // {"#/definitions/nestedThing/properties/record/items/2/allOf/1", // "#/definitions/nestedThingRecordItems2/allOf/1", // spec.MustCreateRef("#/definitions/nestedThingRecordItems2AllOf1"), // }, {"#/definitions/nestedThing/properties/record/items/2/properties/name", "#/definitions/nestedThingRecordItems2/properties/name", spec.MustCreateRef("#/definitions/nestedThingRecordItems2Name"), }, {"#/definitions/nestedThing/properties/record/items/1", "#/definitions/nestedThingRecord/items/1", spec.MustCreateRef("#/definitions/nestedThingRecordItems1"), }, {"#/definitions/nestedThing/properties/record/items/2", "#/definitions/nestedThingRecord/items/2", spec.MustCreateRef("#/definitions/nestedThingRecordItems2"), }, {"#/definitions/datedRecords/items/1", "#/definitions/datedRecords/items/1", spec.MustCreateRef("#/definitions/datedRecordsItems1"), }, {"#/definitions/datedTaggedRecords/items/1", "#/definitions/datedTaggedRecords/items/1", spec.MustCreateRef("#/definitions/datedTaggedRecordsItems1"), }, {"#/definitions/namedThing/properties/name", "#/definitions/namedThing/properties/name", spec.MustCreateRef("#/definitions/namedThingName"), }, {"#/definitions/nestedThing/properties/record", "#/definitions/nestedThing/properties/record", spec.MustCreateRef("#/definitions/nestedThingRecord"), }, {"#/definitions/records/items/0", "#/definitions/records/items/0", spec.MustCreateRef("#/definitions/recordsItems0"), }, {"#/definitions/datedTaggedRecords/additionalItems", "#/definitions/datedTaggedRecords/additionalItems", spec.MustCreateRef("#/definitions/datedTaggedRecordsItemsAdditionalItems"), }, {"#/definitions/otherRecords/items", "#/definitions/otherRecords/items", spec.MustCreateRef("#/definitions/otherRecordsItems"), }, {"#/definitions/tags/additionalProperties", "#/definitions/tags/additionalProperties", spec.MustCreateRef("#/definitions/tagsAdditionalProperties"), }, } bp := filepath.Join("fixtures", "nested_inline_schemas.yml") sp := antest.LoadOrFail(t, bp) require.NoError(t, spec.ExpandSpec(sp, &spec.ExpandOptions{ RelativeBase: bp, SkipSchemas: true, })) require.NoError(t, nameInlinedSchemas(&FlattenOpts{ Spec: New(sp), BasePath: bp, })) for i, v := range values { ptr, err := jsonpointer.New(v.Location[1:]) require.NoErrorf(t, err, "at %d for %s", i, v.Key) vv, _, err := ptr.Get(sp) require.NoErrorf(t, err, "at %d for %s", i, v.Key) switch tv := vv.(type) { case *spec.Schema: assert.Equal(t, v.Ref.String(), tv.Ref.String(), "at %d for %s", i, v.Key) case spec.Schema: assert.Equal(t, v.Ref.String(), tv.Ref.String(), "at %d for %s", i, v.Key) case *spec.SchemaOrBool: var sRef spec.Ref if tv != nil && tv.Schema != nil { sRef = tv.Schema.Ref } assert.Equal(t, v.Ref.String(), sRef.String(), "at %d for %s", i, v.Key) case *spec.SchemaOrArray: var sRef spec.Ref if tv != nil && tv.Schema != nil { sRef = tv.Schema.Ref } assert.Equal(t, v.Ref.String(), sRef.String(), "at %d for %s", i, v.Key) default: assert.Fail(t, "unknown type", "got %T", vv) } } for k, rr := range New(sp).allSchemas { if strings.HasPrefix(k, "#/responses") || strings.HasPrefix(k, "#/parameters") { continue } if rr.Schema == nil || rr.Schema.Ref.String() != "" || rr.TopLevel { continue } asch, err := Schema(SchemaOpts{Schema: rr.Schema, Root: sp, BasePath: bp}) require.NoErrorf(t, err, "for key: %s", k) if !asch.IsSimpleSchema && !asch.IsArray && !asch.IsMap { assert.Fail(t, "not a top level schema", "for key: %s", k) } } } func TestFlattenSchema_UnitGuards(t *testing.T) { t.Parallel() parts := sortref.KeyParts("#/nowhere/arbitrary/pointer") res := GenLocation(parts) assert.Equal(t, "", res) } func definitionPtr(key string) string { if !strings.HasPrefix(key, "#/definitions") { return key } return strings.Join(strings.Split(key, "/")[:3], "/") }