182 lines
6.3 KiB
Go
182 lines
6.3 KiB
Go
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
|
|
staleReads int
|
|
}
|
|
|
|
func (r *countingStoryRepository) StaleSEO(afterID uint32, limit int) ([]domainArticle.Article, error) {
|
|
stale, err := r.ArticleRepository.StaleSEO(afterID, limit)
|
|
r.staleReads += len(stale)
|
|
return stale, err
|
|
}
|
|
|
|
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: "<p>Short body.</p>",
|
|
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)}
|
|
sites, rows := persistence.NewSiteRepository(db), persistence.NewLocalizationRepository(db)
|
|
if err := useCases.NewRefreshArticleSEOUseCase(repository, sites, rows).RefreshStale(); err != nil {
|
|
t.Fatalf("backfill: %v", err)
|
|
}
|
|
if repository.staleReads != seoScaleVersions {
|
|
t.Fatalf("the backfill assessed %d versions, want %d", repository.staleReads, seoScaleVersions)
|
|
}
|
|
list := useCases.NewListArticlesUseCase(repository, &fakeBodyRenderer{}, sites, rows)
|
|
|
|
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, repository.staleReads = 0, 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 != 0 || repository.staleReads != 0 {
|
|
t.Errorf("title searches %d, page loads %d, stale versions read %d per request", repository.titleSearches, repository.pageLoads, repository.staleReads)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArticleRepositoryListByStoriesSkipsBodiesAndUnreadableDrafts(t *testing.T) {
|
|
db := openContractSQLite(t)
|
|
site := storageDefaultSite(t, db)
|
|
story := fixtureStory()
|
|
versions := []entity.Article{
|
|
{Title: "Published", Body: "<p>Published body</p>", SiteID: site.ID, Lang: "en", StoryID: story, Slug: uniqueTestSlug(), IsOnline: true, CreatedBy: localizationOtherUserID},
|
|
{Title: "Own draft", Body: "<p>Own body</p>", SiteID: site.ID, Lang: "fr", StoryID: story, Slug: uniqueTestSlug(), CreatedBy: localizationWriterID},
|
|
{Title: "Foreign draft", Body: "<p>Foreign body</p>", 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)
|
|
}
|
|
})
|
|
}
|
|
}
|