feature/core-service-api-structure #3
348
core-service/examples/api_test.go
Normal file
348
core-service/examples/api_test.go
Normal file
@@ -0,0 +1,348 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
183
core-service/examples/auth_test.go
Normal file
183
core-service/examples/auth_test.go
Normal file
@@ -0,0 +1,183 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"erp-mvp/core-service/internal/auth"
|
||||
)
|
||||
|
||||
// TestJWTService_GenerateToken тестирует генерацию JWT токенов
|
||||
func TestJWTService_GenerateToken(t *testing.T) {
|
||||
// Arrange
|
||||
secret := "test_secret_key"
|
||||
ttl := 24 * time.Hour
|
||||
jwtService := auth.NewJWTService(secret, ttl)
|
||||
userID := uuid.New()
|
||||
orgID := uuid.New()
|
||||
email := "test@example.com"
|
||||
role := "admin"
|
||||
|
||||
// Act
|
||||
token, err := jwtService.GenerateToken(userID, orgID, email, role)
|
||||
|
||||
// Assert
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, token)
|
||||
|
||||
// Проверяем, что токен можно декодировать
|
||||
parsedToken, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
assert.True(t, parsedToken.Valid)
|
||||
|
||||
// Проверяем claims
|
||||
claims, ok := parsedToken.Claims.(jwt.MapClaims)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, userID.String(), claims["user_id"])
|
||||
assert.Equal(t, orgID.String(), claims["organization_id"])
|
||||
assert.Equal(t, email, claims["email"])
|
||||
assert.Equal(t, role, claims["role"])
|
||||
}
|
||||
|
||||
// TestJWTService_ValidateToken тестирует валидацию JWT токенов
|
||||
func TestJWTService_ValidateToken(t *testing.T) {
|
||||
// Arrange
|
||||
secret := "test_secret_key"
|
||||
ttl := 24 * time.Hour
|
||||
jwtService := auth.NewJWTService(secret, ttl)
|
||||
|
||||
// Создаем валидный токен для тестирования
|
||||
userID := uuid.New()
|
||||
orgID := uuid.New()
|
||||
validToken, err := jwtService.GenerateToken(userID, orgID, "test@example.com", "admin")
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
secret string
|
||||
token string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "valid token",
|
||||
secret: secret,
|
||||
token: validToken,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "invalid signature",
|
||||
secret: "wrong_secret",
|
||||
token: validToken,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "invalid token format",
|
||||
secret: secret,
|
||||
token: "invalid_token_format",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Arrange
|
||||
testJWTService := auth.NewJWTService(tt.secret, ttl)
|
||||
|
||||
// Act
|
||||
claims, err := testJWTService.ValidateToken(tt.token)
|
||||
|
||||
// Assert
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, claims)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, claims)
|
||||
assert.Equal(t, userID, claims.UserID)
|
||||
assert.Equal(t, orgID, claims.OrganizationID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPasswordHashing тестирует хеширование и проверку паролей
|
||||
func TestPasswordHashing(t *testing.T) {
|
||||
// Arrange
|
||||
password := "mySecurePassword123"
|
||||
|
||||
// Act - хешируем пароль
|
||||
hashedPassword, err := auth.HashPassword(password)
|
||||
|
||||
// Assert
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, hashedPassword)
|
||||
assert.NotEqual(t, password, hashedPassword)
|
||||
|
||||
// Act - проверяем правильный пароль
|
||||
isValid := auth.CheckPassword(password, hashedPassword)
|
||||
|
||||
// Assert
|
||||
assert.True(t, isValid)
|
||||
|
||||
// Act - проверяем неправильный пароль
|
||||
isValid = auth.CheckPassword("wrongPassword", hashedPassword)
|
||||
|
||||
// Assert
|
||||
assert.False(t, isValid)
|
||||
}
|
||||
|
||||
// TestPasswordHashing_EmptyPassword тестирует обработку пустого пароля
|
||||
func TestPasswordHashing_EmptyPassword(t *testing.T) {
|
||||
// Arrange
|
||||
password := ""
|
||||
|
||||
// Act
|
||||
hashedPassword, err := auth.HashPassword(password)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, hashedPassword)
|
||||
|
||||
// Проверяем, что пустой пароль работает
|
||||
isValid := auth.CheckPassword(password, hashedPassword)
|
||||
assert.True(t, isValid)
|
||||
}
|
||||
|
||||
// BenchmarkPasswordHashing тестирует производительность хеширования
|
||||
func BenchmarkPasswordHashing(b *testing.B) {
|
||||
password := "benchmarkPassword123"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := auth.HashPassword(password)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkJWTGeneration тестирует производительность генерации JWT
|
||||
func BenchmarkJWTGeneration(b *testing.B) {
|
||||
secret := "benchmark_secret_key"
|
||||
ttl := 24 * time.Hour
|
||||
jwtService := auth.NewJWTService(secret, ttl)
|
||||
userID := uuid.New()
|
||||
orgID := uuid.New()
|
||||
email := "benchmark@example.com"
|
||||
role := "admin"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := jwtService.GenerateToken(userID, orgID, email, role)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
373
core-service/examples/repository_test.go
Normal file
373
core-service/examples/repository_test.go
Normal file
@@ -0,0 +1,373 @@
|
||||
package examples
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"erp-mvp/core-service/internal/models"
|
||||
"erp-mvp/core-service/internal/repository"
|
||||
)
|
||||
|
||||
// TestOrganizationRepository_Create тестирует создание организации
|
||||
func TestOrganizationRepository_Create(t *testing.T) {
|
||||
// Arrange
|
||||
db, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewOrganizationRepository(db)
|
||||
|
||||
orgID := uuid.New()
|
||||
org := &models.Organization{
|
||||
ID: orgID,
|
||||
Name: "Test Organization",
|
||||
Type: "workshop",
|
||||
Settings: models.JSON{
|
||||
"created_at": 1234567890,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Ожидаем SQL запрос
|
||||
mock.ExpectExec("INSERT INTO organizations").
|
||||
WithArgs(orgID, org.Name, org.Type, sqlmock.AnyArg(), sqlmock.AnyArg()).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
|
||||
// Act
|
||||
err = repo.Create(context.Background(), org)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// TestOrganizationRepository_GetByID тестирует получение организации по ID
|
||||
func TestOrganizationRepository_GetByID(t *testing.T) {
|
||||
// Arrange
|
||||
db, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewOrganizationRepository(db)
|
||||
|
||||
orgID := uuid.New()
|
||||
expectedOrg := &models.Organization{
|
||||
ID: orgID,
|
||||
Name: "Test Organization",
|
||||
Type: "workshop",
|
||||
Settings: models.JSON{
|
||||
"created_at": 1234567890,
|
||||
},
|
||||
}
|
||||
|
||||
// Ожидаем SQL запрос
|
||||
rows := sqlmock.NewRows([]string{"id", "name", "type", "settings", "created_at"}).
|
||||
AddRow(orgID, expectedOrg.Name, expectedOrg.Type, `{"created_at":1234567890}`, time.Now())
|
||||
|
||||
mock.ExpectQuery("SELECT (.+) FROM organizations").
|
||||
WithArgs(orgID).
|
||||
WillReturnRows(rows)
|
||||
|
||||
// Act
|
||||
org, err := repo.GetByID(context.Background(), orgID)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, org)
|
||||
assert.Equal(t, expectedOrg.ID, org.ID)
|
||||
assert.Equal(t, expectedOrg.Name, org.Name)
|
||||
assert.Equal(t, expectedOrg.Type, org.Type)
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// TestOrganizationRepository_GetByID_NotFound тестирует случай, когда организация не найдена
|
||||
func TestOrganizationRepository_GetByID_NotFound(t *testing.T) {
|
||||
// Arrange
|
||||
db, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewOrganizationRepository(db)
|
||||
orgID := uuid.New()
|
||||
|
||||
// Ожидаем SQL запрос с пустым результатом
|
||||
mock.ExpectQuery("SELECT (.+) FROM organizations").
|
||||
WithArgs(orgID).
|
||||
WillReturnError(sql.ErrNoRows)
|
||||
|
||||
// Act
|
||||
org, err := repo.GetByID(context.Background(), orgID)
|
||||
|
||||
// Assert
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, org)
|
||||
assert.Equal(t, "organization not found", err.Error())
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// TestUserRepository_Create тестирует создание пользователя
|
||||
func TestUserRepository_Create(t *testing.T) {
|
||||
// Arrange
|
||||
db, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewUserRepository(db)
|
||||
|
||||
userID := uuid.New()
|
||||
orgID := uuid.New()
|
||||
user := &models.User{
|
||||
ID: userID,
|
||||
OrganizationID: orgID,
|
||||
Email: "test@example.com",
|
||||
PasswordHash: "hashed_password",
|
||||
Role: "admin",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
password := "hashed_password"
|
||||
|
||||
// Ожидаем SQL запрос
|
||||
mock.ExpectExec("INSERT INTO users").
|
||||
WithArgs(userID, orgID, user.Email, password, user.Role, sqlmock.AnyArg()).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
|
||||
// Act
|
||||
err = repo.Create(context.Background(), user, password)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// TestUserRepository_GetByEmail тестирует получение пользователя по email
|
||||
func TestUserRepository_GetByEmail(t *testing.T) {
|
||||
// Arrange
|
||||
db, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewUserRepository(db)
|
||||
|
||||
userID := uuid.New()
|
||||
orgID := uuid.New()
|
||||
email := "test@example.com"
|
||||
expectedUser := &models.User{
|
||||
ID: userID,
|
||||
OrganizationID: orgID,
|
||||
Email: email,
|
||||
PasswordHash: "hashed_password",
|
||||
Role: "admin",
|
||||
}
|
||||
|
||||
// Ожидаем SQL запрос
|
||||
rows := sqlmock.NewRows([]string{"id", "organization_id", "email", "password_hash", "role", "created_at"}).
|
||||
AddRow(userID, orgID, email, "hashed_password", "admin", time.Now())
|
||||
|
||||
mock.ExpectQuery("SELECT (.+) FROM users").
|
||||
WithArgs(email).
|
||||
WillReturnRows(rows)
|
||||
|
||||
// Act
|
||||
user, err := repo.GetByEmail(context.Background(), email)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, user)
|
||||
assert.Equal(t, expectedUser.ID, user.ID)
|
||||
assert.Equal(t, expectedUser.Email, user.Email)
|
||||
assert.Equal(t, expectedUser.Role, user.Role)
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// TestLocationRepository_Create тестирует создание места хранения
|
||||
func TestLocationRepository_Create(t *testing.T) {
|
||||
// Arrange
|
||||
db, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewLocationRepository(db)
|
||||
|
||||
locationID := uuid.New()
|
||||
orgID := uuid.New()
|
||||
location := &models.StorageLocation{
|
||||
ID: locationID,
|
||||
OrganizationID: orgID,
|
||||
Name: "Test Location",
|
||||
Address: "Test Address",
|
||||
Type: "warehouse",
|
||||
Coordinates: models.JSON{
|
||||
"lat": 55.7558,
|
||||
"lng": 37.6176,
|
||||
},
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Ожидаем SQL запрос
|
||||
mock.ExpectExec("INSERT INTO storage_locations").
|
||||
WithArgs(locationID, orgID, sqlmock.AnyArg(), location.Name, location.Address, location.Type, sqlmock.AnyArg(), sqlmock.AnyArg()).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
|
||||
// Act
|
||||
err = repo.Create(context.Background(), location)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// TestLocationRepository_GetByID тестирует получение места хранения по ID
|
||||
func TestLocationRepository_GetByID(t *testing.T) {
|
||||
// Arrange
|
||||
db, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewLocationRepository(db)
|
||||
|
||||
locationID := uuid.New()
|
||||
orgID := uuid.New()
|
||||
expectedLocation := &models.StorageLocation{
|
||||
ID: locationID,
|
||||
OrganizationID: orgID,
|
||||
Name: "Test Location",
|
||||
Address: "Test Address",
|
||||
Type: "warehouse",
|
||||
Coordinates: models.JSON{
|
||||
"lat": 55.7558,
|
||||
"lng": 37.6176,
|
||||
},
|
||||
}
|
||||
|
||||
// Ожидаем SQL запрос
|
||||
rows := sqlmock.NewRows([]string{"id", "organization_id", "parent_id", "name", "address", "type", "coordinates", "created_at"}).
|
||||
AddRow(locationID, orgID, nil, expectedLocation.Name, expectedLocation.Address, expectedLocation.Type, `{"lat":55.7558,"lng":37.6176}`, time.Now())
|
||||
|
||||
mock.ExpectQuery("SELECT (.+) FROM storage_locations").
|
||||
WithArgs(locationID, orgID).
|
||||
WillReturnRows(rows)
|
||||
|
||||
// Act
|
||||
location, err := repo.GetByID(context.Background(), locationID, orgID)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, location)
|
||||
assert.Equal(t, expectedLocation.ID, location.ID)
|
||||
assert.Equal(t, expectedLocation.Name, location.Name)
|
||||
assert.Equal(t, expectedLocation.Type, location.Type)
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// TestItemRepository_Create тестирует создание товара
|
||||
func TestItemRepository_Create(t *testing.T) {
|
||||
// Arrange
|
||||
db, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewItemRepository(db)
|
||||
|
||||
itemID := uuid.New()
|
||||
orgID := uuid.New()
|
||||
item := &models.Item{
|
||||
ID: itemID,
|
||||
OrganizationID: orgID,
|
||||
Name: "Test Item",
|
||||
Description: "Test Description",
|
||||
Category: "Test Category",
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Ожидаем SQL запрос
|
||||
mock.ExpectExec("INSERT INTO items").
|
||||
WithArgs(itemID, orgID, item.Name, item.Description, item.Category, sqlmock.AnyArg()).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
|
||||
// Act
|
||||
err = repo.Create(context.Background(), item)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// TestOperationsRepository_PlaceItem тестирует размещение товара
|
||||
func TestOperationsRepository_PlaceItem(t *testing.T) {
|
||||
// Arrange
|
||||
db, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewOperationsRepository(db)
|
||||
|
||||
placementID := uuid.New()
|
||||
orgID := uuid.New()
|
||||
itemID := uuid.New()
|
||||
locationID := uuid.New()
|
||||
placement := &models.ItemPlacement{
|
||||
ID: placementID,
|
||||
OrganizationID: orgID,
|
||||
ItemID: itemID,
|
||||
LocationID: locationID,
|
||||
Quantity: 5,
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// Ожидаем SQL запрос
|
||||
mock.ExpectExec("INSERT INTO item_placements").
|
||||
WithArgs(placementID, orgID, itemID, locationID, placement.Quantity, sqlmock.AnyArg()).
|
||||
WillReturnResult(sqlmock.NewResult(1, 1))
|
||||
|
||||
// Act
|
||||
err = repo.PlaceItem(context.Background(), placement)
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
|
||||
// TestOperationsRepository_Search тестирует поиск товаров с местами размещения
|
||||
func TestOperationsRepository_Search(t *testing.T) {
|
||||
// Arrange
|
||||
db, mock, err := sqlmock.New()
|
||||
require.NoError(t, err)
|
||||
defer db.Close()
|
||||
|
||||
repo := repository.NewOperationsRepository(db)
|
||||
|
||||
orgID := uuid.New()
|
||||
itemID := uuid.New()
|
||||
locationID := uuid.New()
|
||||
|
||||
// Ожидаем SQL запрос с JOIN
|
||||
rows := sqlmock.NewRows([]string{
|
||||
"i.id", "i.organization_id", "i.name", "i.description", "i.category", "i.created_at",
|
||||
"sl.id", "sl.organization_id", "sl.parent_id", "sl.name", "sl.address", "sl.type", "sl.coordinates", "sl.created_at",
|
||||
"ip.quantity",
|
||||
}).AddRow(
|
||||
itemID, orgID, "Test Item", "Test Description", "Test Category", time.Now(),
|
||||
locationID, orgID, nil, "Test Location", "Test Address", "warehouse", `{"lat":55.7558,"lng":37.6176}`, time.Now(),
|
||||
5,
|
||||
)
|
||||
|
||||
mock.ExpectQuery("SELECT (.+) FROM items i").
|
||||
WithArgs(orgID, "%test%").
|
||||
WillReturnRows(rows)
|
||||
|
||||
// Act
|
||||
results, err := repo.Search(context.Background(), orgID, "test", "", "")
|
||||
|
||||
// Assert
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, results)
|
||||
assert.Len(t, results, 1)
|
||||
assert.NoError(t, mock.ExpectationsWereMet())
|
||||
}
|
||||
@@ -14,10 +14,12 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
@@ -30,6 +32,9 @@ require (
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
|
||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||
@@ -36,6 +38,7 @@ github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
@@ -60,6 +63,7 @@ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVs
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -69,6 +73,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
|
||||
Reference in New Issue
Block a user