package service import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gochat/gochat/internal/repository" ) // ========== Test Setup ========== func setupCategoryService(t *testing.T) (*CategoryService, uint, uint) { t.Helper() db := setupServiceTestDB(t) repo := repository.NewCategoryRepo(db) relatedRepo := repository.NewRelatedCategoryRepo(db) svc := NewCategoryService(repo, relatedRepo) account := createTestAccount(t, db) portal := createTestPortal(t, db, account.ID) return svc, portal.ID, account.ID } // ========== Create ========== func TestCategoryService_Create(t *testing.T) { svc, portalID, accountID := setupCategoryService(t) req := &CreateCategoryRequest{ Name: "Announcements", Slug: "announcements", Description: "Official announcements", Icon: "📢", Position: 1, Locale: "en", } category, err := svc.Create(context.Background(), portalID, accountID, req) require.NoError(t, err) assert.NotZero(t, category.ID) assert.Equal(t, portalID, category.PortalID) assert.Equal(t, accountID, category.AccountID) assert.Equal(t, "Announcements", category.Name) assert.Equal(t, "announcements", category.Slug) assert.Equal(t, "Official announcements", category.Description) assert.Equal(t, "📢", category.Icon) assert.Equal(t, 1, category.Position) assert.Equal(t, "en", category.Locale) } // ========== GetByID ========== func TestCategoryService_GetByID(t *testing.T) { svc, portalID, accountID := setupCategoryService(t) req := &CreateCategoryRequest{ Name: "FAQ", Slug: "faq", } created, err := svc.Create(context.Background(), portalID, accountID, req) require.NoError(t, err) category, err := svc.GetByID(context.Background(), created.ID) require.NoError(t, err) assert.Equal(t, created.ID, category.ID) assert.Equal(t, "FAQ", category.Name) assert.Equal(t, "faq", category.Slug) } func TestCategoryService_GetByID_NotFound(t *testing.T) { svc, _, _ := setupCategoryService(t) category, err := svc.GetByID(context.Background(), 99999) assert.Nil(t, category) assert.Error(t, err) } // ========== Update ========== func TestCategoryService_Update(t *testing.T) { svc, portalID, accountID := setupCategoryService(t) req := &CreateCategoryRequest{ Name: "Guides", Slug: "guides", Position: 0, Locale: "en", } created, err := svc.Create(context.Background(), portalID, accountID, req) require.NoError(t, err) newPos := 5 updateReq := &UpdateCategoryRequest{ Name: "User Guides", Description: "Updated description", Icon: "📖", Position: &newPos, Locale: "zh", } updated, err := svc.Update(context.Background(), created.ID, updateReq) require.NoError(t, err) assert.Equal(t, "User Guides", updated.Name) assert.Equal(t, "Updated description", updated.Description) assert.Equal(t, "📖", updated.Icon) assert.Equal(t, 5, updated.Position) assert.Equal(t, "zh", updated.Locale) } func TestCategoryService_Update_NotFound(t *testing.T) { svc, _, _ := setupCategoryService(t) newPos := 1 updateReq := &UpdateCategoryRequest{ Name: "Nonexistent", Position: &newPos, } updated, err := svc.Update(context.Background(), 99999, updateReq) assert.Nil(t, updated) assert.Error(t, err) } func TestCategoryService_Update_WithRelatedCategories(t *testing.T) { svc, portalID, accountID := setupCategoryService(t) // Create three categories in the same portal so related links can reference real IDs cat1, err := svc.Create(context.Background(), portalID, accountID, &CreateCategoryRequest{ Name: "Cat1", Slug: "cat1", Locale: "en", }) require.NoError(t, err) cat2, err := svc.Create(context.Background(), portalID, accountID, &CreateCategoryRequest{ Name: "Cat2", Slug: "cat2", Locale: "en", }) require.NoError(t, err) cat3, err := svc.Create(context.Background(), portalID, accountID, &CreateCategoryRequest{ Name: "Cat3", Slug: "cat3", Locale: "en", }) require.NoError(t, err) // Update cat1 to have cat2 and cat3 as related categories updateReq := &UpdateCategoryRequest{ RelatedCategoryIDs: []uint{cat2.ID, cat3.ID}, } updated, err := svc.Update(context.Background(), cat1.ID, updateReq) require.NoError(t, err) assert.Equal(t, cat1.ID, updated.ID) // Verify the related categories were saved by fetching the category again fetched, err := svc.GetByID(context.Background(), cat1.ID) require.NoError(t, err) assert.Len(t, fetched.RelatedCategories, 2) // Verify the related IDs match relatedIDs := make([]uint, 0, len(fetched.RelatedCategories)) for _, rc := range fetched.RelatedCategories { relatedIDs = append(relatedIDs, rc.RelatedCategoryID) } assert.Contains(t, relatedIDs, cat2.ID) assert.Contains(t, relatedIDs, cat3.ID) // Now update to replace with only cat3 updateReq2 := &UpdateCategoryRequest{ RelatedCategoryIDs: []uint{cat3.ID}, } _, err = svc.Update(context.Background(), cat1.ID, updateReq2) require.NoError(t, err) fetched2, err := svc.GetByID(context.Background(), cat1.ID) require.NoError(t, err) assert.Len(t, fetched2.RelatedCategories, 1) assert.Equal(t, cat3.ID, fetched2.RelatedCategories[0].RelatedCategoryID) } // ========== Delete ========== func TestCategoryService_Delete(t *testing.T) { svc, portalID, accountID := setupCategoryService(t) req := &CreateCategoryRequest{ Name: "Temporary", Slug: "temporary", } created, err := svc.Create(context.Background(), portalID, accountID, req) require.NoError(t, err) err = svc.Delete(context.Background(), created.ID) require.NoError(t, err) // Verify it's gone category, err := svc.GetByID(context.Background(), created.ID) assert.Error(t, err) assert.Nil(t, category) } func TestCategoryService_Delete_NotFound(t *testing.T) { svc, _, _ := setupCategoryService(t) err := svc.Delete(context.Background(), 99999) assert.Error(t, err) } func TestCategoryService_Delete_CleansUpRelatedCategories(t *testing.T) { svc, portalID, accountID := setupCategoryService(t) // Create two categories cat1, err := svc.Create(context.Background(), portalID, accountID, &CreateCategoryRequest{ Name: "Main", Slug: "main", Locale: "en", }) require.NoError(t, err) cat2, err := svc.Create(context.Background(), portalID, accountID, &CreateCategoryRequest{ Name: "Related", Slug: "related", Locale: "en", }) require.NoError(t, err) // Link cat1 → cat2 as related updateReq := &UpdateCategoryRequest{ RelatedCategoryIDs: []uint{cat2.ID}, } _, err = svc.Update(context.Background(), cat1.ID, updateReq) require.NoError(t, err) // Delete cat1 — should clean up the related category link err = svc.Delete(context.Background(), cat1.ID) require.NoError(t, err) // cat2 should still exist cat2Fetched, err := svc.GetByID(context.Background(), cat2.ID) require.NoError(t, err) assert.Equal(t, cat2.ID, cat2Fetched.ID) } // ========== ListByPortalID ========== func TestCategoryService_ListByPortalID(t *testing.T) { svc, portalID, accountID := setupCategoryService(t) // Create several categories with different positions names := []string{"First", "Second", "Third"} for i, name := range names { _, err := svc.Create(context.Background(), portalID, accountID, &CreateCategoryRequest{ Name: name, Slug: name, Position: i + 1, Locale: "en", }) require.NoError(t, err) } // List page 1, perPage 2 categories, count, err := svc.ListByPortalID(context.Background(), portalID, "", 1, 2) require.NoError(t, err) assert.Equal(t, int64(3), count) assert.Len(t, categories, 2) // List page 2, perPage 2 categories2, count2, err := svc.ListByPortalID(context.Background(), portalID, "", 2, 2) require.NoError(t, err) assert.Equal(t, int64(3), count2) assert.Len(t, categories2, 1) // List all at once categories3, count3, err := svc.ListByPortalID(context.Background(), portalID, "", 1, 10) require.NoError(t, err) assert.Equal(t, int64(3), count3) assert.Len(t, categories3, 3) // Verify ordering by position assert.Equal(t, "First", categories3[0].Name) assert.Equal(t, "Second", categories3[1].Name) assert.Equal(t, "Third", categories3[2].Name) } func TestCategoryService_ListByPortalID_WithLocale(t *testing.T) { svc, portalID, accountID := setupCategoryService(t) // Create categories with different locales _, err := svc.Create(context.Background(), portalID, accountID, &CreateCategoryRequest{ Name: "English Cat", Slug: "english-cat", Locale: "en", }) require.NoError(t, err) _, err = svc.Create(context.Background(), portalID, accountID, &CreateCategoryRequest{ Name: "Chinese Cat", Slug: "chinese-cat", Locale: "zh", }) require.NoError(t, err) _, err = svc.Create(context.Background(), portalID, accountID, &CreateCategoryRequest{ Name: "Another English", Slug: "another-english", Locale: "en", }) require.NoError(t, err) // Filter by locale "en" categories, count, err := svc.ListByPortalID(context.Background(), portalID, "en", 1, 10) require.NoError(t, err) assert.Equal(t, int64(2), count) assert.Len(t, categories, 2) for _, cat := range categories { assert.Equal(t, "en", cat.Locale) } }