diff --git a/internal/logging/logging.go b/internal/logging/logging.go index d6a0af9..1a0ff39 100644 --- a/internal/logging/logging.go +++ b/internal/logging/logging.go @@ -10,17 +10,17 @@ type ContextHandler struct { slog.Handler } -func (h *ContextHandler) Handle(ctx context.Context, r slog.Record) error { +func (h *ContextHandler) Handle(ctx context.Context, r slog.Record) error { if requestID, ok := ctx.Value("trace_id").(string); ok { - r.AddAttrs(slog.String("trace_id", requestID)) - } - return h.Handler.Handle(ctx, r) + r.AddAttrs(slog.String("trace_id", requestID)) + } + return h.Handler.Handle(ctx, r) } func NewLogger() *slog.Logger { - baseHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: false}) - customHandler := &ContextHandler{Handler: baseHandler} - logger := slog.New(customHandler) + baseHandler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{AddSource: false}) + customHandler := &ContextHandler{Handler: baseHandler} + logger := slog.New(customHandler) return logger } diff --git a/internal/todo/handler/create_test.go b/internal/todo/handler/create_test.go index 8706a79..7cf517a 100644 --- a/internal/todo/handler/create_test.go +++ b/internal/todo/handler/create_test.go @@ -2,7 +2,9 @@ package handler import ( "bytes" + "context" "encoding/json" + "log/slog" "errors" "net/http" @@ -14,26 +16,27 @@ import ( ) type MockTodoCreater struct { - CreateTodoFunc func(todo service.Todo) (service.Todo, error) + CreateTodoFunc func(cxt context.Context, todo service.Todo) (service.Todo, error) } -func (tg *MockTodoCreater) CreateTodo(todo service.Todo) (service.Todo, error) { - return tg.CreateTodoFunc(todo) +func (tg *MockTodoCreater) CreateTodo(ctx context.Context, todo service.Todo) (service.Todo, error) { + return tg.CreateTodoFunc(ctx, todo) } func TestCreateTodo(t *testing.T) { + logger := slog.Default() t.Run("create todo", func(t *testing.T) { createTodoRequest := CreateTodoRequest{Name: "clean dishes", Done: false} createdTodo := service.Todo{Id: 1, Name: "clean dishes", Done: false} want := CreateTodoResponse{Id: 1, Name: "clean dishes", Done: false} service := MockTodoCreater{ - CreateTodoFunc: func(todo service.Todo) (service.Todo, error) { + CreateTodoFunc: func(ctx context.Context, todo service.Todo) (service.Todo, error) { return createdTodo, nil }, } - handler := HandleTodoCreate(&service) + handler := HandleTodoCreate(logger, &service) requestBody, err := json.Marshal(createTodoRequest) @@ -67,7 +70,7 @@ func TestCreateTodo(t *testing.T) { }) t.Run("returns 400 with bad json", func(t *testing.T) { - handler := HandleTodoCreate(nil) + handler := HandleTodoCreate(logger, nil) badStruct := struct { Foo string @@ -95,12 +98,12 @@ func TestCreateTodo(t *testing.T) { createTodoRequest := CreateTodoRequest{Name: "clean dishes", Done: false} service := MockTodoCreater{ - CreateTodoFunc: func(todo service.Todo) (service.Todo, error) { + CreateTodoFunc: func(ctx context.Context, todo service.Todo) (service.Todo, error) { return service.Todo{}, errors.New("foo bar") }, } - handler := HandleTodoCreate(&service) + handler := HandleTodoCreate(logger, &service) requestBody, err := json.Marshal(createTodoRequest) diff --git a/internal/todo/handler/delete_test.go b/internal/todo/handler/delete_test.go index 2ee2ee1..fd282f0 100644 --- a/internal/todo/handler/delete_test.go +++ b/internal/todo/handler/delete_test.go @@ -1,7 +1,9 @@ package handler import ( + "context" "errors" + "log/slog" "net/http" "net/http/httptest" "testing" @@ -10,22 +12,23 @@ import ( ) type MockTodoDeleter struct { - DeleteTodoFunc func(id int64) error + DeleteTodoFunc func(ctx context.Context, id int64) error } -func (tg *MockTodoDeleter) DeleteTodo(id int64) error { - return tg.DeleteTodoFunc(id) +func (tg *MockTodoDeleter) DeleteTodo(ctx context.Context, id int64) error { + return tg.DeleteTodoFunc(ctx, id) } func TestDeleteTodo(t *testing.T) { + logger := slog.Default() t.Run("deletes existing todo", func(t *testing.T) { service := MockTodoDeleter{ - DeleteTodoFunc: func(id int64) error { + DeleteTodoFunc: func(ctx context.Context, id int64) error { return nil }, } - handler := HandleTodoDelete(&service) + handler := HandleTodoDelete(logger, &service) req := httptest.NewRequest(http.MethodDelete, "/todo/1", nil) res := httptest.NewRecorder() @@ -39,7 +42,7 @@ func TestDeleteTodo(t *testing.T) { }) t.Run("returns 400 with bad id", func(t *testing.T) { - handler := HandleTodoDelete(nil) + handler := HandleTodoDelete(logger, nil) req := httptest.NewRequest(http.MethodDelete, "/todo/hello", nil) res := httptest.NewRecorder() @@ -54,12 +57,12 @@ func TestDeleteTodo(t *testing.T) { t.Run("returns 404 for not found todo", func(t *testing.T) { service := MockTodoDeleter{ - DeleteTodoFunc: func(id int64) error { + DeleteTodoFunc: func(ctx context.Context, id int64) error { return service.ErrNotFound }, } - handler := HandleTodoDelete(&service) + handler := HandleTodoDelete(logger, &service) req := httptest.NewRequest(http.MethodDelete, "/todo/1", nil) res := httptest.NewRecorder() @@ -74,12 +77,12 @@ func TestDeleteTodo(t *testing.T) { t.Run("returns 500 for arbitrary errors", func(t *testing.T) { service := MockTodoDeleter{ - DeleteTodoFunc: func(id int64) error { + DeleteTodoFunc: func(ctx context.Context, id int64) error { return errors.New("foo bar") }, } - handler := HandleTodoDelete(&service) + handler := HandleTodoDelete(logger, &service) req := httptest.NewRequest(http.MethodDelete, "/todo/1", nil) res := httptest.NewRecorder() diff --git a/internal/todo/handler/get_test.go b/internal/todo/handler/get_test.go index 8e11ccb..f3980e3 100644 --- a/internal/todo/handler/get_test.go +++ b/internal/todo/handler/get_test.go @@ -1,9 +1,11 @@ package handler import ( + "context" "encoding/json" "errors" "fmt" + "log/slog" "net/http" "net/http/httptest" "reflect" @@ -13,25 +15,26 @@ import ( ) type MockTodoGetter struct { - GetTodoFunc func(id int64) (service.Todo, error) + GetTodoFunc func(ctx context.Context, id int64) (service.Todo, error) } -func (tg *MockTodoGetter) GetTodo(id int64) (service.Todo, error) { - return tg.GetTodoFunc(id) +func (tg *MockTodoGetter) GetTodo(ctx context.Context, id int64) (service.Todo, error) { + return tg.GetTodoFunc(ctx, id) } func TestGetTodo(t *testing.T) { + logger := slog.Default() t.Run("gets existing todo", func(t *testing.T) { todo := service.Todo{Id: 1, Name: "clean dishes", Done: false} wantedTodo := GetTodoResponse{Id: 1, Name: "clean dishes", Done: false} service := MockTodoGetter{ - GetTodoFunc: func(id int64) (service.Todo, error) { + GetTodoFunc: func(ctx context.Context, id int64) (service.Todo, error) { return todo, nil }, } - handler := HandleTodoGet(&service) + handler := HandleTodoGet(logger, &service) req := httptest.NewRequest(http.MethodGet, fmt.Sprintf("/todo/%d", todo.Id), nil) res := httptest.NewRecorder() @@ -56,7 +59,7 @@ func TestGetTodo(t *testing.T) { }) t.Run("returns 400 with bad id", func(t *testing.T) { - handler := HandleTodoGet(nil) + handler := HandleTodoGet(logger, nil) req := httptest.NewRequest(http.MethodGet, "/todo/hello", nil) res := httptest.NewRecorder() @@ -71,12 +74,12 @@ func TestGetTodo(t *testing.T) { t.Run("returns 404 for not found todo", func(t *testing.T) { service := MockTodoGetter{ - GetTodoFunc: func(id int64) (service.Todo, error) { + GetTodoFunc: func(ctx context.Context, id int64) (service.Todo, error) { return service.Todo{}, service.ErrNotFound }, } - handler := HandleTodoGet(&service) + handler := HandleTodoGet(logger, &service) req := httptest.NewRequest(http.MethodGet, "/todo/1", nil) res := httptest.NewRecorder() @@ -91,12 +94,12 @@ func TestGetTodo(t *testing.T) { t.Run("returns 500 for arbitrary errors", func(t *testing.T) { service := MockTodoGetter{ - GetTodoFunc: func(id int64) (service.Todo, error) { + GetTodoFunc: func(ctx context.Context, id int64) (service.Todo, error) { return service.Todo{}, errors.New("foo bar") }, } - handler := HandleTodoGet(&service) + handler := HandleTodoGet(logger, &service) req := httptest.NewRequest(http.MethodGet, "/todo/1", nil) res := httptest.NewRecorder() diff --git a/internal/todo/handler/update_test.go b/internal/todo/handler/update_test.go index b8db30b..b9e6e9d 100644 --- a/internal/todo/handler/update_test.go +++ b/internal/todo/handler/update_test.go @@ -2,7 +2,9 @@ package handler import ( "bytes" + "context" "encoding/json" + "log/slog" "errors" "net/http" @@ -13,24 +15,25 @@ import ( ) type MockTodoUpdater struct { - UpdateTodoFunc func(todo service.Todo) error + UpdateTodoFunc func(ctx context.Context, todo service.Todo) error } -func (tg *MockTodoUpdater) UpdateTodo(todo service.Todo) error { - return tg.UpdateTodoFunc(todo) +func (tg *MockTodoUpdater) UpdateTodo(ctx context.Context, todo service.Todo) error { + return tg.UpdateTodoFunc(ctx, todo) } func TestUpdateTodo(t *testing.T) { + logger := slog.Default() t.Run("update todo", func(t *testing.T) { updateTodoRequest := UpdateTodoRequest{Name: "clean dishes", Done: false} service := MockTodoUpdater{ - UpdateTodoFunc: func(todo service.Todo) error { + UpdateTodoFunc: func(ctx context.Context, todo service.Todo) error { return nil }, } - handler := HandleTodoUpdate(&service) + handler := HandleTodoUpdate(logger, &service) requestBody, err := json.Marshal(updateTodoRequest) @@ -50,7 +53,7 @@ func TestUpdateTodo(t *testing.T) { }) t.Run("returns 400 with bad json", func(t *testing.T) { - handler := HandleTodoUpdate(nil) + handler := HandleTodoUpdate(logger, nil) badStruct := struct { Foo string @@ -75,7 +78,7 @@ func TestUpdateTodo(t *testing.T) { }) t.Run("returns 400 with bad id", func(t *testing.T) { - handler := HandleTodoUpdate(nil) + handler := HandleTodoUpdate(logger, nil) req := httptest.NewRequest(http.MethodPut, "/todo/hello", nil) res := httptest.NewRecorder() @@ -92,12 +95,12 @@ func TestUpdateTodo(t *testing.T) { updateTodoRequest := UpdateTodoRequest{Name: "clean dishes", Done: false} service := MockTodoUpdater{ - UpdateTodoFunc: func(todo service.Todo) error { + UpdateTodoFunc: func(ctx context.Context, todo service.Todo) error { return errors.New("foo bar") }, } - handler := HandleTodoUpdate(&service) + handler := HandleTodoUpdate(logger, &service) requestBody, err := json.Marshal(updateTodoRequest) diff --git a/internal/todo/repository/postgres/postgres.go b/internal/todo/repository/postgres/postgres.go index 1cea36a..b80ff27 100644 --- a/internal/todo/repository/postgres/postgres.go +++ b/internal/todo/repository/postgres/postgres.go @@ -9,13 +9,13 @@ import ( type PostgresTodoRepository struct { logger *slog.Logger - db *sql.DB + db *sql.DB } func NewPostgresTodoRepository(logger *slog.Logger, db *sql.DB) *PostgresTodoRepository { return &PostgresTodoRepository{ logger: logger, - db: db, + db: db, } } diff --git a/internal/todo/repository/postgres/postgres_test.go b/internal/todo/repository/postgres/postgres_test.go index 67f9342..5d2a096 100644 --- a/internal/todo/repository/postgres/postgres_test.go +++ b/internal/todo/repository/postgres/postgres_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "errors" + "log/slog" "testing" "time" @@ -64,9 +65,10 @@ func (tdb *TestDatabase) TearDown() { } func TestCRUD(t *testing.T) { + logger := slog.Default() tdb := NewTestDatabase(t) defer tdb.TearDown() - r := NewPostgresTodoRepository(tdb.Db) + r := NewPostgresTodoRepository(logger, tdb.Db) t.Run("creates new todo", func(t *testing.T) { want := repository.TodoRow{Id: 1, Name: "clean dishes", Done: false} diff --git a/internal/todo/service/service.go b/internal/todo/service/service.go index 84baa20..4e5714e 100644 --- a/internal/todo/service/service.go +++ b/internal/todo/service/service.go @@ -43,13 +43,13 @@ type TodoRepository interface { type TodoService struct { logger *slog.Logger - repo TodoRepository + repo TodoRepository } func NewTodoService(logger *slog.Logger, todoRepo TodoRepository) *TodoService { return &TodoService{ logger: logger, - repo: todoRepo, + repo: todoRepo, } } @@ -88,7 +88,7 @@ func (s *TodoService) DeleteTodo(ctx context.Context, id int64) error { return ErrNotFound } - if (err != nil) { + if err != nil { s.logger.ErrorContext(ctx, err.Error()) } @@ -104,7 +104,7 @@ func (s *TodoService) UpdateTodo(ctx context.Context, todo Todo) error { return ErrNotFound } - if (err != nil) { + if err != nil { s.logger.ErrorContext(ctx, err.Error()) } diff --git a/internal/todo/service/service_test.go b/internal/todo/service/service_test.go index 6fb7422..c4c154c 100644 --- a/internal/todo/service/service_test.go +++ b/internal/todo/service/service_test.go @@ -1,6 +1,8 @@ package service_test import ( + "context" + "log/slog" "testing" "gitea.michaelthomson.dev/mthomson/habits/internal/todo/repository" @@ -9,74 +11,83 @@ import ( ) func TestCreateTodo(t *testing.T) { + ctx := context.Background() + logger := slog.Default() + todoRepository := inmemory.NewInMemoryTodoRepository() - todoService := service.NewTodoService(&todoRepository) + todoService := service.NewTodoService(logger, &todoRepository) t.Run("Create todo", func(t *testing.T) { todo := service.NewTodo("clean dishes", false) - _, err := todoService.CreateTodo(todo) + _, err := todoService.CreateTodo(ctx, todo) AssertNoError(t, err) }) } func TestGetTodo(t *testing.T) { + ctx := context.Background() + logger := slog.Default() todoRepository := inmemory.NewInMemoryTodoRepository() todoRepository.Db[1] = repository.TodoRow{Id: 1, Name: "clean dishes", Done: false} - todoService := service.NewTodoService(&todoRepository) + todoService := service.NewTodoService(logger, &todoRepository) t.Run("Get exisiting todo", func(t *testing.T) { - _, err := todoService.GetTodo(1) + _, err := todoService.GetTodo(ctx, 1) AssertNoError(t, err) }) t.Run("Get non-existant todo", func(t *testing.T) { - _, err := todoService.GetTodo(2) + _, err := todoService.GetTodo(ctx, 2) AssertErrors(t, err, service.ErrNotFound) }) } func TestDeleteTodo(t *testing.T) { + ctx := context.Background() + logger := slog.Default() todoRepository := inmemory.NewInMemoryTodoRepository() todoRepository.Db[1] = repository.TodoRow{Id: 1, Name: "clean dishes", Done: false} - todoService := service.NewTodoService(&todoRepository) + todoService := service.NewTodoService(logger, &todoRepository) t.Run("Delete exisiting todo", func(t *testing.T) { - err := todoService.DeleteTodo(1) + err := todoService.DeleteTodo(ctx, 1) AssertNoError(t, err) }) t.Run("Delete non-existant todo", func(t *testing.T) { - err := todoService.DeleteTodo(1) + err := todoService.DeleteTodo(ctx, 1) AssertErrors(t, err, service.ErrNotFound) }) } func TestUpdateTodo(t *testing.T) { + ctx := context.Background() + logger := slog.Default() todoRepository := inmemory.NewInMemoryTodoRepository() todoRepository.Db[1] = repository.TodoRow{Id: 1, Name: "clean dishes", Done: false} - todoService := service.NewTodoService(&todoRepository) + todoService := service.NewTodoService(logger, &todoRepository) t.Run("Update exisiting todo", func(t *testing.T) { todo := service.Todo{1, "clean dishes", true} - err := todoService.UpdateTodo(todo) + err := todoService.UpdateTodo(ctx, todo) AssertNoError(t, err) - newTodo, err := todoService.GetTodo(1) + newTodo, err := todoService.GetTodo(ctx, 1) AssertNoError(t, err) @@ -86,7 +97,7 @@ func TestUpdateTodo(t *testing.T) { t.Run("Update non-existant todo", func(t *testing.T) { todo := service.Todo{2, "clean dishes", true} - err := todoService.UpdateTodo(todo) + err := todoService.UpdateTodo(ctx, todo) AssertErrors(t, err, service.ErrNotFound) })