/* * 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 "../src/libxml_dumper.h" #include "../include/book.h" #include "../include/bookmark.h" #include "../include/library.h" #include "../include/tools.h" #include "gtest/gtest.h" namespace { using namespace kiwix; Book createBook(const std::string& id, const std::string& title) { Book book; book.setId(id); // A relative path is resolved by Book::setPath() against the current // directory; the test dumper's baseDir is set to that same directory // below, so the dumped "path" attribute is portably just ".zim" on // every platform (a hardcoded absolute path like "/local/path/..." is // POSIX-only - Windows treats a leading '/' without a drive letter as // relative, which silently prefixes it with the build's cwd instead). book.setPath(id + ".zim"); book.setTitle(title); book.setDescription("Description of " + title); book.setLanguage("eng,spa"); book.setCreator(title + " Creator"); book.setPublisher(title + " Publisher"); book.setDate("2021-03-25"); book.setUrl("http://download.kiwix.org/zim/" + id + ".zim"); book.setName("test_" + id); book.setFlavour("nopic"); book.setTags("tag1;tag2"); book.setArticleCount(42); book.setMediaCount(7); book.setSize(122880); // 120 KiB - stored on disk as KiB (>>10) book.setDownloadId("download-" + id); return book; } class LibXMLDumperTest : public ::testing::Test { protected: void SetUp() override { lib = Library::create(); lib->addBook(createBook("book1-id", "First Book")); lib->addBook(createBook("book2-id", "Second Book")); dumper.setLibrary(lib.get()); dumper.setBaseDir(getCurrentDirectory()); } LibraryPtr lib; LibXMLDumper dumper; }; TEST_F(LibXMLDumperTest, emptyBookIdListProducesEmptyLibraryElement) { EXPECT_EQ(dumper.dumpLibXMLContent({}), "\n" ); } TEST_F(LibXMLDumperTest, dumpsRequestedBookWithAllAttributes) { EXPECT_EQ(dumper.dumpLibXMLContent({"book1-id"}), "\n" // The book's path is stored relative to the dumper's base dir, and // size on disk in KiB (bytes >> 10). " \n" "\n" ); } TEST_F(LibXMLDumperTest, dumpsOnlyRequestedBooksInOrder) { EXPECT_EQ(dumper.dumpLibXMLContent({"book2-id", "book1-id"}), "\n" " \n" " \n" "\n" ); } TEST_F(LibXMLDumperTest, omitsEmptyOrZeroOptionalAttributes) { Book book = createBook("bare-book-id", "Bare Book"); book.setUrl(""); book.setDownloadId(""); book.setArticleCount(0); book.setMediaCount(0); book.setSize(0); lib->addBook(book); EXPECT_EQ(dumper.dumpLibXMLContent({"bare-book-id"}), "\n" " \n" "\n" ); } TEST_F(LibXMLDumperTest, skipsReadOnlyBooks) { Book readOnlyBook = createBook("readonly-book-id", "Read Only Book"); readOnlyBook.setReadOnly(true); lib->addBook(readOnlyBook); // The read-only book is entirely omitted from the dump, not just stripped // of its attributes. EXPECT_EQ(dumper.dumpLibXMLContent({"book1-id", "readonly-book-id"}), "\n" " \n" "\n" ); } TEST_F(LibXMLDumperTest, aliasBookWithOrigIdOnlyEmitsIdAndOrigId) { Book aliasBook = createBook("alias-book-id", "Alias Book"); aliasBook.setOrigId("book1-id"); lib->addBook(aliasBook); // Metadata that's only meaningful for the "real" entry is skipped for a // flavour alias - only path (above the origId/title branch) and // date/url/counts/size/downloadId (below it) are still emitted. EXPECT_EQ(dumper.dumpLibXMLContent({"alias-book-id"}), "\n" " \n" "\n" ); } TEST_F(LibXMLDumperTest, dumpLibXMLContentThrowsForUnknownBookId) { EXPECT_THROW(dumper.dumpLibXMLContent({"no-such-book"}), std::out_of_range); } TEST_F(LibXMLDumperTest, defaultConstructedDumperHasNoLibrary) { LibXMLDumper freshDumper; EXPECT_EQ(freshDumper.dumpLibXMLContent({"book1-id"}), "\n" ); EXPECT_EQ(freshDumper.dumpLibXMLBookmark(), "\n" ); } TEST_F(LibXMLDumperTest, setLibraryReplacesTheDumpedLibrary) { auto otherLib = Library::create(); otherLib->addBook(createBook("other-book-id", "Other Book")); dumper.setLibrary(otherLib.get()); EXPECT_EQ(dumper.dumpLibXMLContent({"other-book-id"}), "\n" " \n" "\n" ); // "book1-id" belonged to the previously set library and is no longer known. EXPECT_THROW(dumper.dumpLibXMLContent({"book1-id"}), std::out_of_range); } TEST_F(LibXMLDumperTest, emptyBookmarksProducesEmptyBookmarksElement) { EXPECT_EQ(dumper.dumpLibXMLBookmark(), "\n"); } TEST_F(LibXMLDumperTest, dumpsBookmarkUsingLiveBookDataWhenBookIsInLibrary) { Bookmark bookmark; bookmark.setBookId("book1-id"); bookmark.setUrl("/A/Some_Article"); bookmark.setTitle("Some Article Title"); lib->addBookmark(bookmark); // These values come from the live library entry, not from the bookmark's // own (unset) book* fields. EXPECT_EQ(dumper.dumpLibXMLBookmark(), "\n" " \n" " \n" " book1-id\n" " First Book\n" " test_book1-id\n" " nopic\n" " eng,spa\n" " 2021-03-25\n" " \n" " Some Article Title\n" " /A/Some_Article\n" " \n" "\n" ); } TEST_F(LibXMLDumperTest, dumpsBookmarkUsingStoredFieldsWhenBookIsUnknown) { Bookmark bookmark; bookmark.setBookId("no-such-book"); bookmark.setBookTitle("Vanished Book"); bookmark.setBookName("vanished_book"); bookmark.setBookFlavour("full"); bookmark.setLanguage("fra"); bookmark.setDate("2019-01-01"); bookmark.setUrl("/A/Some_Article"); bookmark.setTitle("Some Article Title"); lib->addBookmark(bookmark); EXPECT_EQ(dumper.dumpLibXMLBookmark(), "\n" " \n" " \n" " no-such-book\n" " Vanished Book\n" " vanished_book\n" " full\n" " fra\n" " 2019-01-01\n" " \n" " Some Article Title\n" " /A/Some_Article\n" " \n" "\n" ); } } // unnamed namespace