test: content descriptors, journal-tag type and translated slugs

The descriptor matrix for posts, categories and tags: Describe per locale
with and without Polish unprefixed, Match exact and inexact per locale,
Records paging and cursors, host patterns, unknown and malformed ids;
journal-tag with tag_pages by default, on and off; translated slugs in
menu items and matching, including a changed Polish slug.
This commit is contained in:
2026-10-27 13:29:46 +01:00
parent c5b71472ac
commit fea88f8ad7
2 changed files with 230 additions and 0 deletions

View File

@@ -249,3 +249,147 @@ func TestJournalTagType(t *testing.T) {
t.Fatalf("tag resolved without tag pages: %+v %v", got, err)
}
}
// TestContentDescriptorsMatrix (D-93, D-20c, D-58): the descriptor matrix
// for posts, categories and tags: Describe per locale with and without an
// unprefixed locale, Match exact and inexact per locale, Records paging,
// host patterns, and unknown or malformed ids.
func TestContentDescriptorsMatrix(t *testing.T) {
requirePostgres(t)
ctx := context.Background()
t.Run("describe and match with Polish unprefixed", func(t *testing.T) {
_, gdb, reg := bootMenuItemTypes(t, map[string]string{})
s := seedContent(t, gdb)
for _, tc := range []struct{ key, id, want string }{
{classes.MenuTypePost, id(s.hello.ID), "en=/en/blog/hello pl=/blog/witaj"},
{classes.MenuTypeCategory, id(s.news.ID), "en=/en/blog/category/news pl=/blog/category/wiadomosci"},
{classes.MenuTypeTag, id(s.tag.ID), "en=/en/blog/tag/mushrooms pl=/blog/tag/mushrooms"},
} {
rec, err := reg.Describe(ctx, tc.key, tc.id, "pl")
if err != nil || rec == nil || localeURLs(rec) != tc.want {
t.Fatalf("Describe(%s) = %+v, %v; want %s", tc.key, rec, err, tc.want)
}
}
for _, tc := range []struct {
path, key, locale string
exact bool
}{
{"/blog/witaj", classes.MenuTypePost, "pl", true},
{"/en/blog/hello", classes.MenuTypePost, "en", true},
{"/pl/blog/witaj", classes.MenuTypePost, "pl", false},
{"/blog/hello", classes.MenuTypePost, "pl", false},
{"/blog/category/wiadomosci", classes.MenuTypeCategory, "pl", true},
{"/en/blog/category/news", classes.MenuTypeCategory, "en", true},
{"/blog/category/news", classes.MenuTypeCategory, "pl", false},
{"/blog/tag/mushrooms", classes.MenuTypeTag, "pl", true},
{"/en/blog/tag/mushrooms", classes.MenuTypeTag, "en", true},
} {
key, res, err := reg.MatchPath(ctx, tc.path, "pl")
if err != nil || key != tc.key || res == nil || res.Locale != tc.locale || res.Exact != tc.exact {
t.Fatalf("MatchPath(%s, pl) = %q %+v %v, want %s %s exact=%v", tc.path, key, res, err, tc.key, tc.locale, tc.exact)
}
}
})
t.Run("match per locale without an unprefixed locale", func(t *testing.T) {
_, gdb, reg := bootMenuItemTypes(t, map[string]string{})
s := seedContent(t, gdb)
for _, tc := range []struct {
path, key, locale string
exact bool
}{
{"/blog/category/wiadomosci", classes.MenuTypeCategory, "en", false},
{"/pl/blog/category/news", classes.MenuTypeCategory, "pl", false},
{"/en/blog/category/news", classes.MenuTypeCategory, "en", false},
{"/pl/blog/tag/mushrooms", classes.MenuTypeTag, "pl", true},
} {
key, res, err := reg.MatchPath(ctx, tc.path, "")
if err != nil || key != tc.key || res == nil || res.Locale != tc.locale || res.Exact != tc.exact {
t.Fatalf("MatchPath(%s) = %q %+v %v, want %s %s exact=%v", tc.path, key, res, err, tc.key, tc.locale, tc.exact)
}
}
for _, path := range []string{"/pl/blog/draft", "/blog/tag/missing", "/blog/category/missing", "/blog/category/news/extra", "/blog/tag/"} {
if key, res, err := reg.MatchPath(ctx, path, ""); err != nil || res != nil {
t.Fatalf("MatchPath(%s) = %q %+v %v, want no match", path, key, res, err)
}
}
for _, tc := range []struct{ key, id string }{
{classes.MenuTypePost, "999999"}, {classes.MenuTypePost, "abc"},
{classes.MenuTypeCategory, "999999"}, {classes.MenuTypeTag, "999999"}, {classes.MenuTypeTag, ""},
} {
if rec, err := reg.Describe(ctx, tc.key, tc.id, ""); err != nil || rec != nil {
t.Fatalf("Describe(%s, %q) = %+v, %v; want nothing", tc.key, tc.id, rec, err)
}
}
draft, err := reg.Describe(ctx, classes.MenuTypePost, id(s.draft.ID), "")
if err != nil || draft == nil || draft.Published || localeURLs(draft) != "en=/blog/draft pl=/pl/blog/draft" {
t.Fatalf("Describe draft = %+v, %v", draft, err)
}
})
t.Run("records paging", func(t *testing.T) {
_, gdb, reg := bootMenuItemTypes(t, map[string]string{})
s := seedContent(t, gdb)
second := models.Tag{Name: "Forest", Slug: "forest"}
if err := gdb.Create(&second).Error; err != nil {
t.Fatal(err)
}
all, err := reg.Records(ctx, classes.MenuTypePost, "", 0, "")
if err != nil || len(all.Records) != 2 || all.Next != "" {
t.Fatalf("limit 0 = %+v, %v", all, err)
}
if again, err := reg.Records(ctx, classes.MenuTypePost, "not-an-id", 0, ""); err != nil || len(again.Records) != 2 {
t.Fatalf("a malformed cursor = %+v, %v; want the first page", again, err)
}
if end, err := reg.Records(ctx, classes.MenuTypePost, id(s.draft.ID), 10, ""); err != nil || len(end.Records) != 0 || end.Next != "" {
t.Fatalf("after the last post = %+v, %v", end, err)
}
first, err := reg.Records(ctx, classes.MenuTypeTag, "", 1, "")
if err != nil || len(first.Records) != 1 || first.Records[0].ID != id(s.tag.ID) || first.Next != id(s.tag.ID) {
t.Fatalf("first tag page = %+v, %v", first, err)
}
next, err := reg.Records(ctx, classes.MenuTypeTag, first.Next, 1, "")
if err != nil || len(next.Records) != 1 || next.Records[0].ID != id(second.ID) || next.Next != "" {
t.Fatalf("second tag page = %+v, %v", next, err)
}
if got := localeURLs(&next.Records[0]); got != "en=/blog/tag/forest pl=/pl/blog/tag/forest" {
t.Fatalf("paged tag URLs = %s", got)
}
if !all.Records[0].Published || all.Records[1].Published {
t.Fatalf("published flags = %v %v", all.Records[0].Published, all.Records[1].Published)
}
})
t.Run("host patterns", func(t *testing.T) {
_, gdb, reg := bootMenuItemTypes(t, map[string]string{"menu_items.yaml": "patterns:\n journal-category: /c/:slug\n journal-tag: /t/:slug\n journal-post: /:category/:slug\n"})
s := seedContent(t, gdb)
for _, tc := range []struct{ key, id, want string }{
{classes.MenuTypeCategory, id(s.news.ID), "en=/c/news pl=/pl/c/wiadomosci"},
{classes.MenuTypeTag, id(s.tag.ID), "en=/t/mushrooms pl=/pl/t/mushrooms"},
{classes.MenuTypePost, id(s.hello.ID), "en=/news/hello pl=/pl/wiadomosci/witaj"},
} {
rec, err := reg.Describe(ctx, tc.key, tc.id, "")
if err != nil || rec == nil || localeURLs(rec) != tc.want {
t.Fatalf("Describe(%s) = %+v, %v; want %s", tc.key, rec, err, tc.want)
}
}
for _, tc := range []struct {
path, key string
exact bool
}{
{"/c/news", classes.MenuTypeCategory, true},
{"/t/mushrooms", classes.MenuTypeTag, true},
{"/news/hello", classes.MenuTypePost, true},
{"/pl/wiadomosci/witaj", classes.MenuTypePost, true},
} {
key, res, err := reg.MatchPath(ctx, tc.path, "")
if err != nil || key != tc.key || res == nil || res.Exact != tc.exact {
t.Fatalf("MatchPath(%s) = %q %+v %v", tc.path, key, res, err)
}
}
if key, res, err := reg.MatchPath(ctx, "/blog/category/news", ""); err != nil || res != nil {
t.Fatalf("the default pattern still matched: %q %+v %v", key, res, err)
}
})
}

View File

@@ -247,3 +247,89 @@ func resolve(t *testing.T, reg *signpost.Registry, key, ref, pattern string, nes
}
return got
}
// TestJournalTagTypePages (D-102, P-11): journal-tag with tag_pages on
// (the default and set explicitly) and off: references, Resolve, Describe
// and Match by slug, by id pattern and under the Polish prefix.
func TestJournalTagTypePages(t *testing.T) {
requirePostgres(t)
ctx := context.Background()
for _, tc := range []struct {
name string
files map[string]string
on bool
}{
{"default", map[string]string{}, true},
{"explicitly on", map[string]string{"golem15.yaml": "journal:\n tag_pages: true\n"}, true},
{"off", map[string]string{"golem15.yaml": "journal:\n tag_pages: false\n"}, false},
} {
t.Run(tc.name, func(t *testing.T) {
_, gdb, reg := bootMenuItemTypes(t, tc.files)
s := seedContent(t, gdb)
refs, err := signpost.ReferenceOptions(ctx, reg, classes.MenuTypeTag)
if err != nil || len(refs.Options) != 1 || refs.Options[0].Value != id(s.tag.ID) {
t.Fatalf("references = %+v, %v (listed whether or not the site has tag pages)", refs, err)
}
item, err := reg.Resolve(ctx, classes.MenuTypeTag, id(s.tag.ID), "", false, "")
if err != nil || (item != nil) != tc.on || (tc.on && (item.URL != "/blog/tag/mushrooms" || altString(item.Alternates) != "en=/blog/tag/mushrooms pl=/pl/blog/tag/mushrooms")) {
t.Fatalf("Resolve = %+v, %v", item, err)
}
rec, err := reg.Describe(ctx, classes.MenuTypeTag, id(s.tag.ID), "")
want := "en= pl="
if tc.on {
want = "en=/blog/tag/mushrooms pl=/pl/blog/tag/mushrooms"
}
if err != nil || rec == nil || localeURLs(rec) != want || rec.Locales[0].Title != "Mushrooms" {
t.Fatalf("Describe = %+v, %v; want %s", rec, err, want)
}
typ, _ := reg.ContentType(classes.MenuTypeTag)
for _, m := range []pact.ContentMatch{
{Path: "/blog/tag/mushrooms", Pattern: classes.DefaultTagPattern},
{Path: "/pl/blog/tag/mushrooms", Pattern: classes.DefaultTagPattern},
{Path: "/t/" + id(s.tag.ID), Pattern: "/t/:id"},
} {
res, err := typ.Content.Match(ctx, m)
if err != nil || (res != nil) != tc.on || (tc.on && (!res.Exact || res.ID != id(s.tag.ID))) {
t.Fatalf("Match(%s) = %+v, %v", m.Path, res, err)
}
}
if res, err := typ.Content.Match(ctx, pact.ContentMatch{Path: "/t/abc", Pattern: "/t/:id"}); err != nil || res != nil {
t.Fatalf("a malformed id matched: %+v %v", res, err)
}
})
}
}
// TestTranslatedSlugMenuItems (D-58): a post and a category resolve and
// match through their translated slugs in each locale, and the default
// slug stays the English address.
func TestTranslatedSlugMenuItems(t *testing.T) {
requirePostgres(t)
ctx := context.Background()
_, gdb, reg := bootMenuItemTypes(t, map[string]string{})
s := seedContent(t, gdb)
post := resolve(t, reg, classes.MenuTypePost, id(s.hello.ID), "", false)
if altString(post.Alternates) != "en=/blog/hello pl=/pl/blog/witaj" {
t.Fatalf("post alternates = %s", altString(post.Alternates))
}
cat := resolve(t, reg, classes.MenuTypeCategory, id(s.news.ID), "", false)
if altString(cat.Alternates) != "en=/blog/category/news pl=/pl/blog/category/wiadomosci" {
t.Fatalf("category alternates = %s", altString(cat.Alternates))
}
unprefixed := resolve(t, reg, classes.MenuTypePost, id(s.hello.ID), "", false, "pl")
if altString(unprefixed.Alternates) != "en=/en/blog/hello pl=/blog/witaj" {
t.Fatalf("unprefixed pl alternates = %s", altString(unprefixed.Alternates))
}
if key, res, err := reg.MatchPath(ctx, "/pl/blog/witaj", ""); err != nil || key != classes.MenuTypePost || res == nil || !res.Exact || res.ID != id(s.hello.ID) {
t.Fatalf("the translated slug = %q %+v %v", key, res, err)
}
if err := translate.SetTranslated(ctx, gdb, &s.hello, "slug", "pl", "witamy"); err != nil {
t.Fatal(err)
}
if key, res, err := reg.MatchPath(ctx, "/pl/blog/witamy", ""); err != nil || key != classes.MenuTypePost || res == nil || !res.Exact {
t.Fatalf("a changed translated slug = %q %+v %v", key, res, err)
}
if _, res, err := reg.MatchPath(ctx, "/pl/blog/witaj", ""); err != nil || res != nil {
t.Fatalf("the old translated slug still matches: %+v %v", res, err)
}
}