mirror of https://github.com/kiwix/libkiwix.git
Merge pull request #779 from kiwix/serving_customized_resources
This commit is contained in:
commit
15cb9025bb
|
@ -68,6 +68,7 @@ extern "C" {
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <fstream>
|
||||||
#include "kiwixlib-resources.h"
|
#include "kiwixlib-resources.h"
|
||||||
|
|
||||||
#ifndef _WIN32
|
#ifndef _WIN32
|
||||||
|
@ -212,6 +213,12 @@ void checkBookNumber(const Library::BookIdSet& bookIds, size_t limit) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CustomizedResourceData
|
||||||
|
{
|
||||||
|
std::string mimeType;
|
||||||
|
std::string resourceFilePath;
|
||||||
|
};
|
||||||
|
|
||||||
} // unnamed namespace
|
} // unnamed namespace
|
||||||
|
|
||||||
std::pair<std::string, Library::BookIdSet> InternalServer::selectBooks(const RequestContext& request) const
|
std::pair<std::string, Library::BookIdSet> InternalServer::selectBooks(const RequestContext& request) const
|
||||||
|
@ -327,7 +334,6 @@ zim::Query SearchInfo::getZimQuery(bool verbose) const {
|
||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static IdNameMapper defaultNameMapper;
|
static IdNameMapper defaultNameMapper;
|
||||||
|
|
||||||
static MHD_Result staticHandlerCallback(void* cls,
|
static MHD_Result staticHandlerCallback(void* cls,
|
||||||
|
@ -339,6 +345,27 @@ static MHD_Result staticHandlerCallback(void* cls,
|
||||||
size_t* upload_data_size,
|
size_t* upload_data_size,
|
||||||
void** cont_cls);
|
void** cont_cls);
|
||||||
|
|
||||||
|
class InternalServer::CustomizedResources : public std::map<std::string, CustomizedResourceData>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CustomizedResources()
|
||||||
|
{
|
||||||
|
const char* fname = ::getenv("KIWIX_SERVE_CUSTOMIZED_RESOURCES");
|
||||||
|
if ( fname )
|
||||||
|
{
|
||||||
|
std::cout << "Populating customized resources" << std::endl;
|
||||||
|
std::ifstream file(fname);
|
||||||
|
std::string url, mimeType, resourceFilePath;
|
||||||
|
while ( file >> url >> mimeType >> resourceFilePath )
|
||||||
|
{
|
||||||
|
std::cout << "Got " << url << " " << mimeType << " " << resourceFilePath << std::endl;
|
||||||
|
(*this)[url] = CustomizedResourceData{mimeType, resourceFilePath};
|
||||||
|
}
|
||||||
|
std::cout << "Done populating customized resources" << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
InternalServer::InternalServer(Library* library,
|
InternalServer::InternalServer(Library* library,
|
||||||
NameMapper* nameMapper,
|
NameMapper* nameMapper,
|
||||||
|
@ -368,9 +395,12 @@ InternalServer::InternalServer(Library* library,
|
||||||
mp_library(library),
|
mp_library(library),
|
||||||
mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper),
|
mp_nameMapper(nameMapper ? nameMapper : &defaultNameMapper),
|
||||||
searchCache(getEnvVar<int>("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)),
|
searchCache(getEnvVar<int>("KIWIX_SEARCH_CACHE_SIZE", DEFAULT_CACHE_SIZE)),
|
||||||
suggestionSearcherCache(getEnvVar<int>("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U)))
|
suggestionSearcherCache(getEnvVar<int>("KIWIX_SUGGESTION_SEARCHER_CACHE_SIZE", std::max((unsigned int) (mp_library->getBookCount(true, true)*0.1), 1U))),
|
||||||
|
m_customizedResources(new CustomizedResources)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
InternalServer::~InternalServer() = default;
|
||||||
|
|
||||||
bool InternalServer::start() {
|
bool InternalServer::start() {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
int flags = MHD_USE_SELECT_INTERNALLY;
|
int flags = MHD_USE_SELECT_INTERNALLY;
|
||||||
|
@ -507,6 +537,9 @@ std::unique_ptr<Response> InternalServer::handle_request(const RequestContext& r
|
||||||
if ( etag )
|
if ( etag )
|
||||||
return Response::build_304(*this, etag);
|
return Response::build_304(*this, etag);
|
||||||
|
|
||||||
|
if ( isLocallyCustomizedResource(request.get_url()) )
|
||||||
|
return handle_locally_customized_resource(request);
|
||||||
|
|
||||||
if (startsWith(request.get_url(), "/skin/"))
|
if (startsWith(request.get_url(), "/skin/"))
|
||||||
return handle_skin(request);
|
return handle_skin(request);
|
||||||
|
|
||||||
|
@ -1067,4 +1100,34 @@ std::unique_ptr<Response> InternalServer::handle_raw(const RequestContext& reque
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool InternalServer::isLocallyCustomizedResource(const std::string& url) const
|
||||||
|
{
|
||||||
|
return m_customizedResources->find(url) != m_customizedResources->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Response> InternalServer::handle_locally_customized_resource(const RequestContext& request)
|
||||||
|
{
|
||||||
|
if (m_verbose.load()) {
|
||||||
|
printf("** running handle_locally_customized_resource\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
const CustomizedResourceData& crd = m_customizedResources->at(request.get_url());
|
||||||
|
|
||||||
|
if (m_verbose.load()) {
|
||||||
|
std::cout << "Reading " << crd.resourceFilePath << std::endl;
|
||||||
|
}
|
||||||
|
const auto resourceData = getFileContent(crd.resourceFilePath);
|
||||||
|
|
||||||
|
auto byteRange = request.get_range().resolve(resourceData.size());
|
||||||
|
if (byteRange.kind() != ByteRange::RESOLVED_FULL_CONTENT) {
|
||||||
|
return Response::build_416(*this, resourceData.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
return ContentResponse::build(*this,
|
||||||
|
resourceData,
|
||||||
|
crd.mimeType,
|
||||||
|
/*isHomePage=*/false,
|
||||||
|
/*raw=*/true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@ class InternalServer {
|
||||||
bool blockExternalLinks,
|
bool blockExternalLinks,
|
||||||
std::string indexTemplateString,
|
std::string indexTemplateString,
|
||||||
int ipConnectionLimit);
|
int ipConnectionLimit);
|
||||||
virtual ~InternalServer() = default;
|
virtual ~InternalServer();
|
||||||
|
|
||||||
MHD_Result handlerCallback(struct MHD_Connection* connection,
|
MHD_Result handlerCallback(struct MHD_Connection* connection,
|
||||||
const char* url,
|
const char* url,
|
||||||
|
@ -142,6 +142,7 @@ class InternalServer {
|
||||||
std::unique_ptr<Response> handle_captured_external(const RequestContext& request);
|
std::unique_ptr<Response> handle_captured_external(const RequestContext& request);
|
||||||
std::unique_ptr<Response> handle_content(const RequestContext& request);
|
std::unique_ptr<Response> handle_content(const RequestContext& request);
|
||||||
std::unique_ptr<Response> handle_raw(const RequestContext& request);
|
std::unique_ptr<Response> handle_raw(const RequestContext& request);
|
||||||
|
std::unique_ptr<Response> handle_locally_customized_resource(const RequestContext& request);
|
||||||
|
|
||||||
std::vector<std::string> search_catalog(const RequestContext& request,
|
std::vector<std::string> search_catalog(const RequestContext& request,
|
||||||
kiwix::OPDSDumper& opdsDumper);
|
kiwix::OPDSDumper& opdsDumper);
|
||||||
|
@ -153,6 +154,8 @@ class InternalServer {
|
||||||
std::pair<std::string, Library::BookIdSet> selectBooks(const RequestContext& r) const;
|
std::pair<std::string, Library::BookIdSet> selectBooks(const RequestContext& r) const;
|
||||||
SearchInfo getSearchInfo(const RequestContext& r) const;
|
SearchInfo getSearchInfo(const RequestContext& r) const;
|
||||||
|
|
||||||
|
bool isLocallyCustomizedResource(const std::string& url) const;
|
||||||
|
|
||||||
private: // data
|
private: // data
|
||||||
std::string m_addr;
|
std::string m_addr;
|
||||||
int m_port;
|
int m_port;
|
||||||
|
@ -176,6 +179,9 @@ class InternalServer {
|
||||||
std::string m_server_id;
|
std::string m_server_id;
|
||||||
std::string m_library_id;
|
std::string m_library_id;
|
||||||
|
|
||||||
|
class CustomizedResources;
|
||||||
|
std::unique_ptr<CustomizedResources> m_customizedResources;
|
||||||
|
|
||||||
friend std::unique_ptr<Response> Response::build(const InternalServer& server);
|
friend std::unique_ptr<Response> Response::build(const InternalServer& server);
|
||||||
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage, bool raw);
|
friend std::unique_ptr<ContentResponse> ContentResponse::build(const InternalServer& server, const std::string& content, const std::string& mimetype, bool isHomePage, bool raw);
|
||||||
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw);
|
friend std::unique_ptr<Response> ItemResponse::build(const InternalServer& server, const RequestContext& request, const zim::Item& item, bool raw);
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
/non-existent-item text/plain ./test/helloworld.txt
|
||||||
|
/ text/html ./test/welcome.html
|
||||||
|
/skin/index.css application/json ./test/helloworld.txt
|
||||||
|
/zimfile/A/Ray_Charles ray/charles ./test/welcome.html
|
||||||
|
/search text/html ./test/helloworld.txt
|
|
@ -0,0 +1 @@
|
||||||
|
Hello world!
|
|
@ -0,0 +1 @@
|
||||||
|
<html><head></head><body>Welcome</body></html>
|
|
@ -0,0 +1,713 @@
|
||||||
|
|
||||||
|
#define CPPHTTPLIB_ZLIB_SUPPORT 1
|
||||||
|
#include "./httplib.h"
|
||||||
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
|
#define SERVER_PORT 8001
|
||||||
|
#include "server_testing_tools.h"
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Testing of the library-related functionality of the server
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class LibraryServerTest : public ::testing::Test
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
std::unique_ptr<ZimFileServer> zfs1_;
|
||||||
|
|
||||||
|
const int PORT = 8002;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void SetUp() override {
|
||||||
|
zfs1_.reset(new ZimFileServer(PORT, "./test/library.xml"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TearDown() override {
|
||||||
|
zfs1_.reset();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Returns a copy of 'text' where every line that fully matches 'pattern'
|
||||||
|
// preceded by optional whitespace is replaced with the fixed string
|
||||||
|
// 'replacement' preserving the leading whitespace
|
||||||
|
std::string replaceLines(const std::string& text,
|
||||||
|
const std::string& pattern,
|
||||||
|
const std::string& replacement)
|
||||||
|
{
|
||||||
|
std::regex regex("^ *" + pattern + "$");
|
||||||
|
std::ostringstream oss;
|
||||||
|
std::istringstream iss(text);
|
||||||
|
std::string line;
|
||||||
|
while ( std::getline(iss, line) ) {
|
||||||
|
if ( std::regex_match(line, regex) ) {
|
||||||
|
for ( size_t i = 0; i < line.size() && line[i] == ' '; ++i )
|
||||||
|
oss << ' ';
|
||||||
|
oss << replacement << "\n";
|
||||||
|
} else {
|
||||||
|
oss << line << "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return oss.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string maskVariableOPDSFeedData(std::string s)
|
||||||
|
{
|
||||||
|
s = replaceLines(s, R"(<updated>\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ</updated>)",
|
||||||
|
"<updated>YYYY-MM-DDThh:mm:ssZ</updated>");
|
||||||
|
s = replaceLines(s, "<id>[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}</id>",
|
||||||
|
"<id>12345678-90ab-cdef-1234-567890abcdef</id>");
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define OPDS_FEED_TAG \
|
||||||
|
"<feed xmlns=\"http://www.w3.org/2005/Atom\"\n" \
|
||||||
|
" xmlns:dc=\"http://purl.org/dc/terms/\"\n" \
|
||||||
|
" xmlns:opds=\"http://opds-spec.org/2010/catalog\">\n"
|
||||||
|
|
||||||
|
#define CATALOG_LINK_TAGS \
|
||||||
|
" <link rel=\"self\" href=\"\" type=\"application/atom+xml\" />\n" \
|
||||||
|
" <link rel=\"search\"" \
|
||||||
|
" type=\"application/opensearchdescription+xml\"" \
|
||||||
|
" href=\"/ROOT/catalog/searchdescription.xml\" />\n"
|
||||||
|
|
||||||
|
#define CHARLES_RAY_CATALOG_ENTRY \
|
||||||
|
" <entry>\n" \
|
||||||
|
" <id>urn:uuid:charlesray</id>\n" \
|
||||||
|
" <title>Charles, Ray</title>\n" \
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" \
|
||||||
|
" <summary>Wikipedia articles about Ray Charles</summary>\n" \
|
||||||
|
" <language>fra</language>\n" \
|
||||||
|
" <name>wikipedia_fr_ray_charles</name>\n" \
|
||||||
|
" <flavour></flavour>\n" \
|
||||||
|
" <category>jazz</category>\n" \
|
||||||
|
" <tags>unittest;wikipedia;_category:jazz;_pictures:no;_videos:no;_details:no;_ftindex:yes</tags>\n" \
|
||||||
|
" <articleCount>284</articleCount>\n" \
|
||||||
|
" <mediaCount>2</mediaCount>\n" \
|
||||||
|
" <link type=\"text/html\" href=\"/ROOT/zimfile%26other\" />\n" \
|
||||||
|
" <author>\n" \
|
||||||
|
" <name>Wikipedia</name>\n" \
|
||||||
|
" </author>\n" \
|
||||||
|
" <publisher>\n" \
|
||||||
|
" <name>Kiwix</name>\n" \
|
||||||
|
" </publisher>\n" \
|
||||||
|
" <dc:issued>2020-03-31T00:00:00Z</dc:issued>\n" \
|
||||||
|
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile%26other.zim\" length=\"569344\" />\n" \
|
||||||
|
" </entry>\n"
|
||||||
|
|
||||||
|
#define RAY_CHARLES_CATALOG_ENTRY \
|
||||||
|
" <entry>\n" \
|
||||||
|
" <id>urn:uuid:raycharles</id>\n" \
|
||||||
|
" <title>Ray Charles</title>\n" \
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" \
|
||||||
|
" <summary>Wikipedia articles about Ray Charles</summary>\n" \
|
||||||
|
" <language>eng</language>\n" \
|
||||||
|
" <name>wikipedia_en_ray_charles</name>\n" \
|
||||||
|
" <flavour></flavour>\n" \
|
||||||
|
" <category>wikipedia</category>\n" \
|
||||||
|
" <tags>unittest;wikipedia;_category:wikipedia;_pictures:no;_videos:no;_details:no;_ftindex:yes</tags>\n" \
|
||||||
|
" <articleCount>284</articleCount>\n" \
|
||||||
|
" <mediaCount>2</mediaCount>\n" \
|
||||||
|
" <link rel=\"http://opds-spec.org/image/thumbnail\"\n" \
|
||||||
|
" href=\"/ROOT/catalog/v2/illustration/zimfile/?size=48\"\n" \
|
||||||
|
" type=\"image/png;width=48;height=48;scale=1\"/>\n" \
|
||||||
|
" <link type=\"text/html\" href=\"/ROOT/zimfile\" />\n" \
|
||||||
|
" <author>\n" \
|
||||||
|
" <name>Wikipedia</name>\n" \
|
||||||
|
" </author>\n" \
|
||||||
|
" <publisher>\n" \
|
||||||
|
" <name>Kiwix</name>\n" \
|
||||||
|
" </publisher>\n" \
|
||||||
|
" <dc:issued>2020-03-31T00:00:00Z</dc:issued>\n" \
|
||||||
|
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim\" length=\"569344\" />\n" \
|
||||||
|
" </entry>\n"
|
||||||
|
|
||||||
|
#define UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY \
|
||||||
|
" <entry>\n" \
|
||||||
|
" <id>urn:uuid:raycharles_uncategorized</id>\n" \
|
||||||
|
" <title>Ray (uncategorized) Charles</title>\n" \
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" \
|
||||||
|
" <summary>No category is assigned to this library entry.</summary>\n" \
|
||||||
|
" <language>rus</language>\n" \
|
||||||
|
" <name>wikipedia_ru_ray_charles</name>\n" \
|
||||||
|
" <flavour></flavour>\n" \
|
||||||
|
" <category></category>\n" \
|
||||||
|
" <tags>unittest;wikipedia;_pictures:no;_videos:no;_details:no</tags>\n" \
|
||||||
|
" <articleCount>284</articleCount>\n" \
|
||||||
|
" <mediaCount>2</mediaCount>\n" \
|
||||||
|
" <link type=\"text/html\" href=\"/ROOT/zimfile\" />\n" \
|
||||||
|
" <author>\n" \
|
||||||
|
" <name>Wikipedia</name>\n" \
|
||||||
|
" </author>\n" \
|
||||||
|
" <publisher>\n" \
|
||||||
|
" <name>Kiwix</name>\n" \
|
||||||
|
" </publisher>\n" \
|
||||||
|
" <dc:issued>2020-03-31T00:00:00Z</dc:issued>\n" \
|
||||||
|
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim\" length=\"125952\" />\n" \
|
||||||
|
" </entry>\n"
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_root_xml)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/root.xml");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>All zims</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
"\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_searchdescription_xml)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/searchdescription.xml");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(r->body,
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||||
|
"<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\">\n"
|
||||||
|
" <ShortName>Zim catalog search</ShortName>\n"
|
||||||
|
" <Description>Search zim files in the catalog.</Description>\n"
|
||||||
|
" <Url type=\"application/atom+xml;profile=opds-catalog\"\n"
|
||||||
|
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
||||||
|
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
||||||
|
" indexOffset=\"0\"\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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_search_by_phrase)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?q=\"ray%20charles\"");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (q="ray charles")</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>2</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>2</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_search_by_words)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?q=ray%20charles");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (q=ray charles)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>3</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>3</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_prefix_search)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?q=description:ray%20description:charles");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (q=description:ray description:charles)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>2</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>2</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?q=title:\"ray%20charles\"");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (q=title:"ray charles")</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>1</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>1</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_search_with_word_exclusion)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?q=ray%20-uncategorized");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (q=ray -uncategorized)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>2</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>2</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_search_by_tag)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?tag=_category:jazz");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (tag=_category:jazz)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>1</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>1</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_search_by_category)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?category=jazz");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (category=jazz)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>1</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>1</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?count=0");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (count=0)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>3</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>3</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?count=1");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (count=1)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>3</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>1</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?start=1&count=1");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (count=1&start=1)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>3</totalResults>\n"
|
||||||
|
" <startIndex>1</startIndex>\n"
|
||||||
|
" <itemsPerPage>1</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/search?start=100&count=10");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
OPDS_FEED_TAG
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
||||||
|
" <title>Filtered zims (count=10&start=100)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>3</totalResults>\n"
|
||||||
|
" <startIndex>100</startIndex>\n"
|
||||||
|
" <itemsPerPage>0</itemsPerPage>\n"
|
||||||
|
CATALOG_LINK_TAGS
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_root)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/root.xml");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||||
|
xmlns:opds="https://specs.opds.io/opds-1.2">
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
<link rel="self"
|
||||||
|
href="/ROOT/catalog/v2/root.xml"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
|
<link rel="start"
|
||||||
|
href="/ROOT/catalog/v2/root.xml"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
|
<link rel="search"
|
||||||
|
href="/ROOT/catalog/v2/searchdescription.xml"
|
||||||
|
type="application/opensearchdescription+xml"/>
|
||||||
|
<title>OPDS Catalog Root</title>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
|
||||||
|
<entry>
|
||||||
|
<title>All entries</title>
|
||||||
|
<link rel="subsection"
|
||||||
|
href="/ROOT/catalog/v2/entries"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
<content type="text">All entries from this catalog.</content>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>All entries (partial)</title>
|
||||||
|
<link rel="subsection"
|
||||||
|
href="/ROOT/catalog/v2/partial_entries"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
<content type="text">All entries from this catalog in partial format.</content>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>List of categories</title>
|
||||||
|
<link rel="subsection"
|
||||||
|
href="/ROOT/catalog/v2/categories"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
<content type="text">List of all categories in this catalog.</content>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>List of languages</title>
|
||||||
|
<link rel="subsection"
|
||||||
|
href="/ROOT/catalog/v2/languages"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
<content type="text">List of all languages in this catalog.</content>
|
||||||
|
</entry>
|
||||||
|
</feed>
|
||||||
|
)";
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_searchdescription_xml)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/searchdescription.xml");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(r->body,
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||||
|
"<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\">\n"
|
||||||
|
" <ShortName>Zim catalog search</ShortName>\n"
|
||||||
|
" <Description>Search zim files in the catalog.</Description>\n"
|
||||||
|
" <Url type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"\n"
|
||||||
|
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
||||||
|
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
||||||
|
" indexOffset=\"0\"\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"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_categories)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/categories");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||||
|
xmlns:opds="https://specs.opds.io/opds-1.2">
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
<link rel="self"
|
||||||
|
href="/ROOT/catalog/v2/categories"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
|
<link rel="start"
|
||||||
|
href="/ROOT/catalog/v2/root.xml"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
|
<title>List of categories</title>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
|
||||||
|
<entry>
|
||||||
|
<title>jazz</title>
|
||||||
|
<link rel="subsection"
|
||||||
|
href="/ROOT/catalog/v2/entries?category=jazz"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
<content type="text">All entries with category of 'jazz'.</content>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>wikipedia</title>
|
||||||
|
<link rel="subsection"
|
||||||
|
href="/ROOT/catalog/v2/entries?category=wikipedia"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
<content type="text">All entries with category of 'wikipedia'.</content>
|
||||||
|
</entry>
|
||||||
|
</feed>
|
||||||
|
)";
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_languages)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/languages");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<feed xmlns="http://www.w3.org/2005/Atom"
|
||||||
|
xmlns:dc="http://purl.org/dc/terms/"
|
||||||
|
xmlns:opds="https://specs.opds.io/opds-1.2"
|
||||||
|
xmlns:thr="http://purl.org/syndication/thread/1.0">
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
<link rel="self"
|
||||||
|
href="/ROOT/catalog/v2/languages"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
|
<link rel="start"
|
||||||
|
href="/ROOT/catalog/v2/root.xml"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
||||||
|
<title>List of languages</title>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
|
||||||
|
<entry>
|
||||||
|
<title>English</title>
|
||||||
|
<dc:language>eng</dc:language>
|
||||||
|
<thr:count>1</thr:count>
|
||||||
|
<link rel="subsection"
|
||||||
|
href="/ROOT/catalog/v2/entries?lang=eng"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>français</title>
|
||||||
|
<dc:language>fra</dc:language>
|
||||||
|
<thr:count>1</thr:count>
|
||||||
|
<link rel="subsection"
|
||||||
|
href="/ROOT/catalog/v2/entries?lang=fra"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
</entry>
|
||||||
|
<entry>
|
||||||
|
<title>русский</title>
|
||||||
|
<dc:language>rus</dc:language>
|
||||||
|
<thr:count>1</thr:count>
|
||||||
|
<link rel="subsection"
|
||||||
|
href="/ROOT/catalog/v2/entries?lang=rus"
|
||||||
|
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
||||||
|
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
||||||
|
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
||||||
|
</entry>
|
||||||
|
</feed>
|
||||||
|
)";
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CATALOG_V2_ENTRIES_PREAMBLE0(x) \
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
|
||||||
|
"<feed xmlns=\"http://www.w3.org/2005/Atom\"\n" \
|
||||||
|
" xmlns:dc=\"http://purl.org/dc/terms/\"\n" \
|
||||||
|
" xmlns:opds=\"https://specs.opds.io/opds-1.2\"\n" \
|
||||||
|
" xmlns:opensearch=\"http://a9.com/-/spec/opensearch/1.1/\">\n" \
|
||||||
|
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" \
|
||||||
|
"\n" \
|
||||||
|
" <link rel=\"self\"\n" \
|
||||||
|
" href=\"/ROOT/catalog/v2/" x "\"\n" \
|
||||||
|
" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"/>\n" \
|
||||||
|
" <link rel=\"start\"\n" \
|
||||||
|
" href=\"/ROOT/catalog/v2/root.xml\"\n" \
|
||||||
|
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
||||||
|
" <link rel=\"up\"\n" \
|
||||||
|
" href=\"/ROOT/catalog/v2/root.xml\"\n" \
|
||||||
|
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
||||||
|
"\n" \
|
||||||
|
|
||||||
|
#define CATALOG_V2_ENTRIES_PREAMBLE(q) \
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE0("entries" q)
|
||||||
|
|
||||||
|
#define CATALOG_V2_PARTIAL_ENTRIES_PREAMBLE(q) \
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE0("partial_entries" q)
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_entries)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE("")
|
||||||
|
" <title>All Entries</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
"\n"
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?start=1");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE("?start=1")
|
||||||
|
" <title>Filtered Entries (start=1)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>3</totalResults>\n"
|
||||||
|
" <startIndex>1</startIndex>\n"
|
||||||
|
" <itemsPerPage>2</itemsPerPage>\n"
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?count=2");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE("?count=2")
|
||||||
|
" <title>Filtered Entries (count=2)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>3</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>2</itemsPerPage>\n"
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?start=1&count=1");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE("?count=1&start=1")
|
||||||
|
" <title>Filtered Entries (count=1&start=1)</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>3</totalResults>\n"
|
||||||
|
" <startIndex>1</startIndex>\n"
|
||||||
|
" <itemsPerPage>1</itemsPerPage>\n"
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_search_terms)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?q=\"ray%20charles\"");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
CATALOG_V2_ENTRIES_PREAMBLE("?q=%22ray%20charles%22")
|
||||||
|
" <title>Filtered Entries (q="ray charles")</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <totalResults>2</totalResults>\n"
|
||||||
|
" <startIndex>0</startIndex>\n"
|
||||||
|
" <itemsPerPage>2</itemsPerPage>\n"
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
CHARLES_RAY_CATALOG_ENTRY
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_individual_entry_access)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/entry/raycharles");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
||||||
|
RAY_CHARLES_CATALOG_ENTRY
|
||||||
|
);
|
||||||
|
|
||||||
|
const auto r1 = zfs1_->GET("/ROOT/catalog/v2/entry/non-existent-entry");
|
||||||
|
EXPECT_EQ(r1->status, 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/catalog/v2/partial_entries");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
||||||
|
CATALOG_V2_PARTIAL_ENTRIES_PREAMBLE("")
|
||||||
|
" <title>All Entries</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
"\n"
|
||||||
|
" <entry>\n"
|
||||||
|
" <id>urn:uuid:charlesray</id>\n"
|
||||||
|
" <title>Charles, Ray</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <link rel=\"alternate\"\n"
|
||||||
|
" href=\"/ROOT/catalog/v2/entry/charlesray\"\n"
|
||||||
|
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
||||||
|
" </entry>\n"
|
||||||
|
" <entry>\n"
|
||||||
|
" <id>urn:uuid:raycharles</id>\n"
|
||||||
|
" <title>Ray Charles</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <link rel=\"alternate\"\n"
|
||||||
|
" href=\"/ROOT/catalog/v2/entry/raycharles\"\n"
|
||||||
|
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
||||||
|
" </entry>\n"
|
||||||
|
" <entry>\n"
|
||||||
|
" <id>urn:uuid:raycharles_uncategorized</id>\n"
|
||||||
|
" <title>Ray (uncategorized) Charles</title>\n"
|
||||||
|
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
||||||
|
" <link rel=\"alternate\"\n"
|
||||||
|
" href=\"/ROOT/catalog/v2/entry/raycharles_uncategorized\"\n"
|
||||||
|
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
||||||
|
" </entry>\n"
|
||||||
|
"</feed>\n"
|
||||||
|
);
|
||||||
|
}
|
|
@ -17,6 +17,7 @@ tests = [
|
||||||
if build_machine.system() != 'windows'
|
if build_machine.system() != 'windows'
|
||||||
tests += [
|
tests += [
|
||||||
'server',
|
'server',
|
||||||
|
'library_server',
|
||||||
'server_search'
|
'server_search'
|
||||||
]
|
]
|
||||||
endif
|
endif
|
||||||
|
@ -35,7 +36,10 @@ if gtest_dep.found() and not meson.is_cross_build()
|
||||||
'zimfile&other.zim',
|
'zimfile&other.zim',
|
||||||
'corner_cases.zim',
|
'corner_cases.zim',
|
||||||
'poor.zim',
|
'poor.zim',
|
||||||
'library.xml'
|
'library.xml',
|
||||||
|
'customized_resources.txt',
|
||||||
|
'helloworld.txt',
|
||||||
|
'welcome.html',
|
||||||
]
|
]
|
||||||
foreach file : data_files
|
foreach file : data_files
|
||||||
# configure_file(input : 'data/' + file,
|
# configure_file(input : 'data/' + file,
|
||||||
|
|
768
test/server.cpp
768
test/server.cpp
|
@ -287,6 +287,68 @@ TEST_F(ServerTest, 404)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CustomizedServerTest : ServerTest
|
||||||
|
{
|
||||||
|
void SetUp()
|
||||||
|
{
|
||||||
|
setenv("KIWIX_SERVE_CUSTOMIZED_RESOURCES", "./test/customized_resources.txt", 1);
|
||||||
|
ServerTest::SetUp();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<std::string> StringCollection;
|
||||||
|
|
||||||
|
std::string getHeaderValue(const Headers& headers, const std::string& name)
|
||||||
|
{
|
||||||
|
const auto er = headers.equal_range(name);
|
||||||
|
const auto n = std::distance(er.first, er.second);
|
||||||
|
if (n == 0)
|
||||||
|
throw std::runtime_error("Missing header: " + name);
|
||||||
|
if (n > 1)
|
||||||
|
throw std::runtime_error("Multiple occurrences of header: " + name);
|
||||||
|
return er.first->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CustomizedServerTest, NewResourcesCanBeAdded)
|
||||||
|
{
|
||||||
|
// ServerTest.404 verifies that "/ROOT/non-existent-item" doesn't exist
|
||||||
|
const auto r = zfs1_->GET("/ROOT/non-existent-item");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(getHeaderValue(r->headers, "Content-Type"), "text/plain");
|
||||||
|
EXPECT_EQ(r->body, "Hello world!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(CustomizedServerTest, ContentOfAnyServableUrlCanBeOverriden)
|
||||||
|
{
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(getHeaderValue(r->headers, "Content-Type"), "text/html");
|
||||||
|
EXPECT_EQ(r->body, "<html><head></head><body>Welcome</body></html>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/skin/index.css");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(getHeaderValue(r->headers, "Content-Type"), "application/json");
|
||||||
|
EXPECT_EQ(r->body, "Hello world!\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/zimfile/A/Ray_Charles");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(getHeaderValue(r->headers, "Content-Type"), "ray/charles");
|
||||||
|
EXPECT_EQ(r->body, "<html><head></head><body>Welcome</body></html>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const auto r = zfs1_->GET("/ROOT/search?pattern=la+femme");
|
||||||
|
EXPECT_EQ(r->status, 200);
|
||||||
|
EXPECT_EQ(getHeaderValue(r->headers, "Content-Type"), "text/html");
|
||||||
|
EXPECT_EQ(r->body, "Hello world!\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
namespace TestingOfHtmlResponses
|
namespace TestingOfHtmlResponses
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -1476,709 +1538,3 @@ TEST_F(ServerTest, suggestions_in_range)
|
||||||
ASSERT_EQ(currCount, 0);
|
ASSERT_EQ(currCount, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
// Testing of the library-related functionality of the server
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
class LibraryServerTest : public ::testing::Test
|
|
||||||
{
|
|
||||||
protected:
|
|
||||||
std::unique_ptr<ZimFileServer> zfs1_;
|
|
||||||
|
|
||||||
const int PORT = 8002;
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void SetUp() override {
|
|
||||||
zfs1_.reset(new ZimFileServer(PORT, "./test/library.xml"));
|
|
||||||
}
|
|
||||||
|
|
||||||
void TearDown() override {
|
|
||||||
zfs1_.reset();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns a copy of 'text' where every line that fully matches 'pattern'
|
|
||||||
// preceded by optional whitespace is replaced with the fixed string
|
|
||||||
// 'replacement' preserving the leading whitespace
|
|
||||||
std::string replaceLines(const std::string& text,
|
|
||||||
const std::string& pattern,
|
|
||||||
const std::string& replacement)
|
|
||||||
{
|
|
||||||
std::regex regex("^ *" + pattern + "$");
|
|
||||||
std::ostringstream oss;
|
|
||||||
std::istringstream iss(text);
|
|
||||||
std::string line;
|
|
||||||
while ( std::getline(iss, line) ) {
|
|
||||||
if ( std::regex_match(line, regex) ) {
|
|
||||||
for ( size_t i = 0; i < line.size() && line[i] == ' '; ++i )
|
|
||||||
oss << ' ';
|
|
||||||
oss << replacement << "\n";
|
|
||||||
} else {
|
|
||||||
oss << line << "\n";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return oss.str();
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string maskVariableOPDSFeedData(std::string s)
|
|
||||||
{
|
|
||||||
s = replaceLines(s, R"(<updated>\d\d\d\d-\d\d-\d\dT\d\d:\d\d:\d\dZ</updated>)",
|
|
||||||
"<updated>YYYY-MM-DDThh:mm:ssZ</updated>");
|
|
||||||
s = replaceLines(s, "<id>[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}</id>",
|
|
||||||
"<id>12345678-90ab-cdef-1234-567890abcdef</id>");
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
#define OPDS_FEED_TAG \
|
|
||||||
"<feed xmlns=\"http://www.w3.org/2005/Atom\"\n" \
|
|
||||||
" xmlns:dc=\"http://purl.org/dc/terms/\"\n" \
|
|
||||||
" xmlns:opds=\"http://opds-spec.org/2010/catalog\">\n"
|
|
||||||
|
|
||||||
#define CATALOG_LINK_TAGS \
|
|
||||||
" <link rel=\"self\" href=\"\" type=\"application/atom+xml\" />\n" \
|
|
||||||
" <link rel=\"search\"" \
|
|
||||||
" type=\"application/opensearchdescription+xml\"" \
|
|
||||||
" href=\"/ROOT/catalog/searchdescription.xml\" />\n"
|
|
||||||
|
|
||||||
#define CHARLES_RAY_CATALOG_ENTRY \
|
|
||||||
" <entry>\n" \
|
|
||||||
" <id>urn:uuid:charlesray</id>\n" \
|
|
||||||
" <title>Charles, Ray</title>\n" \
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" \
|
|
||||||
" <summary>Wikipedia articles about Ray Charles</summary>\n" \
|
|
||||||
" <language>fra</language>\n" \
|
|
||||||
" <name>wikipedia_fr_ray_charles</name>\n" \
|
|
||||||
" <flavour></flavour>\n" \
|
|
||||||
" <category>jazz</category>\n" \
|
|
||||||
" <tags>unittest;wikipedia;_category:jazz;_pictures:no;_videos:no;_details:no;_ftindex:yes</tags>\n" \
|
|
||||||
" <articleCount>284</articleCount>\n" \
|
|
||||||
" <mediaCount>2</mediaCount>\n" \
|
|
||||||
" <link type=\"text/html\" href=\"/ROOT/zimfile%26other\" />\n" \
|
|
||||||
" <author>\n" \
|
|
||||||
" <name>Wikipedia</name>\n" \
|
|
||||||
" </author>\n" \
|
|
||||||
" <publisher>\n" \
|
|
||||||
" <name>Kiwix</name>\n" \
|
|
||||||
" </publisher>\n" \
|
|
||||||
" <dc:issued>2020-03-31T00:00:00Z</dc:issued>\n" \
|
|
||||||
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile%26other.zim\" length=\"569344\" />\n" \
|
|
||||||
" </entry>\n"
|
|
||||||
|
|
||||||
#define RAY_CHARLES_CATALOG_ENTRY \
|
|
||||||
" <entry>\n" \
|
|
||||||
" <id>urn:uuid:raycharles</id>\n" \
|
|
||||||
" <title>Ray Charles</title>\n" \
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" \
|
|
||||||
" <summary>Wikipedia articles about Ray Charles</summary>\n" \
|
|
||||||
" <language>eng</language>\n" \
|
|
||||||
" <name>wikipedia_en_ray_charles</name>\n" \
|
|
||||||
" <flavour></flavour>\n" \
|
|
||||||
" <category>wikipedia</category>\n" \
|
|
||||||
" <tags>unittest;wikipedia;_category:wikipedia;_pictures:no;_videos:no;_details:no;_ftindex:yes</tags>\n" \
|
|
||||||
" <articleCount>284</articleCount>\n" \
|
|
||||||
" <mediaCount>2</mediaCount>\n" \
|
|
||||||
" <link rel=\"http://opds-spec.org/image/thumbnail\"\n" \
|
|
||||||
" href=\"/ROOT/catalog/v2/illustration/zimfile/?size=48\"\n" \
|
|
||||||
" type=\"image/png;width=48;height=48;scale=1\"/>\n" \
|
|
||||||
" <link type=\"text/html\" href=\"/ROOT/zimfile\" />\n" \
|
|
||||||
" <author>\n" \
|
|
||||||
" <name>Wikipedia</name>\n" \
|
|
||||||
" </author>\n" \
|
|
||||||
" <publisher>\n" \
|
|
||||||
" <name>Kiwix</name>\n" \
|
|
||||||
" </publisher>\n" \
|
|
||||||
" <dc:issued>2020-03-31T00:00:00Z</dc:issued>\n" \
|
|
||||||
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim\" length=\"569344\" />\n" \
|
|
||||||
" </entry>\n"
|
|
||||||
|
|
||||||
#define UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY \
|
|
||||||
" <entry>\n" \
|
|
||||||
" <id>urn:uuid:raycharles_uncategorized</id>\n" \
|
|
||||||
" <title>Ray (uncategorized) Charles</title>\n" \
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n" \
|
|
||||||
" <summary>No category is assigned to this library entry.</summary>\n" \
|
|
||||||
" <language>rus</language>\n" \
|
|
||||||
" <name>wikipedia_ru_ray_charles</name>\n" \
|
|
||||||
" <flavour></flavour>\n" \
|
|
||||||
" <category></category>\n" \
|
|
||||||
" <tags>unittest;wikipedia;_pictures:no;_videos:no;_details:no</tags>\n" \
|
|
||||||
" <articleCount>284</articleCount>\n" \
|
|
||||||
" <mediaCount>2</mediaCount>\n" \
|
|
||||||
" <link type=\"text/html\" href=\"/ROOT/zimfile\" />\n" \
|
|
||||||
" <author>\n" \
|
|
||||||
" <name>Wikipedia</name>\n" \
|
|
||||||
" </author>\n" \
|
|
||||||
" <publisher>\n" \
|
|
||||||
" <name>Kiwix</name>\n" \
|
|
||||||
" </publisher>\n" \
|
|
||||||
" <dc:issued>2020-03-31T00:00:00Z</dc:issued>\n" \
|
|
||||||
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"https://github.com/kiwix/libkiwix/raw/master/test/data/zimfile.zim\" length=\"125952\" />\n" \
|
|
||||||
" </entry>\n"
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_root_xml)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/root.xml");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>All zims</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
"\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_searchdescription_xml)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/searchdescription.xml");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(r->body,
|
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
||||||
"<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\">\n"
|
|
||||||
" <ShortName>Zim catalog search</ShortName>\n"
|
|
||||||
" <Description>Search zim files in the catalog.</Description>\n"
|
|
||||||
" <Url type=\"application/atom+xml;profile=opds-catalog\"\n"
|
|
||||||
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
|
||||||
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
|
||||||
" indexOffset=\"0\"\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"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_by_phrase)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?q=\"ray%20charles\"");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (q="ray charles")</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>2</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>2</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_by_words)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?q=ray%20charles");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (q=ray charles)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>3</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>3</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_prefix_search)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?q=description:ray%20description:charles");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (q=description:ray description:charles)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>2</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>2</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?q=title:\"ray%20charles\"");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (q=title:"ray charles")</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>1</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>1</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_with_word_exclusion)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?q=ray%20-uncategorized");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (q=ray -uncategorized)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>2</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>2</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_by_tag)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?tag=_category:jazz");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (tag=_category:jazz)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>1</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>1</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_by_category)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?category=jazz");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (category=jazz)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>1</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>1</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_search_results_pagination)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?count=0");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (count=0)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>3</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>3</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?count=1");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (count=1)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>3</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>1</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?start=1&count=1");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (count=1&start=1)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>3</totalResults>\n"
|
|
||||||
" <startIndex>1</startIndex>\n"
|
|
||||||
" <itemsPerPage>1</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/search?start=100&count=10");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
OPDS_FEED_TAG
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n"
|
|
||||||
" <title>Filtered zims (count=10&start=100)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>3</totalResults>\n"
|
|
||||||
" <startIndex>100</startIndex>\n"
|
|
||||||
" <itemsPerPage>0</itemsPerPage>\n"
|
|
||||||
CATALOG_LINK_TAGS
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_root)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/root.xml");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
|
||||||
xmlns:opds="https://specs.opds.io/opds-1.2">
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
<link rel="self"
|
|
||||||
href="/ROOT/catalog/v2/root.xml"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
|
||||||
<link rel="start"
|
|
||||||
href="/ROOT/catalog/v2/root.xml"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
|
||||||
<link rel="search"
|
|
||||||
href="/ROOT/catalog/v2/searchdescription.xml"
|
|
||||||
type="application/opensearchdescription+xml"/>
|
|
||||||
<title>OPDS Catalog Root</title>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
|
|
||||||
<entry>
|
|
||||||
<title>All entries</title>
|
|
||||||
<link rel="subsection"
|
|
||||||
href="/ROOT/catalog/v2/entries"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
<content type="text">All entries from this catalog.</content>
|
|
||||||
</entry>
|
|
||||||
<entry>
|
|
||||||
<title>All entries (partial)</title>
|
|
||||||
<link rel="subsection"
|
|
||||||
href="/ROOT/catalog/v2/partial_entries"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
<content type="text">All entries from this catalog in partial format.</content>
|
|
||||||
</entry>
|
|
||||||
<entry>
|
|
||||||
<title>List of categories</title>
|
|
||||||
<link rel="subsection"
|
|
||||||
href="/ROOT/catalog/v2/categories"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
<content type="text">List of all categories in this catalog.</content>
|
|
||||||
</entry>
|
|
||||||
<entry>
|
|
||||||
<title>List of languages</title>
|
|
||||||
<link rel="subsection"
|
|
||||||
href="/ROOT/catalog/v2/languages"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
<content type="text">List of all languages in this catalog.</content>
|
|
||||||
</entry>
|
|
||||||
</feed>
|
|
||||||
)";
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_searchdescription_xml)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/searchdescription.xml");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(r->body,
|
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
||||||
"<OpenSearchDescription xmlns=\"http://a9.com/-/spec/opensearch/1.1/\">\n"
|
|
||||||
" <ShortName>Zim catalog search</ShortName>\n"
|
|
||||||
" <Description>Search zim files in the catalog.</Description>\n"
|
|
||||||
" <Url type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"\n"
|
|
||||||
" xmlns:atom=\"http://www.w3.org/2005/Atom\"\n"
|
|
||||||
" xmlns:k=\"http://kiwix.org/opensearchextension/1.0\"\n"
|
|
||||||
" indexOffset=\"0\"\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"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_categories)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/categories");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
|
||||||
xmlns:opds="https://specs.opds.io/opds-1.2">
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
<link rel="self"
|
|
||||||
href="/ROOT/catalog/v2/categories"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
|
||||||
<link rel="start"
|
|
||||||
href="/ROOT/catalog/v2/root.xml"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
|
||||||
<title>List of categories</title>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
|
|
||||||
<entry>
|
|
||||||
<title>jazz</title>
|
|
||||||
<link rel="subsection"
|
|
||||||
href="/ROOT/catalog/v2/entries?category=jazz"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
<content type="text">All entries with category of 'jazz'.</content>
|
|
||||||
</entry>
|
|
||||||
<entry>
|
|
||||||
<title>wikipedia</title>
|
|
||||||
<link rel="subsection"
|
|
||||||
href="/ROOT/catalog/v2/entries?category=wikipedia"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
<content type="text">All entries with category of 'wikipedia'.</content>
|
|
||||||
</entry>
|
|
||||||
</feed>
|
|
||||||
)";
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_languages)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/languages");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
const char expected_output[] = R"(<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<feed xmlns="http://www.w3.org/2005/Atom"
|
|
||||||
xmlns:dc="http://purl.org/dc/terms/"
|
|
||||||
xmlns:opds="https://specs.opds.io/opds-1.2"
|
|
||||||
xmlns:thr="http://purl.org/syndication/thread/1.0">
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
<link rel="self"
|
|
||||||
href="/ROOT/catalog/v2/languages"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
|
||||||
<link rel="start"
|
|
||||||
href="/ROOT/catalog/v2/root.xml"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=navigation"/>
|
|
||||||
<title>List of languages</title>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
|
|
||||||
<entry>
|
|
||||||
<title>English</title>
|
|
||||||
<dc:language>eng</dc:language>
|
|
||||||
<thr:count>1</thr:count>
|
|
||||||
<link rel="subsection"
|
|
||||||
href="/ROOT/catalog/v2/entries?lang=eng"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
</entry>
|
|
||||||
<entry>
|
|
||||||
<title>français</title>
|
|
||||||
<dc:language>fra</dc:language>
|
|
||||||
<thr:count>1</thr:count>
|
|
||||||
<link rel="subsection"
|
|
||||||
href="/ROOT/catalog/v2/entries?lang=fra"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
</entry>
|
|
||||||
<entry>
|
|
||||||
<title>русский</title>
|
|
||||||
<dc:language>rus</dc:language>
|
|
||||||
<thr:count>1</thr:count>
|
|
||||||
<link rel="subsection"
|
|
||||||
href="/ROOT/catalog/v2/entries?lang=rus"
|
|
||||||
type="application/atom+xml;profile=opds-catalog;kind=acquisition"/>
|
|
||||||
<updated>YYYY-MM-DDThh:mm:ssZ</updated>
|
|
||||||
<id>12345678-90ab-cdef-1234-567890abcdef</id>
|
|
||||||
</entry>
|
|
||||||
</feed>
|
|
||||||
)";
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body), expected_output);
|
|
||||||
}
|
|
||||||
|
|
||||||
#define CATALOG_V2_ENTRIES_PREAMBLE0(x) \
|
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" \
|
|
||||||
"<feed xmlns=\"http://www.w3.org/2005/Atom\"\n" \
|
|
||||||
" xmlns:dc=\"http://purl.org/dc/terms/\"\n" \
|
|
||||||
" xmlns:opds=\"https://specs.opds.io/opds-1.2\"\n" \
|
|
||||||
" xmlns:opensearch=\"http://a9.com/-/spec/opensearch/1.1/\">\n" \
|
|
||||||
" <id>12345678-90ab-cdef-1234-567890abcdef</id>\n" \
|
|
||||||
"\n" \
|
|
||||||
" <link rel=\"self\"\n" \
|
|
||||||
" href=\"/ROOT/catalog/v2/" x "\"\n" \
|
|
||||||
" type=\"application/atom+xml;profile=opds-catalog;kind=acquisition\"/>\n" \
|
|
||||||
" <link rel=\"start\"\n" \
|
|
||||||
" href=\"/ROOT/catalog/v2/root.xml\"\n" \
|
|
||||||
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
|
||||||
" <link rel=\"up\"\n" \
|
|
||||||
" href=\"/ROOT/catalog/v2/root.xml\"\n" \
|
|
||||||
" type=\"application/atom+xml;profile=opds-catalog;kind=navigation\"/>\n" \
|
|
||||||
"\n" \
|
|
||||||
|
|
||||||
#define CATALOG_V2_ENTRIES_PREAMBLE(q) \
|
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE0("entries" q)
|
|
||||||
|
|
||||||
#define CATALOG_V2_PARTIAL_ENTRIES_PREAMBLE(q) \
|
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE0("partial_entries" q)
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_entries)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE("")
|
|
||||||
" <title>All Entries</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
"\n"
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_range)
|
|
||||||
{
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?start=1");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE("?start=1")
|
|
||||||
" <title>Filtered Entries (start=1)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>3</totalResults>\n"
|
|
||||||
" <startIndex>1</startIndex>\n"
|
|
||||||
" <itemsPerPage>2</itemsPerPage>\n"
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
UNCATEGORIZED_RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?count=2");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE("?count=2")
|
|
||||||
" <title>Filtered Entries (count=2)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>3</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>2</itemsPerPage>\n"
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?start=1&count=1");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE("?count=1&start=1")
|
|
||||||
" <title>Filtered Entries (count=1&start=1)</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>3</totalResults>\n"
|
|
||||||
" <startIndex>1</startIndex>\n"
|
|
||||||
" <itemsPerPage>1</itemsPerPage>\n"
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_entries_filtered_by_search_terms)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entries?q=\"ray%20charles\"");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
CATALOG_V2_ENTRIES_PREAMBLE("?q=%22ray%20charles%22")
|
|
||||||
" <title>Filtered Entries (q="ray charles")</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <totalResults>2</totalResults>\n"
|
|
||||||
" <startIndex>0</startIndex>\n"
|
|
||||||
" <itemsPerPage>2</itemsPerPage>\n"
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
CHARLES_RAY_CATALOG_ENTRY
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_individual_entry_access)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/entry/raycharles");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
||||||
RAY_CHARLES_CATALOG_ENTRY
|
|
||||||
);
|
|
||||||
|
|
||||||
const auto r1 = zfs1_->GET("/ROOT/catalog/v2/entry/non-existent-entry");
|
|
||||||
EXPECT_EQ(r1->status, 404);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(LibraryServerTest, catalog_v2_partial_entries)
|
|
||||||
{
|
|
||||||
const auto r = zfs1_->GET("/ROOT/catalog/v2/partial_entries");
|
|
||||||
EXPECT_EQ(r->status, 200);
|
|
||||||
EXPECT_EQ(maskVariableOPDSFeedData(r->body),
|
|
||||||
CATALOG_V2_PARTIAL_ENTRIES_PREAMBLE("")
|
|
||||||
" <title>All Entries</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
"\n"
|
|
||||||
" <entry>\n"
|
|
||||||
" <id>urn:uuid:charlesray</id>\n"
|
|
||||||
" <title>Charles, Ray</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <link rel=\"alternate\"\n"
|
|
||||||
" href=\"/ROOT/catalog/v2/entry/charlesray\"\n"
|
|
||||||
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
|
||||||
" </entry>\n"
|
|
||||||
" <entry>\n"
|
|
||||||
" <id>urn:uuid:raycharles</id>\n"
|
|
||||||
" <title>Ray Charles</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <link rel=\"alternate\"\n"
|
|
||||||
" href=\"/ROOT/catalog/v2/entry/raycharles\"\n"
|
|
||||||
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
|
||||||
" </entry>\n"
|
|
||||||
" <entry>\n"
|
|
||||||
" <id>urn:uuid:raycharles_uncategorized</id>\n"
|
|
||||||
" <title>Ray (uncategorized) Charles</title>\n"
|
|
||||||
" <updated>YYYY-MM-DDThh:mm:ssZ</updated>\n"
|
|
||||||
" <link rel=\"alternate\"\n"
|
|
||||||
" href=\"/ROOT/catalog/v2/entry/raycharles_uncategorized\"\n"
|
|
||||||
" type=\"application/atom+xml;type=entry;profile=opds-catalog\"/>\n"
|
|
||||||
" </entry>\n"
|
|
||||||
"</feed>\n"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in New Issue