mirror of https://github.com/kiwix/libkiwix.git
Fix the OPDS stream to handle custom ROOT prefix
As we render the entry's xml in a separated steps, we need to pass the rootLocation to all the internal rendering. Testing with and without root is not so easy. I've simply made all server tests using a ROOT prefix. We can assume that if the ROOT is present everywhere we need it, it will not when we don't need. (As long as we don't hardcode "ROOT" in the server.)
This commit is contained in:
parent
22e5327dcf
commit
66c40817ee
|
@ -95,23 +95,24 @@ kainjow::mustache::object getSingleBookData(const Book& book)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getSingleBookEntryXML(const Book& book, bool withXMLHeader, const std::string& endpointRoot, bool partial)
|
std::string getSingleBookEntryXML(const Book& book, bool withXMLHeader, const std::string& rootLocation, const std::string& endpointRoot, bool partial)
|
||||||
{
|
{
|
||||||
auto data = getSingleBookData(book);
|
auto data = getSingleBookData(book);
|
||||||
data["with_xml_header"] = MustacheData(withXMLHeader);
|
data["with_xml_header"] = MustacheData(withXMLHeader);
|
||||||
data["dump_partial_entries"] = MustacheData(partial);
|
data["dump_partial_entries"] = MustacheData(partial);
|
||||||
data["endpoint_root"] = endpointRoot;
|
data["endpoint_root"] = endpointRoot;
|
||||||
|
data["root"] = rootLocation;
|
||||||
return render_template(RESOURCE::templates::catalog_v2_entry_xml, data);
|
return render_template(RESOURCE::templates::catalog_v2_entry_xml, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
BooksData getBooksData(const Library* library, const std::vector<std::string>& bookIds, const std::string& endpointRoot, bool partial)
|
BooksData getBooksData(const Library* library, const std::vector<std::string>& bookIds, const std::string& rootLocation, const std::string& endpointRoot, bool partial)
|
||||||
{
|
{
|
||||||
BooksData booksData;
|
BooksData booksData;
|
||||||
for ( const auto& bookId : bookIds ) {
|
for ( const auto& bookId : bookIds ) {
|
||||||
try {
|
try {
|
||||||
const Book book = library->getBookByIdThreadSafe(bookId);
|
const Book book = library->getBookByIdThreadSafe(bookId);
|
||||||
booksData.push_back(kainjow::mustache::object{
|
booksData.push_back(kainjow::mustache::object{
|
||||||
{"entry", getSingleBookEntryXML(book, false, endpointRoot, partial)}
|
{"entry", getSingleBookEntryXML(book, false, rootLocation, endpointRoot, partial)}
|
||||||
});
|
});
|
||||||
} catch ( const std::out_of_range& ) {
|
} catch ( const std::out_of_range& ) {
|
||||||
// the book was removed from the library since its id was obtained
|
// the book was removed from the library since its id was obtained
|
||||||
|
@ -135,7 +136,7 @@ std::string getLanguageSelfName(const std::string& lang) {
|
||||||
|
|
||||||
string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const
|
string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const std::string& query) const
|
||||||
{
|
{
|
||||||
const auto booksData = getBooksData(library, bookIds, "", false);
|
const auto booksData = getBooksData(library, bookIds, rootLocation, "", false);
|
||||||
const kainjow::mustache::object template_data{
|
const kainjow::mustache::object template_data{
|
||||||
{"date", gen_date_str()},
|
{"date", gen_date_str()},
|
||||||
{"root", rootLocation},
|
{"root", rootLocation},
|
||||||
|
@ -153,7 +154,7 @@ string OPDSDumper::dumpOPDSFeed(const std::vector<std::string>& bookIds, const s
|
||||||
string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const std::string& query, bool partial) const
|
string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const std::string& query, bool partial) const
|
||||||
{
|
{
|
||||||
const auto endpointRoot = rootLocation + "/catalog/v2";
|
const auto endpointRoot = rootLocation + "/catalog/v2";
|
||||||
const auto booksData = getBooksData(library, bookIds, endpointRoot, partial);
|
const auto booksData = getBooksData(library, bookIds, rootLocation, endpointRoot, partial);
|
||||||
|
|
||||||
const char* const endpoint = partial ? "/partial_entries" : "/entries";
|
const char* const endpoint = partial ? "/partial_entries" : "/entries";
|
||||||
const kainjow::mustache::object template_data{
|
const kainjow::mustache::object template_data{
|
||||||
|
@ -174,7 +175,7 @@ string OPDSDumper::dumpOPDSFeedV2(const std::vector<std::string>& bookIds, const
|
||||||
|
|
||||||
std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const
|
std::string OPDSDumper::dumpOPDSCompleteEntry(const std::string& bookId) const
|
||||||
{
|
{
|
||||||
return getSingleBookEntryXML(library->getBookById(bookId), true, "", false);
|
return getSingleBookEntryXML(library->getBookById(bookId), true, rootLocation, "", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string OPDSDumper::categoriesOPDSFeed() const
|
std::string OPDSDumper::categoriesOPDSFeed() const
|
||||||
|
|
|
@ -16,9 +16,9 @@
|
||||||
<articleCount>{{article_count}}</articleCount>
|
<articleCount>{{article_count}}</articleCount>
|
||||||
<mediaCount>{{media_count}}</mediaCount>
|
<mediaCount>{{media_count}}</mediaCount>
|
||||||
{{#icons}}<link rel="http://opds-spec.org/image/thumbnail"
|
{{#icons}}<link rel="http://opds-spec.org/image/thumbnail"
|
||||||
href="/meta?name=Illustration_{{icon_width}}x{{icon_height}}@{{icon_scale}}&content={{{content_id}}}"
|
href="{{root}}/meta?name=Illustration_{{icon_width}}x{{icon_height}}@{{icon_scale}}&content={{{content_id}}}"
|
||||||
type="image/png;width={{icon_width}};height={{icon_height}};scale={{icon_scale}}"/>
|
type="image/png;width={{icon_width}};height={{icon_height}};scale={{icon_scale}}"/>
|
||||||
{{/icons}}<link type="text/html" href="/{{{content_id}}}" />
|
{{/icons}}<link type="text/html" href="{{root}}/{{{content_id}}}" />
|
||||||
<author>
|
<author>
|
||||||
<name>{{author_name}}</name>
|
<name>{{author_name}}</name>
|
||||||
</author>
|
</author>
|
||||||
|
|
245
test/server.cpp
245
test/server.cpp
|
@ -84,7 +84,6 @@ ZimFileServer::ZimFileServer(int serverPort, std::string libraryFilePath)
|
||||||
if ( kiwix::isRelativePath(libraryFilePath) )
|
if ( kiwix::isRelativePath(libraryFilePath) )
|
||||||
libraryFilePath = kiwix::computeAbsolutePath(kiwix::getCurrentDirectory(), libraryFilePath);
|
libraryFilePath = kiwix::computeAbsolutePath(kiwix::getCurrentDirectory(), libraryFilePath);
|
||||||
manager.readFile(libraryFilePath, true, true);
|
manager.readFile(libraryFilePath, true, true);
|
||||||
|
|
||||||
run(serverPort);
|
run(serverPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,7 +94,6 @@ ZimFileServer::ZimFileServer(int serverPort, const FilePathCollection& zimpaths,
|
||||||
if (!manager.addBookFromPath(zimpath, zimpath, "", false))
|
if (!manager.addBookFromPath(zimpath, zimpath, "", false))
|
||||||
throw std::runtime_error("Unable to add the ZIM file '" + zimpath + "'");
|
throw std::runtime_error("Unable to add the ZIM file '" + zimpath + "'");
|
||||||
}
|
}
|
||||||
|
|
||||||
run(serverPort, indexTemplateString);
|
run(serverPort, indexTemplateString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,6 +102,7 @@ void ZimFileServer::run(int serverPort, std::string indexTemplateString)
|
||||||
const std::string address = "127.0.0.1";
|
const std::string address = "127.0.0.1";
|
||||||
nameMapper.reset(new kiwix::HumanReadableNameMapper(library, false));
|
nameMapper.reset(new kiwix::HumanReadableNameMapper(library, false));
|
||||||
server.reset(new kiwix::Server(&library, nameMapper.get()));
|
server.reset(new kiwix::Server(&library, nameMapper.get()));
|
||||||
|
server->setRoot("ROOT");
|
||||||
server->setAddress(address);
|
server->setAddress(address);
|
||||||
server->setPort(serverPort);
|
server->setPort(serverPort);
|
||||||
server->setNbThreads(2);
|
server->setNbThreads(2);
|
||||||
|
@ -162,52 +161,52 @@ std::ostream& operator<<(std::ostream& out, const Resource& r)
|
||||||
typedef std::vector<Resource> ResourceCollection;
|
typedef std::vector<Resource> ResourceCollection;
|
||||||
|
|
||||||
const ResourceCollection resources200Compressible{
|
const ResourceCollection resources200Compressible{
|
||||||
{ WITH_ETAG, "/" },
|
{ WITH_ETAG, "/ROOT/" },
|
||||||
|
|
||||||
{ WITH_ETAG, "/skin/jquery-ui/jquery-ui.structure.min.css" },
|
{ WITH_ETAG, "/ROOT/skin/jquery-ui/jquery-ui.structure.min.css" },
|
||||||
{ WITH_ETAG, "/skin/jquery-ui/jquery-ui.min.js" },
|
{ WITH_ETAG, "/ROOT/skin/jquery-ui/jquery-ui.min.js" },
|
||||||
{ WITH_ETAG, "/skin/jquery-ui/external/jquery/jquery.js" },
|
{ WITH_ETAG, "/ROOT/skin/jquery-ui/external/jquery/jquery.js" },
|
||||||
{ WITH_ETAG, "/skin/jquery-ui/jquery-ui.theme.min.css" },
|
{ WITH_ETAG, "/ROOT/skin/jquery-ui/jquery-ui.theme.min.css" },
|
||||||
{ WITH_ETAG, "/skin/jquery-ui/jquery-ui.min.css" },
|
{ WITH_ETAG, "/ROOT/skin/jquery-ui/jquery-ui.min.css" },
|
||||||
{ WITH_ETAG, "/skin/taskbar.js" },
|
{ WITH_ETAG, "/ROOT/skin/taskbar.js" },
|
||||||
{ WITH_ETAG, "/skin/taskbar.css" },
|
{ WITH_ETAG, "/ROOT/skin/taskbar.css" },
|
||||||
{ WITH_ETAG, "/skin/block_external.js" },
|
{ WITH_ETAG, "/ROOT/skin/block_external.js" },
|
||||||
|
|
||||||
{ NO_ETAG, "/catalog/root.xml" },
|
{ NO_ETAG, "/ROOT/catalog/root.xml" },
|
||||||
{ NO_ETAG, "/catalog/searchdescription.xml" },
|
{ NO_ETAG, "/ROOT/catalog/searchdescription.xml" },
|
||||||
{ NO_ETAG, "/catalog/search" },
|
{ NO_ETAG, "/ROOT/catalog/search" },
|
||||||
|
|
||||||
{ NO_ETAG, "/search?content=zimfile&pattern=a" },
|
{ NO_ETAG, "/ROOT/search?content=zimfile&pattern=a" },
|
||||||
|
|
||||||
{ NO_ETAG, "/suggest?content=zimfile" },
|
{ NO_ETAG, "/ROOT/suggest?content=zimfile" },
|
||||||
{ NO_ETAG, "/suggest?content=zimfile&term=ray" },
|
{ NO_ETAG, "/ROOT/suggest?content=zimfile&term=ray" },
|
||||||
|
|
||||||
{ NO_ETAG, "/catch/external?source=www.example.com" },
|
{ NO_ETAG, "/ROOT/catch/external?source=www.example.com" },
|
||||||
|
|
||||||
{ WITH_ETAG, "/zimfile/A/index" },
|
{ WITH_ETAG, "/ROOT/zimfile/A/index" },
|
||||||
{ WITH_ETAG, "/zimfile/A/Ray_Charles" },
|
{ WITH_ETAG, "/ROOT/zimfile/A/Ray_Charles" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const ResourceCollection resources200Uncompressible{
|
const ResourceCollection resources200Uncompressible{
|
||||||
{ WITH_ETAG, "/skin/jquery-ui/images/animated-overlay.gif" },
|
{ WITH_ETAG, "/ROOT/skin/jquery-ui/images/animated-overlay.gif" },
|
||||||
{ WITH_ETAG, "/skin/caret.png" },
|
{ WITH_ETAG, "/ROOT/skin/caret.png" },
|
||||||
|
|
||||||
{ WITH_ETAG, "/meta?content=zimfile&name=title" },
|
{ WITH_ETAG, "/ROOT/meta?content=zimfile&name=title" },
|
||||||
{ WITH_ETAG, "/meta?content=zimfile&name=description" },
|
{ WITH_ETAG, "/ROOT/meta?content=zimfile&name=description" },
|
||||||
{ WITH_ETAG, "/meta?content=zimfile&name=language" },
|
{ WITH_ETAG, "/ROOT/meta?content=zimfile&name=language" },
|
||||||
{ WITH_ETAG, "/meta?content=zimfile&name=name" },
|
{ WITH_ETAG, "/ROOT/meta?content=zimfile&name=name" },
|
||||||
{ WITH_ETAG, "/meta?content=zimfile&name=tags" },
|
{ WITH_ETAG, "/ROOT/meta?content=zimfile&name=tags" },
|
||||||
{ WITH_ETAG, "/meta?content=zimfile&name=date" },
|
{ WITH_ETAG, "/ROOT/meta?content=zimfile&name=date" },
|
||||||
{ WITH_ETAG, "/meta?content=zimfile&name=creator" },
|
{ WITH_ETAG, "/ROOT/meta?content=zimfile&name=creator" },
|
||||||
{ WITH_ETAG, "/meta?content=zimfile&name=publisher" },
|
{ WITH_ETAG, "/ROOT/meta?content=zimfile&name=publisher" },
|
||||||
{ WITH_ETAG, "/meta?content=zimfile&name=favicon" },
|
{ WITH_ETAG, "/ROOT/meta?content=zimfile&name=favicon" },
|
||||||
{ WITH_ETAG, "/meta?content=zimfile&name=Illustration_48x48@1" },
|
{ WITH_ETAG, "/ROOT/meta?content=zimfile&name=Illustration_48x48@1" },
|
||||||
|
|
||||||
{ WITH_ETAG, "/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg" },
|
{ WITH_ETAG, "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg" },
|
||||||
|
|
||||||
{ WITH_ETAG, "/corner_cases/A/empty.html" },
|
{ WITH_ETAG, "/ROOT/corner_cases/A/empty.html" },
|
||||||
{ WITH_ETAG, "/corner_cases/-/empty.css" },
|
{ WITH_ETAG, "/ROOT/corner_cases/-/empty.css" },
|
||||||
{ WITH_ETAG, "/corner_cases/-/empty.js" },
|
{ WITH_ETAG, "/ROOT/corner_cases/-/empty.js" },
|
||||||
};
|
};
|
||||||
|
|
||||||
ResourceCollection all200Resources()
|
ResourceCollection all200Resources()
|
||||||
|
@ -223,7 +222,7 @@ TEST(indexTemplateStringTest, emptyIndexTemplate) {
|
||||||
};
|
};
|
||||||
|
|
||||||
ZimFileServer zfs(PORT, ZIMFILES, "");
|
ZimFileServer zfs(PORT, ZIMFILES, "");
|
||||||
EXPECT_EQ(200, zfs.GET("/")->status);
|
EXPECT_EQ(200, zfs.GET("/ROOT/")->status);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST(indexTemplateStringTest, indexTemplateCheck) {
|
TEST(indexTemplateStringTest, indexTemplateCheck) {
|
||||||
|
@ -239,9 +238,9 @@ TEST(indexTemplateStringTest, indexTemplateCheck) {
|
||||||
"</html>");
|
"</html>");
|
||||||
EXPECT_EQ("<!DOCTYPE html><head>"
|
EXPECT_EQ("<!DOCTYPE html><head>"
|
||||||
"<title>Welcome to kiwix library</title>"
|
"<title>Welcome to kiwix library</title>"
|
||||||
"<link type=\"root\" href=\"\">"
|
"<link type=\"root\" href=\"/ROOT\">"
|
||||||
"</head>"
|
"</head>"
|
||||||
"</html>", zfs.GET("/")->body);
|
"</html>", zfs.GET("/ROOT/")->body);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, 200)
|
TEST_F(ServerTest, 200)
|
||||||
|
@ -270,22 +269,24 @@ TEST_F(ServerTest, UncompressibleContentIsNotCompressed)
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* urls404[] = {
|
const char* urls404[] = {
|
||||||
"/non-existent-item",
|
"/",
|
||||||
"/skin/non-existent-skin-resource",
|
"/zimfile",
|
||||||
"/catalog",
|
"/ROOT/non-existent-item",
|
||||||
"/catalog/non-existent-item",
|
"/ROOT/skin/non-existent-skin-resource",
|
||||||
"/catalogBLABLABLA/root.xml",
|
"/ROOT/catalog",
|
||||||
"/meta",
|
"/ROOT/catalog/non-existent-item",
|
||||||
"/meta?content=zimfile",
|
"/ROOT/catalogBLABLABLA/root.xml",
|
||||||
"/meta?content=zimfile&name=non-existent-item",
|
"/ROOT/meta",
|
||||||
"/meta?content=non-existent-book&name=title",
|
"/ROOT/meta?content=zimfile",
|
||||||
"/random",
|
"/ROOT/meta?content=zimfile&name=non-existent-item",
|
||||||
"/random?content=non-existent-book",
|
"/ROOT/meta?content=non-existent-book&name=title",
|
||||||
"/search",
|
"/ROOT/random",
|
||||||
"/suggest",
|
"/ROOT/random?content=non-existent-book",
|
||||||
"/suggest?content=non-existent-book&term=abcd",
|
"/ROOT/search",
|
||||||
"/catch/external",
|
"/ROOT/suggest",
|
||||||
"/zimfile/A/non-existent-article",
|
"/ROOT/suggest?content=non-existent-book&term=abcd",
|
||||||
|
"/ROOT/catch/external",
|
||||||
|
"/ROOT/zimfile/A/non-existent-article",
|
||||||
};
|
};
|
||||||
|
|
||||||
TEST_F(ServerTest, 404)
|
TEST_F(ServerTest, 404)
|
||||||
|
@ -296,7 +297,7 @@ TEST_F(ServerTest, 404)
|
||||||
|
|
||||||
TEST_F(ServerTest, RandomPageRedirectsToAnExistingArticle)
|
TEST_F(ServerTest, RandomPageRedirectsToAnExistingArticle)
|
||||||
{
|
{
|
||||||
auto g = zfs1_->GET("/random?content=zimfile");
|
auto g = zfs1_->GET("/ROOT/random?content=zimfile");
|
||||||
ASSERT_EQ(302, g->status);
|
ASSERT_EQ(302, g->status);
|
||||||
ASSERT_TRUE(g->has_header("Location"));
|
ASSERT_TRUE(g->has_header("Location"));
|
||||||
ASSERT_TRUE(g->get_header_value("Location").find("/zimfile/A/") != std::string::npos);
|
ASSERT_TRUE(g->get_header_value("Location").find("/zimfile/A/") != std::string::npos);
|
||||||
|
@ -304,10 +305,10 @@ TEST_F(ServerTest, RandomPageRedirectsToAnExistingArticle)
|
||||||
|
|
||||||
TEST_F(ServerTest, BookMainPageIsRedirectedToArticleIndex)
|
TEST_F(ServerTest, BookMainPageIsRedirectedToArticleIndex)
|
||||||
{
|
{
|
||||||
auto g = zfs1_->GET("/zimfile");
|
auto g = zfs1_->GET("/ROOT/zimfile");
|
||||||
ASSERT_EQ(302, g->status);
|
ASSERT_EQ(302, g->status);
|
||||||
ASSERT_TRUE(g->has_header("Location"));
|
ASSERT_TRUE(g->has_header("Location"));
|
||||||
ASSERT_EQ("/zimfile/A/index", g->get_header_value("Location"));
|
ASSERT_EQ("/ROOT/zimfile/A/index", g->get_header_value("Location"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(ServerTest, HeadMethodIsSupported)
|
TEST_F(ServerTest, HeadMethodIsSupported)
|
||||||
|
@ -466,7 +467,7 @@ TEST_F(ServerTest, IfNoneMatchRequestsWithMismatchingETagResultIn200Responses)
|
||||||
|
|
||||||
TEST_F(ServerTest, ValidSingleRangeByteRangeRequestsAreHandledProperly)
|
TEST_F(ServerTest, ValidSingleRangeByteRangeRequestsAreHandledProperly)
|
||||||
{
|
{
|
||||||
const char url[] = "/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
const char url[] = "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||||
const auto full = zfs1_->GET(url);
|
const auto full = zfs1_->GET(url);
|
||||||
EXPECT_FALSE(full->has_header("Content-Range"));
|
EXPECT_FALSE(full->has_header("Content-Range"));
|
||||||
EXPECT_EQ("bytes", full->get_header_value("Accept-Ranges"));
|
EXPECT_EQ("bytes", full->get_header_value("Accept-Ranges"));
|
||||||
|
@ -516,7 +517,7 @@ TEST_F(ServerTest, ValidSingleRangeByteRangeRequestsAreHandledProperly)
|
||||||
|
|
||||||
TEST_F(ServerTest, InvalidAndMultiRangeByteRangeRequestsResultIn416Responses)
|
TEST_F(ServerTest, InvalidAndMultiRangeByteRangeRequestsResultIn416Responses)
|
||||||
{
|
{
|
||||||
const char url[] = "/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
const char url[] = "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||||
|
|
||||||
const char* invalidRanges[] = {
|
const char* invalidRanges[] = {
|
||||||
"0-10", "bytes=", "bytes=123", "bytes=-10-20", "bytes=10-20xxx",
|
"0-10", "bytes=", "bytes=123", "bytes=-10-20", "bytes=10-20xxx",
|
||||||
|
@ -537,7 +538,7 @@ TEST_F(ServerTest, InvalidAndMultiRangeByteRangeRequestsResultIn416Responses)
|
||||||
|
|
||||||
TEST_F(ServerTest, ValidByteRangeRequestsOfZeroSizedEntriesResultIn416Responses)
|
TEST_F(ServerTest, ValidByteRangeRequestsOfZeroSizedEntriesResultIn416Responses)
|
||||||
{
|
{
|
||||||
const char url[] = "/corner_cases/-/empty.js";
|
const char url[] = "/ROOT/corner_cases/-/empty.js";
|
||||||
|
|
||||||
const char* ranges[] = {
|
const char* ranges[] = {
|
||||||
"bytes=0-",
|
"bytes=0-",
|
||||||
|
@ -556,7 +557,7 @@ TEST_F(ServerTest, ValidByteRangeRequestsOfZeroSizedEntriesResultIn416Responses)
|
||||||
|
|
||||||
TEST_F(ServerTest, RangeHasPrecedenceOverCompression)
|
TEST_F(ServerTest, RangeHasPrecedenceOverCompression)
|
||||||
{
|
{
|
||||||
const char url[] = "/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
const char url[] = "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||||
|
|
||||||
const Headers onlyRange{ {"Range", "bytes=123-456"} };
|
const Headers onlyRange{ {"Range", "bytes=123-456"} };
|
||||||
Headers rangeAndCompression(onlyRange);
|
Headers rangeAndCompression(onlyRange);
|
||||||
|
@ -571,7 +572,7 @@ TEST_F(ServerTest, RangeHasPrecedenceOverCompression)
|
||||||
|
|
||||||
TEST_F(ServerTest, RangeHeaderIsCaseInsensitive)
|
TEST_F(ServerTest, RangeHeaderIsCaseInsensitive)
|
||||||
{
|
{
|
||||||
const char url[] = "/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
const char url[] = "/ROOT/zimfile/I/m/Ray_Charles_classic_piano_pose.jpg";
|
||||||
const auto r0 = zfs1_->GET(url, { {"Range", "bytes=100-200"} } );
|
const auto r0 = zfs1_->GET(url, { {"Range", "bytes=100-200"} } );
|
||||||
|
|
||||||
const char* header_variations[] = { "RANGE", "range", "rAnGe", "RaNgE" };
|
const char* header_variations[] = { "RANGE", "range", "rAnGe", "RaNgE" };
|
||||||
|
@ -644,7 +645,7 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||||
" <link rel=\"self\" href=\"\" type=\"application/atom+xml\" />\n" \
|
" <link rel=\"self\" href=\"\" type=\"application/atom+xml\" />\n" \
|
||||||
" <link rel=\"search\"" \
|
" <link rel=\"search\"" \
|
||||||
" type=\"application/opensearchdescription+xml\"" \
|
" type=\"application/opensearchdescription+xml\"" \
|
||||||
" href=\"/catalog/searchdescription.xml\" />\n"
|
" href=\"/ROOT/catalog/searchdescription.xml\" />\n"
|
||||||
|
|
||||||
#define CHARLES_RAY_CATALOG_ENTRY \
|
#define CHARLES_RAY_CATALOG_ENTRY \
|
||||||
" <entry>\n" \
|
" <entry>\n" \
|
||||||
|
@ -659,7 +660,7 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||||
" <tags>unittest;wikipedia;_category:jazz;_pictures:no;_videos:no;_details:no;_ftindex:yes</tags>\n" \
|
" <tags>unittest;wikipedia;_category:jazz;_pictures:no;_videos:no;_details:no;_ftindex:yes</tags>\n" \
|
||||||
" <articleCount>284</articleCount>\n" \
|
" <articleCount>284</articleCount>\n" \
|
||||||
" <mediaCount>2</mediaCount>\n" \
|
" <mediaCount>2</mediaCount>\n" \
|
||||||
" <link type=\"text/html\" href=\"/zimfile\" />\n" \
|
" <link type=\"text/html\" href=\"/ROOT/zimfile\" />\n" \
|
||||||
" <author>\n" \
|
" <author>\n" \
|
||||||
" <name>Wikipedia</name>\n" \
|
" <name>Wikipedia</name>\n" \
|
||||||
" </author>\n" \
|
" </author>\n" \
|
||||||
|
@ -683,9 +684,9 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||||
" <articleCount>284</articleCount>\n" \
|
" <articleCount>284</articleCount>\n" \
|
||||||
" <mediaCount>2</mediaCount>\n" \
|
" <mediaCount>2</mediaCount>\n" \
|
||||||
" <link rel=\"http://opds-spec.org/image/thumbnail\"\n" \
|
" <link rel=\"http://opds-spec.org/image/thumbnail\"\n" \
|
||||||
" href=\"/meta?name=Illustration_48x48@1&content=zimfile\"\n" \
|
" href=\"/ROOT/meta?name=Illustration_48x48@1&content=zimfile\"\n" \
|
||||||
" type=\"image/png;width=48;height=48;scale=1\"/>\n" \
|
" type=\"image/png;width=48;height=48;scale=1\"/>\n" \
|
||||||
" <link type=\"text/html\" href=\"/zimfile\" />\n" \
|
" <link type=\"text/html\" href=\"/ROOT/zimfile\" />\n" \
|
||||||
" <author>\n" \
|
" <author>\n" \
|
||||||
" <name>Wikipedia</name>\n" \
|
" <name>Wikipedia</name>\n" \
|
||||||
" </author>\n" \
|
" </author>\n" \
|
||||||
|
@ -708,7 +709,7 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||||
" <tags>unittest;wikipedia;_pictures:no;_videos:no;_details:no</tags>\n" \
|
" <tags>unittest;wikipedia;_pictures:no;_videos:no;_details:no</tags>\n" \
|
||||||
" <articleCount>284</articleCount>\n" \
|
" <articleCount>284</articleCount>\n" \
|
||||||
" <mediaCount>2</mediaCount>\n" \
|
" <mediaCount>2</mediaCount>\n" \
|
||||||
" <link type=\"text/html\" href=\"/zimfile\" />\n" \
|
" <link type=\"text/html\" href=\"/ROOT/zimfile\" />\n" \
|
||||||
" <author>\n" \
|
" <author>\n" \
|
||||||
" <name>Wikipedia</name>\n" \
|
" <name>Wikipedia</name>\n" \
|
||||||
" </author>\n" \
|
" </author>\n" \
|
||||||
|
@ -720,7 +721,7 @@ std::string maskVariableOPDSFeedData(std::string s)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_root_xml)
|
TEST_F(LibraryServerTest, catalog_root_xml)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/root.xml");
|
const auto r = zfs1_->GET("/ROOT/catalog/root.xml");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -738,7 +739,7 @@ TEST_F(LibraryServerTest, catalog_root_xml)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_searchdescription_xml)
|
TEST_F(LibraryServerTest, catalog_searchdescription_xml)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/searchdescription.xml");
|
const auto r = zfs1_->GET("/ROOT/catalog/searchdescription.xml");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(r->body,
|
EXPECT_EQ(r->body,
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||||
|
@ -749,14 +750,14 @@ TEST_F(LibraryServerTest, catalog_searchdescription_xml)
|
||||||
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
||||||
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
||||||
" indexOffset=\"0\"\n"
|
" indexOffset=\"0\"\n"
|
||||||
" template=\"/catalog/search?q={searchTerms?}&lang={language?}&name={k:name?}&tag={k:tag?}¬ag={k:notag?}&maxsize={k:maxsize?}&count={count?}&start={startIndex?}\"/>\n"
|
" template=\"/ROOT/catalog/search?q={searchTerms?}&lang={language?}&name={k:name?}&tag={k:tag?}¬ag={k:notag?}&maxsize={k:maxsize?}&count={count?}&start={startIndex?}\"/>\n"
|
||||||
"</OpenSearchDescription>\n"
|
"</OpenSearchDescription>\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_by_phrase)
|
TEST_F(LibraryServerTest, catalog_search_by_phrase)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/search?q=\"ray%20charles\"");
|
const auto r = zfs1_->GET("/ROOT/catalog/search?q=\"ray%20charles\"");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -775,7 +776,7 @@ TEST_F(LibraryServerTest, catalog_search_by_phrase)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_by_words)
|
TEST_F(LibraryServerTest, catalog_search_by_words)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/search?q=ray%20charles");
|
const auto r = zfs1_->GET("/ROOT/catalog/search?q=ray%20charles");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -796,7 +797,7 @@ TEST_F(LibraryServerTest, catalog_search_by_words)
|
||||||
TEST_F(LibraryServerTest, catalog_prefix_search)
|
TEST_F(LibraryServerTest, catalog_prefix_search)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/search?q=description:ray%20description:charles");
|
const auto r = zfs1_->GET("/ROOT/catalog/search?q=description:ray%20description:charles");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -813,7 +814,7 @@ TEST_F(LibraryServerTest, catalog_prefix_search)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/search?q=title:\"ray%20charles\"");
|
const auto r = zfs1_->GET("/ROOT/catalog/search?q=title:\"ray%20charles\"");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -832,7 +833,7 @@ TEST_F(LibraryServerTest, catalog_prefix_search)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_with_word_exclusion)
|
TEST_F(LibraryServerTest, catalog_search_with_word_exclusion)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/search?q=ray%20-uncategorized");
|
const auto r = zfs1_->GET("/ROOT/catalog/search?q=ray%20-uncategorized");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -851,7 +852,7 @@ TEST_F(LibraryServerTest, catalog_search_with_word_exclusion)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_by_tag)
|
TEST_F(LibraryServerTest, catalog_search_by_tag)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/search?tag=_category:jazz");
|
const auto r = zfs1_->GET("/ROOT/catalog/search?tag=_category:jazz");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -869,7 +870,7 @@ TEST_F(LibraryServerTest, catalog_search_by_tag)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_by_category)
|
TEST_F(LibraryServerTest, catalog_search_by_category)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/search?category=jazz");
|
const auto r = zfs1_->GET("/ROOT/catalog/search?category=jazz");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -888,7 +889,7 @@ TEST_F(LibraryServerTest, catalog_search_by_category)
|
||||||
TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/search?count=1");
|
const auto r = zfs1_->GET("/ROOT/catalog/search?count=1");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -904,7 +905,7 @@ TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/search?start=1&count=1");
|
const auto r = zfs1_->GET("/ROOT/catalog/search?start=1&count=1");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -920,7 +921,7 @@ TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/search?start=100&count=10");
|
const auto r = zfs1_->GET("/ROOT/catalog/search?start=100&count=10");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
OPDS_FEED_TAG
|
OPDS_FEED_TAG
|
||||||
|
@ -938,20 +939,20 @@ TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_root)
|
TEST_F(LibraryServerTest, catalog_v2_root)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/root.xml");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/root.xml");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||||
xmlns:opds="https://specs.opds.io/opds-1.2">
|
xmlns:opds="https://specs.opds.io/opds-1.2">
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
<link rel="self"
|
<link rel="self"
|
||||||
href="/catalog/v2/root.xml"
|
href="/ROOT/catalog/v2/root.xml"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
<link rel="start"
|
<link rel="start"
|
||||||
href="/catalog/v2/root.xml"
|
href="/ROOT/catalog/v2/root.xml"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
<link rel="search"
|
<link rel="search"
|
||||||
href="/catalog/v2/searchdescription.xml"
|
href="/ROOT/catalog/v2/searchdescription.xml"
|
||||||
type="application/opensearchdescription+xml"/>
|
type="application/opensearchdescription+xml"/>
|
||||||
<title>OPDS Catalog Root</title>
|
<title>OPDS Catalog Root</title>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
@ -959,7 +960,7 @@ TEST_F(LibraryServerTest, catalog_v2_root)
|
||||||
<entry>
|
<entry>
|
||||||
<title>All entries</title>
|
<title>All entries</title>
|
||||||
<link rel="subsection"
|
<link rel="subsection"
|
||||||
href="/catalog/v2/entries"
|
href="/ROOT/catalog/v2/entries"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
@ -968,7 +969,7 @@ TEST_F(LibraryServerTest, catalog_v2_root)
|
||||||
<entry>
|
<entry>
|
||||||
<title>All entries (partial)</title>
|
<title>All entries (partial)</title>
|
||||||
<link rel="subsection"
|
<link rel="subsection"
|
||||||
href="/catalog/v2/partial_entries"
|
href="/ROOT/catalog/v2/partial_entries"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
@ -977,7 +978,7 @@ TEST_F(LibraryServerTest, catalog_v2_root)
|
||||||
<entry>
|
<entry>
|
||||||
<title>List of categories</title>
|
<title>List of categories</title>
|
||||||
<link rel="subsection"
|
<link rel="subsection"
|
||||||
href="/catalog/v2/categories"
|
href="/ROOT/catalog/v2/categories"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
@ -986,7 +987,7 @@ TEST_F(LibraryServerTest, catalog_v2_root)
|
||||||
<entry>
|
<entry>
|
||||||
<title>List of languages</title>
|
<title>List of languages</title>
|
||||||
<link rel="subsection"
|
<link rel="subsection"
|
||||||
href="/catalog/v2/languages"
|
href="/ROOT/catalog/v2/languages"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
@ -999,7 +1000,7 @@ TEST_F(LibraryServerTest, catalog_v2_root)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_searchdescription_xml)
|
TEST_F(LibraryServerTest, catalog_v2_searchdescription_xml)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/searchdescription.xml");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/searchdescription.xml");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(r->body,
|
EXPECT_EQ(r->body,
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||||
|
@ -1010,24 +1011,24 @@ TEST_F(LibraryServerTest, catalog_v2_searchdescription_xml)
|
||||||
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
||||||
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
||||||
" indexOffset=\"0\"\n"
|
" indexOffset=\"0\"\n"
|
||||||
" template=\"/catalog/v2/entries?q={searchTerms?}&lang={language?}&name={k:name?}&tag={k:tag?}&maxsize={k:maxsize?}&count={count?}&start={startIndex?}\"/>\n"
|
" template=\"/ROOT/catalog/v2/entries?q={searchTerms?}&lang={language?}&name={k:name?}&tag={k:tag?}&maxsize={k:maxsize?}&count={count?}&start={startIndex?}\"/>\n"
|
||||||
"</OpenSearchDescription>\n"
|
"</OpenSearchDescription>\n"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_categories)
|
TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/categories");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/categories");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||||
xmlns:opds="https://specs.opds.io/opds-1.2">
|
xmlns:opds="https://specs.opds.io/opds-1.2">
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
<link rel="self"
|
<link rel="self"
|
||||||
href="/catalog/v2/categories"
|
href="/ROOT/catalog/v2/categories"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
<link rel="start"
|
<link rel="start"
|
||||||
href="/catalog/v2/root.xml"
|
href="/ROOT/catalog/v2/root.xml"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
<title>List of categories</title>
|
<title>List of categories</title>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
@ -1035,7 +1036,7 @@ TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||||
<entry>
|
<entry>
|
||||||
<title>jazz</title>
|
<title>jazz</title>
|
||||||
<link rel="subsection"
|
<link rel="subsection"
|
||||||
href="/catalog/v2/entries?category=jazz"
|
href="/ROOT/catalog/v2/entries?category=jazz"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
@ -1044,7 +1045,7 @@ TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||||
<entry>
|
<entry>
|
||||||
<title>wikipedia</title>
|
<title>wikipedia</title>
|
||||||
<link rel="subsection"
|
<link rel="subsection"
|
||||||
href="/catalog/v2/entries?category=wikipedia"
|
href="/ROOT/catalog/v2/entries?category=wikipedia"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
@ -1057,7 +1058,7 @@ TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_languages)
|
TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/languages");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/languages");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||||
|
@ -1066,10 +1067,10 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||||
xmlns:thr="http://purl.org/syndication/thread/1.0">
|
xmlns:thr="http://purl.org/syndication/thread/1.0">
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
<link rel="self"
|
<link rel="self"
|
||||||
href="/catalog/v2/languages"
|
href="/ROOT/catalog/v2/languages"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
<link rel="start"
|
<link rel="start"
|
||||||
href="/catalog/v2/root.xml"
|
href="/ROOT/catalog/v2/root.xml"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
<title>List of languages</title>
|
<title>List of languages</title>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
@ -1079,7 +1080,7 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||||
<dc:language>eng</dc:language>
|
<dc:language>eng</dc:language>
|
||||||
<thr:count>1</thr:count>
|
<thr:count>1</thr:count>
|
||||||
<link rel="subsection"
|
<link rel="subsection"
|
||||||
href="/catalog/v2/entries?lang=eng"
|
href="/ROOT/catalog/v2/entries?lang=eng"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
@ -1089,7 +1090,7 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||||
<dc:language>fra</dc:language>
|
<dc:language>fra</dc:language>
|
||||||
<thr:count>1</thr:count>
|
<thr:count>1</thr:count>
|
||||||
<link rel="subsection"
|
<link rel="subsection"
|
||||||
href="/catalog/v2/entries?lang=fra"
|
href="/ROOT/catalog/v2/entries?lang=fra"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
@ -1099,7 +1100,7 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||||
<dc:language>rus</dc:language>
|
<dc:language>rus</dc:language>
|
||||||
<thr:count>1</thr:count>
|
<thr:count>1</thr:count>
|
||||||
<link rel="subsection"
|
<link rel="subsection"
|
||||||
href="/catalog/v2/entries?lang=rus"
|
href="/ROOT/catalog/v2/entries?lang=rus"
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
@ -1117,13 +1118,13 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" \
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" \
|
||||||
"\n" \
|
"\n" \
|
||||||
" <link rel=\"self\"\n" \
|
" <link rel=\"self\"\n" \
|
||||||
" href=\"/catalog/v2/" x "\"\n" \
|
" href=\"/ROOT/catalog/v2/" x "\"\n" \
|
||||||
" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"/>\n" \
|
" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"/>\n" \
|
||||||
" <link rel=\"start\"\n" \
|
" <link rel=\"start\"\n" \
|
||||||
" href=\"/catalog/v2/root.xml\"\n" \
|
" href=\"/ROOT/catalog/v2/root.xml\"\n" \
|
||||||
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
||||||
" <link rel=\"up\"\n" \
|
" <link rel=\"up\"\n" \
|
||||||
" href=\"/catalog/v2/root.xml\"\n" \
|
" href=\"/ROOT/catalog/v2/root.xml\"\n" \
|
||||||
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
||||||
"\n" \
|
"\n" \
|
||||||
|
|
||||||
|
@ -1135,7 +1136,7 @@ TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_entries)
|
TEST_F(LibraryServerTest, catalog_v2_entries)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/entries");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE("")
|
CATALOG_V2_ENTRIES_PREAMBLE("")
|
||||||
|
@ -1152,7 +1153,7 @@ TEST_F(LibraryServerTest, catalog_v2_entries)
|
||||||
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/entries?start=1");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?start=1");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE("?start=1")
|
CATALOG_V2_ENTRIES_PREAMBLE("?start=1")
|
||||||
|
@ -1168,7 +1169,7 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/entries?count=2");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?count=2");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE("?count=2")
|
CATALOG_V2_ENTRIES_PREAMBLE("?count=2")
|
||||||
|
@ -1184,7 +1185,7 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/entries?start=1&count=1");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?start=1&count=1");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE("?count=1&start=1")
|
CATALOG_V2_ENTRIES_PREAMBLE("?count=1&start=1")
|
||||||
|
@ -1201,7 +1202,7 @@ TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_search_terms)
|
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_search_terms)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/entries?q=\"ray%20charles\"");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?q=\"ray%20charles\"");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE("?q=%22ray%20charles%22")
|
CATALOG_V2_ENTRIES_PREAMBLE("?q=%22ray%20charles%22")
|
||||||
|
@ -1227,7 +1228,7 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||||
{
|
{
|
||||||
int suggCount = 0;
|
int suggCount = 0;
|
||||||
for (int i = 0; i < 10; i++) {
|
for (int i = 0; i < 10; i++) {
|
||||||
std::string url = "/suggest?content=zimfile&term=ray&start=" + std::to_string(i*5) + "&count=5";
|
std::string url = "/ROOT/suggest?content=zimfile&term=ray&start=" + std::to_string(i*5) + "&count=5";
|
||||||
const auto r = zfs1_->GET(url.c_str());
|
const auto r = zfs1_->GET(url.c_str());
|
||||||
std::string body = r->body;
|
std::string body = r->body;
|
||||||
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||||
|
@ -1239,13 +1240,13 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||||
|
|
||||||
// Attempt to get 10 suggestions in steps of 5 even though there are only 8
|
// Attempt to get 10 suggestions in steps of 5 even though there are only 8
|
||||||
{
|
{
|
||||||
std::string url = "/suggest?content=zimfile&term=song+for+you&start=0&count=5";
|
std::string url = "/ROOT/suggest?content=zimfile&term=song+for+you&start=0&count=5";
|
||||||
const auto r1 = zfs1_->GET(url.c_str());
|
const auto r1 = zfs1_->GET(url.c_str());
|
||||||
std::string body = r1->body;
|
std::string body = r1->body;
|
||||||
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||||
ASSERT_EQ(currCount, 5);
|
ASSERT_EQ(currCount, 5);
|
||||||
|
|
||||||
url = "/suggest?content=zimfile&term=song+for+you&start=5&count=5";
|
url = "/ROOT/suggest?content=zimfile&term=song+for+you&start=5&count=5";
|
||||||
const auto r2 = zfs1_->GET(url.c_str());
|
const auto r2 = zfs1_->GET(url.c_str());
|
||||||
body = r2->body;
|
body = r2->body;
|
||||||
currCount = std::count(body.begin(), body.end(), '{') - 1;
|
currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||||
|
@ -1254,7 +1255,7 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||||
|
|
||||||
// Attempt to get 10 suggestions even though there is only 1
|
// Attempt to get 10 suggestions even though there is only 1
|
||||||
{
|
{
|
||||||
std::string url = "/suggest?content=zimfile&term=strong&start=0&count=5";
|
std::string url = "/ROOT/suggest?content=zimfile&term=strong&start=0&count=5";
|
||||||
const auto r = zfs1_->GET(url.c_str());
|
const auto r = zfs1_->GET(url.c_str());
|
||||||
std::string body = r->body;
|
std::string body = r->body;
|
||||||
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||||
|
@ -1263,7 +1264,7 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||||
|
|
||||||
// No Suggestion
|
// No Suggestion
|
||||||
{
|
{
|
||||||
std::string url = "/suggest?content=zimfile&term=oops&start=0&count=5";
|
std::string url = "/ROOT/suggest?content=zimfile&term=oops&start=0&count=5";
|
||||||
const auto r = zfs1_->GET(url.c_str());
|
const auto r = zfs1_->GET(url.c_str());
|
||||||
std::string body = r->body;
|
std::string body = r->body;
|
||||||
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||||
|
@ -1272,7 +1273,7 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||||
|
|
||||||
// Out of bound value
|
// Out of bound value
|
||||||
{
|
{
|
||||||
std::string url = "/suggest?content=zimfile&term=ray&start=-2&count=-1";
|
std::string url = "/ROOT/suggest?content=zimfile&term=ray&start=-2&count=-1";
|
||||||
const auto r = zfs1_->GET(url.c_str());
|
const auto r = zfs1_->GET(url.c_str());
|
||||||
std::string body = r->body;
|
std::string body = r->body;
|
||||||
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
int currCount = std::count(body.begin(), body.end(), '{') - 1;
|
||||||
|
@ -1282,20 +1283,20 @@ TEST_F(LibraryServerTest, suggestions_in_range)
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_individual_entry_access)
|
TEST_F(LibraryServerTest, catalog_v2_individual_entry_access)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/entry/raycharles");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entry/raycharles");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
);
|
);
|
||||||
|
|
||||||
const auto r1 = zfs1_->GET("/catalog/v2/entry/non-existent-entry");
|
const auto r1 = zfs1_->GET("/ROOT/catalog/v2/entry/non-existent-entry");
|
||||||
EXPECT_EQ(r1->status, 404);
|
EXPECT_EQ(r1->status, 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
||||||
{
|
{
|
||||||
const auto r = zfs1_->GET("/catalog/v2/partial_entries");
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/partial_entries");
|
||||||
EXPECT_EQ(r->status, 200);
|
EXPECT_EQ(r->status, 200);
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
CATALOG_V2_PARTIAL_ENTRIES_PREAMBLE("")
|
CATALOG_V2_PARTIAL_ENTRIES_PREAMBLE("")
|
||||||
|
@ -1307,7 +1308,7 @@ TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
||||||
" <title>Charles, Ray</title>\n"
|
" <title>Charles, Ray</title>\n"
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
" <link rel=\"alternate\"\n"
|
" <link rel=\"alternate\"\n"
|
||||||
" href=\"/catalog/v2/entry/charlesray\"\n"
|
" href=\"/ROOT/catalog/v2/entry/charlesray\"\n"
|
||||||
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
||||||
" </entry>\n"
|
" </entry>\n"
|
||||||
" <entry>\n"
|
" <entry>\n"
|
||||||
|
@ -1315,7 +1316,7 @@ TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
||||||
" <title>Ray Charles</title>\n"
|
" <title>Ray Charles</title>\n"
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
" <link rel=\"alternate\"\n"
|
" <link rel=\"alternate\"\n"
|
||||||
" href=\"/catalog/v2/entry/raycharles\"\n"
|
" href=\"/ROOT/catalog/v2/entry/raycharles\"\n"
|
||||||
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
||||||
" </entry>\n"
|
" </entry>\n"
|
||||||
" <entry>\n"
|
" <entry>\n"
|
||||||
|
@ -1323,7 +1324,7 @@ TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
||||||
" <title>Ray (uncategorized) Charles</title>\n"
|
" <title>Ray (uncategorized) Charles</title>\n"
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
" <link rel=\"alternate\"\n"
|
" <link rel=\"alternate\"\n"
|
||||||
" href=\"/catalog/v2/entry/raycharles_uncategorized\"\n"
|
" href=\"/ROOT/catalog/v2/entry/raycharles_uncategorized\"\n"
|
||||||
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
||||||
" </entry>\n"
|
" </entry>\n"
|
||||||
"</feed>\n"
|
"</feed>\n"
|
||||||
|
|
Loading…
Reference in New Issue