diff --git a/docker-compose.yml b/docker-compose.yml index 97d62812..5d07c916 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -62,6 +62,7 @@ services: dockerfile: Dockerfilefile volumes: - ./image:/image + - ./files:/files restart: always ports: - "8083:8083" diff --git a/internal/fileService/controller/controller.go b/internal/fileService/controller/controller.go index 6c6af14f..a6b87b1b 100644 --- a/internal/fileService/controller/controller.go +++ b/internal/fileService/controller/controller.go @@ -6,6 +6,7 @@ import ( "fmt" "mime/multipart" "net/http" + "strings" "github.com/gorilla/mux" @@ -14,16 +15,19 @@ import ( ) var fileFormat = map[string]struct{}{ - "image/jpeg": {}, - "image/jpg": {}, - "image/png": {}, - "image/webp": {}, + "jpeg": {}, + "jpg": {}, + "png": {}, + "webp": {}, + "gif": {}, } //go:generate mockgen -destination=mock.go -source=$GOFILE -package=${GOPACKAGE} type fileService interface { Upload(ctx context.Context, name string) ([]byte, error) - Download(ctx context.Context, file multipart.File) (string, error) + Download(ctx context.Context, file multipart.File, format string) (string, error) + DownloadNonImage(ctx context.Context, file multipart.File, format string) (string, error) + UploadNonImage(ctx context.Context, name string) ([]byte, error) } type responder interface { @@ -46,6 +50,31 @@ func NewFileController(fileService fileService, responder responder) *FileContro } } +func (fc *FileController) UploadNonImage(w http.ResponseWriter, r *http.Request) { + var ( + reqID, ok = r.Context().Value("requestID").(string) + vars = mux.Vars(r) + name = vars["name"] + ) + + if !ok { + fc.responder.LogError(my_err.ErrInvalidContext, "") + } + + if name == "" { + fc.responder.ErrorBadRequest(w, errors.New("name is empty"), reqID) + return + } + + res, err := fc.fileService.UploadNonImage(r.Context(), name) + if err != nil { + fc.responder.ErrorBadRequest(w, fmt.Errorf("%w: %w", err, my_err.ErrWrongFile), reqID) + return + } + + fc.responder.OutputBytes(w, res, reqID) +} + func (fc *FileController) Upload(w http.ResponseWriter, r *http.Request) { var ( reqID, ok = r.Context().Value(middleware.RequestKey).(string) @@ -77,35 +106,49 @@ func (fc *FileController) Download(w http.ResponseWriter, r *http.Request) { fc.responder.LogError(my_err.ErrInvalidContext, "") } - err := r.ParseMultipartForm(10 << 20) // 10Mbyte + err := r.ParseMultipartForm(10 * (10 << 20)) // 100Mbyte + if err != nil { + fc.responder.ErrorBadRequest(w, my_err.ErrToLargeFile, reqID) + return + } defer func() { err = r.MultipartForm.RemoveAll() if err != nil { - panic(err) + fc.responder.LogError(err, reqID) } }() + + file, header, err := r.FormFile("file") if err != nil { - fc.responder.ErrorBadRequest(w, my_err.ErrToLargeFile, reqID) + fc.responder.ErrorBadRequest(w, err, reqID) return } - file, header, err := r.FormFile("file") - if err != nil { - file = nil - } else { - format := header.Header.Get("Content-Type") - if _, ok := fileFormat[format]; !ok { - fc.responder.ErrorBadRequest(w, my_err.ErrWrongFiletype, reqID) - return + defer func(file multipart.File) { + err = file.Close() + if err != nil { + fc.responder.LogError(err, reqID) } + }(file) + + formats := strings.Split(header.Header.Get("Content-Type"), "/") + if len(formats) != 2 { + fc.responder.ErrorBadRequest(w, my_err.ErrWrongFiletype, reqID) + return + } + var url string + format := formats[1] + + if _, ok := fileFormat[format]; ok { + url, err = fc.fileService.Download(r.Context(), file, format) + } else { + + url, err = fc.fileService.DownloadNonImage(r.Context(), file, format) } - defer file.Close() - url, err := fc.fileService.Download(r.Context(), file) if err != nil { fc.responder.ErrorBadRequest(w, err, reqID) return } - fc.responder.OutputJSON(w, url, reqID) } diff --git a/internal/fileService/controller/controller_test.go b/internal/fileService/controller/controller_test.go index 35976ea9..cb536d54 100644 --- a/internal/fileService/controller/controller_test.go +++ b/internal/fileService/controller/controller_test.go @@ -3,6 +3,7 @@ package controller import ( "context" "errors" + "mime/multipart" "net/http" "net/http/httptest" "testing" @@ -56,12 +57,14 @@ func TestUpload(t *testing.T) { ExpectedErr: nil, SetupMock: func(request Request, m *mocks) { m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) - m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do(func(w, err, req any) { - request.w.WriteHeader(http.StatusBadRequest) - if _, err1 := request.w.Write([]byte("bad request")); err1 != nil { - panic(err1) - } - }) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + if _, err1 := request.w.Write([]byte("bad request")); err1 != nil { + panic(err1) + } + }, + ) }, }, { @@ -85,12 +88,14 @@ func TestUpload(t *testing.T) { SetupMock: func(request Request, m *mocks) { m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) m.fileService.EXPECT().Upload(gomock.Any(), gomock.Any()).Return(nil, errMock) - m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do(func(w, err, req any) { - request.w.WriteHeader(http.StatusBadRequest) - if _, err1 := request.w.Write([]byte("bad request")); err1 != nil { - panic(err1) - } - }) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + if _, err1 := request.w.Write([]byte("bad request")); err1 != nil { + panic(err1) + } + }, + ) }, }, { @@ -114,42 +119,268 @@ func TestUpload(t *testing.T) { SetupMock: func(request Request, m *mocks) { m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) m.fileService.EXPECT().Upload(gomock.Any(), gomock.Any()).Return(nil, nil) - m.responder.EXPECT().OutputBytes(request.w, gomock.Any(), gomock.Any()).Do(func(w, err, req any) { - request.w.WriteHeader(http.StatusOK) - if _, err1 := request.w.Write([]byte("OK")); err1 != nil { - panic(err1) - } - }) + m.responder.EXPECT().OutputBytes(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusOK) + if _, err1 := request.w.Write([]byte("OK")); err1 != nil { + panic(err1) + } + }, + ) }, }, } for _, v := range tests { - t.Run(v.name, func(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - serv, mock := getController(ctrl) - ctx := context.Background() - - input, err := v.SetupInput() - if err != nil { - t.Error(err) - } - - v.SetupMock(*input, mock) - - res, err := v.ExpectedResult() - if err != nil { - t.Error(err) - } - - actual, err := v.Run(ctx, serv, *input) - assert.Equal(t, res, actual) - if !errors.Is(err, v.ExpectedErr) { - t.Errorf("expect %v, got %v", v.ExpectedErr, err) - } - }) + t.Run( + v.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + serv, mock := getController(ctrl) + ctx := context.Background() + + input, err := v.SetupInput() + if err != nil { + t.Error(err) + } + + v.SetupMock(*input, mock) + + res, err := v.ExpectedResult() + if err != nil { + t.Error(err) + } + + actual, err := v.Run(ctx, serv, *input) + assert.Equal(t, res, actual) + if !errors.Is(err, v.ExpectedErr) { + t.Errorf("expect %v, got %v", v.ExpectedErr, err) + } + }, + ) + } +} + +func TestUploadNonImage(t *testing.T) { + tests := []TableTest[Response, Request]{ + { + name: "1", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodGet, "/files/default", nil) + w := httptest.NewRecorder() + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *FileController, request Request) (Response, error) { + implementation.UploadNonImage(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + if _, err1 := request.w.Write([]byte("bad request")); err1 != nil { + panic(err1) + } + }, + ) + }, + }, + { + name: "2", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodGet, "/files/default", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"name": "default"}) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *FileController, request Request) (Response, error) { + implementation.UploadNonImage(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.fileService.EXPECT().UploadNonImage(gomock.Any(), gomock.Any()).Return(nil, errMock) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + if _, err1 := request.w.Write([]byte("bad request")); err1 != nil { + panic(err1) + } + }, + ) + }, + }, + { + name: "3", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodGet, "/files/default", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"name": "default"}) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *FileController, request Request) (Response, error) { + implementation.UploadNonImage(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusOK, Body: "OK"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.fileService.EXPECT().UploadNonImage(gomock.Any(), gomock.Any()).Return(nil, nil) + m.responder.EXPECT().OutputBytes(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusOK) + if _, err1 := request.w.Write([]byte("OK")); err1 != nil { + panic(err1) + } + }, + ) + }, + }, + } + + for _, v := range tests { + t.Run( + v.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + serv, mock := getController(ctrl) + ctx := context.Background() + + input, err := v.SetupInput() + if err != nil { + t.Error(err) + } + + v.SetupMock(*input, mock) + + res, err := v.ExpectedResult() + if err != nil { + t.Error(err) + } + + actual, err := v.Run(ctx, serv, *input) + assert.Equal(t, res, actual) + if !errors.Is(err, v.ExpectedErr) { + t.Errorf("expect %v, got %v", v.ExpectedErr, err) + } + }, + ) + } +} + +func TestDownload(t *testing.T) { + tests := []TableTest[Response, Request]{ + { + name: "1", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPost, "/image", nil) + w := httptest.NewRecorder() + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *FileController, request Request) (Response, error) { + implementation.Download(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + if _, err1 := request.w.Write([]byte("bad request")); err1 != nil { + panic(err1) + } + }, + ) + }, + }, + { + name: "2", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPost, "/image", nil) + w := httptest.NewRecorder() + req.MultipartForm = &multipart.Form{ + File: make(map[string][]*multipart.FileHeader), + } + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *FileController, request Request) (Response, error) { + implementation.Download(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + if _, err1 := request.w.Write([]byte("bad request")); err1 != nil { + panic(err1) + } + }, + ) + }, + }, + } + + for _, v := range tests { + t.Run( + v.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + serv, mock := getController(ctrl) + ctx := context.Background() + + input, err := v.SetupInput() + if err != nil { + t.Error(err) + } + + v.SetupMock(*input, mock) + + res, err := v.ExpectedResult() + if err != nil { + t.Error(err) + } + + actual, err := v.Run(ctx, serv, *input) + assert.Equal(t, res, actual) + if !errors.Is(err, v.ExpectedErr) { + t.Errorf("expect %v, got %v", v.ExpectedErr, err) + } + }, + ) } } diff --git a/internal/fileService/controller/mock.go b/internal/fileService/controller/mock.go index ad31747d..ff73012b 100644 --- a/internal/fileService/controller/mock.go +++ b/internal/fileService/controller/mock.go @@ -1,5 +1,10 @@ // Code generated by MockGen. DO NOT EDIT. // Source: controller.go +// +// Generated by this command: +// +// mockgen -destination=mock.go -source=controller.go -package=controller +// // Package controller is a generated GoMock package. package controller @@ -17,6 +22,7 @@ import ( type MockfileService struct { ctrl *gomock.Controller recorder *MockfileServiceMockRecorder + isgomock struct{} } // MockfileServiceMockRecorder is the mock recorder for MockfileService. @@ -37,18 +43,33 @@ func (m *MockfileService) EXPECT() *MockfileServiceMockRecorder { } // Download mocks base method. -func (m *MockfileService) Download(ctx context.Context, file multipart.File) (string, error) { +func (m *MockfileService) Download(ctx context.Context, file multipart.File, format string) (string, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Download", ctx, file) + ret := m.ctrl.Call(m, "Download", ctx, file, format) ret0, _ := ret[0].(string) ret1, _ := ret[1].(error) return ret0, ret1 } // Download indicates an expected call of Download. -func (mr *MockfileServiceMockRecorder) Download(ctx, file interface{}) *gomock.Call { +func (mr *MockfileServiceMockRecorder) Download(ctx, file, format any) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Download", reflect.TypeOf((*MockfileService)(nil).Download), ctx, file) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Download", reflect.TypeOf((*MockfileService)(nil).Download), ctx, file, format) +} + +// DownloadNonImage mocks base method. +func (m *MockfileService) DownloadNonImage(ctx context.Context, file multipart.File, format string) (string, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DownloadNonImage", ctx, file, format) + ret0, _ := ret[0].(string) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// DownloadNonImage indicates an expected call of DownloadNonImage. +func (mr *MockfileServiceMockRecorder) DownloadNonImage(ctx, file, format any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DownloadNonImage", reflect.TypeOf((*MockfileService)(nil).DownloadNonImage), ctx, file, format) } // Upload mocks base method. @@ -61,15 +82,31 @@ func (m *MockfileService) Upload(ctx context.Context, name string) ([]byte, erro } // Upload indicates an expected call of Upload. -func (mr *MockfileServiceMockRecorder) Upload(ctx, name interface{}) *gomock.Call { +func (mr *MockfileServiceMockRecorder) Upload(ctx, name any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upload", reflect.TypeOf((*MockfileService)(nil).Upload), ctx, name) } +// UploadNonImage mocks base method. +func (m *MockfileService) UploadNonImage(ctx context.Context, name string) ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UploadNonImage", ctx, name) + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UploadNonImage indicates an expected call of UploadNonImage. +func (mr *MockfileServiceMockRecorder) UploadNonImage(ctx, name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UploadNonImage", reflect.TypeOf((*MockfileService)(nil).UploadNonImage), ctx, name) +} + // Mockresponder is a mock of responder interface. type Mockresponder struct { ctrl *gomock.Controller recorder *MockresponderMockRecorder + isgomock struct{} } // MockresponderMockRecorder is the mock recorder for Mockresponder. @@ -96,7 +133,7 @@ func (m *Mockresponder) ErrorBadRequest(w http.ResponseWriter, err error, reques } // ErrorBadRequest indicates an expected call of ErrorBadRequest. -func (mr *MockresponderMockRecorder) ErrorBadRequest(w, err, requestID interface{}) *gomock.Call { +func (mr *MockresponderMockRecorder) ErrorBadRequest(w, err, requestID any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ErrorBadRequest", reflect.TypeOf((*Mockresponder)(nil).ErrorBadRequest), w, err, requestID) } @@ -108,7 +145,7 @@ func (m *Mockresponder) LogError(err error, requestID string) { } // LogError indicates an expected call of LogError. -func (mr *MockresponderMockRecorder) LogError(err, requestID interface{}) *gomock.Call { +func (mr *MockresponderMockRecorder) LogError(err, requestID any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogError", reflect.TypeOf((*Mockresponder)(nil).LogError), err, requestID) } @@ -120,7 +157,7 @@ func (m *Mockresponder) OutputBytes(w http.ResponseWriter, data []byte, requestI } // OutputBytes indicates an expected call of OutputBytes. -func (mr *MockresponderMockRecorder) OutputBytes(w, data, requestID interface{}) *gomock.Call { +func (mr *MockresponderMockRecorder) OutputBytes(w, data, requestID any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OutputBytes", reflect.TypeOf((*Mockresponder)(nil).OutputBytes), w, data, requestID) } @@ -132,7 +169,7 @@ func (m *Mockresponder) OutputJSON(w http.ResponseWriter, data any, requestId st } // OutputJSON indicates an expected call of OutputJSON. -func (mr *MockresponderMockRecorder) OutputJSON(w, data, requestId interface{}) *gomock.Call { +func (mr *MockresponderMockRecorder) OutputJSON(w, data, requestId any) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OutputJSON", reflect.TypeOf((*Mockresponder)(nil).OutputJSON), w, data, requestId) } diff --git a/internal/fileService/service/fileService.go b/internal/fileService/service/fileService.go index 6a0b7d5e..2b26d7d8 100644 --- a/internal/fileService/service/fileService.go +++ b/internal/fileService/service/fileService.go @@ -16,14 +16,16 @@ func NewFileService() *FileService { return &FileService{} } -func (f *FileService) Download(ctx context.Context, file multipart.File) (string, error) { +func (f *FileService) Download(ctx context.Context, file multipart.File, format string) (string, error) { var ( fileName = uuid.New().String() - filePath = fmt.Sprintf("/image/%s", fileName) + filePath = fmt.Sprintf("/image/%s.%s", fileName, format) dst, err = os.Create(filePath) ) - defer dst.Close() + defer func(dst *os.File) { + _ = dst.Close() + }(dst) if err != nil { return "", fmt.Errorf("save file: %w", err) } @@ -35,6 +37,25 @@ func (f *FileService) Download(ctx context.Context, file multipart.File) (string return filePath, nil } +func (f *FileService) DownloadNonImage(ctx context.Context, file multipart.File, format string) (string, error) { + var ( + fileName = uuid.New().String() + filePath = fmt.Sprintf("/files/%s.%s", fileName, format) + dst, err = os.Create(filePath) + ) + defer func(dst *os.File) { + _ = dst.Close() + }(dst) + if err != nil { + return "", fmt.Errorf("save file: %w", err) + } + if _, err := io.Copy(dst, file); err != nil { + return "", fmt.Errorf("save file: %w", err) + } + + return filePath, nil +} + func (f *FileService) Upload(ctx context.Context, name string) ([]byte, error) { var ( file, err = os.Open(fmt.Sprintf("/image/%s", name)) @@ -42,7 +63,30 @@ func (f *FileService) Upload(ctx context.Context, name string) ([]byte, error) { sl = make([]byte, 1024) ) - defer file.Close() + defer func(file *os.File) { + _ = file.Close() + }(file) + if err != nil { + return nil, fmt.Errorf("open file: %w", err) + } + + for n, err := file.Read(sl); err != io.EOF; n, err = file.Read(sl) { + res = append(res, sl[:n]...) + } + + return res, nil +} + +func (f *FileService) UploadNonImage(ctx context.Context, name string) ([]byte, error) { + var ( + file, err = os.Open(fmt.Sprintf("/files/%s", name)) + res []byte + sl = make([]byte, 1024) + ) + + defer func(file *os.File) { + _ = file.Close() + }(file) if err != nil { return nil, fmt.Errorf("open file: %w", err) } diff --git a/internal/middleware/fileMetrics.go b/internal/middleware/fileMetrics.go index 9a55a646..315d47ad 100644 --- a/internal/middleware/fileMetrics.go +++ b/internal/middleware/fileMetrics.go @@ -41,35 +41,41 @@ func (rw *fileResponseWriter) Write(b []byte) (int, error) { } func FileMetricsMiddleware(metr *metrics.FileMetrics, next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - respWithCode := NewFileResponseWriter(w) - next.ServeHTTP(respWithCode, r) - statusCode := respWithCode.statusCode - path := r.URL.Path - method := r.Method - var ( - err error - format string - size int64 - ) - if r.Method == http.MethodPost { - format, size, err = getFormatAndSize(r) - } else if r.Method == http.MethodGet { - file := respWithCode.file - format = http.DetectContentType(file[:512]) - size = int64(len(file)) - } - if err != nil { - format = "error" - size = 0 - } - if statusCode != http.StatusOK && statusCode != http.StatusNoContent { - metr.IncErrors(path, strconv.Itoa(statusCode), method, format, size) - } - metr.IncHits(path, strconv.Itoa(statusCode), method, format, size) - metr.ObserveTiming(path, strconv.Itoa(statusCode), method, format, size, time.Since(start).Seconds()) - }) + return http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + respWithCode := NewFileResponseWriter(w) + next.ServeHTTP(respWithCode, r) + statusCode := respWithCode.statusCode + path := r.URL.Path + method := r.Method + var ( + err error + format string + size int64 + ) + if r.Method == http.MethodPost { + format, size, err = getFormatAndSize(r) + } else if r.Method == http.MethodGet { + file := respWithCode.file + size = int64(len(file)) + if size <= 512 { + format = http.DetectContentType(file[:size]) + } else { + format = http.DetectContentType(file[:512]) + } + } + if err != nil { + format = "error" + size = 0 + } + if statusCode != http.StatusOK && statusCode != http.StatusNoContent { + metr.IncErrors(path, strconv.Itoa(statusCode), method, format, size) + } + metr.IncHits(path, strconv.Itoa(statusCode), method, format, size) + metr.ObserveTiming(path, strconv.Itoa(statusCode), method, format, size, time.Since(start).Seconds()) + }, + ) } func getFormatAndSize(r *http.Request) (string, int64, error) { diff --git a/internal/post/controller/controller_test.go b/internal/post/controller/controller_test.go index ed2e5360..43eaeeab 100644 --- a/internal/post/controller/controller_test.go +++ b/internal/post/controller/controller_test.go @@ -20,10 +20,10 @@ func getController(ctrl *gomock.Controller) (*PostController, *mocks) { m := &mocks{ postService: NewMockPostService(ctrl), responder: NewMockResponder(ctrl), - CommentService: NewMockCommentService(ctrl), + commentService: NewMockCommentService(ctrl), } - return NewPostController(m.postService, m.CommentService, m.responder), m + return NewPostController(m.postService, m.commentService, m.responder), m } func TestNewPostController(t *testing.T) { @@ -2115,10 +2115,896 @@ func TestDeleteLikeFromPost(t *testing.T) { } } +func TestComment(t *testing.T) { + tests := []TableTest[Response, Request]{ + { + name: "1", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPost, "/api/v1/feed/2", nil) + w := httptest.NewRecorder() + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.Comment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "2", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPost, "/api/v1/feed/2", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"id": "2"}) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.Comment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "3", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPost, "/api/v1/feed/2", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"id": "2"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.Comment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "4", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPost, "/api/v1/feed/2", bytes.NewBuffer([]byte(`{"id":1}`))) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"id": "2"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.Comment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusInternalServerError, Body: "error"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().Comment(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil, errors.New("error")) + m.responder.EXPECT().ErrorInternal(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusInternalServerError) + _, _ = request.w.Write([]byte("error")) + }, + ) + }, + }, + { + name: "5", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPost, "/api/v1/feed/2", bytes.NewBuffer([]byte(`{"id":1}`))) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"id": "2"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.Comment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusOK, Body: "OK"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().Comment(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return( + &models.Comment{ + Content: models.Content{ + Text: "New comment", + }, + }, nil, + ) + m.responder.EXPECT().OutputJSON(request.w, gomock.Any(), gomock.Any()).Do( + func(w, data, req any) { + request.w.WriteHeader(http.StatusOK) + _, _ = request.w.Write([]byte("OK")) + }, + ) + }, + }, + { + name: "6", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest( + http.MethodPost, "/api/v1/feed/2", + bytes.NewBuffer([]byte(`{"file":"очень большой текст, написанный в поле файл, как он сюда попал - честно говоря хз, надо проверить валидацию на файл длину"}`)), + ) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"id": "2"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.Comment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + } + for _, v := range tests { + t.Run( + v.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + serv, mock := getController(ctrl) + ctx := context.Background() + + input, err := v.SetupInput() + if err != nil { + t.Error(err) + } + + v.SetupMock(*input, mock) + + res, err := v.ExpectedResult() + if err != nil { + t.Error(err) + } + + actual, err := v.Run(ctx, serv, *input) + assert.Equal(t, res, actual) + if !errors.Is(err, v.ExpectedErr) { + t.Errorf("expect %v, got %v", v.ExpectedErr, err) + } + }, + ) + } +} + +func TestGetComment(t *testing.T) { + tests := []TableTest[Response, Request]{ + { + name: "1", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodGet, "/api/v1/feed/2/comment", nil) + w := httptest.NewRecorder() + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.GetComments(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "2", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodGet, "/api/v1/feed/2/comment?id=fnf", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"id": "2"}) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.GetComments(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "3", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodGet, "/api/v1/feed/2/comment?id=4", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"id": "2"}) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.GetComments(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusInternalServerError, Body: "error"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().GetComments(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil, errors.New("error")) + m.responder.EXPECT().ErrorInternal(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusInternalServerError) + _, _ = request.w.Write([]byte("error")) + }, + ) + }, + }, + { + name: "4", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodGet, "/api/v1/feed/2/comment?sort=old", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"id": "2"}) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.GetComments(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusOK, Body: "OK"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().GetComments(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil, nil) + m.responder.EXPECT().OutputJSON(request.w, gomock.Any(), gomock.Any()).Do( + func(w, data, req any) { + request.w.WriteHeader(http.StatusOK) + _, _ = request.w.Write([]byte("OK")) + }, + ) + }, + }, + { + name: "5", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodGet, "/api/v1/feed/2/comment", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{"id": "2"}) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.GetComments(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusNoContent}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().GetComments(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil, my_err.ErrNoMoreContent) + m.responder.EXPECT().OutputNoMoreContentJSON(request.w, gomock.Any()).Do( + func(w, req any) { + request.w.WriteHeader(http.StatusNoContent) + }, + ) + }, + }, + } + + for _, v := range tests { + t.Run( + v.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + serv, mock := getController(ctrl) + ctx := context.Background() + + input, err := v.SetupInput() + if err != nil { + t.Error(err) + } + + v.SetupMock(*input, mock) + + res, err := v.ExpectedResult() + if err != nil { + t.Error(err) + } + + actual, err := v.Run(ctx, serv, *input) + assert.Equal(t, res, actual) + if !errors.Is(err, v.ExpectedErr) { + t.Errorf("expect %v, got %v", v.ExpectedErr, err) + } + }, + ) + } +} + +func TestEditComment(t *testing.T) { + tests := []TableTest[Response, Request]{ + { + name: "1", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPut, "/api/v1/feed/2/", nil) + w := httptest.NewRecorder() + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.EditComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "2", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPut, "/api/v1/feed/2/1", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.EditComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "3", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPut, "/api/v1/feed/2/1", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.EditComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "4", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPut, "/api/v1/feed/2/1", bytes.NewBuffer([]byte(`{"text":"1"}`))) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.EditComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().EditComment(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(my_err.ErrAccessDenied) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "5", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPut, "/api/v1/feed/2/1", bytes.NewBuffer([]byte(`{"text":"1"}`))) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.EditComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().EditComment(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(my_err.ErrWrongComment) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "6", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPut, "/api/v1/feed/2/1", bytes.NewBuffer([]byte(`{"text":"1"}`))) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.EditComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusInternalServerError, Body: "error"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().EditComment(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("err")) + m.responder.EXPECT().ErrorInternal(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusInternalServerError) + _, _ = request.w.Write([]byte("error")) + }, + ) + }, + }, + { + name: "7", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodPut, "/api/v1/feed/2/1", bytes.NewBuffer([]byte(`{"text":"1"}`))) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.EditComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusOK, Body: "OK"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().EditComment(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + m.responder.EXPECT().OutputJSON(request.w, gomock.Any(), gomock.Any()).Do( + func(w, data, req any) { + request.w.WriteHeader(http.StatusOK) + _, _ = request.w.Write([]byte("OK")) + }, + ) + }, + }, + { + name: "8", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest( + http.MethodPut, "/api/v1/feed/2/1", + bytes.NewBuffer([]byte(`{"file":"очень большой текст, написанный в поле файл, как он сюда попал - честно говоря хз, надо проверить валидацию на файл длину"}`)), + ) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.EditComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + } + + for _, v := range tests { + t.Run( + v.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + serv, mock := getController(ctrl) + ctx := context.Background() + + input, err := v.SetupInput() + if err != nil { + t.Error(err) + } + + v.SetupMock(*input, mock) + + res, err := v.ExpectedResult() + if err != nil { + t.Error(err) + } + + actual, err := v.Run(ctx, serv, *input) + assert.Equal(t, res, actual) + if !errors.Is(err, v.ExpectedErr) { + t.Errorf("expect %v, got %v", v.ExpectedErr, err) + } + }, + ) + } +} + +func TestDeleteComment(t *testing.T) { + tests := []TableTest[Response, Request]{ + { + name: "1", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodDelete, "/api/v1/feed/2/", nil) + w := httptest.NewRecorder() + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.DeleteComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "2", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodDelete, "/api/v1/feed/2/1", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.DeleteComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "3", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodDelete, "/api/v1/feed/2/1", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.DeleteComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().DeleteComment(gomock.Any(), gomock.Any(), gomock.Any()). + Return(my_err.ErrAccessDenied) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "4", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodDelete, "/api/v1/feed/2/1", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.DeleteComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusBadRequest, Body: "bad request"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().DeleteComment(gomock.Any(), gomock.Any(), gomock.Any()). + Return(my_err.ErrWrongComment) + m.responder.EXPECT().ErrorBadRequest(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusBadRequest) + _, _ = request.w.Write([]byte("bad request")) + }, + ) + }, + }, + { + name: "5", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodDelete, "/api/v1/feed/2/1", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.DeleteComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusInternalServerError, Body: "error"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().DeleteComment(gomock.Any(), gomock.Any(), gomock.Any()). + Return(errors.New("err")) + m.responder.EXPECT().ErrorInternal(request.w, gomock.Any(), gomock.Any()).Do( + func(w, err, req any) { + request.w.WriteHeader(http.StatusInternalServerError) + _, _ = request.w.Write([]byte("error")) + }, + ) + }, + }, + { + name: "6", + SetupInput: func() (*Request, error) { + req := httptest.NewRequest(http.MethodDelete, "/api/v1/feed/2/1", nil) + w := httptest.NewRecorder() + req = mux.SetURLVars(req, map[string]string{commentIDKey: "1"}) + req = req.WithContext(models.ContextWithSession(req.Context(), &models.Session{ID: "1", UserID: 1})) + res := &Request{r: req, w: w} + return res, nil + }, + Run: func(ctx context.Context, implementation *PostController, request Request) (Response, error) { + implementation.DeleteComment(request.w, request.r) + res := Response{StatusCode: request.w.Code, Body: request.w.Body.String()} + return res, nil + }, + ExpectedResult: func() (Response, error) { + return Response{StatusCode: http.StatusOK, Body: "OK"}, nil + }, + ExpectedErr: nil, + SetupMock: func(request Request, m *mocks) { + m.responder.EXPECT().LogError(gomock.Any(), gomock.Any()) + m.commentService.EXPECT().DeleteComment(gomock.Any(), gomock.Any(), gomock.Any()). + Return(nil) + m.responder.EXPECT().OutputJSON(request.w, gomock.Any(), gomock.Any()).Do( + func(w, data, req any) { + request.w.WriteHeader(http.StatusOK) + _, _ = request.w.Write([]byte("OK")) + }, + ) + }, + }, + } + + for _, v := range tests { + t.Run( + v.name, func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + serv, mock := getController(ctrl) + ctx := context.Background() + + input, err := v.SetupInput() + if err != nil { + t.Error(err) + } + + v.SetupMock(*input, mock) + + res, err := v.ExpectedResult() + if err != nil { + t.Error(err) + } + + actual, err := v.Run(ctx, serv, *input) + assert.Equal(t, res, actual) + if !errors.Is(err, v.ExpectedErr) { + t.Errorf("expect %v, got %v", v.ExpectedErr, err) + } + }, + ) + } +} + type mocks struct { postService *MockPostService - CommentService *MockCommentService responder *MockResponder + commentService *MockCommentService } type Request struct { diff --git a/internal/router/file/router.go b/internal/router/file/router.go index c5ae529a..3beebf22 100644 --- a/internal/router/file/router.go +++ b/internal/router/file/router.go @@ -21,6 +21,7 @@ type SessionManager interface { type FileController interface { Upload(w http.ResponseWriter, r *http.Request) Download(w http.ResponseWriter, r *http.Request) + UploadNonImage(w http.ResponseWriter, r *http.Request) } func NewRouter( @@ -30,6 +31,7 @@ func NewRouter( router.HandleFunc("/image/{name}", fc.Upload).Methods(http.MethodGet, http.MethodOptions) router.HandleFunc("/image", fc.Download).Methods(http.MethodPost, http.MethodOptions) + router.HandleFunc("/files/{name}", fc.UploadNonImage).Methods(http.MethodGet, http.MethodOptions) router.Handle("/api/v1/metrics", promhttp.Handler()) diff --git a/internal/router/file/router_test.go b/internal/router/file/router_test.go index 6af2200b..1d88b5c0 100644 --- a/internal/router/file/router_test.go +++ b/internal/router/file/router_test.go @@ -27,6 +27,8 @@ func (m mockSessionManager) Destroy(sess *models.Session) error { type mockFileController struct{} +func (m mockFileController) UploadNonImage(w http.ResponseWriter, r *http.Request) {} + func (m mockFileController) Upload(w http.ResponseWriter, r *http.Request) {} func (m mockFileController) Download(w http.ResponseWriter, r *http.Request) {}