235 lines
8.2 KiB
Go
235 lines
8.2 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/api/pkg/apperr"
|
|
"RenewCMS/internal/domain/article"
|
|
"net/http"
|
|
"reflect"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func (e *seoOutEnv) cleanArticles() {
|
|
delete(e.articles.articles, seoOutArticleID)
|
|
delete(e.articles.articles, seoOutDraftID)
|
|
}
|
|
|
|
func (e *seoOutEnv) seedListed(id uint32, title string, online bool, mutate func(*article.Article)) {
|
|
e.credit(seoOutAuthorMarta, "Marta Alves", "")
|
|
e.articles.credits[seoOutArticleID] = nil
|
|
e.articles.credit(id, seoOutAuthorMarta)
|
|
e.articles.articles[id] = article.Article{
|
|
ID: id, Title: title, Body: textBody(400, "<h2>h</h2>"), IsOnline: online, SiteID: siteMainID, Slug: "slug-" + strconv.Itoa(int(id)),
|
|
SEOTitle: repeated("t", 45), SEODescription: repeated("d", 100), CoverImageID: idPointer(5), CoverImageAlt: "alt",
|
|
SEODismissed: []string{}, CreatedAt: seoOutCreated, UpdatedAt: seoOutCreated.Add(time.Duration(id) * time.Hour),
|
|
}
|
|
if mutate != nil {
|
|
found := e.articles.articles[id]
|
|
mutate(&found)
|
|
e.articles.articles[id] = found
|
|
}
|
|
}
|
|
|
|
func (e *seoOutEnv) seedFourListed() {
|
|
e.cleanArticles()
|
|
e.seedListed(1, "Ready", true, nil)
|
|
e.seedListed(2, "Two", true, func(a *article.Article) { a.CoverImageID, a.CoverImageAlt = nil, ""; a.SEOTitle = repeated("t", 70) })
|
|
e.seedListed(3, "Hidden", true, func(a *article.Article) {
|
|
a.Noindex = true
|
|
a.CoverImageID, a.CoverImageAlt = nil, ""
|
|
a.SEOTitle = repeated("t", 70)
|
|
})
|
|
e.seedListed(4, "Four", true, func(a *article.Article) {
|
|
a.CoverImageID, a.CoverImageAlt = nil, ""
|
|
a.SEOTitle = repeated("t", 70)
|
|
a.Slug = repeated("s", 80)
|
|
a.SEODismissed = []string{"slug_long"}
|
|
})
|
|
}
|
|
|
|
func (e *seoOutEnv) list(t *testing.T, who string, query string) map[string]any {
|
|
t.Helper()
|
|
rec := e.call(t, who, http.MethodGet, "/v1/article/"+query, "")
|
|
if rec.Code != http.StatusOK {
|
|
t.Fatalf("list %s: %d %s", query, rec.Code, rec.Body.String())
|
|
}
|
|
return decodeSeoObject(t, rec)
|
|
}
|
|
|
|
func itemByID(list map[string]any, id int) map[string]any {
|
|
for _, item := range list["items"].([]any) {
|
|
if found := item.(map[string]any); int(found["id"].(float64)) == id {
|
|
return found
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func TestSeoOutputList(t *testing.T) {
|
|
t.Run("Items carry seo_status and seo_suggestions", func(t *testing.T) {
|
|
e := newSeoOutEnv(t)
|
|
e.seedFourListed()
|
|
list := e.list(t, "owner", "")
|
|
want := map[int][2]any{1: {"ready", 0.0}, 2: {"suggestions", 2.0}, 3: {"hidden", 2.0}, 4: {"suggestions", 2.0}}
|
|
for id, expected := range want {
|
|
item := itemByID(list, id)
|
|
if item["seo_status"] != expected[0] || item["seo_suggestions"] != expected[1] {
|
|
t.Errorf("item %d = %v %v", id, item["seo_status"], item["seo_suggestions"])
|
|
}
|
|
if _, present := item["seo_dismissed"]; present {
|
|
t.Errorf("item %d carries seo_dismissed", id)
|
|
}
|
|
if _, present := item["excerpt"]; !present {
|
|
t.Errorf("item %d has no excerpt", id)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("The list and the check agree", func(t *testing.T) {
|
|
e := newSeoOutEnv(t)
|
|
e.seedFourListed()
|
|
list := e.list(t, "owner", "")
|
|
for id := 1; id <= 4; id++ {
|
|
rec := e.call(t, "owner", http.MethodPost, "/v1/article/"+strconv.Itoa(id)+"/seo/check", `{}`)
|
|
checked := decodeSeoObject(t, rec)
|
|
item := itemByID(list, id)
|
|
if checked["status"] != item["seo_status"] || float64(len(checked["suggestions"].([]any))) != item["seo_suggestions"] {
|
|
t.Errorf("article %d: list %v/%v, check %v", id, item["seo_status"], item["seo_suggestions"], checked)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Anonymous callers see online items with their status", func(t *testing.T) {
|
|
e := newSeoOutEnv(t)
|
|
e.seedFourListed()
|
|
e.seedListed(5, "Draft", false, nil)
|
|
list := e.list(t, "", "")
|
|
if !reflect.DeepEqual(listIDs(list), []int{4, 3, 2, 1}) || itemByID(list, 2)["seo_status"] != "suggestions" {
|
|
t.Errorf("ids = %v", listIDs(list))
|
|
}
|
|
})
|
|
|
|
t.Run("The seo filter", func(t *testing.T) {
|
|
cases := []struct {
|
|
value string
|
|
ids []int
|
|
total float64
|
|
}{{"ready", []int{1}, 1}, {"suggestions", []int{4, 2}, 2}, {"hidden", []int{3}, 1}}
|
|
for _, c := range cases {
|
|
e := newSeoOutEnv(t)
|
|
e.seedFourListed()
|
|
list := e.list(t, "owner", "?seo="+c.value)
|
|
if !reflect.DeepEqual(listIDs(list), c.ids) || list["total"] != c.total {
|
|
t.Errorf("%s: ids %v total %v", c.value, listIDs(list), list["total"])
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("The filter composes with search, status, site, sort and paging", func(t *testing.T) {
|
|
e := newSeoOutEnv(t)
|
|
e.cleanArticles()
|
|
for id := uint32(1); id <= 7; id++ {
|
|
e.seedListed(id, "Tile "+strconv.Itoa(int(id)), false, func(a *article.Article) { a.SiteID = seoOutJournalID; a.CoverImageID, a.CoverImageAlt = nil, "" })
|
|
}
|
|
for id := uint32(8); id <= 10; id++ {
|
|
e.seedListed(id, "Tile "+strconv.Itoa(int(id)), true, func(a *article.Article) { a.SiteID = seoOutJournalID; a.CoverImageID, a.CoverImageAlt = nil, "" })
|
|
}
|
|
for id := uint32(11); id <= 14; id++ {
|
|
e.seedListed(id, "Tile "+strconv.Itoa(int(id)), false, func(a *article.Article) { a.CoverImageID, a.CoverImageAlt = nil, "" })
|
|
}
|
|
base := "?seo=suggestions&q=tile&status=draft&site=journal&sort=title:asc&size=5&page="
|
|
first, second := e.list(t, "owner", base+"1"), e.list(t, "owner", base+"2")
|
|
if first["total"] != 7.0 || !reflect.DeepEqual(listIDs(second), []int{6, 7}) {
|
|
t.Errorf("first %v second %v total %v", listIDs(first), listIDs(second), first["total"])
|
|
}
|
|
for _, id := range listIDs(first) {
|
|
for _, other := range listIDs(second) {
|
|
if id == other {
|
|
t.Errorf("id %d on both pages", id)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Counts stay unfiltered", func(t *testing.T) {
|
|
e := newSeoOutEnv(t)
|
|
e.cleanArticles()
|
|
for id := uint32(1); id <= 6; id++ {
|
|
online := id <= 4
|
|
e.seedListed(id, "A"+strconv.Itoa(int(id)), online, func(a *article.Article) {
|
|
if id%2 == 0 {
|
|
a.CoverImageID, a.CoverImageAlt = nil, ""
|
|
}
|
|
})
|
|
}
|
|
list := e.list(t, "owner", "?seo=suggestions")
|
|
if !reflect.DeepEqual(list["counts"], map[string]any{"all": 6.0, "online": 4.0, "draft": 2.0}) || list["total"] != 3.0 {
|
|
t.Errorf("list = %v %v", list["counts"], list["total"])
|
|
}
|
|
})
|
|
|
|
t.Run("The filter is applied before paging", func(t *testing.T) {
|
|
e := newSeoOutEnv(t)
|
|
e.cleanArticles()
|
|
for id := uint32(1); id <= 30; id++ {
|
|
e.seedListed(id, "A"+strconv.Itoa(int(id)), true, func(a *article.Article) {
|
|
if id <= 12 {
|
|
a.CoverImageID, a.CoverImageAlt = nil, ""
|
|
}
|
|
})
|
|
}
|
|
list := e.list(t, "owner", "?seo=suggestions&size=5&page=3")
|
|
if len(listIDs(list)) != 2 || list["total"] != 12.0 {
|
|
t.Errorf("ids %v total %v", listIDs(list), list["total"])
|
|
}
|
|
})
|
|
|
|
t.Run("The filter respects visibility", func(t *testing.T) {
|
|
e := newSeoOutEnv(t)
|
|
e.cleanArticles()
|
|
e.seedListed(1, "Offline", false, func(a *article.Article) { a.CoverImageID, a.CoverImageAlt = nil, "" })
|
|
list := e.list(t, "", "?seo=suggestions")
|
|
if len(listIDs(list)) != 0 || list["total"] != 0.0 {
|
|
t.Errorf("list = %v", list)
|
|
}
|
|
})
|
|
|
|
t.Run("Dismissed codes are never counted in the filter", func(t *testing.T) {
|
|
e := newSeoOutEnv(t)
|
|
e.cleanArticles()
|
|
e.seedListed(1, "Dismissed", true, func(a *article.Article) {
|
|
a.CoverImageID, a.CoverImageAlt = nil, ""
|
|
a.SEODismissed = []string{"cover_none"}
|
|
})
|
|
if ids := listIDs(e.list(t, "owner", "?seo=suggestions")); len(ids) != 0 {
|
|
t.Errorf("ids = %v", ids)
|
|
}
|
|
})
|
|
|
|
t.Run("An invalid seo value is refused", func(t *testing.T) {
|
|
for _, query := range []string{"seo=maybe", "seo=", "seo=READY", "seo=ready,hidden", "seo=1"} {
|
|
e := newSeoOutEnv(t)
|
|
e.seedFourListed()
|
|
before := e.counted.readCalls()
|
|
expectStatusAndText(t, e.call(t, "owner", http.MethodGet, "/v1/article/?"+query, ""), 400, apperr.BodyErrorMessage)
|
|
if e.counted.readCalls() != before {
|
|
t.Errorf("%s: the repository was called", query)
|
|
}
|
|
}
|
|
})
|
|
|
|
t.Run("Invalid format is still reported first", func(t *testing.T) {
|
|
e := newSeoOutEnv(t)
|
|
expectStatusAndText(t, e.call(t, "owner", http.MethodGet, "/v1/article/?seo=maybe&format=pdf", ""), 400, apperr.ArticleBodyFormatErrorMessage)
|
|
})
|
|
|
|
t.Run("The existing list behaviour is unchanged without the filter", func(t *testing.T) {
|
|
e := newSeoOutEnv(t)
|
|
list := e.list(t, "owner", "")
|
|
if list["total"] != 2.0 || list["page"] != 1.0 || list["size"] != 25.0 || !reflect.DeepEqual(list["counts"], map[string]any{"all": 2.0, "online": 1.0, "draft": 1.0}) {
|
|
t.Errorf("list = %v", list)
|
|
}
|
|
})
|
|
}
|