mirror of https://github.com/kiwix/libkiwix.git
test/server_search.cpp covers XML search too
This commit is contained in:
parent
f279769435
commit
06d7a2320f
|
@ -137,6 +137,25 @@ std::string makeSearchResultsHtml(const std::string& pattern,
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string makeSearchResultsXml(const std::string& header,
|
||||||
|
const std::string& results)
|
||||||
|
{
|
||||||
|
const char SEARCHRESULTS_XML_TEMPLATE[] = R"XML(<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<rss version="2.0"
|
||||||
|
xmlns:opensearch="http://a9.com/-/spec/opensearch/1.1/"
|
||||||
|
xmlns:atom="http://www.w3.org/2005/Atom">
|
||||||
|
<channel>
|
||||||
|
%HEADER%%RESULTS%
|
||||||
|
</channel>
|
||||||
|
</rss>
|
||||||
|
)XML";
|
||||||
|
|
||||||
|
std::string html = removeEOLWhitespaceMarkers(SEARCHRESULTS_XML_TEMPLATE);
|
||||||
|
html = replace(html, "%HEADER%", header);
|
||||||
|
html = replace(html, "%RESULTS%", results);
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
struct SearchResult
|
struct SearchResult
|
||||||
{
|
{
|
||||||
std::string link;
|
std::string link;
|
||||||
|
@ -155,6 +174,18 @@ struct SearchResult
|
||||||
+ " <div class=\"book-title\">from " + bookTitle + "</div>\n"
|
+ " <div class=\"book-title\">from " + bookTitle + "</div>\n"
|
||||||
+ " <div class=\"informations\">" + wordCount + " words</div>\n";
|
+ " <div class=\"informations\">" + wordCount + " words</div>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string getXml() const
|
||||||
|
{
|
||||||
|
return std::string()
|
||||||
|
+ " <title>" + title + "</title>\n"
|
||||||
|
+ " <link>" + replace(link, "'", "'") + "</link>\n"
|
||||||
|
+ " <description>" + snippet + "</description>\n"
|
||||||
|
+ " <book>\n"
|
||||||
|
+ " <title>" + bookTitle + "</title>\n"
|
||||||
|
+ " </book>\n"
|
||||||
|
+ " <wordCount>" + wordCount + "</wordCount>";
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#define SEARCH_RESULT(LINK, TITLE, SNIPPET, BOOK_TITLE, WORDCOUNT) \
|
#define SEARCH_RESULT(LINK, TITLE, SNIPPET, BOOK_TITLE, WORDCOUNT) \
|
||||||
|
@ -569,6 +600,27 @@ Snippets extractSearchResultSnippetsFromHtml(const std::string& html)
|
||||||
return snippets;
|
return snippets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char SNIPPET_REGEX_FOR_XML[] = "<description>(?!Search result for)(.+)</description>";
|
||||||
|
|
||||||
|
std::string maskSnippetsInXmlSearchResults(std::string s)
|
||||||
|
{
|
||||||
|
return replace(s, SNIPPET_REGEX_FOR_XML, "<description>SNIPPET TEXT WAS MASKED</description>");
|
||||||
|
}
|
||||||
|
|
||||||
|
Snippets extractSearchResultSnippetsFromXml(const std::string& xml)
|
||||||
|
{
|
||||||
|
Snippets snippets;
|
||||||
|
const std::regex snippetRegex(SNIPPET_REGEX_FOR_XML);
|
||||||
|
std::sregex_iterator snippetIt(xml.begin(), xml.end(), snippetRegex);
|
||||||
|
const std::sregex_iterator end;
|
||||||
|
for ( ; snippetIt != end; ++snippetIt)
|
||||||
|
{
|
||||||
|
const std::smatch snippetMatch = *snippetIt;
|
||||||
|
snippets.push_back(snippetMatch[1].str());
|
||||||
|
}
|
||||||
|
return snippets;
|
||||||
|
}
|
||||||
|
|
||||||
bool isValidSnippet(const std::string& s)
|
bool isValidSnippet(const std::string& s)
|
||||||
{
|
{
|
||||||
return s.size() >= 250
|
return s.size() >= 250
|
||||||
|
@ -650,19 +702,37 @@ struct TestData
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getPattern() const
|
std::string extractQueryValue(const std::string& key) const
|
||||||
{
|
{
|
||||||
const std::string p = "pattern=";
|
const std::string p = key + "=";
|
||||||
const size_t i = query.find(p);
|
const size_t i = query.find(p);
|
||||||
|
if (i == std::string::npos) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
std::string r = query.substr(i + p.size());
|
std::string r = query.substr(i + p.size());
|
||||||
return r.substr(0, r.find("&"));
|
return r.substr(0, r.find("&"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string getPattern() const
|
||||||
|
{
|
||||||
|
return extractQueryValue("pattern");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string getLang() const
|
||||||
|
{
|
||||||
|
return extractQueryValue("books.filter.lang");
|
||||||
|
}
|
||||||
|
|
||||||
std::string url() const
|
std::string url() const
|
||||||
{
|
{
|
||||||
return makeUrl(query, start, resultsPerPage);
|
return makeUrl(query, start, resultsPerPage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string xmlSearchUrl() const
|
||||||
|
{
|
||||||
|
return url() + "&format=xml";
|
||||||
|
}
|
||||||
|
|
||||||
std::string expectedHtmlHeader() const
|
std::string expectedHtmlHeader() const
|
||||||
{
|
{
|
||||||
if ( totalResultCount == 0 ) {
|
if ( totalResultCount == 0 ) {
|
||||||
|
@ -736,11 +806,70 @@ struct TestData
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string expectedXmlHeader() const
|
||||||
|
{
|
||||||
|
std::string header = R"(<title>Search: PATTERN</title>
|
||||||
|
<link>URL</link>
|
||||||
|
<description>Search result for PATTERN</description>
|
||||||
|
<opensearch:totalResults>RESULTCOUNT</opensearch:totalResults>
|
||||||
|
<opensearch:startIndex>FIRSTRESULT</opensearch:startIndex>
|
||||||
|
<opensearch:itemsPerPage>ITEMCOUNT</opensearch:itemsPerPage>
|
||||||
|
<atom:link rel="search" type="application/opensearchdescription+xml" href="/ROOT/search/searchdescription.xml"/>
|
||||||
|
<opensearch:Query role="request"
|
||||||
|
searchTerms="PATTERN"LANGQUERY
|
||||||
|
startIndex="FIRSTRESULT"
|
||||||
|
count="ITEMCOUNT"
|
||||||
|
/>)";
|
||||||
|
|
||||||
|
const auto realResultsPerPage = resultsPerPage?resultsPerPage:25;
|
||||||
|
const auto url = makeUrl(query + "&format=xml", firstResultIndex, realResultsPerPage);
|
||||||
|
header = replace(header, "URL", replace(url, "&", "&"));
|
||||||
|
header = replace(header, "FIRSTRESULT", to_string(firstResultIndex));
|
||||||
|
header = replace(header, "ITEMCOUNT", to_string(realResultsPerPage));
|
||||||
|
header = replace(header, "RESULTCOUNT", to_string(totalResultCount));
|
||||||
|
header = replace(header, "PATTERN", getPattern());
|
||||||
|
auto queryLang = getLang();
|
||||||
|
if (queryLang.empty()) {
|
||||||
|
header = replace(header, "LANGQUERY", "");
|
||||||
|
} else {
|
||||||
|
header = replace(header, "LANGQUERY", "\n language=\""+queryLang+"\"");
|
||||||
|
}
|
||||||
|
return header;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string expectedXmlResultsString() const
|
||||||
|
{
|
||||||
|
if ( results.empty() ) {
|
||||||
|
return "\n ";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string s;
|
||||||
|
for ( const auto& r : results ) {
|
||||||
|
s += "\n <item>\n";
|
||||||
|
s += maskSnippetsInXmlSearchResults(r.getXml());
|
||||||
|
s += "\n </item>";
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string expectedXml() const
|
||||||
|
{
|
||||||
|
return makeSearchResultsXml(
|
||||||
|
expectedXmlHeader(),
|
||||||
|
expectedXmlResultsString()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
TestContext testContext() const
|
TestContext testContext() const
|
||||||
{
|
{
|
||||||
return TestContext{ { "url", url() } };
|
return TestContext{ { "url", url() } };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TestContext xmlTestContext() const
|
||||||
|
{
|
||||||
|
return TestContext{ { "url", xmlSearchUrl() } };
|
||||||
|
}
|
||||||
|
|
||||||
void checkHtml(const std::string& html) const
|
void checkHtml(const std::string& html) const
|
||||||
{
|
{
|
||||||
EXPECT_EQ(maskSnippetsInHtmlSearchResults(html), expectedHtml())
|
EXPECT_EQ(maskSnippetsInHtmlSearchResults(html), expectedHtml())
|
||||||
|
@ -749,6 +878,14 @@ struct TestData
|
||||||
checkSnippets(extractSearchResultSnippetsFromHtml(html));
|
checkSnippets(extractSearchResultSnippetsFromHtml(html));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void checkXml(const std::string& xml) const
|
||||||
|
{
|
||||||
|
EXPECT_EQ(maskSnippetsInXmlSearchResults(xml), expectedXml())
|
||||||
|
<< xmlTestContext();
|
||||||
|
|
||||||
|
checkSnippets(extractSearchResultSnippetsFromXml(xml));
|
||||||
|
}
|
||||||
|
|
||||||
void checkSnippets(const Snippets& snippets) const
|
void checkSnippets(const Snippets& snippets) const
|
||||||
{
|
{
|
||||||
ASSERT_EQ(snippets.size(), results.size());
|
ASSERT_EQ(snippets.size(), results.size());
|
||||||
|
@ -1319,5 +1456,13 @@ TEST_F(ServerTest, searchResults)
|
||||||
const auto htmlRes = taskbarlessZimFileServer().GET(htmlSearchUrl.c_str());
|
const auto htmlRes = taskbarlessZimFileServer().GET(htmlSearchUrl.c_str());
|
||||||
EXPECT_EQ(htmlRes->status, 200);
|
EXPECT_EQ(htmlRes->status, 200);
|
||||||
t.checkHtml(htmlRes->body);
|
t.checkHtml(htmlRes->body);
|
||||||
|
|
||||||
|
const std::string xmlSearchUrl = t.xmlSearchUrl();
|
||||||
|
const auto xmlRes1 = zfs1_->GET(xmlSearchUrl.c_str());
|
||||||
|
const auto xmlRes2 = taskbarlessZimFileServer().GET(xmlSearchUrl.c_str());
|
||||||
|
EXPECT_EQ(xmlRes1->status, 200);
|
||||||
|
EXPECT_EQ(xmlRes2->status, 200);
|
||||||
|
EXPECT_EQ(xmlRes1->body, xmlRes2->body);
|
||||||
|
t.checkXml(xmlRes1->body);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue