Restructured project
- backend moved to backend directory - added and initialized frontend with vue - moved infrastructure files to infra directory
This commit is contained in:
@@ -0,0 +1,345 @@
|
||||
package routers
|
||||
|
||||
import (
|
||||
"FamilyHub/src/api/services"
|
||||
"FamilyHub/src/domain"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type userServiceMock struct {
|
||||
createFn func(ctx context.Context, req domain.CreateUserRequest) (*domain.UserModel, error)
|
||||
getByIDFn func(ctx context.Context, id int64) (*domain.UserModel, error)
|
||||
getByTelegramIDFn func(ctx context.Context, telegramID int64) (*domain.UserModel, error)
|
||||
updateFn func(ctx context.Context, id int64, req domain.UpdateUserRequest) (*domain.UserModel, error)
|
||||
deleteFn func(ctx context.Context, id int64) error
|
||||
}
|
||||
|
||||
func (m *userServiceMock) Create(ctx context.Context, req domain.CreateUserRequest) (*domain.UserModel, error) {
|
||||
if m.createFn != nil {
|
||||
return m.createFn(ctx, req)
|
||||
}
|
||||
return nil, errors.New("mock create is not configured")
|
||||
}
|
||||
|
||||
func (m *userServiceMock) GetByID(ctx context.Context, id int64) (*domain.UserModel, error) {
|
||||
if m.getByIDFn != nil {
|
||||
return m.getByIDFn(ctx, id)
|
||||
}
|
||||
return nil, errors.New("mock getByID is not configured")
|
||||
}
|
||||
|
||||
func (m *userServiceMock) GetByTelegramID(ctx context.Context, telegramID int64) (*domain.UserModel, error) {
|
||||
if m.getByTelegramIDFn != nil {
|
||||
return m.getByTelegramIDFn(ctx, telegramID)
|
||||
}
|
||||
return nil, errors.New("mock getByTelegramID is not configured")
|
||||
}
|
||||
|
||||
func (m *userServiceMock) Update(ctx context.Context, id int64, req domain.UpdateUserRequest) (*domain.UserModel, error) {
|
||||
if m.updateFn != nil {
|
||||
return m.updateFn(ctx, id, req)
|
||||
}
|
||||
return nil, errors.New("mock update is not configured")
|
||||
}
|
||||
|
||||
func (m *userServiceMock) Delete(ctx context.Context, id int64) error {
|
||||
if m.deleteFn != nil {
|
||||
return m.deleteFn(ctx, id)
|
||||
}
|
||||
return errors.New("mock delete is not configured")
|
||||
}
|
||||
|
||||
func setupUsersRouter(mock services.UserService) *gin.Engine {
|
||||
gin.SetMode(gin.TestMode)
|
||||
r := gin.New()
|
||||
apiV1 := r.Group("/api/v1")
|
||||
router := NewUsersRouter(mock)
|
||||
router.RegisterRoutes(apiV1)
|
||||
return r
|
||||
}
|
||||
|
||||
func sampleUser() *domain.UserModel {
|
||||
username := "john"
|
||||
firstName := "John"
|
||||
lastName := "Doe"
|
||||
languageCode := "en"
|
||||
|
||||
return &domain.UserModel{
|
||||
ID: 10,
|
||||
TelegramID: 100500,
|
||||
Username: &username,
|
||||
FirstName: &firstName,
|
||||
LastName: &lastName,
|
||||
LanguageCode: &languageCode,
|
||||
CreatedAt: time.Date(2026, time.January, 21, 10, 11, 12, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2026, time.January, 21, 12, 11, 12, 0, time.UTC),
|
||||
}
|
||||
}
|
||||
|
||||
func TestUsersRouter_CreateUser(t *testing.T) {
|
||||
t.Run("bad request on malformed body", func(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBufferString(`{"telegram_id":`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "error")
|
||||
})
|
||||
|
||||
t.Run("bad request on domain validation error", func(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{createFn: func(ctx context.Context, req domain.CreateUserRequest) (*domain.UserModel, error) {
|
||||
return nil, services.ErrTelegramIDMissing
|
||||
}})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBufferString(`{"telegram_id":1,"first_name":"A"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, w.Body.String(), services.ErrTelegramIDMissing.Error())
|
||||
})
|
||||
|
||||
t.Run("created", func(t *testing.T) {
|
||||
expected := sampleUser()
|
||||
r := setupUsersRouter(&userServiceMock{createFn: func(ctx context.Context, req domain.CreateUserRequest) (*domain.UserModel, error) {
|
||||
assert.Equal(t, int64(100500), req.TelegramID)
|
||||
require.NotNil(t, req.FirstName)
|
||||
assert.Equal(t, "John", *req.FirstName)
|
||||
return expected, nil
|
||||
}})
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBufferString(`{"telegram_id":100500,"first_name":"John"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusCreated, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "100500")
|
||||
assert.Contains(t, w.Body.String(), "John")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUsersRouter_GetByID(t *testing.T) {
|
||||
t.Run("bad request on invalid id", func(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/abc", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "invalid id")
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{getByIDFn: func(ctx context.Context, id int64) (*domain.UserModel, error) {
|
||||
return nil, services.ErrUserNotFound
|
||||
}})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/1", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.Contains(t, w.Body.String(), services.ErrUserNotFound.Error())
|
||||
})
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
expected := sampleUser()
|
||||
r := setupUsersRouter(&userServiceMock{getByIDFn: func(ctx context.Context, id int64) (*domain.UserModel, error) {
|
||||
assert.Equal(t, int64(10), id)
|
||||
return expected, nil
|
||||
}})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/10", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "100500")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUsersRouter_GetByTelegramID(t *testing.T) {
|
||||
t.Run("bad request on invalid telegram id", func(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/by-telegram/abc", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "invalid telegram id")
|
||||
})
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
expected := sampleUser()
|
||||
r := setupUsersRouter(&userServiceMock{getByTelegramIDFn: func(ctx context.Context, telegramID int64) (*domain.UserModel, error) {
|
||||
assert.Equal(t, int64(100500), telegramID)
|
||||
return expected, nil
|
||||
}})
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/by-telegram/100500", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "John")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUsersRouter_Update(t *testing.T) {
|
||||
t.Run("bad request on invalid id", func(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{})
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/v1/users/abc", bytes.NewBufferString(`{"first_name":"John"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "invalid id")
|
||||
})
|
||||
|
||||
t.Run("bad request on malformed body", func(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{})
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/v1/users/10", bytes.NewBufferString(`{"first_name":`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "error")
|
||||
})
|
||||
|
||||
t.Run("bad request on invalid patch", func(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{updateFn: func(ctx context.Context, id int64, req domain.UpdateUserRequest) (*domain.UserModel, error) {
|
||||
return nil, services.ErrInvalidPatch
|
||||
}})
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/v1/users/10", bytes.NewBufferString(`{"first_name":"John"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, w.Body.String(), services.ErrInvalidPatch.Error())
|
||||
})
|
||||
|
||||
t.Run("ok", func(t *testing.T) {
|
||||
expected := sampleUser()
|
||||
r := setupUsersRouter(&userServiceMock{updateFn: func(ctx context.Context, id int64, req domain.UpdateUserRequest) (*domain.UserModel, error) {
|
||||
assert.Equal(t, int64(10), id)
|
||||
require.NotNil(t, req.FirstName)
|
||||
assert.Equal(t, "John", *req.FirstName)
|
||||
return expected, nil
|
||||
}})
|
||||
req := httptest.NewRequest(http.MethodPatch, "/api/v1/users/10", bytes.NewBufferString(`{"first_name":"John"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "100500")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUsersRouter_Delete(t *testing.T) {
|
||||
t.Run("bad request on invalid id", func(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{})
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/users/abc", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusBadRequest, w.Code)
|
||||
assert.Contains(t, w.Body.String(), "invalid id")
|
||||
})
|
||||
|
||||
t.Run("not found", func(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{deleteFn: func(ctx context.Context, id int64) error {
|
||||
return services.ErrUserNotFound
|
||||
}})
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/users/1", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusNotFound, w.Code)
|
||||
assert.Contains(t, w.Body.String(), services.ErrUserNotFound.Error())
|
||||
})
|
||||
|
||||
t.Run("no content", func(t *testing.T) {
|
||||
called := false
|
||||
r := setupUsersRouter(&userServiceMock{deleteFn: func(ctx context.Context, id int64) error {
|
||||
called = true
|
||||
assert.Equal(t, int64(10), id)
|
||||
return nil
|
||||
}})
|
||||
req := httptest.NewRequest(http.MethodDelete, "/api/v1/users/10", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusNoContent, w.Code)
|
||||
assert.True(t, called)
|
||||
assert.Empty(t, strings.TrimSpace(w.Body.String()))
|
||||
})
|
||||
}
|
||||
|
||||
func TestUsersRouter_CreateUser_ResponseShape(t *testing.T) {
|
||||
expected := sampleUser()
|
||||
r := setupUsersRouter(&userServiceMock{createFn: func(ctx context.Context, req domain.CreateUserRequest) (*domain.UserModel, error) {
|
||||
return expected, nil
|
||||
}})
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/users", bytes.NewBufferString(`{"telegram_id":100500,"first_name":"John"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusCreated, w.Code)
|
||||
var resp domain.UserResponse
|
||||
err := json.Unmarshal(w.Body.Bytes(), &resp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected.ID, resp.ID)
|
||||
assert.Equal(t, expected.TelegramID, resp.TelegramID)
|
||||
assert.Equal(t, expected.FirstName, resp.FirstName)
|
||||
assert.Equal(t, expected.CreatedAt.Format(time.RFC3339), resp.CreatedAt)
|
||||
assert.Equal(t, expected.UpdatedAt.Format(time.RFC3339), resp.UpdatedAt)
|
||||
}
|
||||
|
||||
func TestUsersRouter_GetByID_UsesPathID(t *testing.T) {
|
||||
r := setupUsersRouter(&userServiceMock{getByIDFn: func(ctx context.Context, id int64) (*domain.UserModel, error) {
|
||||
assert.Equal(t, int64(42), id)
|
||||
u := sampleUser()
|
||||
u.ID = id
|
||||
return u, nil
|
||||
}})
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/v1/users/42", nil)
|
||||
w := httptest.NewRecorder()
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Contains(t, w.Body.String(), strconv.FormatInt(42, 10))
|
||||
}
|
||||
Reference in New Issue
Block a user