Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions internal/application/usecase/category_usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ type UpdateCategory struct {
CategoryID uint `json:"category_id" validate:"required"`
Name string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
Description string `json:"description,omitempty" validate:"max=1000"`
ParentID *uint `json:"parent_id,omitempty"` // Optional parent category ID
ParentID *uint `json:"parent_id,omitempty"` // Optional parent category ID (0 means remove parent)
}

// UpdateCategory updates an existing category
Expand All @@ -92,14 +92,24 @@ func (uc *CategoryUseCase) UpdateCategory(input UpdateCategory) (*entity.Categor
return nil, fmt.Errorf("failed to get category: %w", err)
}

// Validate parent category exists if parentID is provided
// Handle ParentID logic: if ParentID is provided and is 0, set it to nil (remove parent)
var actualParentID *uint
if input.ParentID != nil {
if *input.ParentID == 0 {
actualParentID = nil
} else {
actualParentID = input.ParentID
}
}

// Validate parent category exists if parentID is provided and not 0
if actualParentID != nil {
// Check for circular reference (category cannot be its own parent)
if *input.ParentID == input.CategoryID {
if *actualParentID == input.CategoryID {
return nil, errors.New("category cannot be its own parent")
}

parent, err := uc.categoryRepo.GetByID(*input.ParentID)
parent, err := uc.categoryRepo.GetByID(*actualParentID)
if err != nil {
return nil, fmt.Errorf("parent category not found: %w", err)
}
Expand All @@ -116,7 +126,7 @@ func (uc *CategoryUseCase) UpdateCategory(input UpdateCategory) (*entity.Categor
category.Description = input.Description
}
if input.ParentID != nil {
category.ParentID = input.ParentID
category.ParentID = actualParentID
}

// Save updated category
Expand All @@ -134,7 +144,18 @@ func (uc *CategoryUseCase) UpdateCategory(input UpdateCategory) (*entity.Categor
return nil, fmt.Errorf("failed to update category: %w", err)
}

return category, nil
// Clear associations if ParentID was set to nil
if input.ParentID != nil && actualParentID == nil {
category.Parent = nil
}

// Refetch the category to get the updated data with proper associations
updatedCategory, err := uc.categoryRepo.GetByID(category.ID)
if err != nil {
return nil, fmt.Errorf("failed to fetch updated category: %w", err)
}

return updatedCategory, nil
}

// DeleteCategory deletes a category by ID
Expand Down
45 changes: 45 additions & 0 deletions internal/application/usecase/category_usecase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,48 @@ func TestCategoryUseCase_DeleteCategory_WithProducts(t *testing.T) {
assert.Equal(t, "cannot delete category with child categories", err.Error())
})
}

func TestCategoryUseCase_UpdateCategory_ParentIDZero(t *testing.T) {
t.Run("should remove parent when parent_id is 0", func(t *testing.T) {
// Setup
db := testutil.SetupTestDB(t)
defer testutil.CleanupTestDB(t, db)

categoryRepo := gorm.NewCategoryRepository(db)
productRepo := gorm.NewProductRepository(db)
categoryUseCase := NewCategoryUseCase(categoryRepo, productRepo)

// Create parent category
parentCategory, err := categoryUseCase.CreateCategory(CreateCategory{
Name: "Parent Category",
Description: "Parent category for test",
ParentID: nil,
})
require.NoError(t, err)

// Create child category with parent
childCategory, err := categoryUseCase.CreateCategory(CreateCategory{
Name: "Child Category",
Description: "Child category for test",
ParentID: &parentCategory.ID,
})
require.NoError(t, err)

// Verify initial state
assert.NotNil(t, childCategory.ParentID)
assert.Equal(t, parentCategory.ID, *childCategory.ParentID)

// Update child to remove parent using parent_id: 0
zeroID := uint(0)
_, err = categoryUseCase.UpdateCategory(UpdateCategory{
CategoryID: childCategory.ID,
ParentID: &zeroID,
})
require.NoError(t, err)

// Verify in database directly (this is the most reliable test)
fetchedCategory, err := categoryRepo.GetByID(childCategory.ID)
require.NoError(t, err)
assert.Nil(t, fetchedCategory.ParentID, "ParentID should be nil after setting to 0")
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func (c *CategoryRepository) Create(category *entity.Category) error {
func (c *CategoryRepository) Delete(categoryID uint) error {
// Note: This will fail if there are products in this category due to RESTRICT constraint
// which is the intended behavior for data integrity
return c.db.Delete(&entity.Category{}, categoryID).Error
return c.db.Unscoped().Delete(&entity.Category{}, categoryID).Error
}

// GetByID implements repository.CategoryRepository.
Expand Down Expand Up @@ -61,7 +61,8 @@ func (c *CategoryRepository) List() ([]*entity.Category, error) {

// Update implements repository.CategoryRepository.
func (c *CategoryRepository) Update(category *entity.Category) error {
return c.db.Save(category).Error
// Use explicit field updates to ensure parent_id is properly updated when nil
return c.db.Model(category).Select("name", "description", "parent_id").Updates(category).Error
}

// NewCategoryRepository creates a new GORM-based CategoryRepository
Expand Down
150 changes: 150 additions & 0 deletions internal/infrastructure/repository/gorm/category_repository_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package gorm

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/zenfulcode/commercify/internal/domain/entity"
"github.com/zenfulcode/commercify/testutil"
)

func TestCategoryRepository_UpdateParentID(t *testing.T) {
// Setup
db := testutil.SetupTestDB(t)
defer testutil.CleanupTestDB(t, db)

repo := NewCategoryRepository(db)

t.Run("should update ParentID from nil to valid ID", func(t *testing.T) {
// Create parent category
parentCategory, err := entity.NewCategory("Parent Category", "Parent description", nil)
require.NoError(t, err)
err = repo.Create(parentCategory)
require.NoError(t, err)

// Create child category without parent
childCategory, err := entity.NewCategory("Child Category", "Child description", nil)
require.NoError(t, err)
err = repo.Create(childCategory)
require.NoError(t, err)

// Verify initial state
assert.Nil(t, childCategory.ParentID)

// Update child to have parent
childCategory.ParentID = &parentCategory.ID
err = repo.Update(childCategory)
require.NoError(t, err)

// Fetch from database to verify
updated, err := repo.GetByID(childCategory.ID)
require.NoError(t, err)
assert.NotNil(t, updated.ParentID)
assert.Equal(t, parentCategory.ID, *updated.ParentID)
})

t.Run("should update ParentID from valid ID to nil using 0", func(t *testing.T) {
// Create parent category
parentCategory, err := entity.NewCategory("Parent Category 2", "Parent description", nil)
require.NoError(t, err)
err = repo.Create(parentCategory)
require.NoError(t, err)

// Create child category with parent
childCategory, err := entity.NewCategory("Child Category 2", "Child description", &parentCategory.ID)
require.NoError(t, err)
err = repo.Create(childCategory)
require.NoError(t, err)

// Verify initial state
assert.NotNil(t, childCategory.ParentID)
assert.Equal(t, parentCategory.ID, *childCategory.ParentID)

// Update child to remove parent (simulate sending parent_id: 0 from API)
childCategory.ParentID = nil
err = repo.Update(childCategory)
require.NoError(t, err)

// Fetch from database to verify
updated, err := repo.GetByID(childCategory.ID)
require.NoError(t, err)
assert.Nil(t, updated.ParentID)
})

t.Run("should update ParentID from one valid ID to another", func(t *testing.T) {
// Create parent categories
parentCategory1, err := entity.NewCategory("Parent Category 3", "Parent description", nil)
require.NoError(t, err)
err = repo.Create(parentCategory1)
require.NoError(t, err)

parentCategory2, err := entity.NewCategory("Parent Category 4", "Parent description", nil)
require.NoError(t, err)
err = repo.Create(parentCategory2)
require.NoError(t, err)

// Create child category with first parent
childCategory, err := entity.NewCategory("Child Category 3", "Child description", &parentCategory1.ID)
require.NoError(t, err)
err = repo.Create(childCategory)
require.NoError(t, err)

// Verify initial state
assert.NotNil(t, childCategory.ParentID)
assert.Equal(t, parentCategory1.ID, *childCategory.ParentID)

// Update child to have second parent
childCategory.ParentID = &parentCategory2.ID
err = repo.Update(childCategory)
require.NoError(t, err)

// Fetch from database to verify
updated, err := repo.GetByID(childCategory.ID)
require.NoError(t, err)
assert.NotNil(t, updated.ParentID)
assert.Equal(t, parentCategory2.ID, *updated.ParentID)
})
}

func TestCategoryRepository_UpdateParentIDToNil(t *testing.T) {
// Setup
db := testutil.SetupTestDB(t)
defer testutil.CleanupTestDB(t, db)

repo := NewCategoryRepository(db)

t.Run("should update ParentID to nil when explicitly set", func(t *testing.T) {
// Create parent category
parentCategory, err := entity.NewCategory("Parent Category", "Parent description", nil)
require.NoError(t, err)
err = repo.Create(parentCategory)
require.NoError(t, err)

// Create child category with parent
childCategory, err := entity.NewCategory("Child Category", "Child description", &parentCategory.ID)
require.NoError(t, err)
err = repo.Create(childCategory)
require.NoError(t, err)

// Verify initial state
initial, err := repo.GetByID(childCategory.ID)
require.NoError(t, err)
assert.NotNil(t, initial.ParentID)
assert.Equal(t, parentCategory.ID, *initial.ParentID)

// Update child to remove parent by setting ParentID to nil
childCategory.ParentID = nil
t.Logf("Before update: childCategory.ParentID = %v", childCategory.ParentID)

err = repo.Update(childCategory)
require.NoError(t, err)

// Verify the update worked by fetching from database
updated, err := repo.GetByID(childCategory.ID)
require.NoError(t, err)
t.Logf("After update: updated.ParentID = %v", updated.ParentID)
assert.Nil(t, updated.ParentID, "ParentID should be nil after update")
})
}