From 36a0e2a6a1218c547e3846170f73ea71615ed392 Mon Sep 17 00:00:00 2001 From: vipul-rawat Date: Wed, 11 Dec 2024 18:55:04 +0530 Subject: [PATCH 1/8] added application add command to create applications --- application/handler/application.go | 34 ++++++ application/handler/handler_test.go | 68 +++++++++++ application/handler/interface.go | 7 ++ application/handler/mock_interface.go | 55 +++++++++ application/service/application.go | 65 ++++++++++ application/service/application_test.go | 153 ++++++++++++++++++++++++ application/service/models.go | 53 ++++++++ application/service/models_test.go | 122 +++++++++++++++++++ main.go | 16 ++- 9 files changed, 569 insertions(+), 4 deletions(-) create mode 100644 application/handler/application.go create mode 100644 application/handler/handler_test.go create mode 100644 application/handler/interface.go create mode 100644 application/handler/mock_interface.go create mode 100644 application/service/application.go create mode 100644 application/service/application_test.go create mode 100644 application/service/models.go create mode 100644 application/service/models_test.go diff --git a/application/handler/application.go b/application/handler/application.go new file mode 100644 index 0000000..79400cc --- /dev/null +++ b/application/handler/application.go @@ -0,0 +1,34 @@ +package handler + +import ( + "errors" + "gofr.dev/pkg/gofr" +) + +var ( + ErrorApplicationNameNotProvided = errors.New("please enter application name, -name=") +) + +type Handler struct { + appAdd ApplicationAdder +} + +func New(appAdd ApplicationAdder) *Handler { + return &Handler{ + appAdd: appAdd, + } +} + +func (h *Handler) Add(ctx *gofr.Context) (any, error) { + name := ctx.Param("name") + if name == "" { + return nil, ErrorApplicationNameNotProvided + } + + err := h.appAdd.AddApplication(ctx, name) + if err != nil { + return nil, err + } + + return "Application " + name + " added successfully!", nil +} diff --git a/application/handler/handler_test.go b/application/handler/handler_test.go new file mode 100644 index 0000000..6133938 --- /dev/null +++ b/application/handler/handler_test.go @@ -0,0 +1,68 @@ +package handler + +import ( + "errors" + "go.uber.org/mock/gomock" + "gofr.dev/pkg/gofr/cmd" + + "github.com/stretchr/testify/require" + "gofr.dev/pkg/gofr" + "gofr.dev/pkg/gofr/container" + "testing" +) + +func TestHandler_Add(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockAppAdder := NewMockApplicationAdder(ctrl) + + testCases := []struct { + name string + appName string + mockCalls []*gomock.Call + expected any + expErr error + }{ + { + name: "success", + appName: "test-app", + mockCalls: []*gomock.Call{ + mockAppAdder.EXPECT().AddApplication(gomock.Any(), "test-app").Return(nil), + }, + expected: "Application test-app added successfully!", + expErr: nil, + }, + { + name: "missing name parameter", + appName: "", + expected: nil, + expErr: ErrorApplicationNameNotProvided, + }, + { + name: "error adding application", + appName: "test-app", + mockCalls: []*gomock.Call{ + mockAppAdder.EXPECT().AddApplication(gomock.Any(), "test-app").Return(errors.New("internal error")), + }, + expected: nil, + expErr: errors.New("internal error"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + mockCont, _ := container.NewMockContainer(t) + ctx := &gofr.Context{ + Container: mockCont, + Request: cmd.NewRequest([]string{"", "-name=" + tc.appName}), + } + + h := New(mockAppAdder) + res, err := h.Add(ctx) + + require.Equal(t, tc.expErr, err) + require.Equal(t, tc.expected, res) + }) + } +} diff --git a/application/handler/interface.go b/application/handler/interface.go new file mode 100644 index 0000000..fb29e0e --- /dev/null +++ b/application/handler/interface.go @@ -0,0 +1,7 @@ +package handler + +import "gofr.dev/pkg/gofr" + +type ApplicationAdder interface { + AddApplication(ctx *gofr.Context, name string) error +} diff --git a/application/handler/mock_interface.go b/application/handler/mock_interface.go new file mode 100644 index 0000000..1435ad3 --- /dev/null +++ b/application/handler/mock_interface.go @@ -0,0 +1,55 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: interface.go +// +// Generated by this command: +// +// mockgen -source=interface.go -destination=mock_interface.go -package=handler +// + +// Package handler is a generated GoMock package. +package handler + +import ( + reflect "reflect" + + gomock "go.uber.org/mock/gomock" + gofr "gofr.dev/pkg/gofr" +) + +// MockApplicationAdder is a mock of ApplicationAdder interface. +type MockApplicationAdder struct { + ctrl *gomock.Controller + recorder *MockApplicationAdderMockRecorder + isgomock struct{} +} + +// MockApplicationAdderMockRecorder is the mock recorder for MockApplicationAdder. +type MockApplicationAdderMockRecorder struct { + mock *MockApplicationAdder +} + +// NewMockApplicationAdder creates a new mock instance. +func NewMockApplicationAdder(ctrl *gomock.Controller) *MockApplicationAdder { + mock := &MockApplicationAdder{ctrl: ctrl} + mock.recorder = &MockApplicationAdderMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockApplicationAdder) EXPECT() *MockApplicationAdderMockRecorder { + return m.recorder +} + +// AddApplication mocks base method. +func (m *MockApplicationAdder) AddApplication(ctx *gofr.Context, name string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddApplication", ctx, name) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddApplication indicates an expected call of AddApplication. +func (mr *MockApplicationAdderMockRecorder) AddApplication(ctx, name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddApplication", reflect.TypeOf((*MockApplicationAdder)(nil).AddApplication), ctx, name) +} diff --git a/application/service/application.go b/application/service/application.go new file mode 100644 index 0000000..3456abf --- /dev/null +++ b/application/service/application.go @@ -0,0 +1,65 @@ +package service + +import ( + "encoding/json" + "fmt" + "net/http" + + "gofr.dev/pkg/gofr" +) + +type Service struct { +} + +func New() *Service { + return &Service{} +} + +func (s *Service) AddApplication(ctx *gofr.Context, name string) error { + var ( + envs []Environment + input string + ) + + app := &Application{Name: name} + api := ctx.GetHTTPService("api-service") + order := 1 + + ctx.Out.Print("Do you wish to add environments to the application? (y/n) ") + + fmt.Scanf("%s", &input) + + for { + if input == "y" { + ctx.Out.Println("Enter environment name:") + fmt.Scanf("%s", &input) + + envs = append(envs, Environment{Name: input, Order: order}) + order++ + } else { + break + } + + ctx.Out.Print("Do you wish to add more? (y/n) ") + fmt.Scanf("%s", &input) + if input == "n" { + break + } + } + + app.Envs = envs + body, _ := json.Marshal(app) + + resp, err := api.PostWithHeaders(ctx, "application", nil, body, nil) + if err != nil { + return err + } + + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + return getAPIError(resp) + } + + return nil +} diff --git a/application/service/application_test.go b/application/service/application_test.go new file mode 100644 index 0000000..0bec1d2 --- /dev/null +++ b/application/service/application_test.go @@ -0,0 +1,153 @@ +package service + +import ( + "bytes" + "encoding/json" + "errors" + "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" + "gofr.dev/pkg/gofr" + "gofr.dev/pkg/gofr/cmd/terminal" + "gofr.dev/pkg/gofr/container" + "gofr.dev/pkg/gofr/service" + "io" + "net/http" + "os" + "testing" +) + +var errAPICall = errors.New("error in API call") + +func Test_AddApplication(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockCont, mocks := container.NewMockContainer(t, func(c *container.Container, ctrl *gomock.Controller) any { + return service.NewMockHTTP(ctrl) + }) + mockCont.Services["api-service"] = mocks.HTTPService + ctx := &gofr.Context{Container: mockCont, Out: terminal.New()} + + b, err := json.Marshal(MockErrorResponse{Error: "Something went wrong"}) + if err != nil { + t.Fatalf("Failed to marshal test response body: %v", err) + } + + testCases := []struct { + name string + mockCalls []*gomock.Call + expError error + }{ + { + name: "success Post call", + mockCalls: []*gomock.Call{ + mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). + Return(&http.Response{StatusCode: http.StatusCreated, Body: io.NopCloser(&errorReader{})}, nil), + }, + expError: nil, + }, + { + name: "error in Post call", + mockCalls: []*gomock.Call{ + mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). + Return(nil, errAPICall), + }, + expError: errAPICall, + }, + { + name: "unexpected response", + mockCalls: []*gomock.Call{ + mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). + Return(&http.Response{StatusCode: http.StatusInternalServerError, Body: io.NopCloser(bytes.NewBuffer(b))}, nil), + }, + expError: &ErrAPIService{StatusCode: http.StatusInternalServerError, Message: "Something went wrong"}, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + s := New() + + errSvc := s.AddApplication(ctx, "test") + + require.Equal(t, tt.expError, errSvc) + }) + } +} + +func Test_AddApplication_WithEnvs(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockCont, mocks := container.NewMockContainer(t, func(c *container.Container, ctrl *gomock.Controller) any { + return service.NewMockHTTP(ctrl) + }) + + mockCont.Services["api-service"] = mocks.HTTPService + ctx := &gofr.Context{Container: mockCont, Out: terminal.New()} + + testCases := []struct { + name string + mockCalls []*gomock.Call + userInput string + expectedEnvs []Environment + expError error + }{ + { + name: "success with environments", + userInput: "y\nprod\ny\ndev\nn\n", + expectedEnvs: []Environment{ + {Name: "prod", Order: 1}, + {Name: "dev", Order: 2}, + }, + mockCalls: []*gomock.Call{ + mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). + DoAndReturn(func(ctx *gofr.Context, endpoint string, headers, body, query interface{}) (*http.Response, error) { + var app Application + _ = json.Unmarshal(body.([]byte), &app) + require.Equal(t, "test", app.Name) + require.Equal(t, []Environment{ + {Name: "prod", Order: 1}, + {Name: "dev", Order: 2}, + }, app.Envs) + return &http.Response{StatusCode: http.StatusCreated, Body: io.NopCloser(bytes.NewBuffer(nil))}, nil + }), + }, + expError: nil, + }, + { + name: "no environments added", + userInput: "n\n", + expectedEnvs: []Environment{}, + mockCalls: []*gomock.Call{ + mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). + DoAndReturn(func(ctx *gofr.Context, endpoint string, headers, body, query interface{}) (*http.Response, error) { + var app Application + _ = json.Unmarshal(body.([]byte), &app) + require.Equal(t, "test", app.Name) + require.Empty(t, app.Envs) + return &http.Response{StatusCode: http.StatusCreated, Body: io.NopCloser(bytes.NewBuffer(nil))}, nil + }), + }, + expError: nil, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + s := New() + + // Mock user input + r, w, _ := os.Pipe() + w.Write([]byte(tt.userInput)) + + oldStdin := os.Stdin + os.Stdin = r + + defer func() { os.Stdin = oldStdin }() + + errSvc := s.AddApplication(ctx, "test") + require.Equal(t, tt.expError, errSvc) + }) + } +} diff --git a/application/service/models.go b/application/service/models.go new file mode 100644 index 0000000..44fe149 --- /dev/null +++ b/application/service/models.go @@ -0,0 +1,53 @@ +package service + +import ( + "encoding/json" + "io" + "net/http" +) + +type ErrAPIService struct { + StatusCode int + Message string +} + +func (e *ErrAPIService) Error() string { + return e.Message +} + +var errInternal = &ErrAPIService{ + StatusCode: http.StatusInternalServerError, + Message: "error in POST /application zop-api, invalid response", +} + +func getAPIError(resp *http.Response) *ErrAPIService { + var errResp struct { + Error string `json:"error"` + } + + b, err := io.ReadAll(resp.Body) + if err != nil { + return errInternal + } + + err = json.Unmarshal(b, &errResp) + if err != nil { + return errInternal + } + + return &ErrAPIService{ + StatusCode: resp.StatusCode, + Message: errResp.Error, + } +} + +type Environment struct { + Name string `json:"name"` + Order int `json:"order"` + DeploymentSpace any `json:"deploymentSpace,omitempty"` +} + +type Application struct { + Name string `json:"name"` + Envs []Environment `json:"environments,omitempty"` +} diff --git a/application/service/models_test.go b/application/service/models_test.go new file mode 100644 index 0000000..f67aecd --- /dev/null +++ b/application/service/models_test.go @@ -0,0 +1,122 @@ +package service + +import ( + "bytes" + "encoding/json" + "github.com/stretchr/testify/require" + "io" + "net/http" + "testing" +) + +func TestGetAPIError(t *testing.T) { + tests := []struct { + name string + responseBody interface{} + responseCode int + expectError *ErrAPIService + }{ + { + name: "Valid error response", + responseBody: MockErrorResponse{ + Error: "Something went wrong", + }, + responseCode: http.StatusBadRequest, + expectError: &ErrAPIService{ + StatusCode: http.StatusBadRequest, + Message: "Something went wrong", + }, + }, + { + name: "Malformed JSON response", + responseBody: "{error: unquoted string}", + responseCode: http.StatusBadRequest, + expectError: errInternal, + }, + { + name: "Empty response body", + responseBody: "", + responseCode: http.StatusBadRequest, + expectError: errInternal, + }, + { + name: "Error reading body", + responseBody: nil, + responseCode: http.StatusInternalServerError, + expectError: errInternal, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var bodyReader io.ReadCloser + + if tt.responseBody == nil { + // Simulate error reading body by providing a reader that always errors + bodyReader = io.NopCloser(&errorReader{}) + } else { + b, err := json.Marshal(tt.responseBody) + if err != nil { + t.Fatalf("Failed to marshal test response body: %v", err) + } + + bodyReader = io.NopCloser(bytes.NewBuffer(b)) + } + + // Create a mock response + resp := &http.Response{ + StatusCode: tt.responseCode, + Body: bodyReader, + } + + // Call the function and check results + actualError := getAPIError(resp) + + require.Equal(t, tt.expectError.StatusCode, actualError.StatusCode, "Unexpected status code") + require.Equal(t, tt.expectError.Message, actualError.Message, "Unexpected error message") + }) + } +} + +type MockErrorResponse struct { + Error string `json:"error"` +} + +type errorReader struct{} + +func (e *errorReader) Read(p []byte) (n int, err error) { + return 0, io.ErrUnexpectedEOF +} + +func (e *errorReader) Close() error { + return nil +} + +func Test_ErrAPIError(t *testing.T) { + tests := []struct { + name string + err *ErrAPIService + }{ + { + name: "Valid error", + err: &ErrAPIService{ + StatusCode: http.StatusBadRequest, + Message: "Something went wrong", + }, + }, + { + name: "Empty error message", + err: &ErrAPIService{ + StatusCode: http.StatusBadRequest, + Message: "", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + actual := tt.err.Error() + require.Equal(t, tt.err.Message, actual, "Unexpected error message") + }) + } +} diff --git a/main.go b/main.go index fff82e9..a2426d1 100644 --- a/main.go +++ b/main.go @@ -6,10 +6,12 @@ import ( "os" "path/filepath" - _ "github.com/mattn/go-sqlite3" "gofr.dev/pkg/gofr" "gofr.dev/pkg/gofr/service" + _ "modernc.org/sqlite" + applicationHandler "zop.dev/cli/zop/application/handler" + applicationSvc "zop.dev/cli/zop/application/service" impHandler "zop.dev/cli/zop/cloud/handler" impService "zop.dev/cli/zop/cloud/service/gcp" listSvc "zop.dev/cli/zop/cloud/service/list" @@ -42,13 +44,19 @@ func main() { } defer db.Close() - aStore := impStore.New(db) - aSvc := impService.New(aStore) + accStore := impStore.New() + accSvc := impService.New(accStore) lSvc := listSvc.New() - h := impHandler.New(aSvc, lSvc) + h := impHandler.New(accSvc, lSvc) app.SubCommand("cloud import", h.Import) app.SubCommand("cloud list", h.List) + app.SubCommand("cloud select", h.Set) + + appSvc := applicationSvc.New() + appH := applicationHandler.New(appSvc) + + app.SubCommand("application add", appH.Add) app.Run() } From 7bc24e2b99633bfa68e3f1ed5b53911c6dda6e5e Mon Sep 17 00:00:00 2001 From: vipul-rawat Date: Wed, 11 Dec 2024 19:07:01 +0530 Subject: [PATCH 2/8] fix linters --- application/handler/application.go | 1 + application/handler/handler_test.go | 12 +++++++----- application/service/application.go | 22 ++++++++++++---------- application/service/application_test.go | 19 ++++++++++--------- application/service/models_test.go | 7 ++++--- main.go | 2 +- 6 files changed, 35 insertions(+), 28 deletions(-) diff --git a/application/handler/application.go b/application/handler/application.go index 79400cc..65b9dbd 100644 --- a/application/handler/application.go +++ b/application/handler/application.go @@ -2,6 +2,7 @@ package handler import ( "errors" + "gofr.dev/pkg/gofr" ) diff --git a/application/handler/handler_test.go b/application/handler/handler_test.go index 6133938..deb430b 100644 --- a/application/handler/handler_test.go +++ b/application/handler/handler_test.go @@ -2,15 +2,17 @@ package handler import ( "errors" - "go.uber.org/mock/gomock" - "gofr.dev/pkg/gofr/cmd" + "testing" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" "gofr.dev/pkg/gofr" + "gofr.dev/pkg/gofr/cmd" "gofr.dev/pkg/gofr/container" - "testing" ) +var errAPICall = errors.New("error in API call") + func TestHandler_Add(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -43,10 +45,10 @@ func TestHandler_Add(t *testing.T) { name: "error adding application", appName: "test-app", mockCalls: []*gomock.Call{ - mockAppAdder.EXPECT().AddApplication(gomock.Any(), "test-app").Return(errors.New("internal error")), + mockAppAdder.EXPECT().AddApplication(gomock.Any(), "test-app").Return(errAPICall), }, expected: nil, - expErr: errors.New("internal error"), + expErr: errAPICall, }, } diff --git a/application/service/application.go b/application/service/application.go index 3456abf..afd0e64 100644 --- a/application/service/application.go +++ b/application/service/application.go @@ -15,7 +15,7 @@ func New() *Service { return &Service{} } -func (s *Service) AddApplication(ctx *gofr.Context, name string) error { +func (*Service) AddApplication(ctx *gofr.Context, name string) error { var ( envs []Environment input string @@ -27,21 +27,23 @@ func (s *Service) AddApplication(ctx *gofr.Context, name string) error { ctx.Out.Print("Do you wish to add environments to the application? (y/n) ") - fmt.Scanf("%s", &input) + _, _ = fmt.Scanf("%s", &input) for { - if input == "y" { - ctx.Out.Println("Enter environment name:") - fmt.Scanf("%s", &input) - - envs = append(envs, Environment{Name: input, Order: order}) - order++ - } else { + if input != "y" { break } + ctx.Out.Print("Enter environment name: ") + + _, _ = fmt.Scanf("%s", &input) + envs = append(envs, Environment{Name: input, Order: order}) + order++ + ctx.Out.Print("Do you wish to add more? (y/n) ") - fmt.Scanf("%s", &input) + + _, _ = fmt.Scanf("%s", &input) + if input == "n" { break } diff --git a/application/service/application_test.go b/application/service/application_test.go index 0bec1d2..1642983 100644 --- a/application/service/application_test.go +++ b/application/service/application_test.go @@ -4,16 +4,17 @@ import ( "bytes" "encoding/json" "errors" + "io" + "net/http" + "os" + "testing" + "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" "gofr.dev/pkg/gofr" "gofr.dev/pkg/gofr/cmd/terminal" "gofr.dev/pkg/gofr/container" "gofr.dev/pkg/gofr/service" - "io" - "net/http" - "os" - "testing" ) var errAPICall = errors.New("error in API call") @@ -22,7 +23,7 @@ func Test_AddApplication(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockCont, mocks := container.NewMockContainer(t, func(c *container.Container, ctrl *gomock.Controller) any { + mockCont, mocks := container.NewMockContainer(t, func(_ *container.Container, ctrl *gomock.Controller) any { return service.NewMockHTTP(ctrl) }) mockCont.Services["api-service"] = mocks.HTTPService @@ -79,7 +80,7 @@ func Test_AddApplication_WithEnvs(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockCont, mocks := container.NewMockContainer(t, func(c *container.Container, ctrl *gomock.Controller) any { + mockCont, mocks := container.NewMockContainer(t, func(_ *container.Container, ctrl *gomock.Controller) any { return service.NewMockHTTP(ctrl) }) @@ -102,7 +103,7 @@ func Test_AddApplication_WithEnvs(t *testing.T) { }, mockCalls: []*gomock.Call{ mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). - DoAndReturn(func(ctx *gofr.Context, endpoint string, headers, body, query interface{}) (*http.Response, error) { + DoAndReturn(func(body any) (*http.Response, error) { var app Application _ = json.Unmarshal(body.([]byte), &app) require.Equal(t, "test", app.Name) @@ -121,7 +122,7 @@ func Test_AddApplication_WithEnvs(t *testing.T) { expectedEnvs: []Environment{}, mockCalls: []*gomock.Call{ mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). - DoAndReturn(func(ctx *gofr.Context, endpoint string, headers, body, query interface{}) (*http.Response, error) { + DoAndReturn(func(body any) (*http.Response, error) { var app Application _ = json.Unmarshal(body.([]byte), &app) require.Equal(t, "test", app.Name) @@ -139,7 +140,7 @@ func Test_AddApplication_WithEnvs(t *testing.T) { // Mock user input r, w, _ := os.Pipe() - w.Write([]byte(tt.userInput)) + _, _ = w.WriteString(tt.userInput) oldStdin := os.Stdin os.Stdin = r diff --git a/application/service/models_test.go b/application/service/models_test.go index f67aecd..2dbc281 100644 --- a/application/service/models_test.go +++ b/application/service/models_test.go @@ -3,10 +3,11 @@ package service import ( "bytes" "encoding/json" - "github.com/stretchr/testify/require" "io" "net/http" "testing" + + "github.com/stretchr/testify/require" ) func TestGetAPIError(t *testing.T) { @@ -84,11 +85,11 @@ type MockErrorResponse struct { type errorReader struct{} -func (e *errorReader) Read(p []byte) (n int, err error) { +func (*errorReader) Read(_ []byte) (n int, err error) { return 0, io.ErrUnexpectedEOF } -func (e *errorReader) Close() error { +func (*errorReader) Close() error { return nil } diff --git a/main.go b/main.go index a2426d1..e036e5c 100644 --- a/main.go +++ b/main.go @@ -44,7 +44,7 @@ func main() { } defer db.Close() - accStore := impStore.New() + accStore := impStore.New(db) accSvc := impService.New(accStore) lSvc := listSvc.New() h := impHandler.New(accSvc, lSvc) From 650e5e07ad4c05bd4759687fc7643ad51eda926d Mon Sep 17 00:00:00 2001 From: vipul-rawat Date: Thu, 12 Dec 2024 11:19:34 +0530 Subject: [PATCH 3/8] fix tests for application --- application/service/application_test.go | 4 ++-- main.go | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/application/service/application_test.go b/application/service/application_test.go index 1642983..70c46ec 100644 --- a/application/service/application_test.go +++ b/application/service/application_test.go @@ -103,7 +103,7 @@ func Test_AddApplication_WithEnvs(t *testing.T) { }, mockCalls: []*gomock.Call{ mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). - DoAndReturn(func(body any) (*http.Response, error) { + DoAndReturn(func(_ *gofr.Context, _ string, _, body, _ interface{}) (*http.Response, error) { var app Application _ = json.Unmarshal(body.([]byte), &app) require.Equal(t, "test", app.Name) @@ -122,7 +122,7 @@ func Test_AddApplication_WithEnvs(t *testing.T) { expectedEnvs: []Environment{}, mockCalls: []*gomock.Call{ mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). - DoAndReturn(func(body any) (*http.Response, error) { + DoAndReturn(func(_ *gofr.Context, _ string, _, body, _ interface{}) (*http.Response, error) { var app Application _ = json.Unmarshal(body.([]byte), &app) require.Equal(t, "test", app.Name) diff --git a/main.go b/main.go index e036e5c..7713c0a 100644 --- a/main.go +++ b/main.go @@ -51,7 +51,6 @@ func main() { app.SubCommand("cloud import", h.Import) app.SubCommand("cloud list", h.List) - app.SubCommand("cloud select", h.Set) appSvc := applicationSvc.New() appH := applicationHandler.New(appSvc) From 6b4f023ed3dba49849ce91d92fe1437f3348bb76 Mon Sep 17 00:00:00 2001 From: vipul-rawat Date: Thu, 12 Dec 2024 16:57:41 +0530 Subject: [PATCH 4/8] add application lister command to display all applications and environments --- application/handler/application.go | 32 +++++++++++++- application/handler/handler_test.go | 61 ++++++++++++++++++++++++++- application/handler/interface.go | 8 +++- application/handler/mock_interface.go | 44 +++++++++++++------ application/service/application.go | 24 +++++++++++ application/service/models.go | 1 + main.go | 1 + 7 files changed, 152 insertions(+), 19 deletions(-) diff --git a/application/handler/application.go b/application/handler/application.go index 65b9dbd..e518c81 100644 --- a/application/handler/application.go +++ b/application/handler/application.go @@ -2,6 +2,9 @@ package handler import ( "errors" + "fmt" + "sort" + "strings" "gofr.dev/pkg/gofr" ) @@ -11,10 +14,10 @@ var ( ) type Handler struct { - appAdd ApplicationAdder + appAdd ApplicationService } -func New(appAdd ApplicationAdder) *Handler { +func New(appAdd ApplicationService) *Handler { return &Handler{ appAdd: appAdd, } @@ -33,3 +36,28 @@ func (h *Handler) Add(ctx *gofr.Context) (any, error) { return "Application " + name + " added successfully!", nil } + +func (h *Handler) List(ctx *gofr.Context) (any, error) { + apps, err := h.appAdd.GetApplications(ctx) + if err != nil { + return nil, err + } + + ctx.Out.Println("Applications and their environments:") + + s := strings.Builder{} + for _, app := range apps { + s.WriteString(fmt.Sprintf("%s ", app.Name)) + + sort.Slice(app.Envs, func(i, j int) bool { return app.Envs[i].Order < app.Envs[j].Order }) + + for _, env := range app.Envs { + s.WriteString(fmt.Sprintf("%s > ", env.Name)) + } + + ctx.Out.Println(s.String()[:s.Len()-2]) + s.Reset() + } + + return nil, nil +} diff --git a/application/handler/handler_test.go b/application/handler/handler_test.go index deb430b..94e6dda 100644 --- a/application/handler/handler_test.go +++ b/application/handler/handler_test.go @@ -2,7 +2,10 @@ package handler import ( "errors" + "gofr.dev/pkg/gofr/cmd/terminal" + "gofr.dev/pkg/gofr/testutil" "testing" + svc "zop.dev/cli/zop/application/service" "github.com/stretchr/testify/require" "go.uber.org/mock/gomock" @@ -17,7 +20,7 @@ func TestHandler_Add(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() - mockAppAdder := NewMockApplicationAdder(ctrl) + mockAppAdder := NewMockApplicationService(ctrl) testCases := []struct { name string @@ -68,3 +71,59 @@ func TestHandler_Add(t *testing.T) { }) } } + +func TestHandler_List(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockSvc := NewMockApplicationService(ctrl) + + testCases := []struct { + name string + mockCalls []*gomock.Call + expected string + expErr error + }{ + { + name: "success", + mockCalls: []*gomock.Call{ + mockSvc.EXPECT().GetApplications(gomock.Any()). + Return([]svc.Application{ + {1, "app1", + []svc.Environment{{"env1", 1, nil}, {"env2", 2, nil}}}, + {2, "app2", + []svc.Environment{{"dev", 1, nil}, {"prod", 2, nil}}}, + }, nil), + }, + expected: "Applications and their environments:\napp1 env1 > env2 \napp2 dev > prod \n", + }, + { + name: "failure", + mockCalls: []*gomock.Call{ + mockSvc.EXPECT().GetApplications(gomock.Any()). + Return(nil, errAPICall), + }, + expErr: errAPICall, + expected: "", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + + h := New(mockSvc) + out := testutil.StdoutOutputForFunc(func() { + ctx := &gofr.Context{ + Request: cmd.NewRequest([]string{""}), + Out: terminal.New(), + } + + _, err := h.List(ctx) + + require.Equal(t, tc.expErr, err) + }) + + require.Equal(t, tc.expected, out) + }) + } +} diff --git a/application/handler/interface.go b/application/handler/interface.go index fb29e0e..9ed4284 100644 --- a/application/handler/interface.go +++ b/application/handler/interface.go @@ -1,7 +1,11 @@ package handler -import "gofr.dev/pkg/gofr" +import ( + "gofr.dev/pkg/gofr" + "zop.dev/cli/zop/application/service" +) -type ApplicationAdder interface { +type ApplicationService interface { AddApplication(ctx *gofr.Context, name string) error + GetApplications(ctx *gofr.Context) ([]service.Application, error) } diff --git a/application/handler/mock_interface.go b/application/handler/mock_interface.go index 1435ad3..7340b69 100644 --- a/application/handler/mock_interface.go +++ b/application/handler/mock_interface.go @@ -14,34 +14,35 @@ import ( gomock "go.uber.org/mock/gomock" gofr "gofr.dev/pkg/gofr" + service "zop.dev/cli/zop/application/service" ) -// MockApplicationAdder is a mock of ApplicationAdder interface. -type MockApplicationAdder struct { +// MockApplicationService is a mock of ApplicationService interface. +type MockApplicationService struct { ctrl *gomock.Controller - recorder *MockApplicationAdderMockRecorder + recorder *MockApplicationServiceMockRecorder isgomock struct{} } -// MockApplicationAdderMockRecorder is the mock recorder for MockApplicationAdder. -type MockApplicationAdderMockRecorder struct { - mock *MockApplicationAdder +// MockApplicationServiceMockRecorder is the mock recorder for MockApplicationService. +type MockApplicationServiceMockRecorder struct { + mock *MockApplicationService } -// NewMockApplicationAdder creates a new mock instance. -func NewMockApplicationAdder(ctrl *gomock.Controller) *MockApplicationAdder { - mock := &MockApplicationAdder{ctrl: ctrl} - mock.recorder = &MockApplicationAdderMockRecorder{mock} +// NewMockApplicationService creates a new mock instance. +func NewMockApplicationService(ctrl *gomock.Controller) *MockApplicationService { + mock := &MockApplicationService{ctrl: ctrl} + mock.recorder = &MockApplicationServiceMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockApplicationAdder) EXPECT() *MockApplicationAdderMockRecorder { +func (m *MockApplicationService) EXPECT() *MockApplicationServiceMockRecorder { return m.recorder } // AddApplication mocks base method. -func (m *MockApplicationAdder) AddApplication(ctx *gofr.Context, name string) error { +func (m *MockApplicationService) AddApplication(ctx *gofr.Context, name string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "AddApplication", ctx, name) ret0, _ := ret[0].(error) @@ -49,7 +50,22 @@ func (m *MockApplicationAdder) AddApplication(ctx *gofr.Context, name string) er } // AddApplication indicates an expected call of AddApplication. -func (mr *MockApplicationAdderMockRecorder) AddApplication(ctx, name any) *gomock.Call { +func (mr *MockApplicationServiceMockRecorder) AddApplication(ctx, name any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddApplication", reflect.TypeOf((*MockApplicationAdder)(nil).AddApplication), ctx, name) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddApplication", reflect.TypeOf((*MockApplicationService)(nil).AddApplication), ctx, name) +} + +// GetApplications mocks base method. +func (m *MockApplicationService) GetApplications(ctx *gofr.Context) ([]service.Application, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetApplications", ctx) + ret0, _ := ret[0].([]service.Application) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetApplications indicates an expected call of GetApplications. +func (mr *MockApplicationServiceMockRecorder) GetApplications(ctx any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplications", reflect.TypeOf((*MockApplicationService)(nil).GetApplications), ctx) } diff --git a/application/service/application.go b/application/service/application.go index afd0e64..a9958a6 100644 --- a/application/service/application.go +++ b/application/service/application.go @@ -3,6 +3,7 @@ package service import ( "encoding/json" "fmt" + "io" "net/http" "gofr.dev/pkg/gofr" @@ -65,3 +66,26 @@ func (*Service) AddApplication(ctx *gofr.Context, name string) error { return nil } + +func (*Service) GetApplications(ctx *gofr.Context) ([]Application, error) { + api := ctx.GetHTTPService("api-service") + + reps, err := api.Get(ctx, "applications", nil) + if err != nil { + return nil, err + } + defer reps.Body.Close() + + var apps struct { + Data []Application `json:"data"` + } + + body, _ := io.ReadAll(reps.Body) + + err = json.Unmarshal(body, &apps) + if err != nil { + return nil, err + } + + return apps.Data, nil +} diff --git a/application/service/models.go b/application/service/models.go index 44fe149..d8715f7 100644 --- a/application/service/models.go +++ b/application/service/models.go @@ -48,6 +48,7 @@ type Environment struct { } type Application struct { + ID int `json:"id"` Name string `json:"name"` Envs []Environment `json:"environments,omitempty"` } diff --git a/main.go b/main.go index 7713c0a..7c36332 100644 --- a/main.go +++ b/main.go @@ -56,6 +56,7 @@ func main() { appH := applicationHandler.New(appSvc) app.SubCommand("application add", appH.Add) + app.SubCommand("application list", appH.List) app.Run() } From 59e1868979d40f365d9884a780149db62f8bfea5 Mon Sep 17 00:00:00 2001 From: vipul-rawat Date: Thu, 12 Dec 2024 18:08:41 +0530 Subject: [PATCH 5/8] modify the application list output --- application/handler/application.go | 14 ++++++++++---- application/handler/handler_test.go | 4 +++- application/service/application.go | 4 +++- go.mod | 2 +- main.go | 2 +- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/application/handler/application.go b/application/handler/application.go index e518c81..c56764f 100644 --- a/application/handler/application.go +++ b/application/handler/application.go @@ -3,6 +3,7 @@ package handler import ( "errors" "fmt" + "gofr.dev/pkg/gofr/cmd/terminal" "sort" "strings" @@ -43,11 +44,14 @@ func (h *Handler) List(ctx *gofr.Context) (any, error) { return nil, err } - ctx.Out.Println("Applications and their environments:") + ctx.Out.Println("Applications and their environments:\n") s := strings.Builder{} - for _, app := range apps { - s.WriteString(fmt.Sprintf("%s ", app.Name)) + for i, app := range apps { + ctx.Out.Printf("%d.", i+1) + ctx.Out.SetColor(terminal.Cyan) + ctx.Out.Printf(" %s \n\t", app.Name) + ctx.Out.ResetColor() sort.Slice(app.Envs, func(i, j int) bool { return app.Envs[i].Order < app.Envs[j].Order }) @@ -55,9 +59,11 @@ func (h *Handler) List(ctx *gofr.Context) (any, error) { s.WriteString(fmt.Sprintf("%s > ", env.Name)) } + ctx.Out.SetColor(terminal.Green) ctx.Out.Println(s.String()[:s.Len()-2]) + ctx.Out.ResetColor() s.Reset() } - return nil, nil + return "\n", nil } diff --git a/application/handler/handler_test.go b/application/handler/handler_test.go index 94e6dda..a6bab64 100644 --- a/application/handler/handler_test.go +++ b/application/handler/handler_test.go @@ -95,7 +95,9 @@ func TestHandler_List(t *testing.T) { []svc.Environment{{"dev", 1, nil}, {"prod", 2, nil}}}, }, nil), }, - expected: "Applications and their environments:\napp1 env1 > env2 \napp2 dev > prod \n", + expected: "Applications and their environments:\n\n1.\x1b[38;5;6m app1 " + + "\n\t\x1b[0m\x1b[38;5;2menv1 > env2 \n\x1b[0m2.\x1b[38;5;6m app2 " + + "\n\t\x1b[0m\x1b[38;5;2mdev > prod \n\x1b[0m", }, { name: "failure", diff --git a/application/service/application.go b/application/service/application.go index a9958a6..445354d 100644 --- a/application/service/application.go +++ b/application/service/application.go @@ -53,7 +53,9 @@ func (*Service) AddApplication(ctx *gofr.Context, name string) error { app.Envs = envs body, _ := json.Marshal(app) - resp, err := api.PostWithHeaders(ctx, "application", nil, body, nil) + resp, err := api.PostWithHeaders(ctx, "applications", nil, body, map[string]string{ + "Content-Type": "application/json", + }) if err != nil { return err } diff --git a/go.mod b/go.mod index 179cbf8..92e7d9d 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22.8 require ( github.com/mattn/go-sqlite3 v1.14.24 github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.10.0 go.uber.org/mock v0.5.0 gofr.dev v1.28.0 golang.org/x/oauth2 v0.24.0 @@ -62,7 +63,6 @@ require ( github.com/redis/go-redis/v9 v9.7.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/segmentio/kafka-go v0.4.47 // indirect - github.com/stretchr/testify v1.10.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.54.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.56.0 // indirect diff --git a/main.go b/main.go index 7c36332..010aa3a 100644 --- a/main.go +++ b/main.go @@ -6,9 +6,9 @@ import ( "os" "path/filepath" + _ "github.com/mattn/go-sqlite3" "gofr.dev/pkg/gofr" "gofr.dev/pkg/gofr/service" - _ "modernc.org/sqlite" applicationHandler "zop.dev/cli/zop/application/handler" applicationSvc "zop.dev/cli/zop/application/service" From 27f51a5718b844ab5ddfc89b7a52dd868a76ec8c Mon Sep 17 00:00:00 2001 From: vipul-rawat Date: Mon, 16 Dec 2024 13:24:58 +0530 Subject: [PATCH 6/8] rename methods according to the convention --- application/handler/application.go | 5 +++-- application/handler/handler_test.go | 13 ++++++------- application/handler/interface.go | 4 ++-- application/handler/mock_interface.go | 12 ++++++------ application/service/application.go | 4 ++-- application/service/application_test.go | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/application/handler/application.go b/application/handler/application.go index d4afb09..4bc41e2 100644 --- a/application/handler/application.go +++ b/application/handler/application.go @@ -30,7 +30,7 @@ func (h *Handler) Add(ctx *gofr.Context) (any, error) { return nil, ErrorApplicationNameNotProvided } - err := h.appAdd.AddApplication(ctx, name) + err := h.appAdd.Add(ctx, name) if err != nil { return nil, err } @@ -39,7 +39,7 @@ func (h *Handler) Add(ctx *gofr.Context) (any, error) { } func (h *Handler) List(ctx *gofr.Context) (any, error) { - apps, err := h.appAdd.GetApplications(ctx) + apps, err := h.appAdd.List(ctx) if err != nil { return nil, err } @@ -47,6 +47,7 @@ func (h *Handler) List(ctx *gofr.Context) (any, error) { ctx.Out.Println("Applications and their environments:\n") s := strings.Builder{} + for i, app := range apps { ctx.Out.Printf("%d.", i+1) ctx.Out.SetColor(terminal.Cyan) diff --git a/application/handler/handler_test.go b/application/handler/handler_test.go index 39f10a1..b417817 100644 --- a/application/handler/handler_test.go +++ b/application/handler/handler_test.go @@ -5,12 +5,12 @@ import ( "testing" "github.com/stretchr/testify/require" - "gofr.dev/pkg/gofr/cmd/terminal" - "gofr.dev/pkg/gofr/testutil" "go.uber.org/mock/gomock" "gofr.dev/pkg/gofr" "gofr.dev/pkg/gofr/cmd" + "gofr.dev/pkg/gofr/cmd/terminal" "gofr.dev/pkg/gofr/container" + "gofr.dev/pkg/gofr/testutil" svc "zop.dev/cli/zop/application/service" ) @@ -90,10 +90,10 @@ func TestHandler_List(t *testing.T) { mockCalls: []*gomock.Call{ mockSvc.EXPECT().GetApplications(gomock.Any()). Return([]svc.Application{ - {1, "app1", - []svc.Environment{{"env1", 1, nil}, {"env2", 2, nil}}}, - {2, "app2", - []svc.Environment{{"dev", 1, nil}, {"prod", 2, nil}}}, + {ID: 1, Name: "app1", + Envs: []svc.Environment{{Name: "env1", Order: 1}, {Name: "env2", Order: 2}}}, + {ID: 2, Name: "app2", + Envs: []svc.Environment{{Name: "dev", Order: 1}, {Name: "prod", Order: 2}}}, }, nil), }, expected: "Applications and their environments:\n\n1.\x1b[38;5;6m app1 " + @@ -113,7 +113,6 @@ func TestHandler_List(t *testing.T) { for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { - h := New(mockSvc) out := testutil.StdoutOutputForFunc(func() { ctx := &gofr.Context{ diff --git a/application/handler/interface.go b/application/handler/interface.go index 6d91953..d90b4d3 100644 --- a/application/handler/interface.go +++ b/application/handler/interface.go @@ -7,6 +7,6 @@ import ( ) type ApplicationService interface { - AddApplication(ctx *gofr.Context, name string) error - GetApplications(ctx *gofr.Context) ([]service.Application, error) + Add(ctx *gofr.Context, name string) error + List(ctx *gofr.Context) ([]service.Application, error) } diff --git a/application/handler/mock_interface.go b/application/handler/mock_interface.go index 7340b69..b5693a3 100644 --- a/application/handler/mock_interface.go +++ b/application/handler/mock_interface.go @@ -42,9 +42,9 @@ func (m *MockApplicationService) EXPECT() *MockApplicationServiceMockRecorder { } // AddApplication mocks base method. -func (m *MockApplicationService) AddApplication(ctx *gofr.Context, name string) error { +func (m *MockApplicationService) Add(ctx *gofr.Context, name string) error { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "AddApplication", ctx, name) + ret := m.ctrl.Call(m, "Add", ctx, name) ret0, _ := ret[0].(error) return ret0 } @@ -52,13 +52,13 @@ func (m *MockApplicationService) AddApplication(ctx *gofr.Context, name string) // AddApplication indicates an expected call of AddApplication. func (mr *MockApplicationServiceMockRecorder) AddApplication(ctx, name any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddApplication", reflect.TypeOf((*MockApplicationService)(nil).AddApplication), ctx, name) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockApplicationService)(nil).Add), ctx, name) } // GetApplications mocks base method. -func (m *MockApplicationService) GetApplications(ctx *gofr.Context) ([]service.Application, error) { +func (m *MockApplicationService) List(ctx *gofr.Context) ([]service.Application, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "GetApplications", ctx) + ret := m.ctrl.Call(m, "List", ctx) ret0, _ := ret[0].([]service.Application) ret1, _ := ret[1].(error) return ret0, ret1 @@ -67,5 +67,5 @@ func (m *MockApplicationService) GetApplications(ctx *gofr.Context) ([]service.A // GetApplications indicates an expected call of GetApplications. func (mr *MockApplicationServiceMockRecorder) GetApplications(ctx any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetApplications", reflect.TypeOf((*MockApplicationService)(nil).GetApplications), ctx) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockApplicationService)(nil).List), ctx) } diff --git a/application/service/application.go b/application/service/application.go index 445354d..32eb348 100644 --- a/application/service/application.go +++ b/application/service/application.go @@ -16,7 +16,7 @@ func New() *Service { return &Service{} } -func (*Service) AddApplication(ctx *gofr.Context, name string) error { +func (*Service) Add(ctx *gofr.Context, name string) error { var ( envs []Environment input string @@ -69,7 +69,7 @@ func (*Service) AddApplication(ctx *gofr.Context, name string) error { return nil } -func (*Service) GetApplications(ctx *gofr.Context) ([]Application, error) { +func (*Service) List(ctx *gofr.Context) ([]Application, error) { api := ctx.GetHTTPService("api-service") reps, err := api.Get(ctx, "applications", nil) diff --git a/application/service/application_test.go b/application/service/application_test.go index 70c46ec..cc71beb 100644 --- a/application/service/application_test.go +++ b/application/service/application_test.go @@ -69,7 +69,7 @@ func Test_AddApplication(t *testing.T) { t.Run(tt.name, func(t *testing.T) { s := New() - errSvc := s.AddApplication(ctx, "test") + errSvc := s.Add(ctx, "test") require.Equal(t, tt.expError, errSvc) }) @@ -147,7 +147,7 @@ func Test_AddApplication_WithEnvs(t *testing.T) { defer func() { os.Stdin = oldStdin }() - errSvc := s.AddApplication(ctx, "test") + errSvc := s.Add(ctx, "test") require.Equal(t, tt.expError, errSvc) }) } From 40ffbb717f474ed68a9b7551ef654c75a85d9fe9 Mon Sep 17 00:00:00 2001 From: vipul-rawat Date: Mon, 16 Dec 2024 13:55:47 +0530 Subject: [PATCH 7/8] add test for list function --- application/service/application.go | 5 +- application/service/application_test.go | 87 ++++++++++++++++++++++--- 2 files changed, 81 insertions(+), 11 deletions(-) diff --git a/application/service/application.go b/application/service/application.go index 32eb348..05dae1d 100644 --- a/application/service/application.go +++ b/application/service/application.go @@ -86,7 +86,10 @@ func (*Service) List(ctx *gofr.Context) ([]Application, error) { err = json.Unmarshal(body, &apps) if err != nil { - return nil, err + return nil, &ErrAPIService{ + StatusCode: http.StatusInternalServerError, + Message: "Internal Server Error", + } } return apps.Data, nil diff --git a/application/service/application_test.go b/application/service/application_test.go index cc71beb..9834422 100644 --- a/application/service/application_test.go +++ b/application/service/application_test.go @@ -19,7 +19,7 @@ import ( var errAPICall = errors.New("error in API call") -func Test_AddApplication(t *testing.T) { +func Test_Add(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -36,29 +36,33 @@ func Test_AddApplication(t *testing.T) { testCases := []struct { name string + input string mockCalls []*gomock.Call expError error }{ { - name: "success Post call", + name: "success Post call", + input: "n\n", mockCalls: []*gomock.Call{ - mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). + mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "applications", nil, gomock.Any(), gomock.Any()). Return(&http.Response{StatusCode: http.StatusCreated, Body: io.NopCloser(&errorReader{})}, nil), }, expError: nil, }, { - name: "error in Post call", + name: "error in Post call", + input: "n\n", mockCalls: []*gomock.Call{ - mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). + mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "applications", nil, gomock.Any(), gomock.Any()). Return(nil, errAPICall), }, expError: errAPICall, }, { - name: "unexpected response", + name: "unexpected response", + input: "n\n", mockCalls: []*gomock.Call{ - mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). + mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "applications", nil, gomock.Any(), gomock.Any()). Return(&http.Response{StatusCode: http.StatusInternalServerError, Body: io.NopCloser(bytes.NewBuffer(b))}, nil), }, expError: &ErrAPIService{StatusCode: http.StatusInternalServerError, Message: "Something went wrong"}, @@ -69,14 +73,21 @@ func Test_AddApplication(t *testing.T) { t.Run(tt.name, func(t *testing.T) { s := New() + r, w, _ := os.Pipe() + os.Stdin = r + _, _ = w.WriteString(tt.input) + errSvc := s.Add(ctx, "test") require.Equal(t, tt.expError, errSvc) + + r.Close() + w.Close() }) } } -func Test_AddApplication_WithEnvs(t *testing.T) { +func Test_Add_WithEnvs(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() @@ -102,7 +113,7 @@ func Test_AddApplication_WithEnvs(t *testing.T) { {Name: "dev", Order: 2}, }, mockCalls: []*gomock.Call{ - mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). + mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "applications", nil, gomock.Any(), gomock.Any()). DoAndReturn(func(_ *gofr.Context, _ string, _, body, _ interface{}) (*http.Response, error) { var app Application _ = json.Unmarshal(body.([]byte), &app) @@ -121,7 +132,7 @@ func Test_AddApplication_WithEnvs(t *testing.T) { userInput: "n\n", expectedEnvs: []Environment{}, mockCalls: []*gomock.Call{ - mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "application", nil, gomock.Any(), nil). + mocks.HTTPService.EXPECT().PostWithHeaders(ctx, "applications", nil, gomock.Any(), gomock.Any()). DoAndReturn(func(_ *gofr.Context, _ string, _, body, _ interface{}) (*http.Response, error) { var app Application _ = json.Unmarshal(body.([]byte), &app) @@ -152,3 +163,59 @@ func Test_AddApplication_WithEnvs(t *testing.T) { }) } } + +func Test_List(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockCont, mocks := container.NewMockContainer(t, func(_ *container.Container, ctrl *gomock.Controller) any { + return service.NewMockHTTP(ctrl) + }) + + mockCont.Services["api-service"] = mocks.HTTPService + ctx := &gofr.Context{Container: mockCont, Out: terminal.New()} + + testCases := []struct { + name string + mockCalls []*gomock.Call + expError error + }{ + { + name: "success Get call", + mockCalls: []*gomock.Call{ + mocks.HTTPService.EXPECT().Get(ctx, "applications", nil). + Return(&http.Response{StatusCode: http.StatusOK, Body: io.NopCloser(bytes.NewBufferString(`{ "data" : null }`))}, nil), + }, + expError: nil, + }, + { + name: "error in Get call", + mockCalls: []*gomock.Call{ + mocks.HTTPService.EXPECT().Get(ctx, "applications", nil). + Return(nil, errAPICall), + }, + expError: errAPICall, + }, + { + name: "unexpected response", + mockCalls: []*gomock.Call{ + mocks.HTTPService.EXPECT().Get(ctx, "applications", nil). + Return(&http.Response{StatusCode: http.StatusInternalServerError, Body: io.NopCloser(bytes.NewBuffer(nil))}, nil), + }, + expError: &ErrAPIService{StatusCode: http.StatusInternalServerError, Message: "Internal Server Error"}, + }, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + s := New() + + apps, errSvc := s.List(ctx) + require.Equal(t, tt.expError, errSvc) + + if tt.expError == nil { + require.Empty(t, apps) + } + }) + } +} From 2b924886256cd64c56fc9b7cb5e71bfa718c5a96 Mon Sep 17 00:00:00 2001 From: vipul-rawat Date: Mon, 16 Dec 2024 17:39:51 +0530 Subject: [PATCH 8/8] add godoc for application --- application/handler/application.go | 30 ++++++++++++++++++++++++ application/handler/interface.go | 17 ++++++++++++++ application/service/application.go | 28 ++++++++++++++++++++-- application/service/models.go | 37 ++++++++++++++++++++++-------- 4 files changed, 101 insertions(+), 11 deletions(-) diff --git a/application/handler/application.go b/application/handler/application.go index 4bc41e2..53493a8 100644 --- a/application/handler/application.go +++ b/application/handler/application.go @@ -1,3 +1,6 @@ +// Package handler provides functionalities for managing applications, including adding new applications and +// listing existing ones with their environments. +// It acts as the CMD handler layer, connecting the application logic to the user interface. package handler import ( @@ -10,20 +13,39 @@ import ( "gofr.dev/pkg/gofr/cmd/terminal" ) +// Errors returned by the handler package. var ( + // ErrorApplicationNameNotProvided indicates that the application name was not provided as a parameter. ErrorApplicationNameNotProvided = errors.New("please enter application name, -name=") ) +// Handler represents the HTTP handler responsible for managing applications. type Handler struct { appAdd ApplicationService } +// New creates a new instance of Handler. +// +// Parameters: +// - appAdd: An implementation of the ApplicationService interface used to manage applications. +// +// Returns: +// +// A pointer to a Handler instance. func New(appAdd ApplicationService) *Handler { return &Handler{ appAdd: appAdd, } } +// Add handles the addition of a new application. +// +// Parameters: +// - ctx: The application context containing dependencies and utilities. +// +// Returns: +// +// A success message and an error, if any. func (h *Handler) Add(ctx *gofr.Context) (any, error) { name := ctx.Param("name") if name == "" { @@ -38,6 +60,14 @@ func (h *Handler) Add(ctx *gofr.Context) (any, error) { return "Application " + name + " added successfully!", nil } +// List retrieves and displays all applications along with their environments. +// +// Parameters: +// - ctx: The application context containing dependencies and utilities. +// +// Returns: +// +// A newline-separated string and an error, if any. func (h *Handler) List(ctx *gofr.Context) (any, error) { apps, err := h.appAdd.List(ctx) if err != nil { diff --git a/application/handler/interface.go b/application/handler/interface.go index d90b4d3..a389c9a 100644 --- a/application/handler/interface.go +++ b/application/handler/interface.go @@ -6,7 +6,24 @@ import ( "zop.dev/cli/zop/application/service" ) +// ApplicationService defines the methods required for application management. type ApplicationService interface { + // Add adds a new application with the specified name. + // + // Parameters: + // - ctx: The application context containing dependencies and utilities. + // - name: The name of the application to be added. + // + // Returns: + // An error if the application could not be added. Add(ctx *gofr.Context, name string) error + + // List retrieves the list of applications along with their environments. + // + // Parameters: + // - ctx: The application context containing dependencies and utilities. + // + // Returns: + // A slice of applications and an error, if any. List(ctx *gofr.Context) ([]service.Application, error) } diff --git a/application/service/application.go b/application/service/application.go index 05dae1d..eb0950e 100644 --- a/application/service/application.go +++ b/application/service/application.go @@ -1,3 +1,5 @@ +// Package service provides functionalities for managing applications and their environments. +// It includes methods for adding a new application and listing existing applications. package service import ( @@ -9,13 +11,27 @@ import ( "gofr.dev/pkg/gofr" ) -type Service struct { -} +// Service provides methods for managing applications. +type Service struct{} +// New creates a new instance of Service. +// +// Returns: +// +// A pointer to a Service instance. func New() *Service { return &Service{} } +// Add adds a new application and optionally its environments. +// +// Parameters: +// - ctx: The application context containing dependencies and utilities. +// - name: The name of the application to be added. +// +// Returns: +// +// An error if the application or environments could not be added. func (*Service) Add(ctx *gofr.Context, name string) error { var ( envs []Environment @@ -69,6 +85,14 @@ func (*Service) Add(ctx *gofr.Context, name string) error { return nil } +// List retrieves all applications and their environments. +// +// Parameters: +// - ctx: The application context containing dependencies and utilities. +// +// Returns: +// +// A slice of applications and an error, if any. func (*Service) List(ctx *gofr.Context) ([]Application, error) { api := ctx.GetHTTPService("api-service") diff --git a/application/service/models.go b/application/service/models.go index d8715f7..afbde97 100644 --- a/application/service/models.go +++ b/application/service/models.go @@ -1,3 +1,5 @@ +// Package service defines models and utility functions for error handling +// and data structures representing applications and their environments. package service import ( @@ -6,20 +8,35 @@ import ( "net/http" ) +// ErrAPIService represents an error returned by the API service. type ErrAPIService struct { - StatusCode int - Message string + StatusCode int // HTTP status code of the error + Message string // Message describing the error } +// Error returns the error message for ErrAPIService. +// +// Returns: +// +// A string describing the error. func (e *ErrAPIService) Error() string { return e.Message } +// Predefined internal error for API response issues. var errInternal = &ErrAPIService{ StatusCode: http.StatusInternalServerError, - Message: "error in POST /application zop-api, invalid response", + Message: "error in /applications zop-api, invalid response", } +// getAPIError extracts and constructs an ErrAPIService from an HTTP response. +// +// Parameters: +// - resp: The HTTP response containing the error details. +// +// Returns: +// +// A pointer to an ErrAPIService. func getAPIError(resp *http.Response) *ErrAPIService { var errResp struct { Error string `json:"error"` @@ -41,14 +58,16 @@ func getAPIError(resp *http.Response) *ErrAPIService { } } +// Environment represents an environment associated with an application. type Environment struct { - Name string `json:"name"` - Order int `json:"order"` - DeploymentSpace any `json:"deploymentSpace,omitempty"` + Name string `json:"name"` // Name of the environment + Order int `json:"order"` // Order or priority of the environment + DeploymentSpace any `json:"deploymentSpace,omitempty"` // Optional deployment space information } +// Application represents an application with its associated environments. type Application struct { - ID int `json:"id"` - Name string `json:"name"` - Envs []Environment `json:"environments,omitempty"` + ID int `json:"id"` // Unique identifier of the application + Name string `json:"name"` // Name of the application + Envs []Environment `json:"environments,omitempty"` // List of associated environments }