Skip to content
Merged
71 changes: 68 additions & 3 deletions application/handler/application.go
Original file line number Diff line number Diff line change
@@ -1,35 +1,100 @@
// 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 (
"errors"
"fmt"
"sort"
"strings"

"gofr.dev/pkg/gofr"
"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=<application_name>")
)

// Handler represents the HTTP handler responsible for managing applications.
type Handler struct {
appAdd ApplicationAdder
appAdd ApplicationService
}

func New(appAdd ApplicationAdder) *Handler {
// 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 == "" {
return nil, ErrorApplicationNameNotProvided
}

err := h.appAdd.AddApplication(ctx, name)
err := h.appAdd.Add(ctx, name)
if err != nil {
return nil, err
}

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 {
return nil, err
}

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)
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 })

for _, env := range app.Envs {
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 "\n", nil
}
63 changes: 62 additions & 1 deletion application/handler/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ import (
"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"
)

var errAPICall = errors.New("error in API call")
Expand All @@ -17,7 +21,7 @@ func TestHandler_Add(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

mockAppAdder := NewMockApplicationAdder(ctrl)
mockAppAdder := NewMockApplicationService(ctrl)

testCases := []struct {
name string
Expand Down Expand Up @@ -68,3 +72,60 @@ 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{
{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 " +
"\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",
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)
})
}
}
28 changes: 25 additions & 3 deletions application/handler/interface.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,29 @@
package handler

import "gofr.dev/pkg/gofr"
import (
"gofr.dev/pkg/gofr"

type ApplicationAdder interface {
AddApplication(ctx *gofr.Context, name string) error
"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)
}
46 changes: 31 additions & 15 deletions application/handler/mock_interface.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

61 changes: 57 additions & 4 deletions application/service/application.go
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
// 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 (
"encoding/json"
"fmt"
"io"
"net/http"

"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{}
}

func (*Service) AddApplication(ctx *gofr.Context, name string) error {
// 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
input string
Expand Down Expand Up @@ -52,7 +69,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
}
Expand All @@ -65,3 +84,37 @@ func (*Service) AddApplication(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")

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, &ErrAPIService{
StatusCode: http.StatusInternalServerError,
Message: "Internal Server Error",
}
}

return apps.Data, nil
}
Loading
Loading