mirror of
https://github.com/kiwix/libkiwix.git
synced 2026-09-14 22:56:14 -04:00
388 lines
12 KiB
C++
388 lines
12 KiB
C++
/*
|
|
* Copyright 2026 Hamazasp Avetisyan <hamik.avetisyan@gmail.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License as
|
|
* published by the Free Software Foundation; either version 2 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* is provided AS IS, WITHOUT ANY WARRANTY; without even the implied
|
|
* warranty of MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, and
|
|
* NON-INFRINGEMENT. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#include "gtest/gtest.h"
|
|
|
|
#include "../include/book.h"
|
|
#include "../include/library.h"
|
|
#include "../include/manager.h"
|
|
#include "../src/library_dumper.h"
|
|
|
|
namespace
|
|
{
|
|
|
|
using namespace kiwix;
|
|
|
|
Book createBook()
|
|
{
|
|
Book book;
|
|
book.setId("book-id");
|
|
book.setTitle("Some Title");
|
|
book.setDescription("Some Description");
|
|
book.setLanguage("eng");
|
|
book.setCreator("Some Creator");
|
|
book.setPublisher("Some Publisher");
|
|
book.setDate("2021-03-25");
|
|
book.setName("some_name");
|
|
book.setTags("tag1;tag2;_category:wikipedia");
|
|
book.setFlavour("nopic");
|
|
book.setArticleCount(42);
|
|
book.setMediaCount(7);
|
|
return book;
|
|
}
|
|
|
|
#define CORE_ENTRY_BODY \
|
|
" <id>urn:uuid:book-id</id>\n" \
|
|
" <title>Some Title</title>\n" \
|
|
" <updated>2021-03-25T00:00:00Z</updated>\n" \
|
|
" <summary>Some Description</summary>\n" \
|
|
" <language>eng</language>\n" \
|
|
" <name>some_name</name>\n" \
|
|
" <flavour>nopic</flavour>\n" \
|
|
/* Book::getCategory() only reflects a "_category:" tag/attribute */ \
|
|
/* resolved during XML/OPDS parsing - it is not derived on the fly */ \
|
|
/* from setTags(), so a manually-constructed Book always reports an */ \
|
|
/* empty category here. */ \
|
|
" <category></category>\n" \
|
|
" <tags>tag1;tag2;_category:wikipedia</tags>\n" \
|
|
" <articleCount>42</articleCount>\n" \
|
|
" <mediaCount>7</mediaCount>\n"
|
|
|
|
TEST(FullEntryOpdsTest, rendersCoreBookFields)
|
|
{
|
|
const Book book = createBook();
|
|
// No url and no contentAccessUrl were set, so neither the acquisition nor
|
|
// the content <link> is rendered (see rendersAcquisitionLinkWhenUrlIsSet /
|
|
// rendersUrlEncodedContentLinkWhenContentAccessUrlIsSet below for when
|
|
// they are).
|
|
EXPECT_EQ(fullEntryOpds(book, "http://root.location", /*contentAccessUrl=*/"", /*contentId=*/"book-id"),
|
|
" <entry>\n"
|
|
CORE_ENTRY_BODY
|
|
" <author>\n"
|
|
" <name>Some Creator</name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name>Some Publisher</name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>2021-03-25T00:00:00Z</dc:issued>\n"
|
|
" \n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
TEST(FullEntryOpdsTest, omitsAcquisitionLinkWhenUrlIsEmpty)
|
|
{
|
|
const Book book = createBook();
|
|
// url left at its default (empty).
|
|
EXPECT_EQ(fullEntryOpds(book, "http://root.location", "", "book-id"),
|
|
" <entry>\n"
|
|
CORE_ENTRY_BODY
|
|
" <author>\n"
|
|
" <name>Some Creator</name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name>Some Publisher</name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>2021-03-25T00:00:00Z</dc:issued>\n"
|
|
" \n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
TEST(FullEntryOpdsTest, rendersAcquisitionLinkWhenUrlIsSet)
|
|
{
|
|
Book book = createBook();
|
|
book.setUrl("http://download.kiwix.org/zim/book.zim");
|
|
book.setSize(123456);
|
|
|
|
EXPECT_EQ(fullEntryOpds(book, "http://root.location", "", "book-id"),
|
|
" <entry>\n"
|
|
CORE_ENTRY_BODY
|
|
" <author>\n"
|
|
" <name>Some Creator</name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name>Some Publisher</name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>2021-03-25T00:00:00Z</dc:issued>\n"
|
|
" <link rel=\"http://opds-spec.org/acquisition/open-access\" type=\"application/x-zim\" href=\"http://download.kiwix.org/zim/book.zim\" length=\"123456\" />\n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
TEST(FullEntryOpdsTest, omitsContentLinkWhenContentAccessUrlIsEmpty)
|
|
{
|
|
const Book book = createBook();
|
|
// contentAccessUrl left at its default (empty).
|
|
EXPECT_EQ(fullEntryOpds(book, "http://root.location", /*contentAccessUrl=*/"", "book-id"),
|
|
" <entry>\n"
|
|
CORE_ENTRY_BODY
|
|
" <author>\n"
|
|
" <name>Some Creator</name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name>Some Publisher</name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>2021-03-25T00:00:00Z</dc:issued>\n"
|
|
" \n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
TEST(FullEntryOpdsTest, rendersUrlEncodedContentLinkWhenContentAccessUrlIsSet)
|
|
{
|
|
const Book book = createBook();
|
|
// '/' is one of urlEncode()'s "harmless" characters (paths may legitimately
|
|
// contain it) and passes through untouched, while ' ' and '#' do not.
|
|
EXPECT_EQ(fullEntryOpds(book, "http://root.location", "http://root.location/content", "a/b c#d"),
|
|
" <entry>\n"
|
|
CORE_ENTRY_BODY
|
|
" <link type=\"text/html\" href=\"http://root.location/content/a/b%20c%23d\" />\n"
|
|
" <author>\n"
|
|
" <name>Some Creator</name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name>Some Publisher</name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>2021-03-25T00:00:00Z</dc:issued>\n"
|
|
" \n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
TEST(FullEntryOpdsTest, rendersThumbnailLinkForBookIllustration)
|
|
{
|
|
auto lib = Library::create();
|
|
Manager manager(lib);
|
|
const char sampleXML[] = R"(
|
|
<library version="1.0">
|
|
<book
|
|
id="book-with-icon"
|
|
path="/local/path/book.zim"
|
|
title="Book With Icon"
|
|
favicon="AAAA"
|
|
faviconMimeType="image/png"
|
|
faviconUrl="/favicon.png"
|
|
></book>
|
|
</library>
|
|
)";
|
|
manager.readXml(sampleXML, /*readOnly=*/false, "", /*trustLibrary=*/true);
|
|
const Book& book = lib->getBookById("book-with-icon");
|
|
|
|
EXPECT_EQ(fullEntryOpds(book, "http://root.location", "", "book-with-icon"),
|
|
" <entry>\n"
|
|
" <id>urn:uuid:book-with-icon</id>\n"
|
|
" <title>Book With Icon</title>\n"
|
|
" <updated>T00:00:00Z</updated>\n"
|
|
" <summary></summary>\n"
|
|
" <language></language>\n"
|
|
" <name></name>\n"
|
|
" <flavour></flavour>\n"
|
|
" <category></category>\n"
|
|
" <tags></tags>\n"
|
|
" <articleCount>0</articleCount>\n"
|
|
" <mediaCount>0</mediaCount>\n"
|
|
" <link rel=\"http://opds-spec.org/image/thumbnail\"\n"
|
|
" href=\"http://root.location/catalog/v2/illustration/book-with-icon/?size=48\"\n"
|
|
" type=\"image/png;width=48;height=48;scale=1\"/>\n"
|
|
" <author>\n"
|
|
" <name></name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name></name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>T00:00:00Z</dc:issued>\n"
|
|
" \n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
TEST(FullEntryOpdsTest, omitsThumbnailLinkForEmbeddedOnlyIllustrationInFileDump)
|
|
{
|
|
auto lib = Library::create();
|
|
Manager manager(lib);
|
|
const char sampleXML[] = R"(
|
|
<library version="1.0">
|
|
<book
|
|
id="book-with-embedded-icon"
|
|
path="/local/path/book.zim"
|
|
title="Book With Embedded Icon"
|
|
favicon="AAAA"
|
|
faviconMimeType="image/png"
|
|
></book>
|
|
</library>
|
|
)";
|
|
manager.readXml(sampleXML, /*readOnly=*/false, "", /*trustLibrary=*/true);
|
|
const Book& book = lib->getBookById("book-with-embedded-icon");
|
|
|
|
// isLiveCatalog=false (offline file dump): an illustration with no
|
|
// external url is only available as embedded data, and there is no server
|
|
// behind /catalog/v2/illustration there to serve it from, so it must be
|
|
// omitted.
|
|
EXPECT_EQ(fullEntryOpds(book, "http://root.location", "", "book-with-embedded-icon",
|
|
/*selfPath=*/"", /*isLiveCatalog=*/false),
|
|
" <entry>\n"
|
|
" <id>urn:uuid:book-with-embedded-icon</id>\n"
|
|
" <title>Book With Embedded Icon</title>\n"
|
|
" <updated>T00:00:00Z</updated>\n"
|
|
" <summary></summary>\n"
|
|
" <language></language>\n"
|
|
" <name></name>\n"
|
|
" <flavour></flavour>\n"
|
|
" <category></category>\n"
|
|
" <tags></tags>\n"
|
|
" <articleCount>0</articleCount>\n"
|
|
" <mediaCount>0</mediaCount>\n"
|
|
" <author>\n"
|
|
" <name></name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name></name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>T00:00:00Z</dc:issued>\n"
|
|
" \n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
TEST(FullEntryOpdsTest, rendersIllustrationUrlDirectlyInFileDump)
|
|
{
|
|
auto lib = Library::create();
|
|
Manager manager(lib);
|
|
const char sampleXML[] = R"(
|
|
<library version="1.0">
|
|
<book
|
|
id="book-with-icon-url"
|
|
path="/local/path/book.zim"
|
|
title="Book With Icon Url"
|
|
favicon="https://example.com/favicon/zara.png"
|
|
faviconMimeType="image/png"
|
|
faviconUrl="/favicon.png"
|
|
></book>
|
|
</library>
|
|
)";
|
|
manager.readXml(sampleXML, /*readOnly=*/false, "", /*trustLibrary=*/true);
|
|
const Book& book = lib->getBookById("book-with-icon-url");
|
|
|
|
// isLiveCatalog=false (offline file dump): with an external url available,
|
|
// the thumbnail link points straight at that url instead of the live
|
|
// /catalog/v2/illustration endpoint, which has no server behind it there.
|
|
EXPECT_EQ(fullEntryOpds(book, "http://root.location", "", "book-with-icon-url",
|
|
/*selfPath=*/"", /*isLiveCatalog=*/false),
|
|
" <entry>\n"
|
|
" <id>urn:uuid:book-with-icon-url</id>\n"
|
|
" <title>Book With Icon Url</title>\n"
|
|
" <updated>T00:00:00Z</updated>\n"
|
|
" <summary></summary>\n"
|
|
" <language></language>\n"
|
|
" <name></name>\n"
|
|
" <flavour></flavour>\n"
|
|
" <category></category>\n"
|
|
" <tags></tags>\n"
|
|
" <articleCount>0</articleCount>\n"
|
|
" <mediaCount>0</mediaCount>\n"
|
|
" <link rel=\"http://opds-spec.org/image/thumbnail\"\n"
|
|
" href=\"/favicon.png\"\n"
|
|
" type=\"image/png;width=48;height=48;scale=1\"/>\n"
|
|
" <author>\n"
|
|
" <name></name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name></name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>T00:00:00Z</dc:issued>\n"
|
|
" \n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
TEST(FullEntryOpdsTest, omitsSelfLinkWhenSelfPathIsEmpty)
|
|
{
|
|
const Book book = createBook();
|
|
// selfPath left at its default ("") - mirrors how OPDSDumper calls this
|
|
// function for the live HTTP catalog, which must never leak a local path.
|
|
EXPECT_EQ(fullEntryOpds(book, "http://root.location", "", "book-id"),
|
|
" <entry>\n"
|
|
CORE_ENTRY_BODY
|
|
" <author>\n"
|
|
" <name>Some Creator</name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name>Some Publisher</name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>2021-03-25T00:00:00Z</dc:issued>\n"
|
|
" \n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
TEST(FullEntryOpdsTest, rendersSelfLinkWhenSelfPathIsSet)
|
|
{
|
|
const Book book = createBook();
|
|
EXPECT_EQ(fullEntryOpds(book, /*rootLocation=*/"", "", "book-id",
|
|
/*selfPath=*/"/local/path/book.zim"),
|
|
" <entry>\n"
|
|
CORE_ENTRY_BODY
|
|
" <author>\n"
|
|
" <name>Some Creator</name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name>Some Publisher</name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>2021-03-25T00:00:00Z</dc:issued>\n"
|
|
" <link rel=\"self\" href=\"/local/path/book.zim\" type=\"application/x-zim\"/>\n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
TEST(FullEntryOpdsTest, specialCharactersInBookMetadataAreEscaped)
|
|
{
|
|
Book book = createBook();
|
|
book.setTitle("Q&A <Tricky> \"Title\"");
|
|
book.setDescription("Ideas & \"quotes\" <tags>");
|
|
|
|
EXPECT_EQ(fullEntryOpds(book, "http://root.location", "", "book-id"),
|
|
" <entry>\n"
|
|
" <id>urn:uuid:book-id</id>\n"
|
|
" <title>Q&A <Tricky> "Title"</title>\n"
|
|
" <updated>2021-03-25T00:00:00Z</updated>\n"
|
|
" <summary>Ideas & "quotes" <tags></summary>\n"
|
|
" <language>eng</language>\n"
|
|
" <name>some_name</name>\n"
|
|
" <flavour>nopic</flavour>\n"
|
|
" <category></category>\n"
|
|
" <tags>tag1;tag2;_category:wikipedia</tags>\n"
|
|
" <articleCount>42</articleCount>\n"
|
|
" <mediaCount>7</mediaCount>\n"
|
|
" <author>\n"
|
|
" <name>Some Creator</name>\n"
|
|
" </author>\n"
|
|
" <publisher>\n"
|
|
" <name>Some Publisher</name>\n"
|
|
" </publisher>\n"
|
|
" <dc:issued>2021-03-25T00:00:00Z</dc:issued>\n"
|
|
" \n"
|
|
" </entry>\n"
|
|
);
|
|
}
|
|
|
|
#undef CORE_ENTRY_BODY
|
|
|
|
} // unnamed namespace
|