diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 95fb2b1..7dc7335 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -17,5 +17,5 @@ jobs: - name: Run linter uses: golangci/golangci-lint-action@v6 with: - version: latest + version: v1.55.2 args: --timeout 3m diff --git a/CHANGELOG.md b/CHANGELOG.md index dd8ec4c..7e285a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Coinbase Go SDK Changelog +## [0.0.2] - 2024-08-27 + +### Fixed + +- Fixed a bug where we weren't handling the api returned validators properly resulting in an index out of range error. + ## [0.0.1] - 2024-08-23 ### Added diff --git a/Makefile b/Makefile index 700c6b0..5151773 100644 --- a/Makefile +++ b/Makefile @@ -22,8 +22,9 @@ test-coverage: .PHONY: mocks mocks: - mockery --disable-version-string --name StakeAPI --keeptree --dir gen/client --output pkg/mocks mockery --disable-version-string --name AssetsAPI --keeptree --dir gen/client --output pkg/mocks + mockery --disable-version-string --name StakeAPI --keeptree --dir gen/client --output pkg/mocks + mockery --disable-version-string --name ValidatorsAPI --keeptree --dir gen/client --output pkg/mocks .PHONY: docs docs: diff --git a/pkg/coinbase/validator.go b/pkg/coinbase/validator.go index b1bbc21..6dfed94 100644 --- a/pkg/coinbase/validator.go +++ b/pkg/coinbase/validator.go @@ -8,21 +8,21 @@ import ( ) type Validator struct { - validator client.Validator + model client.Validator } func NewValidator(validator client.Validator) Validator { return Validator{ - validator: validator, + model: validator, } } func (v Validator) ID() string { - return v.validator.ValidatorId + return v.model.ValidatorId } func (v Validator) Status() client.ValidatorStatus { - return v.validator.Status + return v.model.Status } func (v Validator) ToString() string { @@ -43,7 +43,7 @@ func (c *Client) ListValidators(ctx context.Context, networkId string, assetId s return nil, err } - validators := make([]Validator, 0, len(validatorList.GetData())) + validators := make([]Validator, len(validatorList.GetData())) for i, validator := range validatorList.GetData() { validators[i] = NewValidator(validator) } diff --git a/pkg/coinbase/validators_test.go b/pkg/coinbase/validators_test.go new file mode 100644 index 0000000..e794d4a --- /dev/null +++ b/pkg/coinbase/validators_test.go @@ -0,0 +1,146 @@ +package coinbase + +import ( + "context" + "fmt" + "testing" + + api "github.com/coinbase/coinbase-sdk-go/gen/client" + "github.com/coinbase/coinbase-sdk-go/pkg/mocks" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" +) + +type ValidatorSuite struct { + suite.Suite + + mockValidatorsAPI *mocks.ValidatorsAPI + client *Client +} + +func TestValidatorTestSuite(t *testing.T) { + suite.Run(t, new(ValidatorSuite)) +} + +func (s *ValidatorSuite) SetupTest() { + s.mockValidatorsAPI = mocks.NewValidatorsAPI(s.T()) + s.client = &Client{client: &api.APIClient{ + ValidatorsAPI: s.mockValidatorsAPI, + }} +} + +func (s *ValidatorSuite) TearDownTest() { + s.mockValidatorsAPI.AssertExpectations(s.T()) +} + +func (s *ValidatorSuite) TestListValidators_Success() { + ctx := context.Background() + networkId := "test-network" + assetId := "test-asset" + + mockValidators := &api.ValidatorList{ + Data: []api.Validator{ + { + ValidatorId: "validator-1", + Status: api.VALIDATORSTATUS_ACTIVE, + }, + }, + } + + s.mockSuccessfulListValidators(ctx, networkId, assetId, mockValidators) + + validators, err := s.client.ListValidators(ctx, networkId, assetId) + + s.Assert().NoError(err) + s.Len(validators, 1) + s.Equal("validator-1", validators[0].ID()) + s.Equal(api.VALIDATORSTATUS_ACTIVE, validators[0].Status()) +} + +func (s *ValidatorSuite) TestListValidators_Failure() { + ctx := context.Background() + networkId := "test-network" + assetId := "test-asset" + errorMessage := "some error calling api" + + s.mockFailedListValidators(ctx, networkId, assetId, fmt.Errorf(errorMessage)) + + validators, err := s.client.ListValidators(ctx, networkId, assetId) + + s.Assert().Nil(validators) + s.EqualError(err, errorMessage) +} + +func (s *ValidatorSuite) TestGetValidator_Success() { + ctx := context.Background() + networkId := "test-network" + assetId := "test-asset" + validatorId := "validator-1" + + s.mockSuccessfulGetValidator(ctx, networkId, assetId, validatorId, &api.Validator{ + ValidatorId: validatorId, + Status: api.VALIDATORSTATUS_ACTIVE, + }) + + validator, err := s.client.GetValidator(ctx, networkId, assetId, validatorId) + + s.Assert().NoError(err) + s.Equal(validatorId, validator.ID()) + s.Equal(api.VALIDATORSTATUS_ACTIVE, validator.Status()) + s.Equal("Validator { Id: 'validator-1' Status: 'active' }", validator.ToString()) +} + +func (s *ValidatorSuite) TestGetValidator_Failure() { + ctx := context.Background() + networkId := "test-network" + assetId := "test-asset" + validatorId := "validator-1" + errorMessage := "some error calling api" + + s.mockFailedGetValidator(ctx, networkId, assetId, validatorId, fmt.Errorf(errorMessage)) + + validator, err := s.client.GetValidator(ctx, networkId, assetId, validatorId) + + s.Assert().Empty(validator) + s.EqualError(err, errorMessage) +} + +func (s *ValidatorSuite) mockSuccessfulListValidators(ctx context.Context, networkId string, assetId string, mockValidators *api.ValidatorList) { + s.T().Helper() + + s.mockValidatorsAPI.On("ListValidators", ctx, networkId, assetId).Return(api.ApiListValidatorsRequest{ + ApiService: s.mockValidatorsAPI, + }, nil, nil).Once() + + s.mockValidatorsAPI.On("ListValidatorsExecute", mock.Anything).Return(mockValidators, nil, nil).Once() +} + +func (s *ValidatorSuite) mockFailedListValidators(ctx context.Context, networkId string, assetId string, err error) { + s.T().Helper() + + s.mockValidatorsAPI.On("ListValidators", ctx, networkId, assetId).Return(api.ApiListValidatorsRequest{ + ApiService: s.mockValidatorsAPI, + }, nil, nil).Once() + + s.mockValidatorsAPI.On("ListValidatorsExecute", mock.Anything).Return(nil, nil, err).Once() +} + +func (s *ValidatorSuite) mockSuccessfulGetValidator(ctx context.Context, networkId string, assetId string, validatorId string, validator *api.Validator) { + s.T().Helper() + + s.mockValidatorsAPI.On("GetValidator", ctx, networkId, assetId, validatorId).Return(api.ApiGetValidatorRequest{ + ApiService: s.mockValidatorsAPI, + }, nil, nil) + + s.mockValidatorsAPI.On("GetValidatorExecute", mock.Anything).Return(validator, nil, nil) +} + +func (s *ValidatorSuite) mockFailedGetValidator(ctx context.Context, networkId string, assetId string, validatorId string, err error) { + s.T().Helper() + + s.mockValidatorsAPI.On("GetValidator", ctx, networkId, assetId, validatorId).Return(api.ApiGetValidatorRequest{ + ApiService: s.mockValidatorsAPI, + }, nil, nil) + + s.mockValidatorsAPI.On("GetValidatorExecute", mock.Anything).Return(nil, nil, err) +} diff --git a/pkg/mocks/ValidatorsAPI.go b/pkg/mocks/ValidatorsAPI.go new file mode 100644 index 0000000..c4fa715 --- /dev/null +++ b/pkg/mocks/ValidatorsAPI.go @@ -0,0 +1,146 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + context "context" + + client "github.com/coinbase/coinbase-sdk-go/gen/client" + + http "net/http" + + mock "github.com/stretchr/testify/mock" +) + +// ValidatorsAPI is an autogenerated mock type for the ValidatorsAPI type +type ValidatorsAPI struct { + mock.Mock +} + +// GetValidator provides a mock function with given fields: ctx, networkId, assetId, validatorId +func (_m *ValidatorsAPI) GetValidator(ctx context.Context, networkId string, assetId string, validatorId string) client.ApiGetValidatorRequest { + ret := _m.Called(ctx, networkId, assetId, validatorId) + + if len(ret) == 0 { + panic("no return value specified for GetValidator") + } + + var r0 client.ApiGetValidatorRequest + if rf, ok := ret.Get(0).(func(context.Context, string, string, string) client.ApiGetValidatorRequest); ok { + r0 = rf(ctx, networkId, assetId, validatorId) + } else { + r0 = ret.Get(0).(client.ApiGetValidatorRequest) + } + + return r0 +} + +// GetValidatorExecute provides a mock function with given fields: r +func (_m *ValidatorsAPI) GetValidatorExecute(r client.ApiGetValidatorRequest) (*client.Validator, *http.Response, error) { + ret := _m.Called(r) + + if len(ret) == 0 { + panic("no return value specified for GetValidatorExecute") + } + + var r0 *client.Validator + var r1 *http.Response + var r2 error + if rf, ok := ret.Get(0).(func(client.ApiGetValidatorRequest) (*client.Validator, *http.Response, error)); ok { + return rf(r) + } + if rf, ok := ret.Get(0).(func(client.ApiGetValidatorRequest) *client.Validator); ok { + r0 = rf(r) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*client.Validator) + } + } + + if rf, ok := ret.Get(1).(func(client.ApiGetValidatorRequest) *http.Response); ok { + r1 = rf(r) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*http.Response) + } + } + + if rf, ok := ret.Get(2).(func(client.ApiGetValidatorRequest) error); ok { + r2 = rf(r) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// ListValidators provides a mock function with given fields: ctx, networkId, assetId +func (_m *ValidatorsAPI) ListValidators(ctx context.Context, networkId string, assetId string) client.ApiListValidatorsRequest { + ret := _m.Called(ctx, networkId, assetId) + + if len(ret) == 0 { + panic("no return value specified for ListValidators") + } + + var r0 client.ApiListValidatorsRequest + if rf, ok := ret.Get(0).(func(context.Context, string, string) client.ApiListValidatorsRequest); ok { + r0 = rf(ctx, networkId, assetId) + } else { + r0 = ret.Get(0).(client.ApiListValidatorsRequest) + } + + return r0 +} + +// ListValidatorsExecute provides a mock function with given fields: r +func (_m *ValidatorsAPI) ListValidatorsExecute(r client.ApiListValidatorsRequest) (*client.ValidatorList, *http.Response, error) { + ret := _m.Called(r) + + if len(ret) == 0 { + panic("no return value specified for ListValidatorsExecute") + } + + var r0 *client.ValidatorList + var r1 *http.Response + var r2 error + if rf, ok := ret.Get(0).(func(client.ApiListValidatorsRequest) (*client.ValidatorList, *http.Response, error)); ok { + return rf(r) + } + if rf, ok := ret.Get(0).(func(client.ApiListValidatorsRequest) *client.ValidatorList); ok { + r0 = rf(r) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*client.ValidatorList) + } + } + + if rf, ok := ret.Get(1).(func(client.ApiListValidatorsRequest) *http.Response); ok { + r1 = rf(r) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*http.Response) + } + } + + if rf, ok := ret.Get(2).(func(client.ApiListValidatorsRequest) error); ok { + r2 = rf(r) + } else { + r2 = ret.Error(2) + } + + return r0, r1, r2 +} + +// NewValidatorsAPI creates a new instance of ValidatorsAPI. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewValidatorsAPI(t interface { + mock.TestingT + Cleanup(func()) +}) *ValidatorsAPI { + mock := &ValidatorsAPI{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +}