diff --git a/go/cmd/entrypoint/main_test.go b/go/cmd/entrypoint/main_test.go new file mode 100644 index 0000000..2954537 --- /dev/null +++ b/go/cmd/entrypoint/main_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "bytes" + "context" + "isle/daemon" + "isle/toolkit" + "reflect" + "testing" + + "dev.mediocregopher.com/mediocre-go-lib.git/mlog" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" +) + +type runHarness struct { + ctx context.Context + logger *mlog.Logger + daemonRPC *daemon.MockRPC + stdout *bytes.Buffer +} + +func newRunHarness(t *testing.T) *runHarness { + t.Parallel() + + var ( + ctx = context.Background() + logger = toolkit.NewTestLogger(t) + daemonRPC = daemon.NewMockRPC(t) + stdout = new(bytes.Buffer) + ) + + return &runHarness{ctx, logger, daemonRPC, stdout} +} + +func (h *runHarness) run(_ *testing.T, args ...string) error { + return doRootCmd(h.ctx, h.logger, &subCmdCtxOpts{ + args: args, + daemonRPC: h.daemonRPC, + stdout: h.stdout, + }) +} + +func (h *runHarness) runAssertStdout( + t *testing.T, + want any, + args ...string, +) { + var ( + gotType = reflect.ValueOf(want) + got = reflect.New(gotType.Type()) + ) + + h.stdout.Reset() + assert.NoError(t, h.run(t, args...)) + assert.NoError(t, yaml.Unmarshal(h.stdout.Bytes(), got.Interface())) + assert.Equal(t, want, got.Elem().Interface()) +} diff --git a/go/cmd/entrypoint/storage_test.go b/go/cmd/entrypoint/storage_test.go new file mode 100644 index 0000000..e21d83a --- /dev/null +++ b/go/cmd/entrypoint/storage_test.go @@ -0,0 +1,85 @@ +package main + +import ( + "context" + "isle/daemon/daecommon" + "isle/toolkit" + "testing" +) + +func TestStorageAllocationList(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + allocs []daecommon.ConfigStorageAllocation + want any + }{ + { + name: "empty", + allocs: nil, + want: []any{}, + }, + { + // results should get sorted according to RPCPort, with index + // reflecting that order. + name: "success", + allocs: []daecommon.ConfigStorageAllocation{ + { + DataPath: "b", + MetaPath: "B", + Capacity: 2, + S3APIPort: 2000, + RPCPort: 2001, + AdminPort: 2002, + }, + { + DataPath: "a", + MetaPath: "A", + Capacity: 1, + S3APIPort: 1000, + RPCPort: 1001, + AdminPort: 1002, + }, + }, + want: []map[string]any{ + { + "index": 0, + "data_path": "a", + "meta_path": "A", + "capacity": 1, + "s3_api_port": 1000, + "rpc_port": 1001, + "admin_port": 1002, + }, + { + "index": 1, + "data_path": "b", + "meta_path": "B", + "capacity": 2, + "s3_api_port": 2000, + "rpc_port": 2001, + "admin_port": 2002, + }, + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + var ( + h = newRunHarness(t) + config daecommon.NetworkConfig + ) + + config.Storage.Allocations = test.allocs + + h.daemonRPC. + On("GetConfig", toolkit.MockArg[context.Context]()). + Return(config, nil). + Once() + + h.runAssertStdout(t, test.want, "storage", "list-allocations") + }) + } +} diff --git a/go/daemon/rpc_mock.go b/go/daemon/rpc_mock.go new file mode 100644 index 0000000..496482c --- /dev/null +++ b/go/daemon/rpc_mock.go @@ -0,0 +1,307 @@ +// Code generated by mockery v2.43.1. DO NOT EDIT. + +package daemon + +import ( + context "context" + bootstrap "isle/bootstrap" + + daecommon "isle/daemon/daecommon" + + mock "github.com/stretchr/testify/mock" + + nebula "isle/nebula" + + network "isle/daemon/network" +) + +// MockRPC is an autogenerated mock type for the RPC type +type MockRPC struct { + mock.Mock +} + +// CreateHost provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockRPC) CreateHost(_a0 context.Context, _a1 nebula.HostName, _a2 network.CreateHostOpts) (network.JoiningBootstrap, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for CreateHost") + } + + var r0 network.JoiningBootstrap + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, nebula.HostName, network.CreateHostOpts) (network.JoiningBootstrap, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, nebula.HostName, network.CreateHostOpts) network.JoiningBootstrap); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(network.JoiningBootstrap) + } + + if rf, ok := ret.Get(1).(func(context.Context, nebula.HostName, network.CreateHostOpts) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateNebulaCertificate provides a mock function with given fields: _a0, _a1, _a2 +func (_m *MockRPC) CreateNebulaCertificate(_a0 context.Context, _a1 nebula.HostName, _a2 nebula.EncryptingPublicKey) (nebula.Certificate, error) { + ret := _m.Called(_a0, _a1, _a2) + + if len(ret) == 0 { + panic("no return value specified for CreateNebulaCertificate") + } + + var r0 nebula.Certificate + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, nebula.HostName, nebula.EncryptingPublicKey) (nebula.Certificate, error)); ok { + return rf(_a0, _a1, _a2) + } + if rf, ok := ret.Get(0).(func(context.Context, nebula.HostName, nebula.EncryptingPublicKey) nebula.Certificate); ok { + r0 = rf(_a0, _a1, _a2) + } else { + r0 = ret.Get(0).(nebula.Certificate) + } + + if rf, ok := ret.Get(1).(func(context.Context, nebula.HostName, nebula.EncryptingPublicKey) error); ok { + r1 = rf(_a0, _a1, _a2) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// CreateNetwork provides a mock function with given fields: ctx, name, domain, ipNet, hostName +func (_m *MockRPC) CreateNetwork(ctx context.Context, name string, domain string, ipNet nebula.IPNet, hostName nebula.HostName) error { + ret := _m.Called(ctx, name, domain, ipNet, hostName) + + if len(ret) == 0 { + panic("no return value specified for CreateNetwork") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, string, nebula.IPNet, nebula.HostName) error); ok { + r0 = rf(ctx, name, domain, ipNet, hostName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetConfig provides a mock function with given fields: _a0 +func (_m *MockRPC) GetConfig(_a0 context.Context) (daecommon.NetworkConfig, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GetConfig") + } + + var r0 daecommon.NetworkConfig + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (daecommon.NetworkConfig, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) daecommon.NetworkConfig); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(daecommon.NetworkConfig) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetGarageClientParams provides a mock function with given fields: _a0 +func (_m *MockRPC) GetGarageClientParams(_a0 context.Context) (network.GarageClientParams, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GetGarageClientParams") + } + + var r0 network.GarageClientParams + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (network.GarageClientParams, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) network.GarageClientParams); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(network.GarageClientParams) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetHosts provides a mock function with given fields: _a0 +func (_m *MockRPC) GetHosts(_a0 context.Context) ([]bootstrap.Host, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GetHosts") + } + + var r0 []bootstrap.Host + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]bootstrap.Host, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) []bootstrap.Host); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]bootstrap.Host) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetNebulaCAPublicCredentials provides a mock function with given fields: _a0 +func (_m *MockRPC) GetNebulaCAPublicCredentials(_a0 context.Context) (nebula.CAPublicCredentials, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GetNebulaCAPublicCredentials") + } + + var r0 nebula.CAPublicCredentials + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) (nebula.CAPublicCredentials, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) nebula.CAPublicCredentials); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(nebula.CAPublicCredentials) + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetNetworks provides a mock function with given fields: _a0 +func (_m *MockRPC) GetNetworks(_a0 context.Context) ([]bootstrap.CreationParams, error) { + ret := _m.Called(_a0) + + if len(ret) == 0 { + panic("no return value specified for GetNetworks") + } + + var r0 []bootstrap.CreationParams + var r1 error + if rf, ok := ret.Get(0).(func(context.Context) ([]bootstrap.CreationParams, error)); ok { + return rf(_a0) + } + if rf, ok := ret.Get(0).(func(context.Context) []bootstrap.CreationParams); ok { + r0 = rf(_a0) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]bootstrap.CreationParams) + } + } + + if rf, ok := ret.Get(1).(func(context.Context) error); ok { + r1 = rf(_a0) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// JoinNetwork provides a mock function with given fields: _a0, _a1 +func (_m *MockRPC) JoinNetwork(_a0 context.Context, _a1 network.JoiningBootstrap) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for JoinNetwork") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, network.JoiningBootstrap) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// RemoveHost provides a mock function with given fields: ctx, hostName +func (_m *MockRPC) RemoveHost(ctx context.Context, hostName nebula.HostName) error { + ret := _m.Called(ctx, hostName) + + if len(ret) == 0 { + panic("no return value specified for RemoveHost") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, nebula.HostName) error); ok { + r0 = rf(ctx, hostName) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// SetConfig provides a mock function with given fields: _a0, _a1 +func (_m *MockRPC) SetConfig(_a0 context.Context, _a1 daecommon.NetworkConfig) error { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for SetConfig") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, daecommon.NetworkConfig) error); ok { + r0 = rf(_a0, _a1) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewMockRPC creates a new instance of MockRPC. 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 NewMockRPC(t interface { + mock.TestingT + Cleanup(func()) +}) *MockRPC { + mock := &MockRPC{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +}