package sitemap_test import ( "context" "encoding/json" "io/fs" "net/http" "testing" "testing/fstest" "time" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/party" ) // labelsPluginID is a fake content plugin whose type label is a phrase // with an English and a Polish translation, as a real plugin's is. const labelsPluginID = "acme.labels" const noteType = "acme-note" type labelsPlugin struct{} func (labelsPlugin) ID() string { return labelsPluginID } func (labelsPlugin) Requires() []string { return nil } func (labelsPlugin) Register(*backpack.App) error { return nil } func (labelsPlugin) Boot(*backpack.App) error { return nil } func (labelsPlugin) LangFS() fs.FS { return fstest.MapFS{ "lang/en/lang.yaml": {Data: []byte("types:\n notes: Notes\n")}, "lang/pl/lang.yaml": {Data: []byte("types:\n notes: Notatki\n")}, } } func (labelsPlugin) MenuItemTypes() []pact.MenuItemType { return []pact.MenuItemType{{ Key: noteType, Label: "acme.labels::lang.types.notes", DefaultPattern: "/notes/:slug", Placeholders: []string{"slug"}, Resolve: func(context.Context, pact.MenuItemResolve) (*pact.ResolvedMenuItem, error) { return nil, nil }, Content: &pact.ContentDescriptor{ Model: "acme_notes", Records: func(_ context.Context, in pact.ContentList) (pact.ContentPage, error) { return contentStore.page(noteType, in), nil }, Describe: func(context.Context, pact.ContentDescribe) (*pact.ContentRecord, error) { return nil, nil }, }, }} } var ( _ pact.HasMenuItemTypes = labelsPlugin{} _ pact.HasLang = labelsPlugin{} ) func init() { party.Register(labelsPlugin{}) } // TestTypeTitlesNotPinned pins L2 WR-05 (D-34): an administrator with an // English admin loads Settings › Sitemaps and saves it back unchanged; no // heading is stored for the content type, so the Polish HTML sitemap keeps // the Polish translation of the type's label and the English one the // English. func TestTypeTitlesNotPinned(t *testing.T) { contentStore.reset(t) e := bootPlugins(t, []string{labelsPluginID, "golem15.sitemap"}) e.exec(t, `UPDATE golem15_sitemap_settings SET default_locale = 'pl', html_enabled = TRUE WHERE id = 1`) at := time.Date(2026, 10, 1, 8, 0, 0, 0, time.UTC) contentStore.put(noteType, fakeRecord{ID: 1, Published: true, LastMod: at, Locales: []pact.ContentLocale{ {Locale: "pl", URL: "/notes/pierwsza", Title: "Pierwsza"}, {Locale: "en", URL: "/en/notes/first", Title: "First"}, }}) token := e.admin(t, permSitemap) const base = "/golem15/sitemap/settings" rec := e.call(t, http.MethodGet, base+"/1", token, nil) if rec.Code != http.StatusOK { t.Fatalf("GET settings: %d %s", rec.Code, rec.Body.String()) } var loaded struct { Data map[string]any `json:"data"` } if err := json.Unmarshal(rec.Body.Bytes(), &loaded); err != nil { t.Fatal(err) } values := loaded.Data if values == nil { if err := json.Unmarshal(rec.Body.Bytes(), &values); err != nil { t.Fatal(err) } } title, ok := values["type_acme_note_title"] if !ok { t.Fatalf("the form loads no heading field: %s", rec.Body.String()) } m, _ := title.(map[string]any) if len(m) != 0 { t.Errorf("the form loads a heading %v for a type without one", title) } // Without a translation writer the form sends the default locale's // text of the loaded value back. sent, _ := m["pl"].(string) if rec := e.call(t, http.MethodPut, base+"/1", token, map[string]any{ "type_acme_note_include": values["type_acme_note_include"], "type_acme_note_slug": values["type_acme_note_slug"], "type_acme_note_title": sent, }); rec.Code != http.StatusOK { t.Fatalf("PUT settings: %d %s", rec.Code, rec.Body.String()) } var stored []string e.gdb.Raw(`SELECT titles::text FROM golem15_sitemap_types WHERE type_key = ?`, noteType).Scan(&stored) for _, s := range stored { if s != "{}" && s != "null" { t.Fatalf("a save that did not touch the heading stored %s", s) } } e.rebuild(t) for locale, want := range map[string]string{"pl": "Notatki", "en": "Notes"} { ov := e.htmlJSON(t, "/_sitemap/api/v1/html?locale="+locale, http.StatusOK) groups, _ := ov["groups"].([]any) if len(groups) != 1 { t.Fatalf("%s groups = %v", locale, ov) } if got := groups[0].(map[string]any)["title"]; got != want { t.Errorf("%s group title = %v, want %s", locale, got, want) } } }