159 lines
5.9 KiB
Go
159 lines
5.9 KiB
Go
package test
|
|
|
|
import (
|
|
"RenewCMS/api"
|
|
"RenewCMS/internal/domain/article"
|
|
"RenewCMS/internal/domain/site"
|
|
"RenewCMS/internal/domain/user"
|
|
"RenewCMS/internal/infrastructure/useCases"
|
|
"encoding/json"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
)
|
|
|
|
const (
|
|
seoEditorID = 5
|
|
seoSpringID = 8
|
|
seoOtherID = 9
|
|
seoCoverImage = 5
|
|
seoSocialImage = 6
|
|
seoSlugHeader = "X-Slug-Suggestion"
|
|
seoSlugTaken = "slug_taken"
|
|
seoTakenReason = "That slug is already used by another article on this site."
|
|
seoSpringTitle = "Spring release"
|
|
seoSpringSlug = "spring-release"
|
|
)
|
|
|
|
type seoEnv struct {
|
|
*producersEnv
|
|
sites *mockSiteRepo
|
|
authors *mockAuthorRepo
|
|
}
|
|
|
|
func newSeoEnv(t *testing.T) *seoEnv {
|
|
t.Helper()
|
|
p := newProducersEnv(t)
|
|
p.users.users[seoEditorID] = user.User{ID: seoEditorID, Username: "edith", Email: "edith@example.com", IsVerified: true, Role: user.Role("editor")}
|
|
p.users.nextID = seoEditorID
|
|
authors := newMockAuthorRepo(p.articles)
|
|
sites := newMockSiteRepo(p.articles)
|
|
sites.seed(siteMainID, "Main", "main", true)
|
|
sites.seed(siteShopID, "Shop", "shop", false)
|
|
for id, seeded := range sites.sites {
|
|
seeded.TitleTemplate = site.DefaultTitleTemplate
|
|
sites.sites[id] = seeded
|
|
}
|
|
recorder := p.audit.recorder
|
|
renderer := &fakeBodyRenderer{}
|
|
mergeMedia := useCases.NewMergeMediaUseCase(p.images, p.articles, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, authors)
|
|
c := p.container
|
|
c.CreateArticleUseCase = useCases.NewCreateArticleUseCase(p.articles, renderer, recorder, sites, authors)
|
|
c.UpdateArticleUseCase = useCases.NewUpdateArticleUseCase(p.articles, renderer, recorder, sites, authors, p.images)
|
|
c.ListArticlesUseCase = useCases.NewListArticlesUseCase(p.articles, renderer, sites)
|
|
c.ListMediaUseCase = useCases.NewListMediaUseCase(p.images, p.articles, p.users, newMockVariantFileRepo(), sites, authors)
|
|
c.DeleteMediaUseCase = useCases.NewDeleteMediaUseCase(p.images, newMockImageStorage(), newMockVariantFileRepo(), recorder, sites, authors, p.articles)
|
|
c.GetMediaUsageUseCase = useCases.NewGetMediaUsageUseCase(p.images, p.articles, sites, authors)
|
|
c.MergeMediaUseCase = mergeMedia
|
|
c.RunPurgeUseCase = useCases.NewRunPurgeUseCase(p.images, p.articles, mergeMedia, newMockPurgeSettingsRepo(), func() time.Time { return p.now }, log.New(io.Discard, "", 0), recorder, sites, authors)
|
|
c.PreviewPurgeUseCase = useCases.NewPreviewPurgeUseCase(p.images, p.articles, sites, authors)
|
|
c.ListSitesUseCase = useCases.NewListSitesUseCase(sites)
|
|
c.CreateSiteUseCase = useCases.NewCreateSiteUseCase(sites, p.images, recorder)
|
|
c.UpdateSiteUseCase = useCases.NewUpdateSiteUseCase(sites, p.images, recorder)
|
|
c.SetDefaultSiteUseCase = useCases.NewSetDefaultSiteUseCase(sites, recorder)
|
|
c.DeleteSiteUseCase = useCases.NewDeleteSiteUseCase(sites, recorder)
|
|
c.ListActivityUseCase = useCases.NewListActivityUseCase(p.audit.store, p.users, p.articles, p.images, p.keys, p.audit.clock, sites, authors)
|
|
router := chi.NewRouter()
|
|
router.Mount("/v1", api.InitBackendRoutes(c, p.settings))
|
|
p.router = router
|
|
p.seedBanner(seoCoverImage, "cover.png", "hash-cover")
|
|
p.seedBanner(seoSocialImage, "social.png", "hash-social")
|
|
return &seoEnv{producersEnv: p, sites: sites, authors: authors}
|
|
}
|
|
|
|
func (e *seoEnv) seedArticle(id uint32, title string, siteID uint32, slug string) article.Article {
|
|
seeded := article.Article{ID: id, Title: title, Body: "Body of " + title, SiteID: siteID, Slug: slug, SEODismissed: []string{}}
|
|
e.articles.articles[id] = seeded
|
|
if id > e.articles.nextID {
|
|
e.articles.nextID = id
|
|
}
|
|
return seeded
|
|
}
|
|
|
|
func (e *seoEnv) seedSpring() article.Article {
|
|
return e.seedArticle(seoSpringID, seoSpringTitle, siteMainID, seoSpringSlug)
|
|
}
|
|
|
|
func (e *seoEnv) seedFullyOptimised(id uint32) {
|
|
stored := e.seedArticle(id, seoSpringTitle, siteMainID, "spring-full")
|
|
stored.Excerpt = "Short teaser."
|
|
stored.SEOTitle = "Spring release notes"
|
|
stored.SEODescription = "Everything new this spring."
|
|
stored.CoverImageID = idPointer(seoCoverImage)
|
|
stored.CoverImageAlt = "A field of tulips"
|
|
stored.SocialTitle = "Spring is here"
|
|
stored.SocialDescription = "Read the notes."
|
|
stored.SocialImageID = idPointer(seoSocialImage)
|
|
stored.CanonicalURL = "https://www.example.com/spring"
|
|
stored.Noindex = true
|
|
stored.SEODismissed = []string{"cover_alt", "short"}
|
|
e.articles.articles[id] = stored
|
|
}
|
|
|
|
func (e *seoEnv) patch(t *testing.T, id uint32, body string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
return e.owner(t, http.MethodPatch, "/v1/article/"+strconv.Itoa(int(id)), body)
|
|
}
|
|
|
|
func (e *seoEnv) owner(t *testing.T, method string, path string, body string) *httptest.ResponseRecorder {
|
|
t.Helper()
|
|
return e.as(t, apiKeyOwnerId, method, path, body)
|
|
}
|
|
|
|
func (e *seoEnv) stored(id uint32) article.Article {
|
|
return e.articles.articles[id]
|
|
}
|
|
|
|
func decodeSeoObject(t *testing.T, rec *httptest.ResponseRecorder) map[string]any {
|
|
t.Helper()
|
|
var decoded map[string]any
|
|
if err := json.Unmarshal(rec.Body.Bytes(), &decoded); err != nil {
|
|
t.Fatalf("decode %q: %v", rec.Body.String(), err)
|
|
}
|
|
return decoded
|
|
}
|
|
|
|
func expectStatusAndText(t *testing.T, rec *httptest.ResponseRecorder, status int, body string) {
|
|
t.Helper()
|
|
if rec.Code != status || strings.TrimSpace(rec.Body.String()) != body {
|
|
t.Fatalf("expected %d %q, got %d %q", status, body, rec.Code, strings.TrimSpace(rec.Body.String()))
|
|
}
|
|
}
|
|
|
|
func expectStatusAndValidatorText(t *testing.T, rec *httptest.ResponseRecorder, status int) {
|
|
t.Helper()
|
|
if rec.Code != status || !strings.Contains(rec.Body.String(), "Field validation") {
|
|
t.Fatalf("expected %d with the validator text, got %d %q", status, rec.Code, rec.Body.String())
|
|
}
|
|
}
|
|
|
|
func repeated(character string, count int) string {
|
|
return strings.Repeat(character, count)
|
|
}
|
|
|
|
func jsonString(t *testing.T, value string) string {
|
|
t.Helper()
|
|
encoded, err := json.Marshal(value)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return string(encoded)
|
|
}
|