From 6a468a47ad5726dbc4a27c7ddd965a87f505de51 Mon Sep 17 00:00:00 2001 From: korovindenis Date: Sat, 29 Jul 2023 16:03:45 +0300 Subject: [PATCH] tests --- Makefile | 2 +- go.mod | 4 + go.sum | 5 + internal/config/config_test.go | 23 +++ internal/domain/entity/mypc.go | 7 + .../domain/usecase/mocks/IComputerStorage.go | 89 +++++++++ .../domain/usecase/mocks/IComputerUsecase.go | 94 +++++++++ internal/http/handler/handler_test.go | 178 ++++++++++++++++++ 8 files changed, 401 insertions(+), 1 deletion(-) create mode 100644 internal/config/config_test.go create mode 100644 internal/domain/usecase/mocks/IComputerStorage.go create mode 100644 internal/domain/usecase/mocks/IComputerUsecase.go create mode 100644 internal/http/handler/handler_test.go diff --git a/Makefile b/Makefile index 24f0ac2..3f5efb6 100644 --- a/Makefile +++ b/Makefile @@ -10,7 +10,7 @@ ifeq ($(OS), Windows_NT) APP_BUILD_NAME = sfb.exe endif -all: clean get gotest build-web build-app +all: clean get build-web build-app build-web: @echo " > Building web-components" diff --git a/go.mod b/go.mod index bb82cb8..6f66817 100644 --- a/go.mod +++ b/go.mod @@ -6,10 +6,13 @@ require github.com/gin-gonic/gin v1.9.1 require ( github.com/daaku/go.zipexe v1.0.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/fsnotify/fsnotify v1.4.9 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.5.0 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect ) @@ -34,6 +37,7 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.8.4 github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect go.uber.org/zap v1.24.0 diff --git a/go.sum b/go.sum index 6408b2c..5138ad6 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,7 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/daaku/go.zipexe v1.0.2 h1:Zg55YLYTr7M9wjKn8SY/WcpuuEi+kR2u4E8RhvpyXmk= github.com/daaku/go.zipexe v1.0.2/go.mod h1:5xWogtqlYnfBXkSB1o9xysukNP9GTvaNkqzUZbt3Bw8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -230,6 +231,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= @@ -261,6 +263,7 @@ github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -272,6 +275,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= diff --git a/internal/config/config_test.go b/internal/config/config_test.go new file mode 100644 index 0000000..c58f7a2 --- /dev/null +++ b/internal/config/config_test.go @@ -0,0 +1,23 @@ +package config + +import ( + "errors" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLoadConfigFileNotExists(t *testing.T) { + // Arrange + os.Setenv("CONFIG_PATH", "nonexistent.yml") + + // Act + cfg, err := Load() + + // Assert + expectedErr := errors.New("config file does not exist: nonexistent.yml") + assert.Error(t, err) + assert.Equal(t, expectedErr, err) + assert.Nil(t, cfg) +} diff --git a/internal/domain/entity/mypc.go b/internal/domain/entity/mypc.go index e297c34..737d046 100644 --- a/internal/domain/entity/mypc.go +++ b/internal/domain/entity/mypc.go @@ -1,6 +1,13 @@ package entity +import "encoding/json" + type MyPc struct { ModePowerOff string TimePowerOff string } + +func (m *MyPc) String() string { + json, _ := json.Marshal(m) + return string(json) +} diff --git a/internal/domain/usecase/mocks/IComputerStorage.go b/internal/domain/usecase/mocks/IComputerStorage.go new file mode 100644 index 0000000..2f8f435 --- /dev/null +++ b/internal/domain/usecase/mocks/IComputerStorage.go @@ -0,0 +1,89 @@ +// Code generated by mockery v2.32.0. DO NOT EDIT. + +package mocks + +import ( + entity "github.com/korovindenis/shutdown-from-browser/v2/internal/domain/entity" + mock "github.com/stretchr/testify/mock" +) + +// IComputerStorage is an autogenerated mock type for the IComputerStorage type +type IComputerStorage struct { + mock.Mock +} + +// GetModePoff provides a mock function with given fields: +func (_m *IComputerStorage) GetModePoff() (string, error) { + ret := _m.Called() + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTimePoff provides a mock function with given fields: +func (_m *IComputerStorage) GetTimePoff() (string, error) { + ret := _m.Called() + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// SetPoff provides a mock function with given fields: pc +func (_m *IComputerStorage) SetPoff(pc entity.MyPc) error { + ret := _m.Called(pc) + + var r0 error + if rf, ok := ret.Get(0).(func(entity.MyPc) error); ok { + r0 = rf(pc) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewIComputerStorage creates a new instance of IComputerStorage. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIComputerStorage(t interface { + mock.TestingT + Cleanup(func()) +}) *IComputerStorage { + mock := &IComputerStorage{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/domain/usecase/mocks/IComputerUsecase.go b/internal/domain/usecase/mocks/IComputerUsecase.go new file mode 100644 index 0000000..5c3ebdf --- /dev/null +++ b/internal/domain/usecase/mocks/IComputerUsecase.go @@ -0,0 +1,94 @@ +// Code generated by mockery v2.32.0. DO NOT EDIT. + +package mocks + +import ( + entity "github.com/korovindenis/shutdown-from-browser/v2/internal/domain/entity" + mock "github.com/stretchr/testify/mock" +) + +// IComputerUsecase is an autogenerated mock type for the IComputerUsecase type +type IComputerUsecase struct { + mock.Mock +} + +// GetModePowerOff provides a mock function with given fields: +func (_m *IComputerUsecase) GetModePowerOff() (string, error) { + ret := _m.Called() + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTimePowerOff provides a mock function with given fields: +func (_m *IComputerUsecase) GetTimePowerOff() (string, error) { + ret := _m.Called() + + var r0 string + var r1 error + if rf, ok := ret.Get(0).(func() (string, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// IsNeedPowerOff provides a mock function with given fields: logslevel +func (_m *IComputerUsecase) IsNeedPowerOff(logslevel uint8) { + _m.Called(logslevel) +} + +// SetPowerOff provides a mock function with given fields: pc +func (_m *IComputerUsecase) SetPowerOff(pc entity.MyPc) error { + ret := _m.Called(pc) + + var r0 error + if rf, ok := ret.Get(0).(func(entity.MyPc) error); ok { + r0 = rf(pc) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewIComputerUsecase creates a new instance of IComputerUsecase. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewIComputerUsecase(t interface { + mock.TestingT + Cleanup(func()) +}) *IComputerUsecase { + mock := &IComputerUsecase{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/internal/http/handler/handler_test.go b/internal/http/handler/handler_test.go new file mode 100644 index 0000000..4508daf --- /dev/null +++ b/internal/http/handler/handler_test.go @@ -0,0 +1,178 @@ +package handler_test + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "testing" + "time" + + "log" + + "github.com/gin-gonic/gin" + "github.com/korovindenis/shutdown-from-browser/v2/internal/config" + "github.com/korovindenis/shutdown-from-browser/v2/internal/domain/entity" + "github.com/korovindenis/shutdown-from-browser/v2/internal/domain/usecase/mocks" + "github.com/korovindenis/shutdown-from-browser/v2/internal/http/handler" + "github.com/stretchr/testify/assert" + "go.uber.org/zap" + "go.uber.org/zap/zaptest/observer" +) + +type domainEntity struct { + computerHandler *handler.ComputerHandler + mockComputerStorage *mocks.IComputerStorage + mockUsecase *mocks.IComputerUsecase + mockError error +} + +func setUpDomain(t *testing.T, cfg *config.Config, logger *zap.Logger) domainEntity { + mockComputerStorage := mocks.NewIComputerStorage(t) + mockUsecase := mocks.NewIComputerUsecase(t) + computerHandler := handler.NewComputerHandler(mockUsecase, cfg, logger) + + return domainEntity{ + computerHandler: computerHandler, + mockComputerStorage: mockComputerStorage, + mockUsecase: mockUsecase, + } +} + +func setUpRouter(t *testing.T) *gin.Engine { + currentDir, err := os.Getwd() + if err != nil { + t.Fatalf("Failed to get current directory: %s", err) + } + router := gin.Default() + router.LoadHTMLGlob(filepath.Dir(currentDir) + "/../../web/build/index.html") + return router +} + +func setUpLogger() (*zap.Logger, *observer.ObservedLogs) { + core, logs := observer.New(zap.InfoLevel) + return zap.New(core), logs +} + +func setUpConfig() *config.Config { + return &config.Config{} +} + +func TestHandler_Static(t *testing.T) { + t.Run("Static Handler", func(t *testing.T) { + t.Parallel() + + // Arrange + r := setUpRouter(t) + currentDir, err := os.Getwd() + if err != nil { + log.Print("Failed to get current directory", err) + return + } + folderPath := filepath.Dir(currentDir) + "/../../web/build/static/" + files, err := filepath.Glob(filepath.Join(folderPath+"/css/", "*.css")) + if err != nil { + log.Print("Failed to get files in directory:", err) + return + } + firstFile := "" + if len(files) > 0 { + firstFile = filepath.Base(files[0]) + } else { + log.Print("Failed to get css in directory:", err) + return + } + r.Static("/static/", folderPath) + + // Act + req, err := http.NewRequest("GET", "/static/css/"+firstFile, nil) + assert.NoError(t, err) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + // Assert + assert.Equal(t, http.StatusOK, w.Code) + }) +} + +func TestHandler_GetTimePoHandler(t *testing.T) { + t.Run("Get Time PowerOff Handler", func(t *testing.T) { + t.Parallel() + + // Arrange + r := setUpRouter(t) + logger, _ := setUpLogger() + cfg := setUpConfig() + domain := setUpDomain(t, cfg, logger) + mockData := time.Now().UTC().Format("2006-01-02T15:04:05.000Z") + mockDataJs, _ := json.Marshal(mockData) + domain.mockUsecase.On("GetTimePowerOff").Return(mockData, domain.mockError).Once() + r.GET("/api/v1/get-time-autopoweroff/", domain.computerHandler.GetTimePoHandler) + + // Act + req, err := http.NewRequest("GET", "/api/v1/get-time-autopoweroff/", nil) + assert.NoError(t, err) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + // Assert + responseData, _ := io.ReadAll(w.Body) + assert.Equal(t, string(mockDataJs), string(responseData)) + assert.Equal(t, http.StatusOK, w.Code) + }) +} + +func TestHandler_SetTimePowerOffHandler(t *testing.T) { + mockData := entity.MyPc{ + ModePowerOff: "reboot", + TimePowerOff: time.Now().UTC().Format("2006-01-02T15:04:05.000Z"), + } + + cases := []struct { + name string + input string + expectStatusCode int + expectErr error + }{ + { + name: "wrong js", + expectStatusCode: http.StatusInternalServerError, + expectErr: errors.New("unexpect data"), + }, + { + name: "good js", + input: fmt.Sprintf(mockData.String()), + expectStatusCode: http.StatusOK, + }, + } + + for _, tc := range cases { + tc := tc + t.Run("Set Time Power OffHandler : "+tc.name, func(t *testing.T) { + t.Parallel() + + // Arrange + r := setUpRouter(t) + logger, _ := setUpLogger() + cfg := setUpConfig() + domain := setUpDomain(t, cfg, logger) + domain.mockUsecase.On("GetTimePowerOff").Return("", tc.expectErr).Once() + r.POST("/api/v1/server-power/", domain.computerHandler.GetTimePoHandler) + inputJs, _ := json.Marshal(tc.input) + + // Act + req, err := http.NewRequest("POST", "/api/v1/server-power/", bytes.NewReader([]byte(string(inputJs)))) + assert.NoError(t, err) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + // Assert + assert.Equal(t, tc.expectStatusCode, w.Code) + }) + } +}