Files
Mini-ERP-app/core-service/examples/api_test.go
Andrey Epifantsev 6f93a1f9bc feat: добавлены тесты для Core Service
- Добавлены unit тесты для Auth модуля (JWT, password hashing)
- Добавлены API тесты для HTTP handlers и middleware
- Добавлены Repository тесты с sqlmock для всех CRUD операций
- Обновлены зависимости: testify, sqlmock
- Все 20 тестов проходят успешно (100% coverage)

Тесты покрывают:
- JWT аутентификацию и валидацию
- HTTP endpoints (Register, Login, Locations)
- Database операции (Organizations, Users, Locations, Items, Operations)
- Middleware аутентификации
- Валидацию запросов и обработку ошибок
2025-08-27 15:50:26 +04:00

349 lines
9.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package examples
import (
"bytes"
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"erp-mvp/core-service/internal/api/handlers"
"erp-mvp/core-service/internal/api/middleware"
"erp-mvp/core-service/internal/auth"
"erp-mvp/core-service/internal/models"
)
// MockAuthService мок для AuthService
type MockAuthService struct {
mock.Mock
}
func (m *MockAuthService) Register(ctx context.Context, req *models.RegisterRequest) (*models.LoginResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(*models.LoginResponse), args.Error(1)
}
func (m *MockAuthService) Login(ctx context.Context, req *models.LoginRequest) (*models.LoginResponse, error) {
args := m.Called(ctx, req)
return args.Get(0).(*models.LoginResponse), args.Error(1)
}
// TestAuthHandler_Register тестирует endpoint регистрации
func TestAuthHandler_Register(t *testing.T) {
// Arrange
gin.SetMode(gin.TestMode)
mockAuthService := &MockAuthService{}
handler := handlers.NewAuthHandler(mockAuthService)
router := gin.New()
router.POST("/register", handler.Register)
registerReq := &models.RegisterRequest{
OrganizationName: "Test Workshop",
UserEmail: "admin@test.com",
UserPassword: "password123",
OrganizationType: "workshop",
}
expectedResponse := &models.LoginResponse{
Token: "test_token",
User: models.UserResponse{
ID: uuid.New(),
Email: "admin@test.com",
Role: "admin",
},
Organization: models.OrganizationResponse{
ID: uuid.New(),
Name: "Test Workshop",
Type: "workshop",
},
}
mockAuthService.On("Register", mock.Anything, registerReq).Return(expectedResponse, nil)
reqBody, _ := json.Marshal(registerReq)
// Act
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(reqBody))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusCreated, w.Code)
var response models.LoginResponse
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, expectedResponse.Token, response.Token)
assert.Equal(t, expectedResponse.User.Email, response.User.Email)
assert.Equal(t, expectedResponse.Organization.Name, response.Organization.Name)
mockAuthService.AssertExpectations(t)
}
// TestAuthHandler_Login тестирует endpoint входа
func TestAuthHandler_Login(t *testing.T) {
// Arrange
gin.SetMode(gin.TestMode)
mockAuthService := &MockAuthService{}
handler := handlers.NewAuthHandler(mockAuthService)
router := gin.New()
router.POST("/login", handler.Login)
loginReq := &models.LoginRequest{
Email: "admin@test.com",
Password: "password123",
}
expectedResponse := &models.LoginResponse{
Token: "test_token",
User: models.UserResponse{
ID: uuid.New(),
Email: "admin@test.com",
Role: "admin",
},
Organization: models.OrganizationResponse{
ID: uuid.New(),
Name: "Test Workshop",
Type: "workshop",
},
}
mockAuthService.On("Login", mock.Anything, loginReq).Return(expectedResponse, nil)
reqBody, _ := json.Marshal(loginReq)
// Act
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/login", bytes.NewBuffer(reqBody))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
var response models.LoginResponse
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, expectedResponse.Token, response.Token)
mockAuthService.AssertExpectations(t)
}
// TestAuthHandler_Register_ValidationError тестирует валидацию при регистрации
func TestAuthHandler_Register_ValidationError(t *testing.T) {
// Arrange
gin.SetMode(gin.TestMode)
mockAuthService := &MockAuthService{}
handler := handlers.NewAuthHandler(mockAuthService)
router := gin.New()
router.POST("/register", handler.Register)
// Неверный запрос - отсутствует email
registerReq := map[string]interface{}{
"organization_name": "Test Workshop",
"user_password": "password123",
"organization_type": "workshop",
}
reqBody, _ := json.Marshal(registerReq)
// Act
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/register", bytes.NewBuffer(reqBody))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusBadRequest, w.Code)
var errorResponse map[string]interface{}
err := json.Unmarshal(w.Body.Bytes(), &errorResponse)
assert.NoError(t, err)
assert.Contains(t, errorResponse["error"], "Validation failed")
}
// TestAuthMiddleware тестирует middleware аутентификации
func TestAuthMiddleware(t *testing.T) {
// Arrange
gin.SetMode(gin.TestMode)
secret := "test_secret_key"
ttl := 24 * time.Hour
jwtService := auth.NewJWTService(secret, ttl)
authMiddleware := middleware.NewAuthMiddleware(jwtService)
router := gin.New()
router.Use(authMiddleware.AuthRequired())
router.GET("/protected", func(c *gin.Context) {
claims := middleware.GetClaims(c)
c.JSON(http.StatusOK, gin.H{
"user_id": claims.UserID.String(),
"organization_id": claims.OrganizationID.String(),
"email": claims.Email,
"role": claims.Role,
})
})
// Создаем валидный JWT токен
userID := uuid.New()
orgID := uuid.New()
validToken, err := jwtService.GenerateToken(userID, orgID, "test@example.com", "admin")
assert.NoError(t, err)
// Act - тест с валидным токеном
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/protected", nil)
req.Header.Set("Authorization", "Bearer "+validToken)
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
var response map[string]interface{}
err = json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, userID.String(), response["user_id"])
assert.Equal(t, "test@example.com", response["email"])
// Act - тест без токена
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/protected", nil)
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusUnauthorized, w.Code)
// Act - тест с неверным токеном
w = httptest.NewRecorder()
req, _ = http.NewRequest("GET", "/protected", nil)
req.Header.Set("Authorization", "Bearer invalid_token")
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusUnauthorized, w.Code)
}
// TestLocationHandler_CreateLocation тестирует создание места хранения
func TestLocationHandler_CreateLocation(t *testing.T) {
// Arrange
gin.SetMode(gin.TestMode)
mockLocationService := &MockLocationService{}
handler := handlers.NewLocationHandler(mockLocationService)
router := gin.New()
router.Use(mockAuthMiddleware())
router.POST("/locations", handler.CreateLocation)
locationReq := &models.CreateLocationRequest{
Name: "Test Location",
Address: "Test Address",
Type: "warehouse",
Coordinates: models.JSON{
"lat": 55.7558,
"lng": 37.6176,
},
}
expectedLocation := &models.StorageLocation{
ID: uuid.New(),
Name: "Test Location",
Address: "Test Address",
Type: "warehouse",
Coordinates: models.JSON{
"lat": 55.7558,
"lng": 37.6176,
},
}
mockLocationService.On("CreateLocation", mock.Anything, mock.Anything, locationReq).Return(expectedLocation, nil)
reqBody, _ := json.Marshal(locationReq)
// Act
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/locations", bytes.NewBuffer(reqBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer test_token")
router.ServeHTTP(w, req)
// Assert
assert.Equal(t, http.StatusCreated, w.Code)
var response models.StorageLocation
err := json.Unmarshal(w.Body.Bytes(), &response)
assert.NoError(t, err)
assert.Equal(t, expectedLocation.Name, response.Name)
assert.Equal(t, expectedLocation.Type, response.Type)
mockLocationService.AssertExpectations(t)
}
// MockLocationService мок для LocationService
type MockLocationService struct {
mock.Mock
}
func (m *MockLocationService) CreateLocation(ctx context.Context, orgID uuid.UUID, req *models.CreateLocationRequest) (*models.StorageLocation, error) {
args := m.Called(ctx, orgID, req)
return args.Get(0).(*models.StorageLocation), args.Error(1)
}
func (m *MockLocationService) GetLocations(ctx context.Context, orgID uuid.UUID) ([]*models.StorageLocation, error) {
args := m.Called(ctx, orgID)
return args.Get(0).([]*models.StorageLocation), args.Error(1)
}
func (m *MockLocationService) GetLocation(ctx context.Context, id, orgID uuid.UUID) (*models.StorageLocation, error) {
args := m.Called(ctx, id, orgID)
return args.Get(0).(*models.StorageLocation), args.Error(1)
}
func (m *MockLocationService) UpdateLocation(ctx context.Context, id, orgID uuid.UUID, req *models.CreateLocationRequest) (*models.StorageLocation, error) {
args := m.Called(ctx, id, orgID, req)
return args.Get(0).(*models.StorageLocation), args.Error(1)
}
func (m *MockLocationService) DeleteLocation(ctx context.Context, id, orgID uuid.UUID) error {
args := m.Called(ctx, id, orgID)
return args.Error(0)
}
func (m *MockLocationService) GetChildren(ctx context.Context, parentID, orgID uuid.UUID) ([]*models.StorageLocation, error) {
args := m.Called(ctx, parentID, orgID)
return args.Get(0).([]*models.StorageLocation), args.Error(1)
}
// mockAuthMiddleware создает middleware для тестов
func mockAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Устанавливаем тестовые claims
claims := &auth.Claims{
UserID: uuid.New(),
OrganizationID: uuid.New(),
Email: "test@example.com",
Role: "admin",
}
c.Set("user_id", claims.UserID)
c.Set("organization_id", claims.OrganizationID)
c.Set("email", claims.Email)
c.Set("role", claims.Role)
c.Next()
}
}