diff --git a/tools/cli/Makefile b/tools/cli/Makefile index 8dc6f04..4453dda 100644 --- a/tools/cli/Makefile +++ b/tools/cli/Makefile @@ -24,6 +24,7 @@ deps: ## Download go module dependencies .PHONY: devtools devtools: ## Install dev tools @echo "==> Installing dev tools..." + go install go.uber.org/mock/mockgen@latest curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin $(GOLANGCI_VERSION) .PHONY: setup @@ -57,6 +58,10 @@ fix-lint: ## Fix linting errors list: ## List all make targets @${MAKE} -pRrn : -f $(MAKEFILE_LIST) 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | sort +.PHONY: gen-mocks +gen-mocks: ## Generate mocks + @echo "==> Generating mocks" + go generate ./internal... .PHONY: help .DEFAULT_GOAL := help diff --git a/tools/cli/go.mod b/tools/cli/go.mod index 816185e..42f46ba 100644 --- a/tools/cli/go.mod +++ b/tools/cli/go.mod @@ -4,9 +4,11 @@ go 1.22.1 require ( github.com/getkin/kin-openapi v0.123.0 + github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.9.0 github.com/tufin/oasdiff v1.10.11 + go.uber.org/mock v0.4.0 ) require ( @@ -24,5 +26,6 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/ugorji/go/codec v1.2.11 // indirect github.com/yargevad/filepathx v1.0.0 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/tools/cli/go.sum b/tools/cli/go.sum index f98dcb9..98ca60a 100644 --- a/tools/cli/go.sum +++ b/tools/cli/go.sum @@ -36,6 +36,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= @@ -48,8 +50,12 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yargevad/filepathx v1.0.0 h1:SYcT+N3tYGi+NvazubCNlvgIPbzAk7i7y2dwg3I5FYc= github.com/yargevad/filepathx v1.0.0/go.mod h1:BprfX/gpYNJHJfc35GjRRpVcwWXS89gGulUIU5tK3tA= +go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= +go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a h1:Q8/wZp0KX97QFTc2ywcOE0YRjZPVIx+MXInMzdvQqcA= golang.org/x/exp v0.0.0-20240119083558-1b970713d09a/go.mod h1:idGWGoKP1toJGkd5/ig9ZLuPcZBC3ewk7SzmH0uou08= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/tools/cli/internal/cli/merge/merge.go b/tools/cli/internal/cli/merge/merge.go index 2d849f9..4bf1abc 100644 --- a/tools/cli/internal/cli/merge/merge.go +++ b/tools/cli/internal/cli/merge/merge.go @@ -17,11 +17,11 @@ package merge import ( "fmt" "log" - "os" "github.com/mongodb/openapi/tools/cli/internal/cli/flag" "github.com/mongodb/openapi/tools/cli/internal/cli/usage" "github.com/mongodb/openapi/tools/cli/internal/openapi" + "github.com/spf13/afero" "github.com/spf13/cobra" ) @@ -31,12 +31,13 @@ const ( type Opts struct { Merger openapi.Merger + fs afero.Fs basePath string outputPath string externalPaths []string } -func (o *Opts) Run(_ []string) error { +func (o *Opts) Run() error { federated, err := o.Merger.MergeOpenAPISpecs(o.externalPaths) if err != nil { return err @@ -65,7 +66,7 @@ func (o *Opts) PreRunE(_ []string) error { } func (o *Opts) saveFile(data []byte) error { - if err := os.WriteFile(o.outputPath, data, 0o600); err != nil { + if err := afero.WriteFile(o.fs, o.outputPath, data, 0o600); err != nil { return err } @@ -76,7 +77,9 @@ func (o *Opts) saveFile(data []byte) error { // Builder builds the merge command with the following signature: // merge -b base-oas -e external-oas-1 -e external-oas-2 func Builder() *cobra.Command { - opts := &Opts{} + opts := &Opts{ + fs: afero.NewOsFs(), + } cmd := &cobra.Command{ Use: "merge -b base-spec [-e spec]...", @@ -85,8 +88,8 @@ func Builder() *cobra.Command { PreRunE: func(_ *cobra.Command, args []string) error { return opts.PreRunE(args) }, - RunE: func(_ *cobra.Command, args []string) error { - return opts.Run(args) + RunE: func(_ *cobra.Command, _ []string) error { + return opts.Run() }, } diff --git a/tools/cli/internal/cli/merge/merge_test.go b/tools/cli/internal/cli/merge/merge_test.go index 856cfb6..cad106f 100644 --- a/tools/cli/internal/cli/merge/merge_test.go +++ b/tools/cli/internal/cli/merge/merge_test.go @@ -17,10 +17,45 @@ package merge import ( "testing" + "github.com/getkin/kin-openapi/openapi3" "github.com/mongodb/openapi/tools/cli/internal/cli/flag" "github.com/mongodb/openapi/tools/cli/internal/cli/validator" + "github.com/mongodb/openapi/tools/cli/internal/openapi" + "github.com/spf13/afero" + "github.com/tufin/oasdiff/load" + "go.uber.org/mock/gomock" ) +func TestSuccessfulMerge_Run(t *testing.T) { + ctrl := gomock.NewController(t) + mockMergerStore := openapi.NewMockMerger(ctrl) + fs := afero.NewMemMapFs() + externalPaths := []string{"external.json"} + opts := &Opts{ + Merger: mockMergerStore, + basePath: "base.json", + outputPath: "foas.json", + externalPaths: externalPaths, + fs: fs, + } + + response := &load.SpecInfo{ + Spec: &openapi3.T{}, + Url: "test", + Version: "3.0.1", + } + + mockMergerStore. + EXPECT(). + MergeOpenAPISpecs(opts.externalPaths). + Return(response, nil). + Times(1) + + if err := opts.Run(); err != nil { + t.Fatalf("Run() unexpected error: %v", err) + } +} + func TestCreateBuilder(t *testing.T) { validator.ValidateSubCommandsAndFlags( t, diff --git a/tools/cli/internal/openapi/errors/path_conflict_error.go b/tools/cli/internal/openapi/errors/path_conflict_error.go index b01db5a..4493b51 100644 --- a/tools/cli/internal/openapi/errors/path_conflict_error.go +++ b/tools/cli/internal/openapi/errors/path_conflict_error.go @@ -1,3 +1,17 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package errors import "fmt" diff --git a/tools/cli/internal/openapi/mock_openapi.go b/tools/cli/internal/openapi/mock_openapi.go new file mode 100644 index 0000000..4b0dfb1 --- /dev/null +++ b/tools/cli/internal/openapi/mock_openapi.go @@ -0,0 +1,93 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: github.com/mongodb/openapi/tools/cli/internal/openapi (interfaces: Parser,Merger) +// +// Generated by this command: +// +// mockgen -destination=../openapi/mock_openapi.go -package=openapi github.com/mongodb/openapi/tools/cli/internal/openapi Parser,Merger +// + +// Package openapi is a generated GoMock package. +package openapi + +import ( + reflect "reflect" + + load "github.com/tufin/oasdiff/load" + gomock "go.uber.org/mock/gomock" +) + +// MockParser is a mock of Parser interface. +type MockParser struct { + ctrl *gomock.Controller + recorder *MockParserMockRecorder +} + +// MockParserMockRecorder is the mock recorder for MockParser. +type MockParserMockRecorder struct { + mock *MockParser +} + +// NewMockParser creates a new mock instance. +func NewMockParser(ctrl *gomock.Controller) *MockParser { + mock := &MockParser{ctrl: ctrl} + mock.recorder = &MockParserMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockParser) EXPECT() *MockParserMockRecorder { + return m.recorder +} + +// CreateOpenAPISpecFromPath mocks base method. +func (m *MockParser) CreateOpenAPISpecFromPath(arg0 string) (*load.SpecInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateOpenAPISpecFromPath", arg0) + ret0, _ := ret[0].(*load.SpecInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CreateOpenAPISpecFromPath indicates an expected call of CreateOpenAPISpecFromPath. +func (mr *MockParserMockRecorder) CreateOpenAPISpecFromPath(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateOpenAPISpecFromPath", reflect.TypeOf((*MockParser)(nil).CreateOpenAPISpecFromPath), arg0) +} + +// MockMerger is a mock of Merger interface. +type MockMerger struct { + ctrl *gomock.Controller + recorder *MockMergerMockRecorder +} + +// MockMergerMockRecorder is the mock recorder for MockMerger. +type MockMergerMockRecorder struct { + mock *MockMerger +} + +// NewMockMerger creates a new mock instance. +func NewMockMerger(ctrl *gomock.Controller) *MockMerger { + mock := &MockMerger{ctrl: ctrl} + mock.recorder = &MockMergerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockMerger) EXPECT() *MockMergerMockRecorder { + return m.recorder +} + +// MergeOpenAPISpecs mocks base method. +func (m *MockMerger) MergeOpenAPISpecs(arg0 []string) (*load.SpecInfo, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "MergeOpenAPISpecs", arg0) + ret0, _ := ret[0].(*load.SpecInfo) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// MergeOpenAPISpecs indicates an expected call of MergeOpenAPISpecs. +func (mr *MockMergerMockRecorder) MergeOpenAPISpecs(arg0 any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "MergeOpenAPISpecs", reflect.TypeOf((*MockMerger)(nil).MergeOpenAPISpecs), arg0) +} diff --git a/tools/cli/internal/openapi/oasdiff.go b/tools/cli/internal/openapi/oasdiff.go index 54bf339..4ea4aa7 100644 --- a/tools/cli/internal/openapi/oasdiff.go +++ b/tools/cli/internal/openapi/oasdiff.go @@ -1,8 +1,20 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package openapi import ( - "log" - "github.com/mongodb/openapi/tools/cli/internal/openapi/errors" "github.com/tufin/oasdiff/diff" "github.com/tufin/oasdiff/load" @@ -16,46 +28,6 @@ type OasDiff struct { parser Parser } -func NewOasDiff(base string) (*OasDiff, error) { - parser := NewOpenAPI3() - baseSpec, err := parser.CreateOpenAPISpecFromPath(base) - if err != nil { - return nil, err - } - - return &OasDiff{ - base: baseSpec, - parser: parser, - config: &diff.Config{ - IncludePathParams: true, - }, - }, nil -} - -func (o *OasDiff) MergeOpenAPISpecs(paths []string) (*load.SpecInfo, error) { - for _, p := range paths { - spec, err := o.parser.CreateOpenAPISpecFromPath(p) - if err != nil { - return nil, err - } - - specDiff, err := diff.Get(o.config, o.base.Spec, spec.Spec) - if err != nil { - log.Fatalf("error in calculating the diff of the specs: %s", err) - return nil, err - } - - o.specDiff = specDiff - o.external = spec - err = o.mergeSpecIntoBase() - if err != nil { - return nil, err - } - } - - return o.base, nil -} - func (o OasDiff) mergeSpecIntoBase() error { return o.mergePaths() } diff --git a/tools/cli/internal/openapi/openapi.go b/tools/cli/internal/openapi/openapi.go index 5bdb959..07922a6 100644 --- a/tools/cli/internal/openapi/openapi.go +++ b/tools/cli/internal/openapi/openapi.go @@ -1,13 +1,71 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package openapi +//go:generate mockgen -destination=../openapi/mock_openapi.go -package=openapi github.com/mongodb/openapi/tools/cli/internal/openapi Parser,Merger import ( + "log" + + "github.com/tufin/oasdiff/diff" "github.com/tufin/oasdiff/load" ) +type Parser interface { + CreateOpenAPISpecFromPath(string) (*load.SpecInfo, error) +} + type Merger interface { MergeOpenAPISpecs([]string) (*load.SpecInfo, error) } -type Parser interface { - CreateOpenAPISpecFromPath(string) (*load.SpecInfo, error) +func (o *OasDiff) MergeOpenAPISpecs(paths []string) (*load.SpecInfo, error) { + for _, p := range paths { + spec, err := o.parser.CreateOpenAPISpecFromPath(p) + if err != nil { + return nil, err + } + + specDiff, err := diff.Get(o.config, o.base.Spec, spec.Spec) + if err != nil { + log.Fatalf("error in calculating the diff of the specs: %s", err) + return nil, err + } + + o.specDiff = specDiff + o.external = spec + err = o.mergeSpecIntoBase() + if err != nil { + return nil, err + } + } + + return o.base, nil +} + +func NewOasDiff(base string) (*OasDiff, error) { + parser := NewOpenAPI3() + baseSpec, err := parser.CreateOpenAPISpecFromPath(base) + if err != nil { + return nil, err + } + + return &OasDiff{ + base: baseSpec, + parser: parser, + config: &diff.Config{ + IncludePathParams: true, + }, + }, nil } diff --git a/tools/cli/internal/openapi/openapi3.go b/tools/cli/internal/openapi/openapi3.go index 8e858f8..011386f 100644 --- a/tools/cli/internal/openapi/openapi3.go +++ b/tools/cli/internal/openapi/openapi3.go @@ -1,3 +1,17 @@ +// Copyright 2024 MongoDB Inc +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package openapi import (