package repository import ( "context" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/gochat/gochat/internal/model" ) func newTestArticle(portalID uint, title string) *model.Article { return &model.Article{ PortalID: portalID, Title: title, Slug: title + "-slug", Content: "Test article content", Status: string(model.ArticleStatusDraft), Locale: "en", } } // ========== Create ========== func TestArticleRepo_Create(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) article := newTestArticle(1, "TestArticle") err := repo.Create(context.Background(), article) require.NoError(t, err) assert.NotZero(t, article.ID) assert.Equal(t, uint(1), article.PortalID) assert.Equal(t, string(model.ArticleStatusDraft), article.Status) } // ========== GetByID ========== func TestArticleRepo_GetByID(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) article := newTestArticle(1, "FindByIDArticle") err := repo.Create(context.Background(), article) require.NoError(t, err) found, err := repo.GetByID(context.Background(), article.ID) require.NoError(t, err) assert.Equal(t, article.ID, found.ID) assert.Equal(t, article.Title, found.Title) } func TestArticleRepo_GetByID_NotFound(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) found, err := repo.GetByID(context.Background(), 99999) assert.Error(t, err) assert.Nil(t, found) } // ========== Update ========== func TestArticleRepo_Update(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) article := newTestArticle(1, "BeforeUpdate") err := repo.Create(context.Background(), article) require.NoError(t, err) article.Title = "AfterUpdate" article.Status = string(model.ArticleStatusPublished) err = repo.Update(context.Background(), article) require.NoError(t, err) found, err := repo.GetByID(context.Background(), article.ID) require.NoError(t, err) assert.Equal(t, "AfterUpdate", found.Title) assert.Equal(t, string(model.ArticleStatusPublished), found.Status) } // ========== Delete ========== func TestArticleRepo_Delete(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) article := newTestArticle(1, "DeleteArticle") err := repo.Create(context.Background(), article) require.NoError(t, err) err = repo.Delete(context.Background(), article.ID) require.NoError(t, err) found, err := repo.GetByID(context.Background(), article.ID) assert.Error(t, err) assert.Nil(t, found) } // ========== FindByPortalID ========== func TestArticleRepo_FindByPortalID(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) portalID := uint(10) for i := 0; i < 3; i++ { a := newTestArticle(portalID, "Article"+string(rune('A'+i))) err := repo.Create(context.Background(), a) require.NoError(t, err) } articles, count, err := repo.FindByPortalID(context.Background(), portalID, 0, 10) require.NoError(t, err) assert.Equal(t, int64(3), count) assert.Len(t, articles, 3) } // ========== FindByCategoryID ========== func TestArticleRepo_FindByCategoryID(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) categoryID := uint(5) for i := 0; i < 2; i++ { a := newTestArticle(1, "CatArticle"+string(rune('A'+i))) a.CategoryID = &categoryID err := repo.Create(context.Background(), a) require.NoError(t, err) } articles, count, err := repo.FindByCategoryID(context.Background(), categoryID, 0, 10) require.NoError(t, err) assert.Equal(t, int64(2), count) assert.Len(t, articles, 2) for _, a := range articles { assert.Equal(t, categoryID, *a.CategoryID) } } // ========== FindByFolderID ========== func TestArticleRepo_FindByFolderID(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) folderID := uint(7) for i := 0; i < 2; i++ { a := newTestArticle(1, "FolderArticle"+string(rune('A'+i))) a.FolderID = &folderID err := repo.Create(context.Background(), a) require.NoError(t, err) } // Also create one without a folder noFolder := newTestArticle(1, "NoFolderArticle") require.NoError(t, repo.Create(context.Background(), noFolder)) articles, count, err := repo.FindByFolderID(context.Background(), folderID, 0, 10) require.NoError(t, err) assert.Equal(t, int64(2), count) assert.Len(t, articles, 2) for _, a := range articles { assert.Equal(t, folderID, *a.FolderID) } } // ========== FindByStatus ========== func TestArticleRepo_FindByStatus(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) portalID := uint(10) // Create 2 draft articles for i := 0; i < 2; i++ { a := newTestArticle(portalID, "DraftArticle"+string(rune('A'+i))) err := repo.Create(context.Background(), a) require.NoError(t, err) } // Create 1 published article published := newTestArticle(portalID, "PublishedArticle") published.Status = string(model.ArticleStatusPublished) err := repo.Create(context.Background(), published) require.NoError(t, err) articles, count, err := repo.FindByStatus(context.Background(), portalID, model.ArticleStatusDraft, 0, 10) require.NoError(t, err) assert.Equal(t, int64(2), count) assert.Len(t, articles, 2) for _, a := range articles { assert.Equal(t, string(model.ArticleStatusDraft), a.Status) } } // ========== FindBySlug ========== func TestArticleRepo_FindBySlug(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) article := newTestArticle(1, "SlugArticle") err := repo.Create(context.Background(), article) require.NoError(t, err) found, err := repo.FindBySlug(context.Background(), article.Slug) require.NoError(t, err) assert.Equal(t, article.ID, found.ID) assert.Equal(t, article.Slug, found.Slug) } func TestArticleRepo_FindBySlug_NotFound(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) found, err := repo.FindBySlug(context.Background(), "nonexistent-slug") assert.Error(t, err) assert.Nil(t, found) } // ========== Search ========== func TestArticleRepo_Search_ByQuery(t *testing.T) { skipIfSQLite(t) // ILIKE requires PG db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) portalID := uint(10) a1 := newTestArticle(portalID, "Getting Started Guide") a1.Description = "A guide to help you get started" require.NoError(t, repo.Create(context.Background(), a1)) a2 := newTestArticle(portalID, "Advanced Configuration") a2.Content = "Configure your advanced settings" require.NoError(t, repo.Create(context.Background(), a2)) a3 := newTestArticle(portalID, "Troubleshooting Guide") require.NoError(t, repo.Create(context.Background(), a3)) params := ArticleSearchParams{ PortalID: portalID, Query: "guide", Offset: 0, Limit: 10, } articles, count, err := repo.Search(context.Background(), params) require.NoError(t, err) // Should match "Getting Started Guide" and "Troubleshooting Guide" assert.Equal(t, int64(2), count) assert.Len(t, articles, 2) } func TestArticleRepo_Search_ByStatus(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) portalID := uint(10) for i := 0; i < 3; i++ { a := newTestArticle(portalID, "DraftSearch"+string(rune('A'+i))) a.Status = string(model.ArticleStatusDraft) require.NoError(t, repo.Create(context.Background(), a)) } pub := newTestArticle(portalID, "PublishedSearch") pub.Status = string(model.ArticleStatusPublished) require.NoError(t, repo.Create(context.Background(), pub)) params := ArticleSearchParams{ PortalID: portalID, Status: string(model.ArticleStatusDraft), Offset: 0, Limit: 10, } articles, count, err := repo.Search(context.Background(), params) require.NoError(t, err) assert.Equal(t, int64(3), count) assert.Len(t, articles, 3) } func TestArticleRepo_Search_ByLocale(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) portalID := uint(10) en := newTestArticle(portalID, "English Article") en.Locale = "en" require.NoError(t, repo.Create(context.Background(), en)) es := newTestArticle(portalID, "Spanish Article") es.Locale = "es" require.NoError(t, repo.Create(context.Background(), es)) params := ArticleSearchParams{ PortalID: portalID, Locale: "en", Offset: 0, Limit: 10, } articles, count, err := repo.Search(context.Background(), params) require.NoError(t, err) assert.Equal(t, int64(1), count) assert.Len(t, articles, 1) assert.Equal(t, "en", articles[0].Locale) } func TestArticleRepo_Search_EmptyParams(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) portalID := uint(10) for i := 0; i < 5; i++ { a := newTestArticle(portalID, "AllArticles"+string(rune('A'+i))) require.NoError(t, repo.Create(context.Background(), a)) } params := ArticleSearchParams{ PortalID: portalID, Offset: 0, Limit: 10, } articles, count, err := repo.Search(context.Background(), params) require.NoError(t, err) assert.Equal(t, int64(5), count) assert.Len(t, articles, 5) } // ========== StatusCounts ========== func TestArticleRepo_StatusCounts(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) portalID := uint(10) // Create 3 draft articles for i := 0; i < 3; i++ { a := newTestArticle(portalID, "DraftCount"+string(rune('A'+i))) a.Status = string(model.ArticleStatusDraft) require.NoError(t, repo.Create(context.Background(), a)) } // Create 2 published articles for i := 0; i < 2; i++ { a := newTestArticle(portalID, "PublishedCount"+string(rune('A'+i))) a.Status = string(model.ArticleStatusPublished) require.NoError(t, repo.Create(context.Background(), a)) } // Create 1 archived article a := newTestArticle(portalID, "ArchivedCount") a.Status = string(model.ArticleStatusArchived) require.NoError(t, repo.Create(context.Background(), a)) counts, err := repo.StatusCounts(context.Background(), portalID) require.NoError(t, err) assert.Equal(t, int64(6), counts["all_count"]) assert.Equal(t, int64(3), counts["draft_count"]) assert.Equal(t, int64(2), counts["published_count"]) assert.Equal(t, int64(1), counts["archived_count"]) } func TestArticleRepo_StatusCounts_EmptyPortal(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) counts, err := repo.StatusCounts(context.Background(), 99999) require.NoError(t, err) assert.Equal(t, int64(0), counts["all_count"]) assert.Equal(t, int64(0), counts["draft_count"]) assert.Equal(t, int64(0), counts["published_count"]) assert.Equal(t, int64(0), counts["archived_count"]) } // ========== UpdatePositions ========== func TestArticleRepo_UpdatePositions(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) portalID := uint(10) a1 := newTestArticle(portalID, "PositionArticle1") require.NoError(t, repo.Create(context.Background(), a1)) a2 := newTestArticle(portalID, "PositionArticle2") require.NoError(t, repo.Create(context.Background(), a2)) positions := map[uint]int{ a1.ID: 5, a2.ID: 10, } err := repo.UpdatePositions(context.Background(), positions) require.NoError(t, err) found1, err := repo.GetByID(context.Background(), a1.ID) require.NoError(t, err) assert.Equal(t, 5, found1.Position) found2, err := repo.GetByID(context.Background(), a2.ID) require.NoError(t, err) assert.Equal(t, 10, found2.Position) } // ========== BulkUpdateStatus ========== func TestArticleRepo_BulkUpdateStatus(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) portalID := uint(10) var ids []uint for i := 0; i < 3; i++ { a := newTestArticle(portalID, "BulkStatus"+string(rune('A'+i))) a.Status = string(model.ArticleStatusDraft) require.NoError(t, repo.Create(context.Background(), a)) ids = append(ids, a.ID) } err := repo.BulkUpdateStatus(context.Background(), ids, string(model.ArticleStatusPublished)) require.NoError(t, err) for _, id := range ids { found, err := repo.GetByID(context.Background(), id) require.NoError(t, err) assert.Equal(t, string(model.ArticleStatusPublished), found.Status) } } func TestArticleRepo_BulkUpdateStatus_EmptyIDs(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) err := repo.BulkUpdateStatus(context.Background(), []uint{}, string(model.ArticleStatusPublished)) require.NoError(t, err) } // ========== BulkDelete ========== func TestArticleRepo_BulkDelete(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) portalID := uint(10) var ids []uint for i := 0; i < 3; i++ { a := newTestArticle(portalID, "BulkDelete"+string(rune('A'+i))) require.NoError(t, repo.Create(context.Background(), a)) ids = append(ids, a.ID) } // Create one that should NOT be deleted keeper := newTestArticle(portalID, "KeeperArticle") require.NoError(t, repo.Create(context.Background(), keeper)) err := repo.BulkDelete(context.Background(), ids) require.NoError(t, err) for _, id := range ids { found, err := repo.GetByID(context.Background(), id) assert.Error(t, err) assert.Nil(t, found) } // Keeper should still exist found, err := repo.GetByID(context.Background(), keeper.ID) require.NoError(t, err) assert.Equal(t, keeper.ID, found.ID) } func TestArticleRepo_BulkDelete_EmptyIDs(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) err := repo.BulkDelete(context.Background(), []uint{}) require.NoError(t, err) } // ========== IncrementViews ========== func TestArticleRepo_IncrementViews(t *testing.T) { db := setupTestDB(t, &model.Article{}) repo := NewArticleRepo(db) article := newTestArticle(1, "ViewArticle") require.NoError(t, repo.Create(context.Background(), article)) assert.Equal(t, 0, article.Views) // Increment twice err := repo.IncrementViews(context.Background(), article.ID) require.NoError(t, err) err = repo.IncrementViews(context.Background(), article.ID) require.NoError(t, err) found, err := repo.GetByID(context.Background(), article.ID) require.NoError(t, err) assert.Equal(t, 2, found.Views) }