diff --git a/core-service/internal/api/handlers/operations_test.go b/core-service/internal/api/handlers/operations_test.go new file mode 100644 index 0000000..8b8e436 --- /dev/null +++ b/core-service/internal/api/handlers/operations_test.go @@ -0,0 +1,319 @@ +package handlers_test + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "net/http/httptest" + "testing" + + "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/models" +) + +// MockOperationsService мок для OperationsService +type MockOperationsService struct { + mock.Mock +} + +func (m *MockOperationsService) PlaceItem(ctx context.Context, orgID uuid.UUID, req *models.PlaceItemRequest) (*models.ItemPlacement, error) { + args := m.Called(ctx, orgID, req) + return args.Get(0).(*models.ItemPlacement), args.Error(1) +} + +func (m *MockOperationsService) MoveItem(ctx context.Context, placementID uuid.UUID, newLocationID uuid.UUID, orgID uuid.UUID) error { + args := m.Called(ctx, placementID, newLocationID, orgID) + return args.Error(0) +} + +func (m *MockOperationsService) GetItemPlacements(ctx context.Context, itemID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) { + args := m.Called(ctx, itemID, orgID) + return args.Get(0).([]*models.ItemPlacement), args.Error(1) +} + +func (m *MockOperationsService) GetLocationPlacements(ctx context.Context, locationID uuid.UUID, orgID uuid.UUID) ([]*models.ItemPlacement, error) { + args := m.Called(ctx, locationID, orgID) + return args.Get(0).([]*models.ItemPlacement), args.Error(1) +} + +func (m *MockOperationsService) UpdateQuantity(ctx context.Context, placementID uuid.UUID, quantity int, orgID uuid.UUID) error { + args := m.Called(ctx, placementID, quantity, orgID) + return args.Error(0) +} + +func (m *MockOperationsService) DeletePlacement(ctx context.Context, placementID uuid.UUID, orgID uuid.UUID) error { + args := m.Called(ctx, placementID, orgID) + return args.Error(0) +} + +func (m *MockOperationsService) Search(ctx context.Context, orgID uuid.UUID, req *models.SearchRequest) (*models.SearchResponse, error) { + args := m.Called(ctx, orgID, req) + return args.Get(0).(*models.SearchResponse), args.Error(1) +} + + + +// TestNewOperationsHandler тестирует создание OperationsHandler +func TestNewOperationsHandler(t *testing.T) { + // Arrange + mockService := &MockOperationsService{} + + // Act + handler := handlers.NewOperationsHandler(mockService) + + // Assert + assert.NotNil(t, handler) +} + +// TestOperationsHandler_PlaceItem_Success тестирует успешное размещение товара +func TestOperationsHandler_PlaceItem_Success(t *testing.T) { + // Arrange + gin.SetMode(gin.TestMode) + + mockOperationsService := &MockOperationsService{} + handler := handlers.NewOperationsHandler(mockOperationsService) + + orgID := uuid.New() + itemID := uuid.New() + locationID := uuid.New() + placementID := uuid.New() + + placeReq := &models.PlaceItemRequest{ + ItemID: itemID, + LocationID: locationID, + Quantity: 10, + } + + expectedPlacement := &models.ItemPlacement{ + ID: placementID, + OrganizationID: orgID, + ItemID: itemID, + LocationID: locationID, + Quantity: 10, + } + + mockOperationsService.On("PlaceItem", mock.Anything, orgID, placeReq).Return(expectedPlacement, nil) + + router := gin.New() + router.POST("/operations/place", func(c *gin.Context) { + setClaims(c, orgID) + handler.PlaceItem(c) + }) + + reqBody, _ := json.Marshal(placeReq) + + // Act + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/operations/place", 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.ItemPlacement + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, itemID, response.ItemID) + assert.Equal(t, locationID, response.LocationID) + assert.Equal(t, 10, response.Quantity) + + mockOperationsService.AssertExpectations(t) +} + +// TestOperationsHandler_PlaceItem_ValidationError тестирует ошибку валидации при размещении товара +func TestOperationsHandler_PlaceItem_ValidationError(t *testing.T) { + // Arrange + gin.SetMode(gin.TestMode) + + mockOperationsService := &MockOperationsService{} + handler := handlers.NewOperationsHandler(mockOperationsService) + + orgID := uuid.New() + + // Невалидный запрос (отрицательное количество) + invalidReq := map[string]interface{}{ + "item_id": uuid.New().String(), + "location_id": uuid.New().String(), + "quantity": -5, // Отрицательное количество + } + + router := gin.New() + router.POST("/operations/place", func(c *gin.Context) { + setClaims(c, orgID) + handler.PlaceItem(c) + }) + + reqBody, _ := json.Marshal(invalidReq) + + // Act + w := httptest.NewRecorder() + req, _ := http.NewRequest("POST", "/operations/place", bytes.NewBuffer(reqBody)) + req.Header.Set("Content-Type", "application/json") + router.ServeHTTP(w, req) + + // Assert + assert.Equal(t, http.StatusBadRequest, w.Code) + + mockOperationsService.AssertNotCalled(t, "PlaceItem") +} + +// TestOperationsHandler_MoveItem_Success тестирует успешное перемещение товара +func TestOperationsHandler_MoveItem_Success(t *testing.T) { + // Arrange + gin.SetMode(gin.TestMode) + + mockOperationsService := &MockOperationsService{} + handler := handlers.NewOperationsHandler(mockOperationsService) + + orgID := uuid.New() + placementID := uuid.New() + newLocationID := uuid.New() + + moveReq := map[string]interface{}{ + "new_location_id": newLocationID.String(), + } + + mockOperationsService.On("MoveItem", mock.Anything, placementID, newLocationID, orgID).Return(nil) + + router := gin.New() + router.PUT("/operations/move/:id", func(c *gin.Context) { + setClaims(c, orgID) + handler.MoveItem(c) + }) + + reqBody, _ := json.Marshal(moveReq) + + // Act + w := httptest.NewRecorder() + req, _ := http.NewRequest("PUT", "/operations/move/"+placementID.String(), bytes.NewBuffer(reqBody)) + req.Header.Set("Content-Type", "application/json") + router.ServeHTTP(w, req) + + // Assert + assert.Equal(t, http.StatusOK, w.Code) + + var response map[string]string + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "Item moved successfully", response["message"]) + + mockOperationsService.AssertExpectations(t) +} + +// TestOperationsHandler_GetItemPlacements_Success тестирует успешное получение размещений товара +func TestOperationsHandler_GetItemPlacements_Success(t *testing.T) { + // Arrange + gin.SetMode(gin.TestMode) + + mockOperationsService := &MockOperationsService{} + handler := handlers.NewOperationsHandler(mockOperationsService) + + orgID := uuid.New() + itemID := uuid.New() + expectedPlacements := []*models.ItemPlacement{ + { + ID: uuid.New(), + OrganizationID: orgID, + ItemID: itemID, + LocationID: uuid.New(), + Quantity: 5, + }, + { + ID: uuid.New(), + OrganizationID: orgID, + ItemID: itemID, + LocationID: uuid.New(), + Quantity: 3, + }, + } + + mockOperationsService.On("GetItemPlacements", mock.Anything, itemID, orgID).Return(expectedPlacements, nil) + + router := gin.New() + router.GET("/operations/items/:item_id/placements", func(c *gin.Context) { + setClaims(c, orgID) + handler.GetItemPlacements(c) + }) + + // Act + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/operations/items/"+itemID.String()+"/placements", nil) + router.ServeHTTP(w, req) + + // Assert + assert.Equal(t, http.StatusOK, w.Code) + + var response []*models.ItemPlacement + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Len(t, response, 2) + assert.Equal(t, itemID, response[0].ItemID) + assert.Equal(t, itemID, response[1].ItemID) + + mockOperationsService.AssertExpectations(t) +} + +// TestOperationsHandler_Search_Success тестирует успешный поиск +func TestOperationsHandler_Search_Success(t *testing.T) { + // Arrange + gin.SetMode(gin.TestMode) + + mockOperationsService := &MockOperationsService{} + handler := handlers.NewOperationsHandler(mockOperationsService) + + orgID := uuid.New() + expectedResponse := &models.SearchResponse{ + Items: []*models.ItemWithLocation{ + { + Item: models.Item{ + ID: uuid.New(), + OrganizationID: orgID, + Name: "Test Item", + Description: "Test Description", + Category: "electronics", + }, + Location: models.StorageLocation{ + ID: uuid.New(), + OrganizationID: orgID, + Name: "Test Location", + Address: "Test Address", + Type: "warehouse", + }, + Quantity: 5, + }, + }, + TotalCount: 1, + } + + mockOperationsService.On("Search", mock.Anything, orgID, mock.AnythingOfType("*models.SearchRequest")).Return(expectedResponse, nil) + + router := gin.New() + router.GET("/operations/search", func(c *gin.Context) { + setClaims(c, orgID) + handler.Search(c) + }) + + // Act + w := httptest.NewRecorder() + req, _ := http.NewRequest("GET", "/operations/search?q=test", nil) + router.ServeHTTP(w, req) + + // Assert + assert.Equal(t, http.StatusOK, w.Code) + + var response models.SearchResponse + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Len(t, response.Items, 1) + assert.Equal(t, "Test Item", response.Items[0].Item.Name) + assert.Equal(t, 1, response.TotalCount) + + mockOperationsService.AssertExpectations(t) +}