diff --git a/fixtures/errors/fixture-unexpandable-2.yaml b/fixtures/errors/fixture-unexpandable-2.yaml index f1358c7..e59f206 100644 --- a/fixtures/errors/fixture-unexpandable-2.yaml +++ b/fixtures/errors/fixture-unexpandable-2.yaml @@ -32,6 +32,22 @@ paths: description: OK schema: $ref: '#/thisIs/anAbitrary/jsonPointer/toNowhere' + /wrong: + get: + operationId: target type mismatch + responses: + '200': + description: OK + schema: + $ref: '#/parameters/someWhere' + /wrongcode: + get: + operationId: target type mismatch + responses: + 'two-hundred': + description: OK + schema: + type: string definitions: somePlace: type: string diff --git a/flatten_test.go b/flatten_test.go index 3cad8d6..4d34933 100644 --- a/flatten_test.go +++ b/flatten_test.go @@ -863,13 +863,13 @@ func TestOperationIDs(t *testing.T) { an := New(sp) require.NoError(t, Flatten(FlattenOpts{Spec: an, BasePath: bp, Verbose: false, Minimal: false, RemoveUnused: false})) - res := operations.GatherOperations(New(sp), []string{"getSomeWhere", "getSomeWhereElse"}) - _, ok := res["getSomeWhere"] - assert.Truef(t, ok, "Expected to find operation") - _, ok = res["getSomeWhereElse"] - assert.Truef(t, ok, "Expected to find operation") - _, ok = res["postSomeWhere"] - assert.Falsef(t, ok, "Did not expect to find operation") + t.Run("should GatherOperations", func(t *testing.T) { + res := operations.GatherOperations(New(sp), []string{"getSomeWhere", "getSomeWhereElse"}) + + assert.Containsf(t, res, "getSomeWhere", "expected to find operation") + assert.Containsf(t, res, "getSomeWhereElse", "expected to find operation") + assert.NotContainsf(t, res, "postSomeWhere", "did not expect to find operation") + }) op, ok := an.OperationFor("GET", "/some/where/else") assert.True(t, ok) diff --git a/internal/antest/helpers.go b/internal/antest/helpers.go index 42341d7..bcefe63 100644 --- a/internal/antest/helpers.go +++ b/internal/antest/helpers.go @@ -45,11 +45,11 @@ func initPathLoader() { } } -// LoadSpec loads a json a yaml spec +// LoadSpec loads a json or a yaml spec func LoadSpec(path string) (*spec.Swagger, error) { oncePathLoader.Do(initPathLoader) - data, err := swag.YAMLDoc(path) + data, err := spec.PathLoader(path) if err != nil { return nil, err } diff --git a/internal/antest/helpers_test.go b/internal/antest/helpers_test.go new file mode 100644 index 0000000..97fae6b --- /dev/null +++ b/internal/antest/helpers_test.go @@ -0,0 +1,104 @@ +package antest + +import ( + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestLongTestEnabled(t *testing.T) { + t.Run("should be false by default", func(t *testing.T) { + require.False(t, LongTestsEnabled()) + }) +} + +func TestLoadSpecErrorCases(t *testing.T) { + t.Run("should not load invalid path", func(t *testing.T) { + _, err := LoadSpec("nowhere.json") + require.Error(t, err) + }) + + t.Run("should not load invalid YAML", func(t *testing.T) { + invalidYAMLFile, clean := prepareBadDoc(t, "yaml", true) + t.Cleanup(clean) + + _, err := LoadSpec(invalidYAMLFile) + require.Error(t, err) + }) + + t.Run("should not load invalid JSON", func(t *testing.T) { + invalidJSONFile, clean := prepareBadDoc(t, "json", true) + t.Cleanup(clean) + + _, err := LoadSpec(invalidJSONFile) + require.Error(t, err) + }) + + t.Run("should not load invalid spec", func(t *testing.T) { + invalidJSONFile, clean := prepareBadDoc(t, "json", false) + t.Cleanup(clean) + + _, err := LoadSpec(invalidJSONFile) + require.Error(t, err) + }) +} + +func prepareBadDoc(t testing.TB, kind string, invalidFormat bool) (string, func()) { + t.Helper() + + var ( + file string + data []byte + ) + + switch kind { + case "yaml", "yml": + f, err := os.CreateTemp("", "*.yaml") + require.NoError(t, err) + file = f.Name() + + if invalidFormat { + data = []byte(`-- +zig: + zag 3, 4 +`) + } else { + data = []byte(`-- +swagger: 2 +info: + title: true +`) + } + + case "json": + f, err := os.CreateTemp("", "*.json") + require.NoError(t, err) + file = f.Name() + + if invalidFormat { + data = []byte(`{ +"zig": { + "zag" +}`) + } else { + data = []byte(`{ +"swagger": 2 +"info": { + "title": true +} +}`) + } + + default: + panic("supports only yaml or json") + } + + require.NoError(t, + os.WriteFile(file, data, 0600), + ) + + return file, func() { + _ = os.RemoveAll(file) + } +} diff --git a/internal/flatten/operations/operations_test.go b/internal/flatten/operations/operations_test.go index 597e454..ac179a9 100644 --- a/internal/flatten/operations/operations_test.go +++ b/internal/flatten/operations/operations_test.go @@ -1,5 +1,67 @@ package operations import ( + "testing" + _ "github.com/go-openapi/analysis/internal/antest" + "github.com/go-openapi/spec" + "github.com/stretchr/testify/require" ) + +var _ Provider = mockOperationsProvider{} + +type mockOperationsProvider struct { + give map[string]map[string]*spec.Operation +} + +func (m mockOperationsProvider) Operations() map[string]map[string]*spec.Operation { + return m.give +} + +func TestGatherOperations(t *testing.T) { + t.Run("should handle empty operation IDs", func(_ *testing.T) { + m := mockOperationsProvider{ + give: map[string]map[string]*spec.Operation{ + "get": { + "/pth1": { + OperationProps: spec.OperationProps{ + ID: "", + Description: "ok", + }, + }, + }, + }, + } + + res := GatherOperations(m, nil) + require.Contains(t, res, "GetPth1") + }) + + t.Run("should handle duplicate operation IDs (when spec validation is skipped)", func(_ *testing.T) { + m := mockOperationsProvider{ + give: map[string]map[string]*spec.Operation{ + "get": { + "/pth1": { + OperationProps: spec.OperationProps{ + ID: "id1", + Description: "ok", + }, + }, + }, + "post": { + "/pth2": { + OperationProps: spec.OperationProps{ + ID: "id1", + Description: "ok", + }, + }, + }, + }, + } + + res := GatherOperations(m, nil) + require.Contains(t, res, "id1") + require.NotContains(t, res, "GetPth1") + require.Contains(t, res, "PostPth2") + }) +} diff --git a/internal/flatten/replace/replace_test.go b/internal/flatten/replace/replace_test.go index 1542429..15ca4e1 100644 --- a/internal/flatten/replace/replace_test.go +++ b/internal/flatten/replace/replace_test.go @@ -93,33 +93,64 @@ func TestRewriteSchemaRef(t *testing.T) { } } -//nolint:dogsled func TestReplace_ErrorHandling(t *testing.T) { t.Parallel() - const wantedFailure = "Expected a failure" + const wantedFailure = "expected a failure" bp := filepath.Join("..", "..", "..", "fixtures", "errors", "fixture-unexpandable-2.yaml") - - // reload original spec sp := antest.LoadOrFail(t, bp) - require.Errorf(t, RewriteSchemaToRef(sp, "#/invalidPointer/key", spec.Ref{}), wantedFailure) - - require.Errorf(t, rewriteParentRef(sp, "#/invalidPointer/key", spec.Ref{}), wantedFailure) - - require.Errorf(t, UpdateRef(sp, "#/invalidPointer/key", spec.Ref{}), wantedFailure) - - require.Errorf(t, UpdateRefWithSchema(sp, "#/invalidPointer/key", &spec.Schema{}), wantedFailure) - - _, _, err := getPointerFromKey(sp, "#/invalidPointer/key") - require.Errorf(t, err, wantedFailure) - - _, _, err = getPointerFromKey(sp, "--->#/invalidJsonPointer") - require.Errorf(t, err, wantedFailure) - - _, _, _, err = getParentFromKey(sp, "#/invalidPointer/key") - require.Errorf(t, err, wantedFailure) - - _, _, _, err = getParentFromKey(sp, "--->#/invalidJsonPointer") - require.Errorf(t, err, wantedFailure) + t.Run("with invalid $ref", func(t *testing.T) { + const ref = "#/invalidPointer/key" + + require.Errorf(t, RewriteSchemaToRef(sp, ref, spec.Ref{}), wantedFailure) + require.Errorf(t, rewriteParentRef(sp, ref, spec.Ref{}), wantedFailure) + require.Errorf(t, UpdateRef(sp, ref, spec.Ref{}), wantedFailure) + require.Errorf(t, UpdateRefWithSchema(sp, ref, &spec.Schema{}), wantedFailure) + _, _, err := getPointerFromKey(sp, ref) + require.Errorf(t, err, wantedFailure) + + _, _, _, err = getParentFromKey(sp, ref) + require.Errorf(t, err, wantedFailure) + }) + + t.Run("with invalid jsonpointer formatting", func(t *testing.T) { + const pointer = "-->#/invalidJsonPointer" + + _, _, err := getPointerFromKey(sp, pointer) + require.Errorf(t, err, wantedFailure) + + _, _, _, err = getParentFromKey(sp, pointer) + require.Errorf(t, err, wantedFailure) + }) + + t.Run("with invalid target", func(t *testing.T) { + require.Errorf(t, RewriteSchemaToRef(sp, "#/parameters/someWhere", spec.Ref{}), wantedFailure) + }) + + t.Run("with invalid response target", func(t *testing.T) { + const ref = "#/paths/~1wrong/get/responses/200" + require.Errorf(t, RewriteSchemaToRef(sp, ref, spec.Ref{}), wantedFailure) + }) + + t.Run("with invalid response code", func(t *testing.T) { + const ref = "#/paths/~1wrongcode/get/responses/two-hundred" + require.Errorf(t, rewriteParentRef(sp, ref, spec.Ref{}), wantedFailure) + }) + + t.Run("with panic case", func(t *testing.T) { + t.Run("should not call with types other than *Schema or *Swagger", func(t *testing.T) { + require.Panics(t, func() { + getPointerFromKey("oops", "#/key") + }) + + require.Panics(t, func() { + getParentFromKey("oops", "#/key") + }) + + require.Panics(t, func() { + UpdateRef("oops", "#/key", spec.Ref{}) + }) + }) + }) }