From d4652246c08c4906418feaf3a1a1e2bcaf872afa Mon Sep 17 00:00:00 2001 From: Oleg Isakov Date: Fri, 28 Mar 2025 15:22:53 +0200 Subject: [PATCH 1/2] add mockgen for account service --- Makefile | 2 ++ internal/mocks/account_service.go | 57 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 internal/mocks/account_service.go diff --git a/Makefile b/Makefile index 4c42774..181e180 100644 --- a/Makefile +++ b/Makefile @@ -14,6 +14,7 @@ generate: deps mockgen --destination ./internal/mocks/load_balancers_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/load_balancers.go mockgen --destination ./internal/mocks/racks_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/racks.go mockgen --destination ./internal/mocks/invoices_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/invoices.go + mockgen --destination ./internal/mocks/account_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/accounts.go sed -i '' 's|github.com/serverscom/srvctl/vendor/github.com/serverscom/serverscom-go-client/pkg|github.com/serverscom/serverscom-go-client/pkg|g' \ ./internal/mocks/ssh_service.go \ ./internal/mocks/hosts_service.go \ @@ -21,5 +22,6 @@ generate: deps ./internal/mocks/load_balancers_service.go \ ./internal/mocks/racks_service.go \ ./internal/mocks/invoices_service.go \ + ./internal/mocks/account_service.go \ ./internal/mocks/collection.go diff --git a/internal/mocks/account_service.go b/internal/mocks/account_service.go new file mode 100644 index 0000000..dceabc0 --- /dev/null +++ b/internal/mocks/account_service.go @@ -0,0 +1,57 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./vendor/github.com/serverscom/serverscom-go-client/pkg/accounts.go +// +// Generated by this command: +// +// mockgen --destination ./internal/mocks/account_service.go --package=mocks --source ./vendor/github.com/serverscom/serverscom-go-client/pkg/accounts.go +// + +// Package mocks is a generated GoMock package. +package mocks + +import ( + context "context" + reflect "reflect" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + gomock "go.uber.org/mock/gomock" +) + +// MockAccountService is a mock of AccountService interface. +type MockAccountService struct { + ctrl *gomock.Controller + recorder *MockAccountServiceMockRecorder + isgomock struct{} +} + +// MockAccountServiceMockRecorder is the mock recorder for MockAccountService. +type MockAccountServiceMockRecorder struct { + mock *MockAccountService +} + +// NewMockAccountService creates a new mock instance. +func NewMockAccountService(ctrl *gomock.Controller) *MockAccountService { + mock := &MockAccountService{ctrl: ctrl} + mock.recorder = &MockAccountServiceMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAccountService) EXPECT() *MockAccountServiceMockRecorder { + return m.recorder +} + +// GetBalance mocks base method. +func (m *MockAccountService) GetBalance(ctx context.Context) (*serverscom.AccountBalance, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetBalance", ctx) + ret0, _ := ret[0].(*serverscom.AccountBalance) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetBalance indicates an expected call of GetBalance. +func (mr *MockAccountServiceMockRecorder) GetBalance(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBalance", reflect.TypeOf((*MockAccountService)(nil).GetBalance), ctx) +} From 6baa5398fa7d33f3f8b92f3d450436ab73a398e9 Mon Sep 17 00:00:00 2001 From: Oleg Isakov Date: Fri, 28 Mar 2025 15:23:55 +0200 Subject: [PATCH 2/2] add account cmd; add tests --- cmd/entities/account/account.go | 37 +++++++++ cmd/entities/account/account_test.go | 96 ++++++++++++++++++++++ cmd/entities/account/get_balance.go | 37 +++++++++ cmd/root.go | 2 + go.mod | 2 +- go.sum | 4 +- internal/output/entities/accounts.go | 26 ++++++ internal/output/entities/init.go | 1 + testdata/entities/account/get_balance.json | 5 ++ testdata/entities/account/get_balance.txt | 2 + testdata/entities/account/get_balance.yaml | 3 + 11 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 cmd/entities/account/account.go create mode 100644 cmd/entities/account/account_test.go create mode 100644 cmd/entities/account/get_balance.go create mode 100644 internal/output/entities/accounts.go create mode 100644 testdata/entities/account/get_balance.json create mode 100644 testdata/entities/account/get_balance.txt create mode 100644 testdata/entities/account/get_balance.yaml diff --git a/cmd/entities/account/account.go b/cmd/entities/account/account.go new file mode 100644 index 0000000..a2d42b9 --- /dev/null +++ b/cmd/entities/account/account.go @@ -0,0 +1,37 @@ +package account + +import ( + "log" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/base" + "github.com/serverscom/srvctl/internal/output/entities" + "github.com/spf13/cobra" +) + +func NewCmd(cmdContext *base.CmdContext) *cobra.Command { + accountBalanceEntity, err := entities.Registry.GetEntityFromValue(serverscom.AccountBalance{}) + if err != nil { + log.Fatal(err) + } + entitiesMap := make(map[string]entities.EntityInterface) + entitiesMap["account"] = accountBalanceEntity + cmd := &cobra.Command{ + Use: "account", + Short: "Manage account operations", + PersistentPreRunE: base.CombinePreRunE( + base.CheckFormatterFlags(cmdContext, entitiesMap), + base.CheckEmptyContexts(cmdContext), + ), + Args: base.NoArgs, + Run: base.UsageRun, + } + + cmd.AddCommand( + newGetBalanceCmd(cmdContext), + ) + + base.AddFormatFlags(cmd) + + return cmd +} diff --git a/cmd/entities/account/account_test.go b/cmd/entities/account/account_test.go new file mode 100644 index 0000000..b4dda68 --- /dev/null +++ b/cmd/entities/account/account_test.go @@ -0,0 +1,96 @@ +package account + +import ( + "errors" + "path/filepath" + "testing" + + . "github.com/onsi/gomega" + serverscom "github.com/serverscom/serverscom-go-client/pkg" + "github.com/serverscom/srvctl/cmd/testutils" + "github.com/serverscom/srvctl/internal/mocks" + "go.uber.org/mock/gomock" +) + +var ( + fixtureBasePath = filepath.Join("..", "..", "..", "testdata", "entities", "account") + testAccountBalance = serverscom.AccountBalance{ + CurrentBalance: 100.0, + NextInvoiceTotalDue: 0.0, + Currency: "EUR", + } +) + +func TestGetAccountBalanceCmd(t *testing.T) { + testCases := []struct { + name string + output string + expectedOutput []byte + expectError bool + }{ + { + name: "get account balance in default format", + output: "", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_balance.txt")), + }, + { + name: "get account balance in JSON format", + output: "json", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_balance.json")), + }, + { + name: "get account balance in YAML format", + output: "yaml", + expectedOutput: testutils.ReadFixture(filepath.Join(fixtureBasePath, "get_balance.yaml")), + }, + { + name: "get account balance with error", + expectError: true, + }, + } + + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + accountServiceHandler := mocks.NewMockAccountService(mockCtrl) + + scClient := serverscom.NewClientWithEndpoint("", "") + scClient.Account = accountServiceHandler + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + g := NewWithT(t) + + var err error + if tc.expectError { + err = errors.New("some error") + } + accountServiceHandler.EXPECT(). + GetBalance(gomock.Any()). + Return(&testAccountBalance, err) + + testCmdContext := testutils.NewTestCmdContext(scClient) + accountCmd := NewCmd(testCmdContext) + + args := []string{"account", "balance"} + if tc.output != "" { + args = append(args, "--output", tc.output) + } + + builder := testutils.NewTestCommandBuilder(). + WithCommand(accountCmd). + WithArgs(args) + + cmd := builder.Build() + + err = cmd.Execute() + + if tc.expectError { + g.Expect(err).To(HaveOccurred()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(builder.GetOutput()).To(BeEquivalentTo(string(tc.expectedOutput))) + } + }) + } +} diff --git a/cmd/entities/account/get_balance.go b/cmd/entities/account/get_balance.go new file mode 100644 index 0000000..9dba1b8 --- /dev/null +++ b/cmd/entities/account/get_balance.go @@ -0,0 +1,37 @@ +package account + +import ( + "github.com/serverscom/srvctl/cmd/base" + "github.com/spf13/cobra" +) + +func newGetBalanceCmd(cmdContext *base.CmdContext) *cobra.Command { + cmd := &cobra.Command{ + Use: "balance", + Short: "Get an account balance info", + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + manager := cmdContext.GetManager() + + ctx, cancel := base.SetupContext(cmd, manager) + defer cancel() + + base.SetupProxy(cmd, manager) + + scClient := cmdContext.GetClient().SetVerbose(manager.GetVerbose(cmd)).GetScClient() + + balance, err := scClient.Account.GetBalance(ctx) + if err != nil { + return err + } + + if balance != nil { + formatter := cmdContext.GetOrCreateFormatter(cmd) + return formatter.Format(balance) + } + return nil + }, + } + + return cmd +} diff --git a/cmd/root.go b/cmd/root.go index a9131f0..6b7b6a4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,6 +4,7 @@ import ( "github.com/serverscom/srvctl/cmd/base" "github.com/serverscom/srvctl/cmd/config" "github.com/serverscom/srvctl/cmd/context" + "github.com/serverscom/srvctl/cmd/entities/account" "github.com/serverscom/srvctl/cmd/entities/hosts" "github.com/serverscom/srvctl/cmd/entities/invoices" loadbalancers "github.com/serverscom/srvctl/cmd/entities/load_balancers" @@ -46,6 +47,7 @@ func NewRootCmd(version string) *cobra.Command { cmd.AddCommand(loadbalancers.NewCmd(cmdContext)) cmd.AddCommand(racks.NewCmd(cmdContext)) cmd.AddCommand(invoices.NewCmd(cmdContext)) + cmd.AddCommand(account.NewCmd(cmdContext)) return cmd } diff --git a/go.mod b/go.mod index 4214043..6b8203d 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( github.com/creack/pty v1.1.24 github.com/jmespath/go-jmespath v0.4.0 github.com/onsi/gomega v1.36.2 - github.com/serverscom/serverscom-go-client v1.0.13 + github.com/serverscom/serverscom-go-client v1.0.14 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.19.0 diff --git a/go.sum b/go.sum index 896c740..cc06d94 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= -github.com/serverscom/serverscom-go-client v1.0.13 h1:HjcODZl8M8SEwhbeBGX2D/M3ZZM7I0JGQq717rW792Y= -github.com/serverscom/serverscom-go-client v1.0.13/go.mod h1:o4lNYX+shv5TZ6miuGAaMDJP8y7Z7TdPEhMsCcL9PrU= +github.com/serverscom/serverscom-go-client v1.0.14 h1:/SR4moqSL6MqW+gt6wtF9Wl5KfckP4RcqeS0AECwwAs= +github.com/serverscom/serverscom-go-client v1.0.14/go.mod h1:o4lNYX+shv5TZ6miuGAaMDJP8y7Z7TdPEhMsCcL9PrU= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= diff --git a/internal/output/entities/accounts.go b/internal/output/entities/accounts.go new file mode 100644 index 0000000..2749ea1 --- /dev/null +++ b/internal/output/entities/accounts.go @@ -0,0 +1,26 @@ +package entities + +import ( + "log" + "reflect" + + serverscom "github.com/serverscom/serverscom-go-client/pkg" +) + +var ( + AccountBalanceType = reflect.TypeOf(serverscom.AccountBalance{}) +) + +func RegisterAccountDefinition() { + balanceEntity := &Entity{ + fields: []Field{ + {ID: "CurrentBalance", Name: "CurrentBalance", Path: "CurrentBalance", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "NextInvoiceTotalDue", Name: "NextInvoiceTotalDue", Path: "NextInvoiceTotalDue", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + {ID: "Currency", Name: "Currency", Path: "Currency", ListHandlerFunc: stringHandler, PageViewHandlerFunc: stringHandler, Default: true}, + }, + eType: AccountBalanceType, + } + if err := Registry.Register(balanceEntity); err != nil { + log.Fatal(err) + } +} diff --git a/internal/output/entities/init.go b/internal/output/entities/init.go index f7bd18e..d88bf12 100644 --- a/internal/output/entities/init.go +++ b/internal/output/entities/init.go @@ -17,4 +17,5 @@ func init() { RegisterLoadBalancerDefinitions() RegisterRackDefinition() RegisterInvoiceDefinition() + RegisterAccountDefinition() } diff --git a/testdata/entities/account/get_balance.json b/testdata/entities/account/get_balance.json new file mode 100644 index 0000000..a8adf1e --- /dev/null +++ b/testdata/entities/account/get_balance.json @@ -0,0 +1,5 @@ +{ + "current_balance": 100, + "next_invoice_total_due": 0, + "currency": "EUR" +} \ No newline at end of file diff --git a/testdata/entities/account/get_balance.txt b/testdata/entities/account/get_balance.txt new file mode 100644 index 0000000..3cde87a --- /dev/null +++ b/testdata/entities/account/get_balance.txt @@ -0,0 +1,2 @@ +CurrentBalance NextInvoiceTotalDue Currency +100 0 EUR diff --git a/testdata/entities/account/get_balance.yaml b/testdata/entities/account/get_balance.yaml new file mode 100644 index 0000000..6d0f04d --- /dev/null +++ b/testdata/entities/account/get_balance.yaml @@ -0,0 +1,3 @@ +currentbalance: 100 +nextinvoicetotaldue: 0 +currency: EUR