/* * Copyright 2026 Hamazasp Avetisyan * * 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 \ " urn:uuid:book-id\n" \ " Some Title\n" \ " 2021-03-25T00:00:00Z\n" \ " Some Description\n" \ " eng\n" \ " some_name\n" \ " nopic\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. */ \ " \n" \ " tag1;tag2;_category:wikipedia\n" \ " 42\n" \ " 7\n" TEST(FullEntryOpdsTest, rendersCoreBookFields) { const Book book = createBook(); // No url and no contentAccessUrl were set, so neither the acquisition nor // the content is rendered (see rendersAcquisitionLinkWhenUrlIsSet / // rendersUrlEncodedContentLinkWhenContentAccessUrlIsSet below for when // they are). EXPECT_EQ(fullEntryOpds(book, "http://root.location", /*contentAccessUrl=*/"", /*contentId=*/"book-id"), " \n" CORE_ENTRY_BODY " \n" " \n" " Some Creator\n" " \n" " \n" " Some Publisher\n" " \n" " 2021-03-25T00:00:00Z\n" " \n" " \n" ); } TEST(FullEntryOpdsTest, omitsAcquisitionLinkWhenUrlIsEmpty) { const Book book = createBook(); // url left at its default (empty). EXPECT_EQ(fullEntryOpds(book, "http://root.location", "", "book-id"), " \n" CORE_ENTRY_BODY " \n" " \n" " Some Creator\n" " \n" " \n" " Some Publisher\n" " \n" " 2021-03-25T00:00:00Z\n" " \n" " \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"), " \n" CORE_ENTRY_BODY " \n" " \n" " Some Creator\n" " \n" " \n" " Some Publisher\n" " \n" " 2021-03-25T00:00:00Z\n" " \n" " \n" ); } TEST(FullEntryOpdsTest, omitsContentLinkWhenContentAccessUrlIsEmpty) { const Book book = createBook(); // contentAccessUrl left at its default (empty). EXPECT_EQ(fullEntryOpds(book, "http://root.location", /*contentAccessUrl=*/"", "book-id"), " \n" CORE_ENTRY_BODY " \n" " \n" " Some Creator\n" " \n" " \n" " Some Publisher\n" " \n" " 2021-03-25T00:00:00Z\n" " \n" " \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"), " \n" CORE_ENTRY_BODY " \n" " \n" " Some Creator\n" " \n" " \n" " Some Publisher\n" " \n" " 2021-03-25T00:00:00Z\n" " \n" " \n" ); } TEST(FullEntryOpdsTest, rendersThumbnailLinkForBookIllustration) { auto lib = Library::create(); Manager manager(lib); const char sampleXML[] = R"( )"; 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"), " \n" " urn:uuid:book-with-icon\n" " Book With Icon\n" " T00:00:00Z\n" " \n" " \n" " \n" " \n" " \n" " \n" " 0\n" " 0\n" " \n" " \n" " \n" " \n" " \n" " \n" " \n" " T00:00:00Z\n" " \n" " \n" ); } TEST(FullEntryOpdsTest, specialCharactersInBookMetadataAreEscaped) { Book book = createBook(); book.setTitle("Q&A \"Title\""); book.setDescription("Ideas & \"quotes\" "); EXPECT_EQ(fullEntryOpds(book, "http://root.location", "", "book-id"), " \n" " urn:uuid:book-id\n" " Q&A <Tricky> "Title"\n" " 2021-03-25T00:00:00Z\n" " Ideas & "quotes" <tags>\n" " eng\n" " some_name\n" " nopic\n" " \n" " tag1;tag2;_category:wikipedia\n" " 42\n" " 7\n" " \n" " \n" " Some Creator\n" " \n" " \n" " Some Publisher\n" " \n" " 2021-03-25T00:00:00Z\n" " \n" " \n" ); } #undef CORE_ENTRY_BODY } // unnamed namespace