feat: storage, language rules and migration
This commit is contained in:
1 parent
8c4a1825ab
commit
0806733d5c
70 files changed
+1771
-132
No files matched your search
@@ -0,0 +1,122 @@
|
||||
package test
|
||||
|
||||
import (
|
||||
domainArticle "RenewCMS/internal/domain/article"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const (
|
||||
localizedArticleID = 12
|
||||
localizedArticleStory = 5
|
||||
)
|
||||
|
||||
var legacyListItemKeys = []string{"id", "title", "body", "body_format", "is_online", "site_id", "authors", "created_at", "updated_at", "excerpt", "slug", "seo_status", "seo_suggestions"}
|
||||
|
||||
var legacyArticleKeys = []string{"id", "title", "body", "is_online", "site_id", "authors", "slug", "excerpt", "seo_title", "seo_description", "cover_image_id", "cover_image_alt", "social_title", "social_description", "social_image_id", "canonical_url", "noindex", "seo_dismissed", "created_at", "updated_at"}
|
||||
|
||||
func localizedArticle(lang string, online bool) domainArticle.Article {
|
||||
return domainArticle.Article{ID: localizedArticleID, Title: "Bonjour le monde", IsOnline: online, CreatedBy: createdByWriterID, Lang: lang, StoryID: localizedArticleStory}
|
||||
}
|
||||
|
||||
func TestArticleLocalizationStorageHTTP(t *testing.T) {
|
||||
assertFields := func(t *testing.T, fields map[string]string, lang string, legacyKeys []string) {
|
||||
t.Helper()
|
||||
if fields["lang"] != `"`+lang+`"` || fields["story_id"] != "5" {
|
||||
t.Errorf(`expected lang %q and story_id 5, got lang=%s story_id=%s`, lang, fields["lang"], fields["story_id"])
|
||||
}
|
||||
for _, key := range legacyKeys {
|
||||
if _, present := fields[key]; !present {
|
||||
t.Errorf("legacy key %q is missing", key)
|
||||
}
|
||||
}
|
||||
}
|
||||
readers := []struct {
|
||||
name string
|
||||
lang string
|
||||
path string
|
||||
auth bool
|
||||
list bool
|
||||
on bool
|
||||
}{
|
||||
{"GET /v1/article/12 with a valid jwt", "fr-CA", "/v1/article/12", true, false, false},
|
||||
{"GET /v1/article/12 with a valid jwt and no language", "", "/v1/article/12", true, false, false},
|
||||
{"GET /v1/article/ list item", "fr-CA", "/v1/article/", true, true, false},
|
||||
{"public article read of an online article", "fr-CA", "/v1/article/12", false, false, true},
|
||||
}
|
||||
for _, reader := range readers {
|
||||
t.Run("Article reads expose lang and story_id "+reader.name, func(t *testing.T) {
|
||||
e, router := newCreatedByEnv(t)
|
||||
e.articles.articles[localizedArticleID] = localizedArticle(reader.lang, reader.on)
|
||||
var options []activityRequest
|
||||
if reader.auth {
|
||||
options = append(options, asUser(e, activityOwnerID))
|
||||
}
|
||||
rec := sendCreatedBy(router, http.MethodGet, reader.path, "", options...)
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status %d: %s", rec.Code, rec.Body)
|
||||
}
|
||||
var fields map[string]string
|
||||
if reader.list {
|
||||
for _, item := range decodeArticleItems(t, rec.Body.Bytes()) {
|
||||
if string(item["id"]) == "12" {
|
||||
fields = rawFields(item)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fields = rawFields(decodeArticleFields(t, rec.Body.Bytes()))
|
||||
}
|
||||
if fields == nil {
|
||||
t.Fatalf("article 12 not found in %s", rec.Body)
|
||||
}
|
||||
legacyKeys := legacyArticleKeys
|
||||
if reader.list {
|
||||
legacyKeys = legacyListItemKeys
|
||||
}
|
||||
assertFields(t, fields, reader.lang, legacyKeys)
|
||||
})
|
||||
}
|
||||
|
||||
t.Run("Article write payloads cannot set lang or story_id", func(t *testing.T) {
|
||||
e, router := newCreatedByEnv(t)
|
||||
e.articles.articles[localizedArticleID] = localizedArticle("fr-CA", false)
|
||||
|
||||
created := sendCreatedBy(router, http.MethodPost, "/v1/article/", `{"title":"Fresh draft title","body":"","lang":"en","story_id":99}`, asUser(e, createdByWriterID))
|
||||
patched := sendCreatedBy(router, http.MethodPatch, "/v1/article/12", `{"excerpt":"Short","lang":"en","story_id":99}`, asUser(e, activityOwnerID))
|
||||
|
||||
if created.Code != http.StatusCreated || patched.Code != http.StatusOK {
|
||||
t.Fatalf("statuses %d and %d: %s %s", created.Code, patched.Code, created.Body, patched.Body)
|
||||
}
|
||||
createdFields := rawFields(decodeArticleFields(t, created.Body.Bytes()))
|
||||
if createdFields["lang"] == `"en"` || createdFields["story_id"] == "99" {
|
||||
t.Errorf("the create payload set lang or story_id: %v", createdFields)
|
||||
}
|
||||
assertFields(t, rawFields(decodeArticleFields(t, patched.Body.Bytes())), "fr-CA", legacyArticleKeys)
|
||||
if stored := e.articles.articles[localizedArticleID]; stored.Lang != "fr-CA" || stored.StoryID != localizedArticleStory {
|
||||
t.Errorf("the stored language or story changed: %+v", stored)
|
||||
}
|
||||
})
|
||||
t.Run("A patch carrying only lang and story_id is refused like a patch with no updatable field", func(t *testing.T) {
|
||||
e, router := newCreatedByEnv(t)
|
||||
e.articles.articles[localizedArticleID] = localizedArticle("fr-CA", false)
|
||||
|
||||
empty := sendCreatedBy(router, http.MethodPatch, "/v1/article/12", `{}`, asUser(e, activityOwnerID))
|
||||
localizationOnly := sendCreatedBy(router, http.MethodPatch, "/v1/article/12", `{"lang":"en","story_id":99}`, asUser(e, activityOwnerID))
|
||||
|
||||
if empty.Code != http.StatusBadRequest || localizationOnly.Code != empty.Code {
|
||||
t.Fatalf("expected both to be refused with 400, got %d and %d", empty.Code, localizationOnly.Code)
|
||||
}
|
||||
if stored := e.articles.articles[localizedArticleID]; stored.Lang != "fr-CA" || stored.StoryID != localizedArticleStory {
|
||||
t.Errorf("the stored language or story changed: %+v", stored)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func rawFields(object map[string]json.RawMessage) map[string]string {
|
||||
flat := make(map[string]string, len(object))
|
||||
for key, value := range object {
|
||||
flat[key] = string(value)
|
||||
}
|
||||
return flat
|
||||
}
|
||||
Reference in new issue
Block a user