package test import ( domainArticle "RenewCMS/internal/domain/article" entity "RenewCMS/internal/infrastructure/persistence/models" persistence "RenewCMS/internal/infrastructure/persistence/repositories" "RenewCMS/internal/infrastructure/useCases" "fmt" "testing" "gorm.io/gorm" ) const ( seoScaleVersions = 40_001 seoScaleBatch = 500 seoScaleHiddenEvery = 1000 seoScalePageSize = 25 seoScaleStoryOffset = 500_000 seoScaleTitledEvery = 7 seoScaleTitleFragment = "Lighthouse" ) type countingStoryRepository struct { *persistence.ArticleRepository titleSearches int pageLoads int } func (r *countingStoryRepository) FindTitleMatches(q string, readable domainArticle.StoryFilters) ([]uint32, error) { r.titleSearches++ return r.ArticleRepository.FindTitleMatches(q, readable) } func (r *countingStoryRepository) FindPage(filters domainArticle.Filters) (domainArticle.PageResult, error) { r.pageLoads++ return r.ArticleRepository.FindPage(filters) } func seoScaleTitle(index int) string { if index%seoScaleTitledEvery == 0 { return fmt.Sprintf("%s %d", seoScaleTitleFragment, index) } return fmt.Sprintf("Harbour %d", index) } func seedSEOScale(t *testing.T, db *gorm.DB, siteID uint32) { t.Helper() rows := make([]entity.Article, 0, seoScaleVersions) for index := 0; index < seoScaleVersions; index++ { rows = append(rows, entity.Article{ Title: seoScaleTitle(index), Body: "
Short body.
", SiteID: siteID, Slug: fmt.Sprintf("scale-%d", index), StoryID: uint32(seoScaleStoryOffset + index), IsOnline: true, Noindex: index%seoScaleHiddenEvery == 0, }) } if err := db.CreateInBatches(rows, seoScaleBatch).Error; err != nil { t.Fatalf("bulk insert: %v", err) } } func expectedSEOScale(hidden bool, titled bool) int { total := 0 for index := 0; index < seoScaleVersions; index++ { if (index%seoScaleHiddenEvery == 0) == hidden && (!titled || index%seoScaleTitledEvery == 0) { total++ } } return total } func TestArticleStoryListSEOFilterAtScaleSQLite(t *testing.T) { db := openContractSQLite(t) site := storageDefaultSite(t, db) seedSEOScale(t, db, site.ID) repository := &countingStoryRepository{ArticleRepository: persistence.NewArticleRepository(db)} list := useCases.NewListArticlesUseCase(repository, &fakeBodyRenderer{}, persistence.NewSiteRepository(db), persistence.NewLocalizationRepository(db)) cases := []struct { name string seo string q string hidden bool titled bool }{ {"More versions with suggestions than the database accepts bind parameters", domainArticle.SEOStatusSuggestions, "", false, false}, {"The hidden versions among them", domainArticle.SEOStatusHidden, "", true, false}, {"The SEO filter combines with a title search run once", domainArticle.SEOStatusSuggestions, seoScaleTitleFragment, false, true}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { repository.titleSearches, repository.pageLoads = 0, 0 want := expectedSEOScale(c.hidden, c.titled) page, err := list.ListStoriesPage(useCases.ListStoriesCommand{Caller: ownerCaller(1), Page: 2, Size: seoScalePageSize, SEO: c.seo, Q: c.q}) if err != nil { t.Fatalf("list: %v", err) } if page.Total != want || page.Counts.All != want { t.Errorf("total %d, counts %+v, want %d", page.Total, page.Counts, want) } if len(page.Rows) != min(seoScalePageSize, max(want-seoScalePageSize, 0)) { t.Errorf("rows = %d", len(page.Rows)) } for _, row := range page.Rows { if row.SEO.Status != c.seo || row.Article.Body == "" { t.Errorf("row %d: status %q body %q", row.Article.ID, row.SEO.Status, row.Article.Body) } } wantSearches := 0 if c.q != "" { wantSearches = 1 } if repository.titleSearches != wantSearches || repository.pageLoads != 1 { t.Errorf("title searches %d, page loads %d per request", repository.titleSearches, repository.pageLoads) } }) } } func TestArticleRepositoryListByStoriesSkipsBodiesAndUnreadableDrafts(t *testing.T) { db := openContractSQLite(t) site := storageDefaultSite(t, db) story := fixtureStory() versions := []entity.Article{ {Title: "Published", Body: "Published body
", SiteID: site.ID, Lang: "en", StoryID: story, Slug: uniqueTestSlug(), IsOnline: true, CreatedBy: localizationOtherUserID}, {Title: "Own draft", Body: "Own body
", SiteID: site.ID, Lang: "fr", StoryID: story, Slug: uniqueTestSlug(), CreatedBy: localizationWriterID}, {Title: "Foreign draft", Body: "Foreign body
", SiteID: site.ID, Lang: "de", StoryID: story, Slug: uniqueTestSlug(), CreatedBy: localizationOtherUserID}, } if err := db.Create(&versions).Error; err != nil { t.Fatal(err) } repository := persistence.NewArticleRepository(db) writer := localizationWriterID cases := []struct { name string readable domainArticle.StoryFilters titles []string }{ {"An editor reads every version", domainArticle.StoryFilters{}, []string{"Published", "Own draft", "Foreign draft"}}, {"An own-scope caller reads the published versions and their drafts", domainArticle.StoryFilters{VisibleTo: &writer}, []string{"Published", "Own draft"}}, {"A reader reads the published versions only", domainArticle.StoryFilters{OnlyOnline: true}, []string{"Published"}}, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { loaded, err := repository.ListByStories([]uint32{story}, c.readable) if err != nil { t.Fatal(err) } titles := []string{} for _, version := range loaded { titles = append(titles, version.Title) if version.Body != "" { t.Errorf("version %q carries its body", version.Title) } } if fmt.Sprint(titles) != fmt.Sprint(c.titles) { t.Errorf("titles = %v, want %v", titles, c.titles) } }) } }