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 }