auth services, middleware, and other stuff
All checks were successful
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/lint Pipeline was successful
ci/woodpecker/pr/test Pipeline was successful

This commit is contained in:
2025-05-22 13:55:43 -04:00
parent 70bb4e66b4
commit e55d419d44
22 changed files with 985 additions and 95 deletions

View File

@@ -0,0 +1,67 @@
package handler
import (
"context"
"encoding/json"
"log/slog"
"net/http"
"gitea.michaelthomson.dev/mthomson/habits/internal/user/service"
"github.com/gofrs/uuid/v5"
)
type UserRegisterer interface {
Register(ctx context.Context, email string, password string) (uuid.UUID, error)
}
type RegisterRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}
type RegisterResponse struct {
Id string `json:"id"`
}
func HandleRegisterUser(logger *slog.Logger, userService UserRegisterer) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
registerRequest := RegisterRequest{}
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields()
err := decoder.Decode(&registerRequest)
if err != nil {
logger.ErrorContext(ctx, err.Error())
http.Error(w, "", http.StatusBadRequest)
return
}
uuid, err := userService.Register(ctx, registerRequest.Email, registerRequest.Password)
if err != nil {
if err == service.ErrUserExists {
http.Error(w, "", http.StatusConflict)
return
}
logger.ErrorContext(ctx, err.Error())
http.Error(w, "", http.StatusInternalServerError)
return
}
response := RegisterResponse{uuid.String()}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(response)
if err != nil {
logger.ErrorContext(ctx, err.Error())
http.Error(w, "", http.StatusInternalServerError)
return
}
}
}

View File

@@ -0,0 +1,148 @@
package handler
import (
"bytes"
"context"
"encoding/json"
"log/slog"
"errors"
"net/http"
"net/http/httptest"
"testing"
"gitea.michaelthomson.dev/mthomson/habits/internal/user/service"
"github.com/gofrs/uuid/v5"
)
type MockUserRegisterer struct {
RegisterUserFunc func(ctx context.Context, email string, password string) (uuid.UUID, error)
}
func (m *MockUserRegisterer) Register(ctx context.Context, email string, password string) (uuid.UUID, error) {
return m.RegisterUserFunc(ctx, email, password)
}
func TestCreateUser(t *testing.T) {
logger := slog.Default()
t.Run("create user", func(t *testing.T) {
createUserRequest := RegisterRequest{Email: "test@test.com", Password: "password"}
newUUID := NewUUID(t)
service := MockUserRegisterer{
RegisterUserFunc: func(ctx context.Context, email string, password string) (uuid.UUID, error) {
return newUUID, nil
},
}
handler := HandleRegisterUser(logger, &service)
requestBody, err := json.Marshal(createUserRequest)
if err != nil {
t.Fatalf("Failed to marshal request %+v: %v", createUserRequest, err)
}
req := httptest.NewRequest(http.MethodPost, "/register", bytes.NewBuffer(requestBody))
res := httptest.NewRecorder()
handler(res, req)
AssertStatusCodes(t, res.Code, http.StatusOK)
})
t.Run("returns 409 when user exists", func(t *testing.T) {
createUserRequest := RegisterRequest{Email: "test@test.com", Password: "password"}
newUUID := NewUUID(t)
service := MockUserRegisterer{
RegisterUserFunc: func(ctx context.Context, email string, password string) (uuid.UUID, error) {
return newUUID, service.ErrUserExists
},
}
handler := HandleRegisterUser(logger, &service)
requestBody, err := json.Marshal(createUserRequest)
if err != nil {
t.Fatalf("Failed to marshal request %+v: %v", createUserRequest, err)
}
req := httptest.NewRequest(http.MethodPost, "/register", bytes.NewBuffer(requestBody))
res := httptest.NewRecorder()
handler(res, req)
AssertStatusCodes(t, res.Code, http.StatusConflict)
})
t.Run("returns 400 with bad json", func(t *testing.T) {
handler := HandleRegisterUser(logger, nil)
badStruct := struct {
Foo string
}{
Foo: "bar",
}
requestBody, err := json.Marshal(badStruct)
if err != nil {
t.Fatalf("Failed to marshal request %+v: %v", badStruct, err)
}
req := httptest.NewRequest(http.MethodPost, "/register", bytes.NewBuffer(requestBody))
res := httptest.NewRecorder()
handler(res, req)
AssertStatusCodes(t, res.Code, http.StatusBadRequest)
})
t.Run("returns 500 arbitrary errors", func(t *testing.T) {
createUserRequest := RegisterRequest{Email: "test@test.com", Password: "password"}
newUUID := NewUUID(t)
service := MockUserRegisterer{
RegisterUserFunc: func(ctx context.Context, email string, password string) (uuid.UUID, error) {
return newUUID, errors.New("foo bar")
},
}
handler := HandleRegisterUser(logger, &service)
requestBody, err := json.Marshal(createUserRequest)
if err != nil {
t.Fatalf("Failed to marshal request %+v: %v", createUserRequest, err)
}
req := httptest.NewRequest(http.MethodPost, "/user", bytes.NewBuffer(requestBody))
res := httptest.NewRecorder()
handler(res, req)
AssertStatusCodes(t, res.Code, http.StatusInternalServerError)
})
}
func AssertStatusCodes(t testing.TB, got, want int) {
t.Helper()
if got != want {
t.Errorf("got status code: %v, want status code: %v", want, got)
}
}
func NewUUID(t testing.TB) uuid.UUID {
t.Helper()
uuid, err := uuid.NewV4()
if err != nil {
t.Errorf("error generation uuid: %v", err)
}
return uuid
}