From 7c2f2d3fa61009ad2ab2817531418f88cf84280c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=ADrio=20Neto?= Date: Mon, 10 Feb 2025 12:54:20 -0300 Subject: [PATCH 1/2] refactor: apply user id context on repositories --- cmd/http/main.go | 2 +- docker-compose.yaml | 2 +- go.mod | 1 + go.sum | 2 + internal/adapters/db/error_sentinels.go | 7 ++ internal/adapters/db/interface.go | 2 + internal/adapters/db/thumb_repository.go | 38 ++++++++--- internal/adapters/db/thumb_repository_test.go | 68 ++++++++++++++----- .../adapters/rest/handler/thumb_handler.go | 19 +++--- .../rest/handler/thumb_handler_test.go | 23 +++---- .../rest/middleware/authmiddleware.go | 9 ++- internal/adapters/rest/server/server.go | 14 ++-- internal/adapters/sqs/queue_adapter_sqs.go | 2 +- internal/config/config.go | 10 +++ .../thumb_queue_adapter_interface.go | 8 ++- .../thumb_repository_adapter_interface.go | 12 ++-- internal/core/thumb/thumb_service.go | 27 +++++--- internal/core/thumb/thumb_service_test.go | 29 ++++---- internal/mocks/adaptermocks/IDB.go | 20 ++++++ .../mocks/adaptermocks/IThumbQueueAdapter.go | 13 ++-- .../adaptermocks/IThumbRepositoryAdapter.go | 41 +++++------ internal/mocks/servicemocks/IThumbService.go | 40 +++++------ 22 files changed, 262 insertions(+), 127 deletions(-) create mode 100644 internal/adapters/db/error_sentinels.go diff --git a/cmd/http/main.go b/cmd/http/main.go index 6d0575b..af667ef 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -42,7 +42,7 @@ func main() { ThumService: thumbService, }) - restServer.Serve() + restServer.Serve(cfg) } func newDatabaseConnection(cfg *config.Config) (*gorm.DB, error) { diff --git a/docker-compose.yaml b/docker-compose.yaml index e56a5dc..e80fa4b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,7 +5,7 @@ services: image: postgres:16.2 restart: always ports: - - "5432:5432" + - "${DB_PORT-5432}:5432" environment: - POSTGRES_USER=${DB_USERNAME} - POSTGRES_PASSWORD=${DB_PASSWORD} diff --git a/go.mod b/go.mod index 00f984d..b964de6 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/caarlos0/env/v11 v11.3.1 github.com/gin-gonic/gin v1.10.0 github.com/google/uuid v1.6.0 + github.com/joho/godotenv v1.5.1 github.com/stretchr/testify v1.10.0 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 diff --git a/go.sum b/go.sum index 2bbcf51..b4f7b2b 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= diff --git a/internal/adapters/db/error_sentinels.go b/internal/adapters/db/error_sentinels.go new file mode 100644 index 0000000..888c841 --- /dev/null +++ b/internal/adapters/db/error_sentinels.go @@ -0,0 +1,7 @@ +package db + +import "errors" + +var ( + RequiredUserIDError = errors.New("logged user ID is required") +) diff --git a/internal/adapters/db/interface.go b/internal/adapters/db/interface.go index b354e80..400eb5e 100644 --- a/internal/adapters/db/interface.go +++ b/internal/adapters/db/interface.go @@ -2,6 +2,7 @@ package db import ( "database/sql" + "gorm.io/gorm" ) @@ -19,6 +20,7 @@ type IDB interface { Last(dest interface{}, conds ...interface{}) (tx *gorm.DB) Find(dest interface{}, conds ...interface{}) (tx *gorm.DB) Update(column string, value interface{}) (tx *gorm.DB) + Updates(values interface{}) (tx *gorm.DB) Delete(value interface{}, conds ...interface{}) (tx *gorm.DB) Count(count *int64) (tx *gorm.DB) Row() *sql.Row diff --git a/internal/adapters/db/thumb_repository.go b/internal/adapters/db/thumb_repository.go index b5f2298..55c7fa4 100644 --- a/internal/adapters/db/thumb_repository.go +++ b/internal/adapters/db/thumb_repository.go @@ -1,6 +1,7 @@ package db import ( + "context" "errors" "github.com/google/uuid" @@ -9,6 +10,7 @@ import ( type ThumbPostgres struct { BaseModel + UserID int VideoPath string Status string Error string @@ -23,11 +25,14 @@ type PostgresThumbRepository struct { db IDB } -func (r *PostgresThumbRepository) Create(process *entity.ThumbProcess) error { +func (r *PostgresThumbRepository) Create(ctx context.Context, process *entity.ThumbProcess) error { + userID, err := r.getUserID(ctx) + if err != nil { + return err + } + record := &ThumbPostgres{ - BaseModel: BaseModel{ - ID: process.ID, - }, + UserID: userID, VideoPath: process.Video.Path, ThumbnailPath: process.Thumbnail.Path, Status: process.Status, @@ -36,14 +41,22 @@ func (r *PostgresThumbRepository) Create(process *entity.ThumbProcess) error { result := r.db.Create(record) + process.ID = record.ID + return result.Error } -func (r *PostgresThumbRepository) List() *[]entity.ThumbProcess { +func (r *PostgresThumbRepository) List(ctx context.Context) *[]entity.ThumbProcess { processes := []entity.ThumbProcess{} + + userID, err := r.getUserID(ctx) + if err != nil { + return &processes + } + records := []ThumbPostgres{} - r.db.Find(&records) + r.db.Find(&records, "user_id = ?", userID) for _, record := range records { processes = append(processes, entity.ThumbProcess{ @@ -66,12 +79,12 @@ func NewPostgresThumbRepository(db IDB) *PostgresThumbRepository { return &PostgresThumbRepository{db: db} } -func (r *PostgresThumbRepository) Update(process *entity.ThumbProcess) (*entity.ThumbProcess, error) { +func (r *PostgresThumbRepository) Update(ctx context.Context, process *entity.ThumbProcess) (*entity.ThumbProcess, error) { if process.ID == uuid.Nil { return nil, errors.New("process id is required") } - result := r.db.Save(&ThumbPostgres{ + result := r.db.Updates(&ThumbPostgres{ BaseModel: BaseModel{ ID: process.ID, }, @@ -97,3 +110,12 @@ func (r *PostgresThumbRepository) Update(process *entity.ThumbProcess) (*entity. }, }, nil } + +func (r *PostgresThumbRepository) getUserID(ctx context.Context) (int, error) { + userID, ok := ctx.Value("logged_user_id").(int) + if !ok { + return 0, RequiredUserIDError + } + + return userID, nil +} diff --git a/internal/adapters/db/thumb_repository_test.go b/internal/adapters/db/thumb_repository_test.go index c2bab3c..4bed5c0 100644 --- a/internal/adapters/db/thumb_repository_test.go +++ b/internal/adapters/db/thumb_repository_test.go @@ -2,6 +2,7 @@ package db import ( + "context" "errors" "testing" @@ -14,8 +15,7 @@ import ( ) func TestPostgresThumbRepository_Create(t *testing.T) { - mockDB := new(adaptermocks.IDB) - repo := NewPostgresThumbRepository(mockDB) + mockDB, repo, _, ctx := setupTest() t.Run("successful creation", func(t *testing.T) { process := &entity.ThumbProcess{ @@ -31,7 +31,7 @@ func TestPostgresThumbRepository_Create(t *testing.T) { mockDB.On("Create", mock.AnythingOfType("*db.ThumbPostgres")).Return(&gorm.DB{}).Once() - err := repo.Create(process) + err := repo.Create(ctx, process) assert.NoError(t, err) }) @@ -51,19 +51,36 @@ func TestPostgresThumbRepository_Create(t *testing.T) { expectedError := errors.New("database error") mockDB.On("Create", mock.AnythingOfType("*db.ThumbPostgres")).Return(&gorm.DB{Error: expectedError}).Once() - err := repo.Create(process) + err := repo.Create(ctx, process) assert.Error(t, err) assert.Equal(t, expectedError, err) }) + + t.Run("create with missing user ID", func(t *testing.T) { + process := &entity.ThumbProcess{ + Video: entity.ThumbProcessVideo{ + Path: "test-video.mp4", + }, + Thumbnail: entity.ThumbProcessThumb{ + Path: "test-thumb.jpg", + }, + Status: "pending", + Error: "", + } + + err := repo.Create(context.Background(), process) + + assert.Error(t, err) + assert.Equal(t, RequiredUserIDError, err) + }) } func TestPostgresThumbRepository_List(t *testing.T) { - mockDB := new(adaptermocks.IDB) - repo := NewPostgresThumbRepository(mockDB) + mockDB, repo, mockedUserID, ctx := setupTest() t.Run("successful list retrieval", func(t *testing.T) { - mockDB.On("Find", mock.AnythingOfType("*[]db.ThumbPostgres"), mock.Anything). + mockDB.On("Find", mock.AnythingOfType("*[]db.ThumbPostgres"), "user_id = ?", mockedUserID). Run(func(args mock.Arguments) { arg := args.Get(0).(*[]ThumbPostgres) *arg = []ThumbPostgres{ @@ -71,6 +88,7 @@ func TestPostgresThumbRepository_List(t *testing.T) { BaseModel: BaseModel{ ID: uuid.New(), }, + UserID: 1, VideoPath: "video1.mp4", ThumbnailPath: "thumb1.jpg", Status: "completed", @@ -80,6 +98,7 @@ func TestPostgresThumbRepository_List(t *testing.T) { BaseModel: BaseModel{ ID: uuid.New(), }, + UserID: 1, VideoPath: "video2.mp4", ThumbnailPath: "thumb2.jpg", Status: "pending", @@ -88,7 +107,7 @@ func TestPostgresThumbRepository_List(t *testing.T) { } }).Return(&gorm.DB{}).Once() - processes := *repo.List() + processes := *repo.List(ctx) assert.Len(t, processes, 2) assert.Equal(t, "video1.mp4", processes[0].Video.Path) @@ -100,21 +119,25 @@ func TestPostgresThumbRepository_List(t *testing.T) { }) t.Run("empty list", func(t *testing.T) { - mockDB.On("Find", mock.AnythingOfType("*[]db.ThumbPostgres"), mock.Anything). + mockDB.On("Find", mock.AnythingOfType("*[]db.ThumbPostgres"), "user_id = ?", mockedUserID). Run(func(args mock.Arguments) { arg := args.Get(0).(*[]ThumbPostgres) *arg = []ThumbPostgres{} }).Return(&gorm.DB{}).Once() - processes := repo.List() + processes := repo.List(ctx) + + assert.Empty(t, processes) + }) + t.Run("list with missing user ID", func(t *testing.T) { + processes := repo.List(context.Background()) assert.Empty(t, processes) }) } func TestPostgresThumbRepository_Update(t *testing.T) { - mockDB := new(adaptermocks.IDB) - repo := NewPostgresThumbRepository(mockDB) + mockDB, repo, _, ctx := setupTest() t.Run("successful update", func(t *testing.T) { processID := uuid.New() @@ -130,9 +153,9 @@ func TestPostgresThumbRepository_Update(t *testing.T) { Error: "", } - mockDB.On("Save", mock.AnythingOfType("*db.ThumbPostgres")).Return(&gorm.DB{}).Once() + mockDB.On("Updates", mock.AnythingOfType("*db.ThumbPostgres")).Return(&gorm.DB{}).Once() - updated, err := repo.Update(process) + updated, err := repo.Update(ctx, process) assert.NoError(t, err) assert.NotNil(t, updated) @@ -150,12 +173,11 @@ func TestPostgresThumbRepository_Update(t *testing.T) { Error: "", } - updated, err := repo.Update(process) + updated, err := repo.Update(ctx, process) assert.Nil(t, updated) assert.Error(t, err) assert.Equal(t, "process id is required", err.Error()) - mockDB.AssertNotCalled(t, "Save") }) t.Run("database error during update", func(t *testing.T) { @@ -173,9 +195,9 @@ func TestPostgresThumbRepository_Update(t *testing.T) { } expectedError := errors.New("database error") - mockDB.On("Save", mock.AnythingOfType("*db.ThumbPostgres")).Return(&gorm.DB{Error: expectedError}).Once() + mockDB.On("Updates", mock.AnythingOfType("*db.ThumbPostgres")).Return(&gorm.DB{Error: expectedError}).Once() - updated, err := repo.Update(process) + updated, err := repo.Update(ctx, process) assert.Nil(t, updated) assert.Error(t, err) @@ -195,3 +217,13 @@ func TestThumbPostgres_TableName(t *testing.T) { thumb := ThumbPostgres{} assert.Equal(t, "thumb", thumb.TableName()) } + +func setupTest() (*adaptermocks.IDB, *PostgresThumbRepository, int, context.Context) { + mockedUserID := 1 + mockDB := new(adaptermocks.IDB) + + return mockDB, + NewPostgresThumbRepository(mockDB), + mockedUserID, + context.WithValue(context.Background(), "logged_user_id", mockedUserID) +} diff --git a/internal/adapters/rest/handler/thumb_handler.go b/internal/adapters/rest/handler/thumb_handler.go index 69ab28e..4b2d5f4 100644 --- a/internal/adapters/rest/handler/thumb_handler.go +++ b/internal/adapters/rest/handler/thumb_handler.go @@ -1,13 +1,12 @@ package handler import ( + "net/http" + "github.com/gin-gonic/gin" "github.com/google/uuid" - "github.com/pangolin-do-golang/thumb-processor-api/internal/adapters/rest/middleware" "github.com/pangolin-do-golang/thumb-processor-api/internal/core/ports" "github.com/pangolin-do-golang/thumb-processor-api/internal/core/thumb" - "github.com/pangolin-do-golang/thumb-processor-api/internal/core/users" - "net/http" ) type ThumbHandler struct { @@ -22,9 +21,13 @@ func NewThumbHandler(service thumb.IThumbService) *ThumbHandler { func (h *ThumbHandler) RegisterRoutes(router *gin.RouterGroup) { thumbGroup := router.Group("/thumbs") - thumbGroup.POST("", middleware.AuthMiddleware(users.GetAllowedUsers), h.CreateProcess) + thumbGroup.POST("", h.CreateProcess) + thumbGroup.GET("", h.ListProcesses) +} + +func (h *ThumbHandler) RegisterInternalRoutes(router *gin.Engine) { + thumbGroup := router.Group("/thumbs") thumbGroup.PUT("/:id", h.UpdateProcess) - thumbGroup.GET("", middleware.AuthMiddleware(users.GetAllowedUsers), h.ListProcesses) } // @Summary Create a new thumbnail process @@ -58,7 +61,7 @@ func (h *ThumbHandler) CreateProcess(c *gin.Context) { if ok { userEmail = ctxUser.(string) } - err := h.thumbService.CreateProcessAsync(&ports.CreateProcessRequest{ + err := h.thumbService.CreateProcessAsync(c.Request.Context(), &ports.CreateProcessRequest{ UserEmail: userEmail, Url: request.URL, }) @@ -103,7 +106,7 @@ func (h *ThumbHandler) UpdateProcess(c *gin.Context) { return } - updated, err := h.thumbService.UpdateProcess(&ports.UpdateProcessRequest{ + updated, err := h.thumbService.UpdateProcess(c.Request.Context(), &ports.UpdateProcessRequest{ ID: id, Status: request.Status, Error: request.Error, @@ -133,7 +136,7 @@ func (h *ThumbHandler) UpdateProcess(c *gin.Context) { // @Failure 500 {object} ErrorResponse // @Router /thumbs [get] func (h *ThumbHandler) ListProcesses(c *gin.Context) { - processes := h.thumbService.ListProcess() + processes := h.thumbService.ListProcess(c.Request.Context()) response := make([]ThumbProcessResponse, len(*processes)) for i, process := range *processes { diff --git a/internal/adapters/rest/handler/thumb_handler_test.go b/internal/adapters/rest/handler/thumb_handler_test.go index 525b606..71e5490 100644 --- a/internal/adapters/rest/handler/thumb_handler_test.go +++ b/internal/adapters/rest/handler/thumb_handler_test.go @@ -23,6 +23,7 @@ func setupTest() (*gin.Engine, *servicemocks.IThumbService) { handler := NewThumbHandler(mockService) group := router.Group("/") handler.RegisterRoutes(group) + handler.RegisterInternalRoutes(router) return router, mockService } @@ -39,7 +40,7 @@ func TestUpdateProcess(t *testing.T) { }, } - mockService.On("UpdateProcess", mock.AnythingOfType("*ports.UpdateProcessRequest")).Return(updatedProcess, nil).Once() + mockService.On("UpdateProcess", mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*ports.UpdateProcessRequest")).Return(updatedProcess, nil).Once() body := UpdateProcessRequest{ Status: "completed", @@ -93,7 +94,7 @@ func TestUpdateProcess(t *testing.T) { }) t.Run("service error", func(t *testing.T) { - mockService.On("UpdateProcess", mock.AnythingOfType("*ports.UpdateProcessRequest")). + mockService.On("UpdateProcess", mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*ports.UpdateProcessRequest")). Return(nil, errors.New("service error")).Once() body := UpdateProcessRequest{ @@ -120,17 +121,14 @@ func TestCreateProcess(t *testing.T) { router, mockService := setupTest() t.Run("successful create", func(t *testing.T) { - body := CreateProcessRequest{ - URL: "http://example.com/video.mp4", - } - jsonBody, _ := json.Marshal(body) + mockService.On("CreateProcessAsync", mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*ports.CreateProcessRequest")).Return(nil).Once() - mockService.On("CreateProcessAsync", mock.AnythingOfType("*ports.CreateProcessRequest")).Return(nil).Once() + body := CreateProcessRequest{URL: "https://example.com/video.mp4"} + jsonBody, _ := json.Marshal(body) w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/thumbs", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") - req.SetBasicAuth("test", "test") router.ServeHTTP(w, req) assert.Equal(t, http.StatusAccepted, w.Code) @@ -170,13 +168,14 @@ func TestCreateProcess(t *testing.T) { }) t.Run("service error", func(t *testing.T) { + mockService.On("CreateProcessAsync", mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*ports.CreateProcessRequest")). + Return(errors.New("service error")).Once() + body := CreateProcessRequest{ - URL: "http://example.com/video.mp4", + URL: "test", } jsonBody, _ := json.Marshal(body) - mockService.On("CreateProcessAsync", mock.AnythingOfType("*ports.CreateProcessRequest")).Return(errors.New("service error")).Once() - w := httptest.NewRecorder() req, _ := http.NewRequest("POST", "/thumbs", bytes.NewBuffer(jsonBody)) req.Header.Set("Content-Type", "application/json") @@ -209,7 +208,7 @@ func TestListProcesses(t *testing.T) { }, } - mockService.On("ListProcess").Return(processes).Once() + mockService.On("ListProcess", mock.AnythingOfType("context.backgroundCtx")).Return(processes).Once() w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/thumbs", nil) diff --git a/internal/adapters/rest/middleware/authmiddleware.go b/internal/adapters/rest/middleware/authmiddleware.go index 7aff4cc..2f011b7 100644 --- a/internal/adapters/rest/middleware/authmiddleware.go +++ b/internal/adapters/rest/middleware/authmiddleware.go @@ -1,8 +1,11 @@ package middleware import ( - "github.com/gin-gonic/gin" + "context" "net/http" + + "github.com/gin-gonic/gin" + "github.com/pangolin-do-golang/thumb-processor-api/internal/core/users" ) func AuthMiddleware(allowedUsersFunc func() gin.Accounts) gin.HandlerFunc { @@ -22,6 +25,10 @@ func AuthMiddleware(allowedUsersFunc func() gin.Accounts) gin.HandlerFunc { } c.Set("user", username) + loggedUser := users.GetUserByNickname(username) + + ctx := context.WithValue(c.Request.Context(), "logged_user_id", loggedUser.ID) + c.Request = c.Request.WithContext(ctx) c.Next() // Continue to the handler } diff --git a/internal/adapters/rest/server/server.go b/internal/adapters/rest/server/server.go index 1f7e786..3c8bf60 100644 --- a/internal/adapters/rest/server/server.go +++ b/internal/adapters/rest/server/server.go @@ -4,7 +4,9 @@ import ( "github.com/gin-gonic/gin" "github.com/pangolin-do-golang/thumb-processor-api/internal/adapters/rest/handler" "github.com/pangolin-do-golang/thumb-processor-api/internal/adapters/rest/middleware" + "github.com/pangolin-do-golang/thumb-processor-api/internal/config" "github.com/pangolin-do-golang/thumb-processor-api/internal/core/thumb" + "github.com/pangolin-do-golang/thumb-processor-api/internal/core/users" ) type RestServer struct { @@ -21,7 +23,7 @@ func NewRestServer(opts *RestServerOptions) *RestServer { } } -func (rs RestServer) Serve() { +func (rs RestServer) Serve(cfg *config.Config) { r := gin.Default() r.Use(middleware.CorsMiddleware()) @@ -32,11 +34,15 @@ func (rs RestServer) Serve() { handler.RegisterUserRoutes(r) // Rotes that need authentication - handler.RegisterLoginHandlers(r.Group("/")) + authorizedGroup := r.Group("/", middleware.AuthMiddleware(users.GetAllowedUsers)) - handler.NewThumbHandler(rs.thumbService).RegisterRoutes(r.Group("/")) + handler.RegisterLoginHandlers(authorizedGroup) - err := r.Run("0.0.0.0:8080") + thumbHandler := handler.NewThumbHandler(rs.thumbService) + thumbHandler.RegisterRoutes(authorizedGroup) + thumbHandler.RegisterInternalRoutes(r) + + err := r.Run("0.0.0.0:" + cfg.API.Port) if err != nil { panic(err) } diff --git a/internal/adapters/sqs/queue_adapter_sqs.go b/internal/adapters/sqs/queue_adapter_sqs.go index 52a9837..ba0dc4c 100644 --- a/internal/adapters/sqs/queue_adapter_sqs.go +++ b/internal/adapters/sqs/queue_adapter_sqs.go @@ -28,7 +28,7 @@ func NewSQSThumbQueue(c *cfg.Config) (*SQSThumbQueue, error) { }, nil } -func (q *SQSThumbQueue) SendEvent(process *entity.ThumbProcess) error { +func (q *SQSThumbQueue) SendEvent(ctx context.Context, process *entity.ThumbProcess) error { messageBody, err := json.Marshal(process) if err != nil { return err diff --git a/internal/config/config.go b/internal/config/config.go index 82c1bf2..0cabe75 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/caarlos0/env/v11" + "github.com/joho/godotenv" ) type S3 struct { @@ -18,9 +19,18 @@ type Config struct { S3 S3 SQS SQS DB Database + API API +} + +type API struct { + Port string `env:"API_PORT"` } func Load() (*Config, error) { + if err := godotenv.Load(); err != nil { + return nil, err + } + cfg := Config{} err := env.Parse(&cfg) return &cfg, err diff --git a/internal/core/domain/contracts/thumb_queue_adapter_interface.go b/internal/core/domain/contracts/thumb_queue_adapter_interface.go index 6195fd3..cada2d0 100644 --- a/internal/core/domain/contracts/thumb_queue_adapter_interface.go +++ b/internal/core/domain/contracts/thumb_queue_adapter_interface.go @@ -1,7 +1,11 @@ package contracts -import "github.com/pangolin-do-golang/thumb-processor-api/internal/core/domain/entity" +import ( + "context" + + "github.com/pangolin-do-golang/thumb-processor-api/internal/core/domain/entity" +) type IThumbQueueAdapter interface { - SendEvent(process *entity.ThumbProcess) error + SendEvent(ctx context.Context, process *entity.ThumbProcess) error } diff --git a/internal/core/domain/contracts/thumb_repository_adapter_interface.go b/internal/core/domain/contracts/thumb_repository_adapter_interface.go index dffd6ef..ead5a45 100644 --- a/internal/core/domain/contracts/thumb_repository_adapter_interface.go +++ b/internal/core/domain/contracts/thumb_repository_adapter_interface.go @@ -1,9 +1,13 @@ package contracts -import "github.com/pangolin-do-golang/thumb-processor-api/internal/core/domain/entity" +import ( + "context" + + "github.com/pangolin-do-golang/thumb-processor-api/internal/core/domain/entity" +) type IThumbRepositoryAdapter interface { - Create(process *entity.ThumbProcess) error - Update(process *entity.ThumbProcess) (*entity.ThumbProcess, error) - List() *[]entity.ThumbProcess + Create(ctx context.Context, process *entity.ThumbProcess) error + Update(ctx context.Context, process *entity.ThumbProcess) (*entity.ThumbProcess, error) + List(ctx context.Context) *[]entity.ThumbProcess } diff --git a/internal/core/thumb/thumb_service.go b/internal/core/thumb/thumb_service.go index fb5b6a8..ac84dab 100644 --- a/internal/core/thumb/thumb_service.go +++ b/internal/core/thumb/thumb_service.go @@ -1,15 +1,18 @@ package thumb import ( + "context" + "errors" + "github.com/pangolin-do-golang/thumb-processor-api/internal/core/domain/contracts" "github.com/pangolin-do-golang/thumb-processor-api/internal/core/domain/entity" "github.com/pangolin-do-golang/thumb-processor-api/internal/core/ports" ) type IThumbService interface { - CreateProcessAsync(request *ports.CreateProcessRequest) error - UpdateProcess(request *ports.UpdateProcessRequest) (*entity.ThumbProcess, error) - ListProcess() *[]entity.ThumbProcess + CreateProcessAsync(ctx context.Context, request *ports.CreateProcessRequest) error + UpdateProcess(ctx context.Context, request *ports.UpdateProcessRequest) (*entity.ThumbProcess, error) + ListProcess(ctx context.Context) *[]entity.ThumbProcess } type Service struct { @@ -21,21 +24,25 @@ func NewThumbService(repo contracts.IThumbRepositoryAdapter, q contracts.IThumbQ return &Service{processRepository: repo, queueAdapter: q} } -func (s *Service) CreateProcessAsync(request *ports.CreateProcessRequest) error { +func (s *Service) CreateProcessAsync(ctx context.Context, request *ports.CreateProcessRequest) error { thumbProcess := entity.NewThumbProcess(request.Url, request.UserEmail) - if err := s.processRepository.Create(thumbProcess); err != nil { + if err := s.processRepository.Create(ctx, thumbProcess); err != nil { return err } - if err := s.queueAdapter.SendEvent(thumbProcess); err != nil { + if err := s.queueAdapter.SendEvent(ctx, thumbProcess); err != nil { return err } return nil } -func (s *Service) UpdateProcess(request *ports.UpdateProcessRequest) (*entity.ThumbProcess, error) { +func (s *Service) UpdateProcess(ctx context.Context, request *ports.UpdateProcessRequest) (*entity.ThumbProcess, error) { + if !entity.AllowedProcessStatus[request.Status] { + return nil, errors.New("informed status not allowed") + } + thumbProcess := &entity.ThumbProcess{ ID: request.ID, Status: request.Status, @@ -43,7 +50,7 @@ func (s *Service) UpdateProcess(request *ports.UpdateProcessRequest) (*entity.Th Thumbnail: entity.ThumbProcessThumb{Path: request.ThumbnailPath}, } - updated, err := s.processRepository.Update(thumbProcess) + updated, err := s.processRepository.Update(ctx, thumbProcess) if err != nil { return nil, err } @@ -51,8 +58,8 @@ func (s *Service) UpdateProcess(request *ports.UpdateProcessRequest) (*entity.Th return updated, nil } -func (s *Service) ListProcess() *[]entity.ThumbProcess { - processList := s.processRepository.List() +func (s *Service) ListProcess(ctx context.Context) *[]entity.ThumbProcess { + processList := s.processRepository.List(ctx) return processList } diff --git a/internal/core/thumb/thumb_service_test.go b/internal/core/thumb/thumb_service_test.go index ba75476..dd0f9b5 100644 --- a/internal/core/thumb/thumb_service_test.go +++ b/internal/core/thumb/thumb_service_test.go @@ -1,6 +1,7 @@ package thumb import ( + "context" "errors" "testing" @@ -20,27 +21,27 @@ func TestCreateProcessAsync(t *testing.T) { service := NewThumbService(mockRepo, mockQueue) t.Run("successful creation", func(t *testing.T) { - mockRepo.On("Create", mock.AnythingOfType("*entity.ThumbProcess")).Return(nil).Once() - mockQueue.On("SendEvent", mock.AnythingOfType("*entity.ThumbProcess")).Return(nil).Once() + mockRepo.On("Create", mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*entity.ThumbProcess")).Return(nil).Once() + mockQueue.On("SendEvent", mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*entity.ThumbProcess")).Return(nil).Once() - err := service.CreateProcessAsync(testAppRequest) + err := service.CreateProcessAsync(context.Background(), testAppRequest) assert.NoError(t, err) }) t.Run("repository error", func(t *testing.T) { - mockRepo.On("Create", mock.AnythingOfType("*entity.ThumbProcess")).Return(errors.New("db error")).Once() + mockRepo.On("Create", mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*entity.ThumbProcess")).Return(errors.New("db error")).Once() - err := service.CreateProcessAsync(testAppRequest) + err := service.CreateProcessAsync(context.Background(), testAppRequest) assert.Error(t, err) }) t.Run("queue error", func(t *testing.T) { - mockRepo.On("Create", mock.AnythingOfType("*entity.ThumbProcess")).Return(nil) - mockQueue.On("SendEvent", mock.AnythingOfType("*entity.ThumbProcess")).Return(errors.New("queue error")).Once() + mockRepo.On("Create", mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*entity.ThumbProcess")).Return(nil) + mockQueue.On("SendEvent", mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*entity.ThumbProcess")).Return(errors.New("queue error")).Once() - err := service.CreateProcessAsync(testAppRequest) + err := service.CreateProcessAsync(context.Background(), testAppRequest) assert.Error(t, err) }) @@ -56,8 +57,8 @@ func TestListProcess(t *testing.T) { *entity.NewThumbProcess("https://example.com/video2.mp4", "teste@teste.com"), } - mockRepo.On("List").Return(expectedList) - result := service.ListProcess() + mockRepo.On("List", mock.AnythingOfType("context.backgroundCtx")).Return(expectedList) + result := service.ListProcess(context.Background()) assert.Equal(t, expectedList, result) } @@ -83,18 +84,18 @@ func TestUpdateProcess(t *testing.T) { } t.Run("successful creation", func(t *testing.T) { - mockRepo.On("Update", thumbProcess).Return(thumbProcess, nil).Once() + mockRepo.On("Update", mock.AnythingOfType("context.backgroundCtx"), thumbProcess).Return(thumbProcess, nil).Once() - updated, err := service.UpdateProcess(request) + updated, err := service.UpdateProcess(context.Background(), request) assert.NotNil(t, updated) assert.NoError(t, err) }) t.Run("repository error", func(t *testing.T) { - mockRepo.On("Update", mock.AnythingOfType("*entity.ThumbProcess")).Return(nil, errors.New("db error")).Once() + mockRepo.On("Update", mock.AnythingOfType("context.backgroundCtx"), mock.AnythingOfType("*entity.ThumbProcess")).Return(nil, errors.New("db error")).Once() - updated, err := service.UpdateProcess(request) + updated, err := service.UpdateProcess(context.Background(), request) assert.Nil(t, updated) assert.Error(t, err) }) diff --git a/internal/mocks/adaptermocks/IDB.go b/internal/mocks/adaptermocks/IDB.go index a8bedbb..cebad04 100644 --- a/internal/mocks/adaptermocks/IDB.go +++ b/internal/mocks/adaptermocks/IDB.go @@ -446,6 +446,26 @@ func (_m *IDB) Update(column string, value interface{}) *gorm.DB { return r0 } +// Updates provides a mock function with given fields: values +func (_m *IDB) Updates(values interface{}) *gorm.DB { + ret := _m.Called(values) + + if len(ret) == 0 { + panic("no return value specified for Updates") + } + + var r0 *gorm.DB + if rf, ok := ret.Get(0).(func(interface{}) *gorm.DB); ok { + r0 = rf(values) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*gorm.DB) + } + } + + return r0 +} + // Where provides a mock function with given fields: query, args func (_m *IDB) Where(query interface{}, args ...interface{}) *gorm.DB { var _ca []interface{} diff --git a/internal/mocks/adaptermocks/IThumbQueueAdapter.go b/internal/mocks/adaptermocks/IThumbQueueAdapter.go index 3a86d19..297b147 100644 --- a/internal/mocks/adaptermocks/IThumbQueueAdapter.go +++ b/internal/mocks/adaptermocks/IThumbQueueAdapter.go @@ -3,7 +3,10 @@ package adaptermocks import ( + context "context" + entity "github.com/pangolin-do-golang/thumb-processor-api/internal/core/domain/entity" + mock "github.com/stretchr/testify/mock" ) @@ -12,17 +15,17 @@ type IThumbQueueAdapter struct { mock.Mock } -// SendEvent provides a mock function with given fields: process -func (_m *IThumbQueueAdapter) SendEvent(process *entity.ThumbProcess) error { - ret := _m.Called(process) +// SendEvent provides a mock function with given fields: ctx, process +func (_m *IThumbQueueAdapter) SendEvent(ctx context.Context, process *entity.ThumbProcess) error { + ret := _m.Called(ctx, process) if len(ret) == 0 { panic("no return value specified for SendEvent") } var r0 error - if rf, ok := ret.Get(0).(func(*entity.ThumbProcess) error); ok { - r0 = rf(process) + if rf, ok := ret.Get(0).(func(context.Context, *entity.ThumbProcess) error); ok { + r0 = rf(ctx, process) } else { r0 = ret.Error(0) } diff --git a/internal/mocks/adaptermocks/IThumbRepositoryAdapter.go b/internal/mocks/adaptermocks/IThumbRepositoryAdapter.go index 295cd6b..a1ebe0a 100644 --- a/internal/mocks/adaptermocks/IThumbRepositoryAdapter.go +++ b/internal/mocks/adaptermocks/IThumbRepositoryAdapter.go @@ -3,7 +3,10 @@ package adaptermocks import ( + context "context" + entity "github.com/pangolin-do-golang/thumb-processor-api/internal/core/domain/entity" + mock "github.com/stretchr/testify/mock" ) @@ -12,17 +15,17 @@ type IThumbRepositoryAdapter struct { mock.Mock } -// Create provides a mock function with given fields: process -func (_m *IThumbRepositoryAdapter) Create(process *entity.ThumbProcess) error { - ret := _m.Called(process) +// Create provides a mock function with given fields: ctx, process +func (_m *IThumbRepositoryAdapter) Create(ctx context.Context, process *entity.ThumbProcess) error { + ret := _m.Called(ctx, process) if len(ret) == 0 { panic("no return value specified for Create") } var r0 error - if rf, ok := ret.Get(0).(func(*entity.ThumbProcess) error); ok { - r0 = rf(process) + if rf, ok := ret.Get(0).(func(context.Context, *entity.ThumbProcess) error); ok { + r0 = rf(ctx, process) } else { r0 = ret.Error(0) } @@ -30,17 +33,17 @@ func (_m *IThumbRepositoryAdapter) Create(process *entity.ThumbProcess) error { return r0 } -// List provides a mock function with no fields -func (_m *IThumbRepositoryAdapter) List() *[]entity.ThumbProcess { - ret := _m.Called() +// List provides a mock function with given fields: ctx +func (_m *IThumbRepositoryAdapter) List(ctx context.Context) *[]entity.ThumbProcess { + ret := _m.Called(ctx) if len(ret) == 0 { panic("no return value specified for List") } var r0 *[]entity.ThumbProcess - if rf, ok := ret.Get(0).(func() *[]entity.ThumbProcess); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) *[]entity.ThumbProcess); ok { + r0 = rf(ctx) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*[]entity.ThumbProcess) @@ -50,9 +53,9 @@ func (_m *IThumbRepositoryAdapter) List() *[]entity.ThumbProcess { return r0 } -// Update provides a mock function with given fields: process -func (_m *IThumbRepositoryAdapter) Update(process *entity.ThumbProcess) (*entity.ThumbProcess, error) { - ret := _m.Called(process) +// Update provides a mock function with given fields: ctx, process +func (_m *IThumbRepositoryAdapter) Update(ctx context.Context, process *entity.ThumbProcess) (*entity.ThumbProcess, error) { + ret := _m.Called(ctx, process) if len(ret) == 0 { panic("no return value specified for Update") @@ -60,19 +63,19 @@ func (_m *IThumbRepositoryAdapter) Update(process *entity.ThumbProcess) (*entity var r0 *entity.ThumbProcess var r1 error - if rf, ok := ret.Get(0).(func(*entity.ThumbProcess) (*entity.ThumbProcess, error)); ok { - return rf(process) + if rf, ok := ret.Get(0).(func(context.Context, *entity.ThumbProcess) (*entity.ThumbProcess, error)); ok { + return rf(ctx, process) } - if rf, ok := ret.Get(0).(func(*entity.ThumbProcess) *entity.ThumbProcess); ok { - r0 = rf(process) + if rf, ok := ret.Get(0).(func(context.Context, *entity.ThumbProcess) *entity.ThumbProcess); ok { + r0 = rf(ctx, process) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*entity.ThumbProcess) } } - if rf, ok := ret.Get(1).(func(*entity.ThumbProcess) error); ok { - r1 = rf(process) + if rf, ok := ret.Get(1).(func(context.Context, *entity.ThumbProcess) error); ok { + r1 = rf(ctx, process) } else { r1 = ret.Error(1) } diff --git a/internal/mocks/servicemocks/IThumbService.go b/internal/mocks/servicemocks/IThumbService.go index 613a24b..1cff3e7 100644 --- a/internal/mocks/servicemocks/IThumbService.go +++ b/internal/mocks/servicemocks/IThumbService.go @@ -3,6 +3,8 @@ package servicemocks import ( + context "context" + entity "github.com/pangolin-do-golang/thumb-processor-api/internal/core/domain/entity" mock "github.com/stretchr/testify/mock" @@ -14,17 +16,17 @@ type IThumbService struct { mock.Mock } -// CreateProcessAsync provides a mock function with given fields: request -func (_m *IThumbService) CreateProcessAsync(request *ports.CreateProcessRequest) error { - ret := _m.Called(request) +// CreateProcessAsync provides a mock function with given fields: ctx, request +func (_m *IThumbService) CreateProcessAsync(ctx context.Context, request *ports.CreateProcessRequest) error { + ret := _m.Called(ctx, request) if len(ret) == 0 { panic("no return value specified for CreateProcessAsync") } var r0 error - if rf, ok := ret.Get(0).(func(*ports.CreateProcessRequest) error); ok { - r0 = rf(request) + if rf, ok := ret.Get(0).(func(context.Context, *ports.CreateProcessRequest) error); ok { + r0 = rf(ctx, request) } else { r0 = ret.Error(0) } @@ -32,17 +34,17 @@ func (_m *IThumbService) CreateProcessAsync(request *ports.CreateProcessRequest) return r0 } -// ListProcess provides a mock function with no fields -func (_m *IThumbService) ListProcess() *[]entity.ThumbProcess { - ret := _m.Called() +// ListProcess provides a mock function with given fields: ctx +func (_m *IThumbService) ListProcess(ctx context.Context) *[]entity.ThumbProcess { + ret := _m.Called(ctx) if len(ret) == 0 { panic("no return value specified for ListProcess") } var r0 *[]entity.ThumbProcess - if rf, ok := ret.Get(0).(func() *[]entity.ThumbProcess); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(context.Context) *[]entity.ThumbProcess); ok { + r0 = rf(ctx) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*[]entity.ThumbProcess) @@ -52,9 +54,9 @@ func (_m *IThumbService) ListProcess() *[]entity.ThumbProcess { return r0 } -// UpdateProcess provides a mock function with given fields: request -func (_m *IThumbService) UpdateProcess(request *ports.UpdateProcessRequest) (*entity.ThumbProcess, error) { - ret := _m.Called(request) +// UpdateProcess provides a mock function with given fields: ctx, request +func (_m *IThumbService) UpdateProcess(ctx context.Context, request *ports.UpdateProcessRequest) (*entity.ThumbProcess, error) { + ret := _m.Called(ctx, request) if len(ret) == 0 { panic("no return value specified for UpdateProcess") @@ -62,19 +64,19 @@ func (_m *IThumbService) UpdateProcess(request *ports.UpdateProcessRequest) (*en var r0 *entity.ThumbProcess var r1 error - if rf, ok := ret.Get(0).(func(*ports.UpdateProcessRequest) (*entity.ThumbProcess, error)); ok { - return rf(request) + if rf, ok := ret.Get(0).(func(context.Context, *ports.UpdateProcessRequest) (*entity.ThumbProcess, error)); ok { + return rf(ctx, request) } - if rf, ok := ret.Get(0).(func(*ports.UpdateProcessRequest) *entity.ThumbProcess); ok { - r0 = rf(request) + if rf, ok := ret.Get(0).(func(context.Context, *ports.UpdateProcessRequest) *entity.ThumbProcess); ok { + r0 = rf(ctx, request) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*entity.ThumbProcess) } } - if rf, ok := ret.Get(1).(func(*ports.UpdateProcessRequest) error); ok { - r1 = rf(request) + if rf, ok := ret.Get(1).(func(context.Context, *ports.UpdateProcessRequest) error); ok { + r1 = rf(ctx, request) } else { r1 = ret.Error(1) } From a02246ef4f1a1cb9789a3467fb81804130c25665 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=ADrio=20Neto?= Date: Mon, 10 Feb 2025 13:00:17 -0300 Subject: [PATCH 2/2] fix: config tests --- cmd/http/main.go | 5 +++++ internal/config/config.go | 5 ----- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/http/main.go b/cmd/http/main.go index af667ef..aa29e1f 100644 --- a/cmd/http/main.go +++ b/cmd/http/main.go @@ -3,6 +3,7 @@ package main import ( "log" + "github.com/joho/godotenv" _ "github.com/pangolin-do-golang/thumb-processor-api/docs" dbAdapter "github.com/pangolin-do-golang/thumb-processor-api/internal/adapters/db" "github.com/pangolin-do-golang/thumb-processor-api/internal/adapters/rest/server" @@ -20,6 +21,10 @@ import ( // @host localhost:8080 // @BasePath / func main() { + if err := godotenv.Load(); err != nil { + log.Fatalln(err) + } + cfg, err := config.Load() if err != nil { log.Fatalln(err) diff --git a/internal/config/config.go b/internal/config/config.go index 0cabe75..2da3429 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/caarlos0/env/v11" - "github.com/joho/godotenv" ) type S3 struct { @@ -27,10 +26,6 @@ type API struct { } func Load() (*Config, error) { - if err := godotenv.Load(); err != nil { - return nil, err - } - cfg := Config{} err := env.Parse(&cfg) return &cfg, err