package handlers import ( "bookhoard/internal/opds" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // rels collects the rel attributes of all links currently on the feed. func rels(feed *opds.Feed) []string { out := make([]string, 0, len(feed.Links)) for _, l := range feed.Links { out = append(out, l.Rel) } return out } func containsRel(feed *opds.Feed, rel string) bool { for _, l := range feed.Links { if l.Rel == rel { return true } } return false } func TestAddCatalogPaginationLinks_MiddlePage(t *testing.T) { feed := opds.NewFeed("urn:uuid:dev", "Library") // 1814 items, 50 per page => 37 pages; on page 2 addCatalogPaginationLinks(feed, "http://h/opds/devices/dev/catalog", 2, 50, 1814, "tok") assert.True(t, containsRel(feed, "self")) assert.True(t, containsRel(feed, "start")) assert.True(t, containsRel(feed, "first")) assert.True(t, containsRel(feed, "last")) assert.True(t, containsRel(feed, "previous"), "middle page must have previous") assert.True(t, containsRel(feed, "next"), "middle page must have next") // self must point to the current page var selfHref string for _, l := range feed.Links { if l.Rel == "self" { selfHref = l.Href } } assert.Contains(t, selfHref, "page=2&per_page=50") assert.Contains(t, selfHref, "token=tok") // next must advance the page var nextHref string for _, l := range feed.Links { if l.Rel == "next" { nextHref = l.Href } } assert.Contains(t, nextHref, "page=3") // OpenSearch metadata require.NotNil(t, feed.TotalResults) assert.Equal(t, 1814, *feed.TotalResults) require.NotNil(t, feed.ItemsPerPage) assert.Equal(t, 50, *feed.ItemsPerPage) require.NotNil(t, feed.StartIndex) assert.Equal(t, 51, *feed.StartIndex, "startIndex should be 1-based offset of first item on page 2") } func TestAddCatalogPaginationLinks_FirstPage_NoPrevious(t *testing.T) { feed := opds.NewFeed("urn:uuid:dev", "Library") addCatalogPaginationLinks(feed, "http://h/opds/devices/dev/catalog", 1, 50, 1814, "") rels := rels(feed) assert.NotContains(t, rels, "previous", "first page must not have previous") assert.Contains(t, rels, "next") } func TestAddCatalogPaginationLinks_LastPage_NoNext(t *testing.T) { feed := opds.NewFeed("urn:uuid:dev", "Library") addCatalogPaginationLinks(feed, "http://h/opds/devices/dev/catalog", 37, 50, 1814, "") rels := rels(feed) assert.NotContains(t, rels, "next", "last page must not have next") assert.Contains(t, rels, "previous") } func TestAddCatalogPaginationLinks_SinglePage(t *testing.T) { feed := opds.NewFeed("urn:uuid:dev", "Library") addCatalogPaginationLinks(feed, "http://h/opds/devices/dev/catalog", 1, 50, 10, "") rels := rels(feed) assert.NotContains(t, rels, "previous") assert.NotContains(t, rels, "next") // still emits self/start/first/last assert.Contains(t, rels, "self") assert.Contains(t, rels, "last") } func TestAddCatalogPaginationLinks_EmptyCatalog(t *testing.T) { feed := opds.NewFeed("urn:uuid:dev", "Library") addCatalogPaginationLinks(feed, "http://h/opds/devices/dev/catalog", 1, 50, 0, "") rels := rels(feed) assert.NotContains(t, rels, "next") assert.NotContains(t, rels, "previous") assert.NotContains(t, rels, "last", "empty catalog should not advertise a last page") require.NotNil(t, feed.TotalResults) assert.Equal(t, 0, *feed.TotalResults) } func TestAddCatalogPaginationLinks_TokenAppended(t *testing.T) { feed := opds.NewFeed("urn:uuid:dev", "Library") addCatalogPaginationLinks(feed, "http://h/opds/devices/dev/catalog", 1, 50, 100, "abc") xml, err := feed.GenerateXMLString() require.NoError(t, err) assert.True(t, strings.Count(xml, "token=abc") >= 3, "token should be appended to generated links") }