diff --git a/README.md b/README.md index 3d7c319..48a9143 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,10 @@ Dynamix SDK - это библиотека, написанная на языке - [Создание универсального клиента](#создание-универсального-клиента) - [Пример создания универсального клиента](#пример-создания-универсального-клиента) - [Пример выполнения запроса](#пример-выполнения-запроса-4) + - [Проверка соответствия версии платформы и версии dynamix](#проверка-соответствия-версии-платформы-и-версии-dynamix) + - [Пример выполнения запроса](#пример-выполнения-запроса-5) + - [Создание mock клиента](#создание-mock-клиента) + - [Пример создания mock клиента](#пример-создания-mock-клиента) ## Установка @@ -1698,4 +1702,35 @@ func main(){ // Проверка соответствия версии checkInfo, err := client.Check() } +``` + +# Создание mock клиента + +Создание клиента происходит с помощью функции-строителя `NewMockDecortClient` из основного пакета `decort-sdk`. Функция принимает mock реализацию интерфейса interfaces.Calller, возвращает структуру `MockDecortClient`, с помощью которой можно производить unit тестирование API Decort SDK без подключения к серверу + +#### Пример создания mock клиента +```go +package unit_test + +import ( + "testing" + "go.uber.org/mock/gomock" + decortsdk "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12" +) + +// Пример юнит тестирования на моках +func TestClient(t *testing.T) { + ctrl := gomock.NewController(t) + // Создаем mock интерфейса Caller + mockCaller := decortsdk.NewMockCaller(ctrl) + // Создаем mock интерфейса DecortClient + mockClient := decortsdk.NewMockDecortClient(mockCaller) + // .... +} +``` +Пример юнит теста можно посмотреть в файле [samples/client/client_test.go](samples/client/client_test.go) + +При редактировании интерфеса interface.Caller необходимо перегенерировать Mock : +``` shell +make gen-mock ``` \ No newline at end of file diff --git a/client_mock.go b/client_mock.go new file mode 100644 index 0000000..5e7ab27 --- /dev/null +++ b/client_mock.go @@ -0,0 +1,32 @@ +package decortsdk + +import ( + "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12/pkg/cloudapi" + "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12/pkg/cloudbroker" + "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12/pkg/sdn" +) + +type MockDecortClient struct { + apiCaller *MockCaller +} + +func NewMockDecortClient(apiCaller *MockCaller) ClientInterface { + return &MockDecortClient{ + apiCaller: apiCaller, + } +} + +// CloudAPI builder +func (mdc *MockDecortClient) CloudAPI() *cloudapi.CloudAPI { + return cloudapi.New(mdc.apiCaller) +} + +// CloudBroker builder +func (mdc *MockDecortClient) CloudBroker() *cloudbroker.CloudBroker { + return cloudbroker.New(mdc.apiCaller) +} + +// SDN builder +func (mdc *MockDecortClient) SDN() *sdn.SDN { + return sdn.New(mdc.apiCaller) +} diff --git a/client_mock_gen.go b/client_mock_gen.go new file mode 100644 index 0000000..e5fec24 --- /dev/null +++ b/client_mock_gen.go @@ -0,0 +1,86 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: interfaces/caller.go +// +// Generated by this command: +// +// mockgen -package decortsdk -source interfaces/caller.go +// + +// Package decortsdk is a generated GoMock package. +package decortsdk + +import ( + context "context" + reflect "reflect" + + gomock "go.uber.org/mock/gomock" +) + +// MockCaller is a mock of Caller interface. +type MockCaller struct { + ctrl *gomock.Controller + recorder *MockCallerMockRecorder + isgomock struct{} +} + +// MockCallerMockRecorder is the mock recorder for MockCaller. +type MockCallerMockRecorder struct { + mock *MockCaller +} + +// NewMockCaller creates a new mock instance. +func NewMockCaller(ctrl *gomock.Controller) *MockCaller { + mock := &MockCaller{ctrl: ctrl} + mock.recorder = &MockCallerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockCaller) EXPECT() *MockCallerMockRecorder { + return m.recorder +} + +// DecortApiCall mocks base method. +func (m *MockCaller) DecortApiCall(ctx context.Context, method, url string, params any) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DecortApiCall", ctx, method, url, params) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DecortApiCall indicates an expected call of DecortApiCall. +func (mr *MockCallerMockRecorder) DecortApiCall(ctx, method, url, params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecortApiCall", reflect.TypeOf((*MockCaller)(nil).DecortApiCall), ctx, method, url, params) +} + +// DecortApiCallCtype mocks base method. +func (m *MockCaller) DecortApiCallCtype(ctx context.Context, method, url, ctype string, params any) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DecortApiCallCtype", ctx, method, url, ctype, params) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DecortApiCallCtype indicates an expected call of DecortApiCallCtype. +func (mr *MockCallerMockRecorder) DecortApiCallCtype(ctx, method, url, ctype, params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecortApiCallCtype", reflect.TypeOf((*MockCaller)(nil).DecortApiCallCtype), ctx, method, url, ctype, params) +} + +// DecortApiCallMP mocks base method. +func (m *MockCaller) DecortApiCallMP(ctx context.Context, method, url string, params any) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DecortApiCallMP", ctx, method, url, params) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DecortApiCallMP indicates an expected call of DecortApiCallMP. +func (mr *MockCallerMockRecorder) DecortApiCallMP(ctx, method, url, params any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecortApiCallMP", reflect.TypeOf((*MockCaller)(nil).DecortApiCallMP), ctx, method, url, params) +} diff --git a/go.mod b/go.mod index 779b0fc..0a56cef 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,20 @@ go 1.24.0 require ( github.com/go-playground/validator/v10 v10.28.0 github.com/google/go-querystring v1.1.0 + github.com/joho/godotenv v1.5.1 + github.com/stretchr/testify v1.9.0 + go.uber.org/mock v0.6.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/crypto v0.42.0 // indirect golang.org/x/sys v0.36.0 // indirect golang.org/x/text v0.29.0 // indirect diff --git a/go.sum b/go.sum index c966c16..905981e 100644 --- a/go.sum +++ b/go.sum @@ -15,12 +15,16 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= diff --git a/pkg/sdn/logicalports/create.go b/pkg/sdn/logicalports/create.go index 11ca1a9..6971b43 100644 --- a/pkg/sdn/logicalports/create.go +++ b/pkg/sdn/logicalports/create.go @@ -11,14 +11,6 @@ import ( // CreateRequest struct to create logical port type CreateRequest struct { - // ID of the logical port - // Required: true - LogicalPortID string `url:"logical_port_id" json:"logical_port_id" validate:"required"` - - // ID of the version - // Required: true - VersionID uint64 `url:"version_id" json:"version_id" validate:"required"` - // ID of the access group // Required: true AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"` diff --git a/samples/client/client.go b/samples/client/client.go new file mode 100644 index 0000000..bf29a18 --- /dev/null +++ b/samples/client/client.go @@ -0,0 +1,42 @@ +package client + +import ( + "context" + "fmt" + + "errors" + + decortsdk "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12" + "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12/pkg/cloudbroker/compute" +) + +type Migrator interface { + Migrate(ctxOrigin context.Context, vmUUID, to uint64) (bool, error) +} + +type migrator struct { + cfg *Config + client decortsdk.ClientInterface +} + +func NewMigrator(cfg *Config, c decortsdk.ClientInterface) Migrator { + return &migrator{ + cfg: cfg, + client: c, + } +} + +func (m *migrator) Migrate(ctxOrigin context.Context, dxVMID, stackID uint64) (bool, error) { + req := compute.MigrateRequest{ + ComputeID: dxVMID, + TargetStackID: stackID, + Force: false, + } + ctx, cancel := context.WithTimeout(ctxOrigin, m.cfg.QueryTimeout) + ok, err := m.client.CloudBroker().Compute().Migrate(ctx, req) + cancel() + if err != nil { + return false, errors.Join(err, fmt.Errorf("Migrate VM %d to Node %d", dxVMID, stackID)) + } + return ok, nil +} diff --git a/samples/client/client_test.go b/samples/client/client_test.go new file mode 100644 index 0000000..faf7792 --- /dev/null +++ b/samples/client/client_test.go @@ -0,0 +1,76 @@ +package client_test + +import ( + "context" + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uber.org/mock/gomock" + decortsdk "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12" + "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12/pkg/cloudbroker/compute" + "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12/samples/client" +) + +// Пример юнит тестирования на моках +func TestClient(t *testing.T) { + ctrl := gomock.NewController(t) + // Создаем мок инстанс интерфейса Caller + mockCaller := decortsdk.NewMockCaller(ctrl) + // Создаем мок инстанс интерфейса DecortClient + mockClient := decortsdk.NewMockDecortClient(mockCaller) + + dxVMID := uint64(100500) + VMID := "vm-100500" + + listComputes := &compute.ListComputes{ + Data: []compute.ItemCompute{ + { + InfoCompute: compute.InfoCompute{ + ID: dxVMID, + ReferenceID: VMID, + }, + }, + }} + + b, err := json.Marshal(listComputes) + assert.NoError(t, err) + // Подготавливаем мок для вызова метода CloudBroker().Compute().List() + mockCaller.EXPECT().DecortApiCall(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Eq(compute.ListRequest{})).Return(b, nil).AnyTimes() + listComputesRet, err := mockClient.CloudBroker().Compute().List(context.Background(), compute.ListRequest{}) + assert.NoError(t, err) + + assert.Equal(t, listComputes, listComputesRet) + +} + +// Пример юнит тестирования на моках +func TestMigrator(t *testing.T) { + ctrl := gomock.NewController(t) + // Создаем мок инстанс интерфейса Caller + mockCaller := decortsdk.NewMockCaller(ctrl) + // Создаем мок инстанс интерфейса interfaces.ClientInterface + mockClient := decortsdk.NewMockDecortClient(mockCaller) + + // Передаем мок инстанс интерфейса interfaces.ClientInterface в конструктор Migrator + migrator := client.NewMigrator(&client.Config{QueryTimeout: time.Second}, mockClient) + + b, err := json.Marshal(true) + assert.NoError(t, err) + + dxVMID := uint64(100500) + stackID := uint64(100501) + + // Записываем поведение клиента + mockCaller.EXPECT().DecortApiCall(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Eq(compute.AsyncWrapperMigrateRequest{ + MigrateRequest: compute.MigrateRequest{ + ComputeID: dxVMID, + TargetStackID: stackID, + }, + SyncMode: true})).Return(b, nil).AnyTimes() + + ok, err := migrator.Migrate(context.Background(), dxVMID, stackID) + assert.NoError(t, err) + assert.True(t, ok) +} diff --git a/samples/client/config.go b/samples/client/config.go new file mode 100644 index 0000000..f41270e --- /dev/null +++ b/samples/client/config.go @@ -0,0 +1,7 @@ +package client + +import "time" + +type Config struct { + QueryTimeout time.Duration +} diff --git a/universal-client.go b/universal-client.go index 13f6b0a..329d7b4 100644 --- a/universal-client.go +++ b/universal-client.go @@ -7,11 +7,13 @@ import ( "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12/config" "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12/pkg/cloudapi" "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12/pkg/cloudbroker" + "repository.basistech.ru/BASIS/dynamix-golang-sdk/v12/pkg/sdn" ) type ClientInterface interface { CloudAPI() *cloudapi.CloudAPI CloudBroker() *cloudbroker.CloudBroker + SDN() *sdn.SDN } func NewUniversal(cfg config.UniversalConfig) (ClientInterface, error) {