Skip to content

Commit cf0405a

Browse files
authored
Merge pull request #52 from Zenfulcode/development
feat: Enhance UpdateCategory to handle ParentID logic
2 parents 36e989b + a1f73e2 commit cf0405a

4 files changed

Lines changed: 225 additions & 8 deletions

File tree

internal/application/usecase/category_usecase.go

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ type UpdateCategory struct {
8181
CategoryID uint `json:"category_id" validate:"required"`
8282
Name string `json:"name,omitempty" validate:"omitempty,min=1,max=255"`
8383
Description string `json:"description,omitempty" validate:"max=1000"`
84-
ParentID *uint `json:"parent_id,omitempty"` // Optional parent category ID
84+
ParentID *uint `json:"parent_id,omitempty"` // Optional parent category ID (0 means remove parent)
8585
}
8686

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

95-
// Validate parent category exists if parentID is provided
95+
// Handle ParentID logic: if ParentID is provided and is 0, set it to nil (remove parent)
96+
var actualParentID *uint
9697
if input.ParentID != nil {
98+
if *input.ParentID == 0 {
99+
actualParentID = nil
100+
} else {
101+
actualParentID = input.ParentID
102+
}
103+
}
104+
105+
// Validate parent category exists if parentID is provided and not 0
106+
if actualParentID != nil {
97107
// Check for circular reference (category cannot be its own parent)
98-
if *input.ParentID == input.CategoryID {
108+
if *actualParentID == input.CategoryID {
99109
return nil, errors.New("category cannot be its own parent")
100110
}
101111

102-
parent, err := uc.categoryRepo.GetByID(*input.ParentID)
112+
parent, err := uc.categoryRepo.GetByID(*actualParentID)
103113
if err != nil {
104114
return nil, fmt.Errorf("parent category not found: %w", err)
105115
}
@@ -116,7 +126,7 @@ func (uc *CategoryUseCase) UpdateCategory(input UpdateCategory) (*entity.Categor
116126
category.Description = input.Description
117127
}
118128
if input.ParentID != nil {
119-
category.ParentID = input.ParentID
129+
category.ParentID = actualParentID
120130
}
121131

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

137-
return category, nil
147+
// Clear associations if ParentID was set to nil
148+
if input.ParentID != nil && actualParentID == nil {
149+
category.Parent = nil
150+
}
151+
152+
// Refetch the category to get the updated data with proper associations
153+
updatedCategory, err := uc.categoryRepo.GetByID(category.ID)
154+
if err != nil {
155+
return nil, fmt.Errorf("failed to fetch updated category: %w", err)
156+
}
157+
158+
return updatedCategory, nil
138159
}
139160

140161
// DeleteCategory deletes a category by ID

internal/application/usecase/category_usecase_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,3 +129,48 @@ func TestCategoryUseCase_DeleteCategory_WithProducts(t *testing.T) {
129129
assert.Equal(t, "cannot delete category with child categories", err.Error())
130130
})
131131
}
132+
133+
func TestCategoryUseCase_UpdateCategory_ParentIDZero(t *testing.T) {
134+
t.Run("should remove parent when parent_id is 0", func(t *testing.T) {
135+
// Setup
136+
db := testutil.SetupTestDB(t)
137+
defer testutil.CleanupTestDB(t, db)
138+
139+
categoryRepo := gorm.NewCategoryRepository(db)
140+
productRepo := gorm.NewProductRepository(db)
141+
categoryUseCase := NewCategoryUseCase(categoryRepo, productRepo)
142+
143+
// Create parent category
144+
parentCategory, err := categoryUseCase.CreateCategory(CreateCategory{
145+
Name: "Parent Category",
146+
Description: "Parent category for test",
147+
ParentID: nil,
148+
})
149+
require.NoError(t, err)
150+
151+
// Create child category with parent
152+
childCategory, err := categoryUseCase.CreateCategory(CreateCategory{
153+
Name: "Child Category",
154+
Description: "Child category for test",
155+
ParentID: &parentCategory.ID,
156+
})
157+
require.NoError(t, err)
158+
159+
// Verify initial state
160+
assert.NotNil(t, childCategory.ParentID)
161+
assert.Equal(t, parentCategory.ID, *childCategory.ParentID)
162+
163+
// Update child to remove parent using parent_id: 0
164+
zeroID := uint(0)
165+
_, err = categoryUseCase.UpdateCategory(UpdateCategory{
166+
CategoryID: childCategory.ID,
167+
ParentID: &zeroID,
168+
})
169+
require.NoError(t, err)
170+
171+
// Verify in database directly (this is the most reliable test)
172+
fetchedCategory, err := categoryRepo.GetByID(childCategory.ID)
173+
require.NoError(t, err)
174+
assert.Nil(t, fetchedCategory.ParentID, "ParentID should be nil after setting to 0")
175+
})
176+
}

internal/infrastructure/repository/gorm/category_repository.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ func (c *CategoryRepository) Create(category *entity.Category) error {
2323
func (c *CategoryRepository) Delete(categoryID uint) error {
2424
// Note: This will fail if there are products in this category due to RESTRICT constraint
2525
// which is the intended behavior for data integrity
26-
return c.db.Delete(&entity.Category{}, categoryID).Error
26+
return c.db.Unscoped().Delete(&entity.Category{}, categoryID).Error
2727
}
2828

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

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

6768
// NewCategoryRepository creates a new GORM-based CategoryRepository
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package gorm
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
9+
"github.com/zenfulcode/commercify/internal/domain/entity"
10+
"github.com/zenfulcode/commercify/testutil"
11+
)
12+
13+
func TestCategoryRepository_UpdateParentID(t *testing.T) {
14+
// Setup
15+
db := testutil.SetupTestDB(t)
16+
defer testutil.CleanupTestDB(t, db)
17+
18+
repo := NewCategoryRepository(db)
19+
20+
t.Run("should update ParentID from nil to valid ID", func(t *testing.T) {
21+
// Create parent category
22+
parentCategory, err := entity.NewCategory("Parent Category", "Parent description", nil)
23+
require.NoError(t, err)
24+
err = repo.Create(parentCategory)
25+
require.NoError(t, err)
26+
27+
// Create child category without parent
28+
childCategory, err := entity.NewCategory("Child Category", "Child description", nil)
29+
require.NoError(t, err)
30+
err = repo.Create(childCategory)
31+
require.NoError(t, err)
32+
33+
// Verify initial state
34+
assert.Nil(t, childCategory.ParentID)
35+
36+
// Update child to have parent
37+
childCategory.ParentID = &parentCategory.ID
38+
err = repo.Update(childCategory)
39+
require.NoError(t, err)
40+
41+
// Fetch from database to verify
42+
updated, err := repo.GetByID(childCategory.ID)
43+
require.NoError(t, err)
44+
assert.NotNil(t, updated.ParentID)
45+
assert.Equal(t, parentCategory.ID, *updated.ParentID)
46+
})
47+
48+
t.Run("should update ParentID from valid ID to nil using 0", func(t *testing.T) {
49+
// Create parent category
50+
parentCategory, err := entity.NewCategory("Parent Category 2", "Parent description", nil)
51+
require.NoError(t, err)
52+
err = repo.Create(parentCategory)
53+
require.NoError(t, err)
54+
55+
// Create child category with parent
56+
childCategory, err := entity.NewCategory("Child Category 2", "Child description", &parentCategory.ID)
57+
require.NoError(t, err)
58+
err = repo.Create(childCategory)
59+
require.NoError(t, err)
60+
61+
// Verify initial state
62+
assert.NotNil(t, childCategory.ParentID)
63+
assert.Equal(t, parentCategory.ID, *childCategory.ParentID)
64+
65+
// Update child to remove parent (simulate sending parent_id: 0 from API)
66+
childCategory.ParentID = nil
67+
err = repo.Update(childCategory)
68+
require.NoError(t, err)
69+
70+
// Fetch from database to verify
71+
updated, err := repo.GetByID(childCategory.ID)
72+
require.NoError(t, err)
73+
assert.Nil(t, updated.ParentID)
74+
})
75+
76+
t.Run("should update ParentID from one valid ID to another", func(t *testing.T) {
77+
// Create parent categories
78+
parentCategory1, err := entity.NewCategory("Parent Category 3", "Parent description", nil)
79+
require.NoError(t, err)
80+
err = repo.Create(parentCategory1)
81+
require.NoError(t, err)
82+
83+
parentCategory2, err := entity.NewCategory("Parent Category 4", "Parent description", nil)
84+
require.NoError(t, err)
85+
err = repo.Create(parentCategory2)
86+
require.NoError(t, err)
87+
88+
// Create child category with first parent
89+
childCategory, err := entity.NewCategory("Child Category 3", "Child description", &parentCategory1.ID)
90+
require.NoError(t, err)
91+
err = repo.Create(childCategory)
92+
require.NoError(t, err)
93+
94+
// Verify initial state
95+
assert.NotNil(t, childCategory.ParentID)
96+
assert.Equal(t, parentCategory1.ID, *childCategory.ParentID)
97+
98+
// Update child to have second parent
99+
childCategory.ParentID = &parentCategory2.ID
100+
err = repo.Update(childCategory)
101+
require.NoError(t, err)
102+
103+
// Fetch from database to verify
104+
updated, err := repo.GetByID(childCategory.ID)
105+
require.NoError(t, err)
106+
assert.NotNil(t, updated.ParentID)
107+
assert.Equal(t, parentCategory2.ID, *updated.ParentID)
108+
})
109+
}
110+
111+
func TestCategoryRepository_UpdateParentIDToNil(t *testing.T) {
112+
// Setup
113+
db := testutil.SetupTestDB(t)
114+
defer testutil.CleanupTestDB(t, db)
115+
116+
repo := NewCategoryRepository(db)
117+
118+
t.Run("should update ParentID to nil when explicitly set", func(t *testing.T) {
119+
// Create parent category
120+
parentCategory, err := entity.NewCategory("Parent Category", "Parent description", nil)
121+
require.NoError(t, err)
122+
err = repo.Create(parentCategory)
123+
require.NoError(t, err)
124+
125+
// Create child category with parent
126+
childCategory, err := entity.NewCategory("Child Category", "Child description", &parentCategory.ID)
127+
require.NoError(t, err)
128+
err = repo.Create(childCategory)
129+
require.NoError(t, err)
130+
131+
// Verify initial state
132+
initial, err := repo.GetByID(childCategory.ID)
133+
require.NoError(t, err)
134+
assert.NotNil(t, initial.ParentID)
135+
assert.Equal(t, parentCategory.ID, *initial.ParentID)
136+
137+
// Update child to remove parent by setting ParentID to nil
138+
childCategory.ParentID = nil
139+
t.Logf("Before update: childCategory.ParentID = %v", childCategory.ParentID)
140+
141+
err = repo.Update(childCategory)
142+
require.NoError(t, err)
143+
144+
// Verify the update worked by fetching from database
145+
updated, err := repo.GetByID(childCategory.ID)
146+
require.NoError(t, err)
147+
t.Logf("After update: updated.ParentID = %v", updated.ParentID)
148+
assert.Nil(t, updated.ParentID, "ParentID should be nil after update")
149+
})
150+
}

0 commit comments

Comments
 (0)