diff --git a/api/handlers/v1/resource.go b/api/handlers/v1/resource.go index 7dd02809..cd41ede7 100644 --- a/api/handlers/v1/resource.go +++ b/api/handlers/v1/resource.go @@ -4,6 +4,7 @@ import ( "context" "errors" "github.com/odpf/entropy/domain" + "github.com/odpf/entropy/pkg/module" "github.com/odpf/entropy/pkg/resource" "github.com/odpf/entropy/store" entropyv1beta1 "go.buf.build/odpf/gwv/odpf/proton/odpf/entropy/v1beta1" @@ -16,16 +17,19 @@ import ( type APIServer struct { entropyv1beta1.UnimplementedResourceServiceServer resourceService resource.ServiceInterface + moduleService module.ServiceInterface } -func NewApiServer(resourceService resource.ServiceInterface) *APIServer { +func NewApiServer(resourceService resource.ServiceInterface, moduleService module.ServiceInterface) *APIServer { return &APIServer{ resourceService: resourceService, + moduleService: moduleService, } } func (server APIServer) CreateResource(ctx context.Context, request *entropyv1beta1.CreateResourceRequest) (*entropyv1beta1.CreateResourceResponse, error) { res := resourceFromProto(request.Resource) + res.Urn = domain.GenerateResourceUrn(res) createdResource, err := server.resourceService.CreateResource(ctx, res) if err != nil { if errors.Is(err, store.ResourceAlreadyExistsError) { @@ -33,34 +37,81 @@ func (server APIServer) CreateResource(ctx context.Context, request *entropyv1be } return nil, status.Error(codes.Internal, "failed to create resource in db") } - createdResponse, err := resourceToProto(createdResource) + syncedResource, err := server.syncResource(ctx, createdResource) + if err != nil { + return nil, err + } + responseResource, err := resourceToProto(syncedResource) if err != nil { return nil, status.Error(codes.Internal, "failed to serialize resource") } response := entropyv1beta1.CreateResourceResponse{ - Resource: createdResponse, + Resource: responseResource, } return &response, nil } func (server APIServer) UpdateResource(ctx context.Context, request *entropyv1beta1.UpdateResourceRequest) (*entropyv1beta1.UpdateResourceResponse, error) { - updatedResource, err := server.resourceService.UpdateResource(ctx, request.GetUrn(), request.GetConfigs().GetStructValue().AsMap()) + res, err := server.resourceService.GetResource(ctx, request.GetUrn()) if err != nil { if errors.Is(err, store.ResourceNotFoundError) { return nil, status.Error(codes.NotFound, "could not find resource with given urn") } return nil, status.Error(codes.Internal, "failed to update resource in db") } - updatedResponse, err := resourceToProto(updatedResource) + res.Configs = request.GetConfigs().GetStructValue().AsMap() + res.Status = domain.ResourceStatusPending + updatedResource, err := server.resourceService.UpdateResource(ctx, res) + if err != nil { + return nil, err + } + syncedResource, err := server.syncResource(ctx, updatedResource) + if err != nil { + return nil, err + } + responseResource, err := resourceToProto(syncedResource) if err != nil { return nil, status.Error(codes.Internal, "failed to serialize resource") } response := entropyv1beta1.UpdateResourceResponse{ - Resource: updatedResponse, + Resource: responseResource, } return &response, nil } +func (server APIServer) GetResource(ctx context.Context, request *entropyv1beta1.GetResourceRequest) (*entropyv1beta1.GetResourceResponse, error) { + res, err := server.resourceService.GetResource(ctx, request.GetUrn()) + if err != nil { + if errors.Is(err, store.ResourceNotFoundError) { + return nil, status.Error(codes.NotFound, "could not find resource with given urn") + } + return nil, status.Error(codes.Internal, "failed to fetch resource from db") + } + responseResource, err := resourceToProto(res) + if err != nil { + return nil, status.Error(codes.Internal, "failed to serialize resource") + } + response := entropyv1beta1.GetResourceResponse{ + Resource: responseResource, + } + return &response, nil +} + +func (server APIServer) syncResource(ctx context.Context, updatedResource *domain.Resource) (*domain.Resource, error) { + syncedResource, err := server.moduleService.Sync(ctx, updatedResource) + if err != nil { + if errors.Is(err, store.ModuleNotFoundError) { + return nil, status.Errorf(codes.Internal, "failed to find module to deploy this kind") + } + return nil, status.Error(codes.Internal, "failed to sync updated resource") + } + responseResource, err := server.resourceService.UpdateResource(ctx, syncedResource) + if err != nil { + return nil, status.Error(codes.Internal, "failed to update resource in db") + } + return responseResource, nil +} + func resourceToProto(res *domain.Resource) (*entropyv1beta1.Resource, error) { conf, err := structpb.NewValue(res.Configs) if err != nil { diff --git a/api/handlers/v1/resource_test.go b/api/handlers/v1/resource_test.go index 848b8231..97022197 100644 --- a/api/handlers/v1/resource_test.go +++ b/api/handlers/v1/resource_test.go @@ -6,6 +6,7 @@ import ( "github.com/odpf/entropy/domain" "github.com/odpf/entropy/mocks" "github.com/odpf/entropy/store" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" entropyv1beta1 "go.buf.build/odpf/gwv/odpf/proton/odpf/entropy/v1beta1" "google.golang.org/grpc/codes" @@ -20,19 +21,19 @@ import ( func TestAPIServer_CreateResource(t *testing.T) { t.Run("test create new resource", func(t *testing.T) { createdAt := time.Now() - updatedAt := createdAt + updatedAt := createdAt.Add(time.Minute) configsStructValue, _ := structpb.NewValue(map[string]interface{}{ "replicas": "10", }) want := &entropyv1beta1.CreateResourceResponse{ Resource: &entropyv1beta1.Resource{ - Urn: "p-testdata-gl-testname-firehose", + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: configsStructValue, Labels: nil, - Status: entropyv1beta1.Resource_STATUS_PENDING, + Status: entropyv1beta1.Resource_STATUS_COMPLETED, CreatedAt: timestamppb.New(createdAt), UpdatedAt: timestamppb.New(updatedAt), }, @@ -44,29 +45,62 @@ func TestAPIServer_CreateResource(t *testing.T) { Resource: &entropyv1beta1.Resource{ Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: configsStructValue, Labels: nil, }, } - resourceService := mocks.ResourceService{} - - resourceService.EXPECT().CreateResource(mock.Anything, mock.Anything).Return(&domain.Resource{ - Urn: "p-testdata-gl-testname-firehose", + resourceService := &mocks.ResourceService{} + resourceService.EXPECT().CreateResource(mock.Anything, mock.Anything).Run(func(ctx context.Context, res *domain.Resource) { + assert.Equal(t, "p-testdata-gl-testname-log", res.Urn) + }).Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: map[string]interface{}{ "replicas": "10", }, Labels: nil, Status: domain.ResourceStatusPending, CreatedAt: createdAt, + UpdatedAt: createdAt, + }, nil).Once() + + resourceService.EXPECT().UpdateResource(mock.Anything, mock.Anything).Run(func(ctx context.Context, res *domain.Resource) { + assert.Equal(t, "p-testdata-gl-testname-log", res.Urn) + assert.Equal(t, domain.ResourceStatusCompleted, res.Status) + }).Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "log", + Configs: map[string]interface{}{ + "replicas": "10", + }, + Labels: nil, + Status: domain.ResourceStatusCompleted, + CreatedAt: createdAt, UpdatedAt: updatedAt, }, nil).Once() - server := NewApiServer(&resourceService) + moduleService := &mocks.ModuleService{} + moduleService.EXPECT().Sync(mock.Anything, mock.Anything).Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "log", + Configs: map[string]interface{}{ + "replicas": "10", + }, + Labels: nil, + Status: domain.ResourceStatusCompleted, + CreatedAt: createdAt, + UpdatedAt: createdAt, + }, nil) + + server := NewApiServer(resourceService, moduleService) got, err := server.CreateResource(ctx, request) if !errors.Is(err, wantErr) { t.Errorf("CreateResource() error = %v, wantErr %v", err, wantErr) @@ -89,20 +123,84 @@ func TestAPIServer_CreateResource(t *testing.T) { Resource: &entropyv1beta1.Resource{ Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: configsStructValue, Labels: nil, }, } - resourceService := mocks.ResourceService{} + resourceService := &mocks.ResourceService{} resourceService.EXPECT(). CreateResource(mock.Anything, mock.Anything). Return(nil, store.ResourceAlreadyExistsError). Once() - server := NewApiServer(&resourceService) + moduleService := &mocks.ModuleService{} + + server := NewApiServer(resourceService, moduleService) + got, err := server.CreateResource(ctx, request) + if !errors.Is(err, wantErr) { + t.Errorf("CreateResource() error = %v, wantErr %v", err, wantErr) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("CreateResource() got = %v, want %v", got, want) + } + }) + + t.Run("test create resource of unknown kind", func(t *testing.T) { + createdAt := time.Now() + updatedAt := createdAt.Add(time.Minute) + configsStructValue, _ := structpb.NewValue(map[string]interface{}{ + "replicas": "10", + }) + want := (*entropyv1beta1.CreateResourceResponse)(nil) + wantErr := status.Error(codes.Internal, "failed to find module to deploy this kind") + + ctx := context.Background() + request := &entropyv1beta1.CreateResourceRequest{ + Resource: &entropyv1beta1.Resource{ + Name: "testname", + Parent: "p-testdata-gl", + Kind: "unknown", + Configs: configsStructValue, + Labels: nil, + }, + } + + resourceService := &mocks.ResourceService{} + + resourceService.EXPECT().CreateResource(mock.Anything, mock.Anything).Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-unknown", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "unkown", + Configs: map[string]interface{}{ + "replicas": "10", + }, + Labels: nil, + Status: domain.ResourceStatusPending, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + }, nil).Once() + + moduleService := &mocks.ModuleService{} + moduleService.EXPECT().Sync(mock.Anything, mock.Anything).Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-unknown", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "unknown", + Configs: map[string]interface{}{ + "replicas": "10", + }, + Labels: nil, + Status: domain.ResourceStatusError, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + }, store.ModuleNotFoundError) + + server := NewApiServer(resourceService, moduleService) got, err := server.CreateResource(ctx, request) if !errors.Is(err, wantErr) { t.Errorf("CreateResource() error = %v, wantErr %v", err, wantErr) @@ -123,13 +221,13 @@ func TestAPIServer_UpdateResource(t *testing.T) { }) want := &entropyv1beta1.UpdateResourceResponse{ Resource: &entropyv1beta1.Resource{ - Urn: "p-testdata-gl-testname-firehose", + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: configsStructValue, Labels: nil, - Status: entropyv1beta1.Resource_STATUS_PENDING, + Status: entropyv1beta1.Resource_STATUS_COMPLETED, CreatedAt: timestamppb.New(createdAt), UpdatedAt: timestamppb.New(updatedAt), }, @@ -138,21 +236,37 @@ func TestAPIServer_UpdateResource(t *testing.T) { ctx := context.Background() request := &entropyv1beta1.UpdateResourceRequest{ - Urn: "p-testdata-gl-testname-firehose", + Urn: "p-testdata-gl-testname-log", Configs: configsStructValue, } - resourceService := mocks.ResourceService{} + resourceService := &mocks.ResourceService{} + resourceService.EXPECT(). + GetResource(mock.Anything, "p-testdata-gl-testname-log"). + Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "log", + Configs: map[string]interface{}{ + "replicas": "9", + }, + Labels: nil, + Status: domain.ResourceStatusCompleted, + CreatedAt: createdAt, + UpdatedAt: createdAt, + }, nil).Once() resourceService.EXPECT(). - UpdateResource(mock.Anything, "p-testdata-gl-testname-firehose", map[string]interface{}{ - "replicas": "10", + UpdateResource(mock.Anything, mock.Anything). + Run(func(ctx context.Context, res *domain.Resource) { + assert.Equal(t, domain.ResourceStatusPending, res.Status) }). Return(&domain.Resource{ - Urn: "p-testdata-gl-testname-firehose", + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: map[string]interface{}{ "replicas": "10", }, @@ -162,7 +276,41 @@ func TestAPIServer_UpdateResource(t *testing.T) { UpdatedAt: updatedAt, }, nil).Once() - server := NewApiServer(&resourceService) + resourceService.EXPECT(). + UpdateResource(mock.Anything, mock.Anything). + Run(func(ctx context.Context, res *domain.Resource) { + assert.Equal(t, domain.ResourceStatusCompleted, res.Status) + }). + Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "log", + Configs: map[string]interface{}{ + "replicas": "10", + }, + Labels: nil, + Status: domain.ResourceStatusCompleted, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + }, nil).Once() + + moduleService := &mocks.ModuleService{} + moduleService.EXPECT().Sync(mock.Anything, mock.Anything).Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "log", + Configs: map[string]interface{}{ + "replicas": "10", + }, + Labels: nil, + Status: domain.ResourceStatusCompleted, + CreatedAt: createdAt, + UpdatedAt: updatedAt, + }, nil) + + server := NewApiServer(resourceService, moduleService) got, err := server.UpdateResource(ctx, request) if !errors.Is(err, wantErr) { t.Errorf("UpdateResource() error = %v, wantErr %v", err, wantErr) @@ -182,19 +330,19 @@ func TestAPIServer_UpdateResource(t *testing.T) { ctx := context.Background() request := &entropyv1beta1.UpdateResourceRequest{ - Urn: "p-testdata-gl-testname-firehose", + Urn: "p-testdata-gl-testname-log", Configs: configsStructValue, } - resourceService := mocks.ResourceService{} + resourceService := &mocks.ResourceService{} resourceService.EXPECT(). - UpdateResource(mock.Anything, "p-testdata-gl-testname-firehose", map[string]interface{}{ - "replicas": "10", - }). + GetResource(mock.Anything, mock.Anything). Return(nil, store.ResourceNotFoundError).Once() - server := NewApiServer(&resourceService) + moduleService := &mocks.ModuleService{} + + server := NewApiServer(resourceService, moduleService) got, err := server.UpdateResource(ctx, request) if !errors.Is(err, wantErr) { t.Errorf("UpdateResource() error = %v, wantErr %v", err, wantErr) @@ -204,4 +352,111 @@ func TestAPIServer_UpdateResource(t *testing.T) { t.Errorf("UpdateResource() got = %v, want %v", got, want) } }) + + t.Run("test update resource with unknown kind", func(t *testing.T) { + configsStructValue, _ := structpb.NewValue(map[string]interface{}{ + "replicas": "10", + }) + want := (*entropyv1beta1.UpdateResourceResponse)(nil) + wantErr := status.Error(codes.Internal, "failed to find module to deploy this kind") + + ctx := context.Background() + request := &entropyv1beta1.UpdateResourceRequest{ + Urn: "p-testdata-gl-testname-log", + Configs: configsStructValue, + } + + resourceService := &mocks.ResourceService{} + resourceService.EXPECT(). + UpdateResource(mock.Anything, mock.Anything). + Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", + }, nil).Once() + resourceService.EXPECT(). + GetResource(mock.Anything, mock.Anything). + Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", + }, nil).Once() + + moduleService := &mocks.ModuleService{} + moduleService.EXPECT().Sync(mock.Anything, mock.Anything).Return(nil, store.ModuleNotFoundError) + + server := NewApiServer(resourceService, moduleService) + got, err := server.UpdateResource(ctx, request) + if !errors.Is(err, wantErr) { + t.Errorf("UpdateResource() error = %v, wantErr %v", err, wantErr) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("UpdateResource() got = %v, want %v", got, want) + } + }) +} + +func TestAPIServer_GetResource(t *testing.T) { + t.Run("test get resource", func(t *testing.T) { + r := &domain.Resource{ + Urn: "p-testdata-gl-testname-mock", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "mock", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusCompleted, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + rProto, _ := resourceToProto(r) + argsRequest := &entropyv1beta1.GetResourceRequest{ + Urn: "p-testdata-gl-testname-mock", + } + want := &entropyv1beta1.GetResourceResponse{ + Resource: rProto, + } + wantErr := error(nil) + + mockResourceService := &mocks.ResourceService{} + mockResourceService.EXPECT().GetResource(mock.Anything, mock.Anything).Return(r, nil).Once() + + mockModuleService := &mocks.ModuleService{} + + server := APIServer{ + resourceService: mockResourceService, + moduleService: mockModuleService, + } + got, err := server.GetResource(context.TODO(), argsRequest) + if !errors.Is(err, wantErr) { + t.Errorf("GetResource() error = %v, wantErr %v", err, wantErr) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("GetResource() got = %v, want %v", got, want) + } + }) + + t.Run("test get non existent resource", func(t *testing.T) { + argsRequest := &entropyv1beta1.GetResourceRequest{ + Urn: "p-testdata-gl-testname-mock", + } + want := (*entropyv1beta1.GetResourceResponse)(nil) + wantErr := status.Error(codes.NotFound, "could not find resource with given urn") + + mockResourceService := &mocks.ResourceService{} + mockResourceService.EXPECT().GetResource(mock.Anything, mock.Anything).Return(nil, store.ResourceNotFoundError).Once() + + mockModuleService := &mocks.ModuleService{} + + server := APIServer{ + resourceService: mockResourceService, + moduleService: mockModuleService, + } + got, err := server.GetResource(context.TODO(), argsRequest) + if !errors.Is(err, wantErr) { + t.Errorf("GetResource() error = %v, wantErr %v", err, wantErr) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("GetResource() got = %v, want %v", got, want) + } + }) } diff --git a/app/app.go b/app/app.go index 369c476d..7ac51e02 100644 --- a/app/app.go +++ b/app/app.go @@ -3,8 +3,11 @@ package app import ( "context" "fmt" + "github.com/odpf/entropy/modules/log" + "github.com/odpf/entropy/pkg/module" "github.com/odpf/entropy/pkg/resource" "github.com/odpf/entropy/store" + "github.com/odpf/entropy/store/inmemory" "github.com/odpf/entropy/store/mongodb" "net/http" "time" @@ -68,7 +71,14 @@ func RunServer(c *Config) error { mongoStore.Collection(store.ResourceRepositoryName), ) + moduleRepository := inmemory.NewModuleRepository() + err = moduleRepository.Register(&log.Module{}) + if err != nil { + return err + } + resourceService := resource.NewService(resourceRepository) + moduleService := module.NewService(moduleRepository) muxServer, err := server.NewMux(server.Config{ Port: c.Service.Port, @@ -107,7 +117,7 @@ func RunServer(c *Config) error { ) muxServer.RegisterService( &entropyv1beta1.ResourceService_ServiceDesc, - handlersv1.NewApiServer(resourceService), + handlersv1.NewApiServer(resourceService, moduleService), ) muxServer.RegisterHandler("/ping", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { diff --git a/domain/module.go b/domain/module.go new file mode 100644 index 00000000..afabfce8 --- /dev/null +++ b/domain/module.go @@ -0,0 +1,6 @@ +package domain + +type Module interface { + ID() string + Apply(r *Resource) (ResourceStatus, error) +} diff --git a/mocks/module.go b/mocks/module.go new file mode 100644 index 00000000..e0988831 --- /dev/null +++ b/mocks/module.go @@ -0,0 +1,101 @@ +// Code generated by mockery v2.10.0. DO NOT EDIT. + +package mocks + +import ( + domain "github.com/odpf/entropy/domain" + mock "github.com/stretchr/testify/mock" +) + +// Module is an autogenerated mock type for the Module type +type Module struct { + mock.Mock +} + +type Module_Expecter struct { + mock *mock.Mock +} + +func (_m *Module) EXPECT() *Module_Expecter { + return &Module_Expecter{mock: &_m.Mock} +} + +// Apply provides a mock function with given fields: r +func (_m *Module) Apply(r *domain.Resource) (domain.ResourceStatus, error) { + ret := _m.Called(r) + + var r0 domain.ResourceStatus + if rf, ok := ret.Get(0).(func(*domain.Resource) domain.ResourceStatus); ok { + r0 = rf(r) + } else { + r0 = ret.Get(0).(domain.ResourceStatus) + } + + var r1 error + if rf, ok := ret.Get(1).(func(*domain.Resource) error); ok { + r1 = rf(r) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Module_Apply_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Apply' +type Module_Apply_Call struct { + *mock.Call +} + +// Apply is a helper method to define mock.On call +// - r *domain.Resource +func (_e *Module_Expecter) Apply(r interface{}) *Module_Apply_Call { + return &Module_Apply_Call{Call: _e.mock.On("Apply", r)} +} + +func (_c *Module_Apply_Call) Run(run func(r *domain.Resource)) *Module_Apply_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(*domain.Resource)) + }) + return _c +} + +func (_c *Module_Apply_Call) Return(_a0 domain.ResourceStatus, _a1 error) *Module_Apply_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +// ID provides a mock function with given fields: +func (_m *Module) ID() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// Module_ID_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ID' +type Module_ID_Call struct { + *mock.Call +} + +// ID is a helper method to define mock.On call +func (_e *Module_Expecter) ID() *Module_ID_Call { + return &Module_ID_Call{Call: _e.mock.On("ID")} +} + +func (_c *Module_ID_Call) Run(run func()) *Module_ID_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Module_ID_Call) Return(_a0 string) *Module_ID_Call { + _c.Call.Return(_a0) + return _c +} diff --git a/mocks/module_repository.go b/mocks/module_repository.go new file mode 100644 index 00000000..99ca2fa5 --- /dev/null +++ b/mocks/module_repository.go @@ -0,0 +1,104 @@ +// Code generated by mockery v2.10.0. DO NOT EDIT. + +package mocks + +import ( + domain "github.com/odpf/entropy/domain" + mock "github.com/stretchr/testify/mock" +) + +// ModuleRepository is an autogenerated mock type for the ModuleRepository type +type ModuleRepository struct { + mock.Mock +} + +type ModuleRepository_Expecter struct { + mock *mock.Mock +} + +func (_m *ModuleRepository) EXPECT() *ModuleRepository_Expecter { + return &ModuleRepository_Expecter{mock: &_m.Mock} +} + +// Get provides a mock function with given fields: id +func (_m *ModuleRepository) Get(id string) (domain.Module, error) { + ret := _m.Called(id) + + var r0 domain.Module + if rf, ok := ret.Get(0).(func(string) domain.Module); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(domain.Module) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ModuleRepository_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type ModuleRepository_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - id string +func (_e *ModuleRepository_Expecter) Get(id interface{}) *ModuleRepository_Get_Call { + return &ModuleRepository_Get_Call{Call: _e.mock.On("Get", id)} +} + +func (_c *ModuleRepository_Get_Call) Run(run func(id string)) *ModuleRepository_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(string)) + }) + return _c +} + +func (_c *ModuleRepository_Get_Call) Return(_a0 domain.Module, _a1 error) *ModuleRepository_Get_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +// Register provides a mock function with given fields: module +func (_m *ModuleRepository) Register(module domain.Module) error { + ret := _m.Called(module) + + var r0 error + if rf, ok := ret.Get(0).(func(domain.Module) error); ok { + r0 = rf(module) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ModuleRepository_Register_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Register' +type ModuleRepository_Register_Call struct { + *mock.Call +} + +// Register is a helper method to define mock.On call +// - module domain.Module +func (_e *ModuleRepository_Expecter) Register(module interface{}) *ModuleRepository_Register_Call { + return &ModuleRepository_Register_Call{Call: _e.mock.On("Register", module)} +} + +func (_c *ModuleRepository_Register_Call) Run(run func(module domain.Module)) *ModuleRepository_Register_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(domain.Module)) + }) + return _c +} + +func (_c *ModuleRepository_Register_Call) Return(_a0 error) *ModuleRepository_Register_Call { + _c.Call.Return(_a0) + return _c +} diff --git a/mocks/module_service.go b/mocks/module_service.go new file mode 100644 index 00000000..bbc0f380 --- /dev/null +++ b/mocks/module_service.go @@ -0,0 +1,70 @@ +// Code generated by mockery v2.10.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + domain "github.com/odpf/entropy/domain" + mock "github.com/stretchr/testify/mock" +) + +// ModuleService is an autogenerated mock type for the ServiceInterface type +type ModuleService struct { + mock.Mock +} + +type ModuleService_Expecter struct { + mock *mock.Mock +} + +func (_m *ModuleService) EXPECT() *ModuleService_Expecter { + return &ModuleService_Expecter{mock: &_m.Mock} +} + +// Sync provides a mock function with given fields: ctx, res +func (_m *ModuleService) Sync(ctx context.Context, res *domain.Resource) (*domain.Resource, error) { + ret := _m.Called(ctx, res) + + var r0 *domain.Resource + if rf, ok := ret.Get(0).(func(context.Context, *domain.Resource) *domain.Resource); ok { + r0 = rf(ctx, res) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain.Resource) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *domain.Resource) error); ok { + r1 = rf(ctx, res) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ModuleService_Sync_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Sync' +type ModuleService_Sync_Call struct { + *mock.Call +} + +// Sync is a helper method to define mock.On call +// - ctx context.Context +// - res *domain.Resource +func (_e *ModuleService_Expecter) Sync(ctx interface{}, res interface{}) *ModuleService_Sync_Call { + return &ModuleService_Sync_Call{Call: _e.mock.On("Sync", ctx, res)} +} + +func (_c *ModuleService_Sync_Call) Run(run func(ctx context.Context, res *domain.Resource)) *ModuleService_Sync_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(*domain.Resource)) + }) + return _c +} + +func (_c *ModuleService_Sync_Call) Return(_a0 *domain.Resource, _a1 error) *ModuleService_Sync_Call { + _c.Call.Return(_a0, _a1) + return _c +} diff --git a/mocks/service_interface.go b/mocks/resource_service.go similarity index 59% rename from mocks/service_interface.go rename to mocks/resource_service.go index ac218fde..d0b26763 100644 --- a/mocks/service_interface.go +++ b/mocks/resource_service.go @@ -69,13 +69,13 @@ func (_c *ResourceService_CreateResource_Call) Return(_a0 *domain.Resource, _a1 return _c } -// UpdateResource provides a mock function with given fields: ctx, urn, configs -func (_m *ResourceService) UpdateResource(ctx context.Context, urn string, configs map[string]interface{}) (*domain.Resource, error) { - ret := _m.Called(ctx, urn, configs) +// GetResource provides a mock function with given fields: ctx, urn +func (_m *ResourceService) GetResource(ctx context.Context, urn string) (*domain.Resource, error) { + ret := _m.Called(ctx, urn) var r0 *domain.Resource - if rf, ok := ret.Get(0).(func(context.Context, string, map[string]interface{}) *domain.Resource); ok { - r0 = rf(ctx, urn, configs) + if rf, ok := ret.Get(0).(func(context.Context, string) *domain.Resource); ok { + r0 = rf(ctx, urn) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*domain.Resource) @@ -83,8 +83,55 @@ func (_m *ResourceService) UpdateResource(ctx context.Context, urn string, confi } var r1 error - if rf, ok := ret.Get(1).(func(context.Context, string, map[string]interface{}) error); ok { - r1 = rf(ctx, urn, configs) + if rf, ok := ret.Get(1).(func(context.Context, string) error); ok { + r1 = rf(ctx, urn) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// ResourceService_GetResource_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetResource' +type ResourceService_GetResource_Call struct { + *mock.Call +} + +// GetResource is a helper method to define mock.On call +// - ctx context.Context +// - urn string +func (_e *ResourceService_Expecter) GetResource(ctx interface{}, urn interface{}) *ResourceService_GetResource_Call { + return &ResourceService_GetResource_Call{Call: _e.mock.On("GetResource", ctx, urn)} +} + +func (_c *ResourceService_GetResource_Call) Run(run func(ctx context.Context, urn string)) *ResourceService_GetResource_Call { + _c.Call.Run(func(args mock.Arguments) { + run(args[0].(context.Context), args[1].(string)) + }) + return _c +} + +func (_c *ResourceService_GetResource_Call) Return(_a0 *domain.Resource, _a1 error) *ResourceService_GetResource_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +// UpdateResource provides a mock function with given fields: ctx, res +func (_m *ResourceService) UpdateResource(ctx context.Context, res *domain.Resource) (*domain.Resource, error) { + ret := _m.Called(ctx, res) + + var r0 *domain.Resource + if rf, ok := ret.Get(0).(func(context.Context, *domain.Resource) *domain.Resource); ok { + r0 = rf(ctx, res) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*domain.Resource) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, *domain.Resource) error); ok { + r1 = rf(ctx, res) } else { r1 = ret.Error(1) } @@ -99,15 +146,14 @@ type ResourceService_UpdateResource_Call struct { // UpdateResource is a helper method to define mock.On call // - ctx context.Context -// - urn string -// - configs map[string]interface{} -func (_e *ResourceService_Expecter) UpdateResource(ctx interface{}, urn interface{}, configs interface{}) *ResourceService_UpdateResource_Call { - return &ResourceService_UpdateResource_Call{Call: _e.mock.On("UpdateResource", ctx, urn, configs)} +// - res *domain.Resource +func (_e *ResourceService_Expecter) UpdateResource(ctx interface{}, res interface{}) *ResourceService_UpdateResource_Call { + return &ResourceService_UpdateResource_Call{Call: _e.mock.On("UpdateResource", ctx, res)} } -func (_c *ResourceService_UpdateResource_Call) Run(run func(ctx context.Context, urn string, configs map[string]interface{})) *ResourceService_UpdateResource_Call { +func (_c *ResourceService_UpdateResource_Call) Run(run func(ctx context.Context, res *domain.Resource)) *ResourceService_UpdateResource_Call { _c.Call.Run(func(args mock.Arguments) { - run(args[0].(context.Context), args[1].(string), args[2].(map[string]interface{})) + run(args[0].(context.Context), args[1].(*domain.Resource)) }) return _c } diff --git a/modules/log/module.go b/modules/log/module.go new file mode 100644 index 00000000..8d472252 --- /dev/null +++ b/modules/log/module.go @@ -0,0 +1,24 @@ +package log + +import ( + "encoding/json" + "fmt" + "github.com/odpf/entropy/domain" +) + +type Module struct{} + +func (m *Module) ID() string { + return "log" +} + +func (m *Module) Apply(r *domain.Resource) (domain.ResourceStatus, error) { + bytes, err := json.MarshalIndent(r, "", " ") + if err != nil { + return domain.ResourceStatusError, err + } + fmt.Println("=======================================================") + fmt.Println(string(bytes)) + fmt.Println("=======================================================") + return domain.ResourceStatusCompleted, nil +} diff --git a/pkg/module/service.go b/pkg/module/service.go new file mode 100644 index 00000000..1a57bb73 --- /dev/null +++ b/pkg/module/service.go @@ -0,0 +1,32 @@ +package module + +import ( + "context" + "github.com/odpf/entropy/domain" + "github.com/odpf/entropy/store" +) + +type ServiceInterface interface { + Sync(ctx context.Context, res *domain.Resource) (*domain.Resource, error) +} + +type Service struct { + moduleRepository store.ModuleRepository +} + +func NewService(moduleRepository store.ModuleRepository) *Service { + return &Service{ + moduleRepository: moduleRepository, + } +} + +func (s *Service) Sync(ctx context.Context, r *domain.Resource) (*domain.Resource, error) { + module, err := s.moduleRepository.Get(r.Kind) + if err != nil { + r.Status = domain.ResourceStatusError + return r, err + } + status, err := module.Apply(r) + r.Status = status + return r, err +} diff --git a/pkg/module/service_test.go b/pkg/module/service_test.go new file mode 100644 index 00000000..99be315e --- /dev/null +++ b/pkg/module/service_test.go @@ -0,0 +1,143 @@ +package module + +import ( + "context" + "errors" + "github.com/odpf/entropy/domain" + "github.com/odpf/entropy/mocks" + "github.com/odpf/entropy/store" + "reflect" + "testing" + "time" +) + +func TestService_Sync(t *testing.T) { + type fields struct { + moduleRepository store.ModuleRepository + } + type args struct { + ctx context.Context + r *domain.Resource + } + currentTime := time.Now() + r := &domain.Resource{ + Urn: "p-testdata-gl-testname-mock", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "mock", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: currentTime, + UpdatedAt: currentTime, + } + applyFailedErr := errors.New("apply failed") + + mockModule := &mocks.Module{} + mockModule.EXPECT().ID().Return("mock") + mockModuleRepo := &mocks.ModuleRepository{} + + tests := []struct { + name string + setup func(t *testing.T) + fields fields + args args + want *domain.Resource + wantErr error + }{ + { + name: "test sync completed", + setup: func(t *testing.T) { + mockModule.EXPECT().Apply(r).Return(domain.ResourceStatusCompleted, nil).Once() + mockModuleRepo.EXPECT().Get("mock").Return(mockModule, nil).Once() + }, + fields: fields{ + moduleRepository: mockModuleRepo, + }, + args: args{ + ctx: nil, + r: r, + }, + want: &domain.Resource{ + Urn: "p-testdata-gl-testname-mock", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "mock", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusCompleted, + CreatedAt: currentTime, + UpdatedAt: currentTime, + }, + wantErr: nil, + }, + { + name: "test sync module not found error", + setup: func(t *testing.T) { + mockModuleRepo.EXPECT().Get("mock").Return(nil, store.ModuleNotFoundError).Once() + }, + fields: fields{ + moduleRepository: mockModuleRepo, + }, + args: args{ + ctx: nil, + r: r, + }, + want: &domain.Resource{ + Urn: "p-testdata-gl-testname-mock", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "mock", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusError, + CreatedAt: currentTime, + UpdatedAt: currentTime, + }, + wantErr: store.ModuleNotFoundError, + }, + { + name: "test sync module error while applying", + setup: func(t *testing.T) { + mockModule.EXPECT().Apply(r).Return(domain.ResourceStatusError, applyFailedErr).Once() + + mockModuleRepo.EXPECT().Get("mock").Return(mockModule, nil).Once() + }, + fields: fields{ + moduleRepository: mockModuleRepo, + }, + args: args{ + ctx: nil, + r: r, + }, + want: &domain.Resource{ + Urn: "p-testdata-gl-testname-mock", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "mock", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusError, + CreatedAt: currentTime, + UpdatedAt: currentTime, + }, + wantErr: applyFailedErr, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s := &Service{ + moduleRepository: tt.fields.moduleRepository, + } + tt.setup(t) + got, err := s.Sync(tt.args.ctx, tt.args.r) + if !errors.Is(err, tt.wantErr) { + t.Errorf("Sync() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Sync() got = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/resource/service.go b/pkg/resource/service.go index 99006277..ee977983 100644 --- a/pkg/resource/service.go +++ b/pkg/resource/service.go @@ -8,7 +8,8 @@ import ( type ServiceInterface interface { CreateResource(ctx context.Context, res *domain.Resource) (*domain.Resource, error) - UpdateResource(ctx context.Context, urn string, configs map[string]interface{}) (*domain.Resource, error) + UpdateResource(ctx context.Context, res *domain.Resource) (*domain.Resource, error) + GetResource(ctx context.Context, urn string) (*domain.Resource, error) } type Service struct { @@ -22,7 +23,6 @@ func NewService(repository store.ResourceRepository) *Service { } func (s *Service) CreateResource(ctx context.Context, res *domain.Resource) (*domain.Resource, error) { - res.Urn = domain.GenerateResourceUrn(res) res.Status = domain.ResourceStatusPending err := s.resourceRepository.Create(res) if err != nil { @@ -35,19 +35,18 @@ func (s *Service) CreateResource(ctx context.Context, res *domain.Resource) (*do return createdResource, nil } -func (s *Service) UpdateResource(ctx context.Context, urn string, configs map[string]interface{}) (*domain.Resource, error) { - res, err := s.resourceRepository.GetByURN(urn) +func (s *Service) UpdateResource(ctx context.Context, res *domain.Resource) (*domain.Resource, error) { + err := s.resourceRepository.Update(res) if err != nil { return nil, err } - res.Configs = configs - err = s.resourceRepository.Update(res) - if err != nil { - return nil, err - } - updatedRes, err := s.resourceRepository.GetByURN(urn) + updatedRes, err := s.resourceRepository.GetByURN(res.Urn) if err != nil { return nil, err } return updatedRes, nil } + +func (s *Service) GetResource(ctx context.Context, urn string) (*domain.Resource, error) { + return s.resourceRepository.GetByURN(urn) +} diff --git a/pkg/resource/service_test.go b/pkg/resource/service_test.go index 5ba0837d..51186318 100644 --- a/pkg/resource/service_test.go +++ b/pkg/resource/service_test.go @@ -15,20 +15,20 @@ import ( func TestService_CreateResource(t *testing.T) { t.Run("test create new resource", func(t *testing.T) { - mockRepo := &mocks.ResourceRepository{} argResource := &domain.Resource{ + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: map[string]interface{}{}, Labels: map[string]string{}, } currentTime := time.Now() want := &domain.Resource{ - Urn: "p-testdata-gl-testname-firehose", + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: map[string]interface{}{}, Labels: map[string]string{}, Status: domain.ResourceStatusPending, @@ -36,16 +36,17 @@ func TestService_CreateResource(t *testing.T) { UpdatedAt: currentTime, } wantErr := error(nil) + + mockRepo := &mocks.ResourceRepository{} mockRepo.EXPECT().Create(mock.Anything).Run(func(r *domain.Resource) { - assert.Equal(t, "p-testdata-gl-testname-firehose", r.Urn) assert.Equal(t, domain.ResourceStatusPending, r.Status) }).Return(nil).Once() - mockRepo.EXPECT().GetByURN("p-testdata-gl-testname-firehose").Return(&domain.Resource{ - Urn: "p-testdata-gl-testname-firehose", + mockRepo.EXPECT().GetByURN("p-testdata-gl-testname-log").Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: map[string]interface{}{}, Labels: map[string]string{}, Status: domain.ResourceStatusPending, @@ -67,16 +68,16 @@ func TestService_CreateResource(t *testing.T) { t.Run("test create duplicate resource", func(t *testing.T) { mockRepo := &mocks.ResourceRepository{} argResource := &domain.Resource{ + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: map[string]interface{}{}, Labels: map[string]string{}, } want := (*domain.Resource)(nil) wantErr := store.ResourceAlreadyExistsError mockRepo.EXPECT().Create(mock.Anything).Run(func(r *domain.Resource) { - assert.Equal(t, "p-testdata-gl-testname-firehose", r.Urn) assert.Equal(t, domain.ResourceStatusPending, r.Status) }).Return(store.ResourceAlreadyExistsError).Once() @@ -95,17 +96,13 @@ func TestService_CreateResource(t *testing.T) { func TestService_UpdateResource(t *testing.T) { t.Run("test update existing resource", func(t *testing.T) { mockRepo := &mocks.ResourceRepository{} - argUrn := "p-testdata-gl-testname-firehose" - argConfigs := map[string]interface{}{ - "replicas": "10", - } currentTime := time.Now() updatedTime := time.Now() want := &domain.Resource{ - Urn: "p-testdata-gl-testname-firehose", + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: map[string]interface{}{ "replicas": "10", }, @@ -116,29 +113,17 @@ func TestService_UpdateResource(t *testing.T) { } wantErr := error(nil) - mockRepo.EXPECT().GetByURN("p-testdata-gl-testname-firehose").Return(&domain.Resource{ - Urn: "p-testdata-gl-testname-firehose", - Name: "testname", - Parent: "p-testdata-gl", - Kind: "firehose", - Configs: map[string]interface{}{}, - Labels: map[string]string{}, - Status: domain.ResourceStatusPending, - CreatedAt: currentTime, - UpdatedAt: currentTime, - }, nil).Once() - mockRepo.EXPECT().Update(mock.Anything).Run(func(r *domain.Resource) { - assert.Equal(t, "p-testdata-gl-testname-firehose", r.Urn) + assert.Equal(t, "p-testdata-gl-testname-log", r.Urn) assert.Equal(t, domain.ResourceStatusPending, r.Status) assert.Equal(t, currentTime, r.CreatedAt) }).Return(nil) - mockRepo.EXPECT().GetByURN("p-testdata-gl-testname-firehose").Return(&domain.Resource{ - Urn: "p-testdata-gl-testname-firehose", + mockRepo.EXPECT().GetByURN("p-testdata-gl-testname-log").Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: map[string]interface{}{ "replicas": "10", }, @@ -149,7 +134,17 @@ func TestService_UpdateResource(t *testing.T) { }, nil).Once() s := NewService(mockRepo) - got, err := s.UpdateResource(context.Background(), argUrn, argConfigs) + got, err := s.UpdateResource(context.Background(), &domain.Resource{ + Urn: "p-testdata-gl-testname-log", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "log", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: currentTime, + UpdatedAt: currentTime, + }) if !errors.Is(err, wantErr) { t.Errorf("UpdateResource() error = %v, wantErr %v", err, wantErr) return @@ -161,20 +156,27 @@ func TestService_UpdateResource(t *testing.T) { t.Run("test update non-existent resource", func(t *testing.T) { mockRepo := &mocks.ResourceRepository{} - argUrn := "p-testdata-gl-testname-firehose" - argConfigs := map[string]interface{}{ - "replicas": "10", - } + want := (*domain.Resource)(nil) wantErr := store.ResourceNotFoundError mockRepo.EXPECT(). - GetByURN("p-testdata-gl-testname-firehose"). - Return(nil, store.ResourceNotFoundError). + Update(mock.Anything). + Return(store.ResourceNotFoundError). Once() s := NewService(mockRepo) - got, err := s.UpdateResource(context.Background(), argUrn, argConfigs) + got, err := s.UpdateResource(context.Background(), &domain.Resource{ + Urn: "p-testdata-gl-testname-log", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "log", + Configs: map[string]interface{}{}, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + }) if !errors.Is(err, wantErr) { t.Errorf("UpdateResource() error = %v, wantErr %v", err, wantErr) return @@ -184,3 +186,71 @@ func TestService_UpdateResource(t *testing.T) { } }) } + +func TestService_GetResource(t *testing.T) { + t.Run("test get existing resource", func(t *testing.T) { + mockRepo := &mocks.ResourceRepository{} + currentTime := time.Now() + updatedTime := time.Now() + want := &domain.Resource{ + Urn: "p-testdata-gl-testname-log", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "log", + Configs: map[string]interface{}{ + "replicas": "10", + }, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: currentTime, + UpdatedAt: updatedTime, + } + wantErr := error(nil) + + mockRepo.EXPECT().GetByURN("p-testdata-gl-testname-log").Return(&domain.Resource{ + Urn: "p-testdata-gl-testname-log", + Name: "testname", + Parent: "p-testdata-gl", + Kind: "log", + Configs: map[string]interface{}{ + "replicas": "10", + }, + Labels: map[string]string{}, + Status: domain.ResourceStatusPending, + CreatedAt: currentTime, + UpdatedAt: updatedTime, + }, nil).Once() + + s := NewService(mockRepo) + got, err := s.GetResource(context.Background(), "p-testdata-gl-testname-log") + if !errors.Is(err, wantErr) { + t.Errorf("GetResource() error = %v, wantErr %v", err, wantErr) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("GetResource() got = %v, want %v", got, want) + } + }) + + t.Run("test get non-existent resource", func(t *testing.T) { + mockRepo := &mocks.ResourceRepository{} + + want := (*domain.Resource)(nil) + wantErr := store.ResourceNotFoundError + + mockRepo.EXPECT(). + GetByURN(mock.Anything). + Return(nil, store.ResourceNotFoundError). + Once() + + s := NewService(mockRepo) + got, err := s.GetResource(context.Background(), "p-testdata-gl-testname-log") + if !errors.Is(err, wantErr) { + t.Errorf("GetResource() error = %v, wantErr %v", err, wantErr) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("GetResource() got = %v, want %v", got, want) + } + }) +} diff --git a/store/inmemory/module_repository.go b/store/inmemory/module_repository.go new file mode 100644 index 00000000..c3a7742b --- /dev/null +++ b/store/inmemory/module_repository.go @@ -0,0 +1,31 @@ +package inmemory + +import ( + "github.com/odpf/entropy/domain" + "github.com/odpf/entropy/store" +) + +type ModuleRepository struct { + collection map[string]domain.Module +} + +func NewModuleRepository() *ModuleRepository { + return &ModuleRepository{ + collection: map[string]domain.Module{}, + } +} + +func (mr *ModuleRepository) Register(module domain.Module) error { + if _, exists := mr.collection[module.ID()]; exists { + return store.ModuleAlreadyExistsError + } + mr.collection[module.ID()] = module + return nil +} + +func (mr *ModuleRepository) Get(id string) (domain.Module, error) { + if module, exists := mr.collection[id]; exists { + return module, nil + } + return nil, store.ModuleNotFoundError +} diff --git a/store/inmemory/module_repository_test.go b/store/inmemory/module_repository_test.go new file mode 100644 index 00000000..8e53ea28 --- /dev/null +++ b/store/inmemory/module_repository_test.go @@ -0,0 +1,118 @@ +package inmemory + +import ( + "errors" + "github.com/odpf/entropy/domain" + "github.com/odpf/entropy/modules/log" + "github.com/odpf/entropy/store" + "reflect" + "testing" +) + +func TestModuleRepository_Get(t *testing.T) { + type fields struct { + collection map[string]domain.Module + } + type args struct { + id string + } + mod := &log.Module{} + tests := []struct { + name string + fields fields + args args + want domain.Module + wantErr error + }{ + { + name: "test get module from repository", + fields: fields{ + collection: map[string]domain.Module{ + mod.ID(): mod, + }, + }, + args: args{ + id: "log", + }, + want: mod, + wantErr: nil, + }, + { + name: "test get non-existent module from repository", + fields: fields{ + collection: map[string]domain.Module{ + mod.ID(): mod, + }, + }, + args: args{ + id: "notlog", + }, + want: nil, + wantErr: store.ModuleNotFoundError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mr := &ModuleRepository{ + collection: tt.fields.collection, + } + got, err := mr.Get(tt.args.id) + if !errors.Is(err, tt.wantErr) { + t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Get() got = %v, want %v", got, tt.want) + } + }) + } +} + +func TestModuleRepository_Register(t *testing.T) { + type fields struct { + collection map[string]domain.Module + } + type args struct { + module domain.Module + } + mod := &log.Module{} + tests := []struct { + name string + fields fields + args args + wantErr error + }{ + { + name: "test register module", + fields: fields{ + collection: map[string]domain.Module{}, + }, + args: args{ + module: mod, + }, + wantErr: nil, + }, + { + name: "test register already added module", + fields: fields{ + collection: map[string]domain.Module{ + mod.ID(): mod, + }, + }, + args: args{ + module: mod, + }, + wantErr: store.ModuleAlreadyExistsError, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mr := &ModuleRepository{ + collection: tt.fields.collection, + } + if err := mr.Register(tt.args.module); !errors.Is(err, tt.wantErr) { + t.Errorf("Register() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/store/mongodb/resource_repository_test.go b/store/mongodb/resource_repository_test.go index 68ba3dea..0d4094fc 100644 --- a/store/mongodb/resource_repository_test.go +++ b/store/mongodb/resource_repository_test.go @@ -56,10 +56,10 @@ func TestResourceRepository_Create(t *testing.T) { fields: func(mt *mtest.T) fields { return fields{mt.Coll} }, args: func(mt *mtest.T) args { return args{&domain.Resource{ - Urn: "p-testdata-gl-testname-firehose", + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: map[string]interface{}{}, Labels: map[string]string{}, Status: domain.ResourceStatusPending, @@ -83,10 +83,10 @@ func TestResourceRepository_Create(t *testing.T) { }, args: func(mt *mtest.T) args { return args{&domain.Resource{ - Urn: "p-testdata-gl-testname-firehose", + Urn: "p-testdata-gl-testname-log", Name: "testname", Parent: "p-testdata-gl", - Kind: "firehose", + Kind: "log", Configs: map[string]interface{}{}, Labels: map[string]string{}, Status: domain.ResourceStatusPending, @@ -129,12 +129,12 @@ func TestResourceRepository_GetByURN(t *testing.T) { name: "test resource get success", setup: func(mt *mtest.T) { mt.AddMockResponses(mtest.CreateCursorResponse(1, "test.ns", mtest.FirstBatch, bson.D{ - {Key: "urn", Value: "p-testdata-gl-testname-firehose"}, + {Key: "urn", Value: "p-testdata-gl-testname-log"}, })) }, fields: func(mt *mtest.T) fields { return fields{mt.Coll} }, - args: func(mt *mtest.T) args { return args{"p-testdata-gl-testname-firehose"} }, - want: func(mt *mtest.T) *domain.Resource { return &domain.Resource{Urn: "p-testdata-gl-testname-firehose"} }, + args: func(mt *mtest.T) args { return args{"p-testdata-gl-testname-log"} }, + want: func(mt *mtest.T) *domain.Resource { return &domain.Resource{Urn: "p-testdata-gl-testname-log"} }, wantErr: nil, }, { @@ -150,7 +150,7 @@ func TestResourceRepository_GetByURN(t *testing.T) { }) }, fields: func(mt *mtest.T) fields { return fields{mt.Coll} }, - args: func(mt *mtest.T) args { return args{"p-testdata-gl-unknown-firehose"} }, + args: func(mt *mtest.T) args { return args{"p-testdata-gl-unknown-log"} }, want: func(mt *mtest.T) *domain.Resource { return nil }, wantErr: store.ResourceNotFoundError, }, diff --git a/store/store.go b/store/store.go index 8ad864c1..15d4a1a2 100644 --- a/store/store.go +++ b/store/store.go @@ -9,6 +9,8 @@ import ( var ( ResourceAlreadyExistsError = errors.New("resource already exists") ResourceNotFoundError = errors.New("no resource(s) found") + ModuleAlreadyExistsError = errors.New("module already exists") + ModuleNotFoundError = errors.New("no module(s) found") ) var ResourceRepositoryName = "resources" @@ -19,3 +21,8 @@ type ResourceRepository interface { GetByURN(urn string) (*domain.Resource, error) Migrate() error } + +type ModuleRepository interface { + Register(module domain.Module) error + Get(id string) (domain.Module, error) +}