diff --git a/content_descriptors_test.go b/content_descriptors_test.go index 162e86e..8dc0332 100644 --- a/content_descriptors_test.go +++ b/content_descriptors_test.go @@ -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) + } + }) +} diff --git a/menu_item_types_test.go b/menu_item_types_test.go index a1fe39a..fcde653 100644 --- a/menu_item_types_test.go +++ b/menu_item_types_test.go @@ -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) + } +}