~aleteoryx/muditaos

7cdfb96db22bc1d7801cf95e98c349611ea300d0 — Bartosz Cichocki 4 years ago 0bc4e65
[EGD-6823] Add quote randomization

Added randomization of the quotes when quote wallpaper is selected
M module-apps/apps-common/popups/presenter/QuotesPresenter.cpp => module-apps/apps-common/popups/presenter/QuotesPresenter.cpp +3 -5
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesPresenter.hpp"


@@ 15,9 15,7 @@ namespace gui

    void QuotesPresenter::requestQuote()
    {
        // TODO: get quote: only enabled, from currently selected language
        constexpr auto quoteID = 1;
        auto query             = std::make_unique<Quotes::Messages::ReadQuoteRequest>(quoteID);
        auto query             = std::make_unique<Quotes::Messages::ReadRandomizedQuoteRequest>();
        auto task              = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Quotes);
        task->setCallback([this](auto response) { return onQuoteRetreived(response); });
        task->execute(application, this);


@@ 25,7 23,7 @@ namespace gui

    bool QuotesPresenter::onQuoteRetreived(db::QueryResult *queryResult)
    {
        auto msgResponse = dynamic_cast<Quotes::Messages::ReadQuoteResponse *>(queryResult);
        auto msgResponse = dynamic_cast<Quotes::Messages::ReadRandomizedQuoteResponse *>(queryResult);
        assert(msgResponse != nullptr);

        if (quoteDisplayCallback) {

M module-apps/apps-common/popups/presenter/QuotesPresenter.hpp => module-apps/apps-common/popups/presenter/QuotesPresenter.hpp +2 -1
@@ 1,10 1,11 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <service-db/QuotesMessages.hpp>
#include <ApplicationCommon.hpp>
#include <module-services/service-db/agents/quotes/RandomizedQuoteModel.hpp>

namespace gui
{

M module-services/service-db/CMakeLists.txt => module-services/service-db/CMakeLists.txt +3 -1
@@ 19,7 19,9 @@ set(SOURCES
    agents/settings/SettingsCache.cpp
    agents/settings/FactorySettings.cpp
    agents/quotes/QuotesAgent.cpp
)
    agents/quotes/RandomizedQuoteModel.cpp
    agents/quotes/QuotesSettingsSerializer.cpp
    )

add_library(${PROJECT_NAME} STATIC ${SOURCES})


M module-services/service-db/agents/quotes/QuotesAgent.cpp => module-services/service-db/agents/quotes/QuotesAgent.cpp +34 -4
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesAgent.hpp"


@@ 9,8 9,11 @@

namespace Quotes
{
    QuotesAgent::QuotesAgent(Database *quotesDB) : database(quotesDB)
    QuotesAgent::QuotesAgent(Database *quotesDB, std::unique_ptr<settings::Settings> settings)
        : database(quotesDB), randomizedQuoteModel(std::move(settings), quotesDB)
    {
        constexpr auto forced = false;
        randomizedQuoteModel.updateList(forced);
    }

    auto QuotesAgent::runQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>


@@ 42,6 45,9 @@ namespace Quotes
        else if (typeid(*query) == typeid(Messages::ReadQuoteRequest)) {
            return handleReadQuote(query);
        }
        else if (typeid(*query) == typeid(Messages::ReadRandomizedQuoteRequest)) {
            return handleReadRandomizedQuote(query);
        }
        else if (typeid(*query) == typeid(Messages::WriteQuoteRequest)) {
            return handleWriteQuote(query);
        }


@@ 66,6 72,8 @@ namespace Quotes
        auto req         = std::dynamic_pointer_cast<Messages::EnableCategoryByIdRequest>(query);
        auto queryResult = database->execute(Queries::enableCategory, req->enable, req->categoryId);
        auto response    = std::make_unique<Messages::EnableCategoryByIdResponse>(std::move(queryResult));
        constexpr auto forced = true;
        randomizedQuoteModel.updateList(forced);
        response->setRequestQuery(query);
        return response;
    }


@@ 116,6 124,8 @@ namespace Quotes
        auto req         = std::dynamic_pointer_cast<Messages::EnableQuoteByIdRequest>(query);
        auto queryResult = database->execute(Queries::enableQuote, req->enable, req->quoteId);
        auto response    = std::make_unique<Messages::EnableQuoteByIdResponse>(queryResult);
        constexpr auto forced = true;
        randomizedQuoteModel.updateList(forced);
        response->setRequestQuery(query);
        return response;
    }


@@ 133,6 143,10 @@ namespace Quotes
        CategoryRecord categoryRecord(queryResult.get());

        auto success = database->execute(Queries::addQuoteToQuoteCategoryMapTable, categoryRecord.category_id, quoteId);
        if (success) {
            constexpr auto forced = true;
            randomizedQuoteModel.updateList(forced);
        }

        auto response = std::make_unique<Messages::AddQuoteResponse>(success, quoteId);
        response->setRequestQuery(query);


@@ 150,11 164,26 @@ namespace Quotes
        return response;
    }

    auto QuotesAgent::handleReadRandomizedQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req          = std::dynamic_pointer_cast<Messages::ReadRandomizedQuoteRequest>(query);
        auto randomizedId = randomizedQuoteModel.getId();
        LOG_DEBUG("Randomized id: %d", randomizedId);
        auto queryResult = database->query(Queries::readQuote, randomizedId);
        QuoteRecord quoteRecord(queryResult.get());
        auto response = std::make_unique<Messages::ReadRandomizedQuoteResponse>(
            quoteRecord.quote_id, quoteRecord.lang_id, quoteRecord.quote, quoteRecord.author, quoteRecord.enabled);
        response->setRequestQuery(query);
        return response;
    }

    auto QuotesAgent::handleWriteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>
    {
        auto req         = std::dynamic_pointer_cast<Messages::WriteQuoteRequest>(query);
        auto queryResult = database->execute(
            Queries::writeQuote, req->langId, req->quote.c_str(), req->author.c_str(), req->enabled, req->quoteId);
        constexpr auto forced = true;
        randomizedQuoteModel.updateList(forced);
        auto response = std::make_unique<Messages::WriteQuoteResponse>(queryResult);
        response->setRequestQuery(query);
        return response;


@@ 164,11 193,12 @@ namespace Quotes
    {
        auto req = std::dynamic_pointer_cast<Messages::DeleteQuoteRequest>(query);
        database->execute(Queries::deleteQuoteFromQuoteCategoryMapTable, req->quoteId);

        auto queryResult = database->execute(Queries::deleteQuoteFromQuoteTable, req->quoteId);

        constexpr auto forced = true;
        randomizedQuoteModel.updateList(forced);
        auto response = std::make_unique<Messages::DeleteQuoteResponse>(queryResult);
        response->setRequestQuery(query);
        return response;
    }

} // namespace Quotes

M module-services/service-db/agents/quotes/QuotesAgent.hpp => module-services/service-db/agents/quotes/QuotesAgent.hpp +5 -2
@@ 1,10 1,11 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <service-db/QuotesMessages.hpp>
#include <Interface/Record.hpp>
#include "RandomizedQuoteModel.hpp"

namespace Quotes
{


@@ 15,7 16,7 @@ namespace Quotes
    class QuotesAgent : public RecordInterface<QuoteRecord, QuotesRecordField>
    {
      public:
        explicit QuotesAgent(Database *quotesDB);
        QuotesAgent(Database *quotesDB, std::unique_ptr<settings::Settings> settings);
        ~QuotesAgent() = default;

        auto runQuery(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;


@@ 30,10 31,12 @@ namespace Quotes
        auto handleEnableQuoteById(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleAddQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleReadQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleReadRandomizedQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleWriteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;
        auto handleDeleteQuote(std::shared_ptr<db::Query> query) -> std::unique_ptr<db::QueryResult>;

      private:
        Database *database;
        RandomizedQuoteModel randomizedQuoteModel;
    };
} // namespace Quotes

A module-services/service-db/agents/quotes/QuotesSettingsSerializer.cpp => module-services/service-db/agents/quotes/QuotesSettingsSerializer.cpp +31 -0
@@ 0,0 1,31 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QuotesSettingsSerializer.hpp"
#include <log/log.hpp>
#include <module-utils/utility/Utils.hpp>

namespace Quotes
{

    auto QuotesSettingsSerializer::serialize(const IdList &list) -> std::string
    {
        std::string output;
        for (const auto &item : list) {
            output.append(utils::to_string(item) + ',');
        }
        return output;
    }
    auto QuotesSettingsSerializer::deserialize(const std::string &listString) -> IdList
    {
        std::stringstream ss(listString);
        IdList list;
        for (int i; ss >> i;) {
            list.push_back(i);
            if (ss.peek() == ',' || ss.peek() == ' ') {
                ss.ignore();
            }
        }
        return list;
    }
} // namespace Quotes

A module-services/service-db/agents/quotes/QuotesSettingsSerializer.hpp => module-services/service-db/agents/quotes/QuotesSettingsSerializer.hpp +18 -0
@@ 0,0 1,18 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once
#include <deque>
#include <string>

namespace Quotes
{
    using IdList = std::deque<int>;

    class QuotesSettingsSerializer
    {
      public:
        [[nodiscard]] auto serialize(const IdList &list) -> std::string;
        [[nodiscard]] auto deserialize(const std::string &listString) -> IdList;
    };
} // namespace Quotes

A module-services/service-db/agents/quotes/RandomizedQuoteModel.cpp => module-services/service-db/agents/quotes/RandomizedQuoteModel.cpp +98 -0
@@ 0,0 1,98 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "RandomizedQuoteModel.hpp"
#include "log/log.hpp"
#include "QuotesQueries.hpp"
#include <service-db/agents/settings/SystemSettings.hpp>
#include <algorithm> // std::random_shuffle
#include <memory>
#include <sstream>

namespace Quotes
{
    constexpr inline auto zeroOffset = 0;
    constexpr inline auto maxLimit   = std::numeric_limits<unsigned int>().max();
    RandomizedQuoteModel::RandomizedQuoteModel(std::unique_ptr<settings::Settings> settings, Database *quotesDB)
        : settings(std::move(settings)), quotesDB(quotesDB), serializer(std::make_unique<QuotesSettingsSerializer>())
    {}

    void RandomizedQuoteModel::randomize(IdList &list)
    {
        srand(time(NULL));
        std::random_shuffle(std::begin(list), std::end(list));
    }
    void RandomizedQuoteModel::updateList(bool forced)
    {
        auto queryResult = quotesDB->query(Queries::getEnabledQuotes);
        auto quotesList  = getList<QuotesList, QuoteRecord>(zeroOffset, maxLimit, std::move(queryResult));
        populateList(std::move(quotesList), forced);
    }
    void RandomizedQuoteModel::populateList(std::unique_ptr<QuotesList> quotesList, bool forcedUpdate)
    {
        IdList list;
        for (const auto &item : quotesList->data) {
            list.push_back(item.quote_id);
        }

        randomize(list);
        if (not forcedUpdate) {
            auto settingsList = settings->getValue(settings::Quotes::randomQuotesList, settings::SettingsScope::Global);
            if (not settingsList.empty()) {
                return;
            }
        }
        settings->setValue(
            settings::Quotes::randomQuotesList, serializer->serialize(list), settings::SettingsScope::Global);
        LOG_ERROR("ID queue length: %zu", list.size());
    }

    auto RandomizedQuoteModel::getId() -> int
    {

        if (isIdExpired()) {
            shiftIdList();
        }

        auto list = serializer->deserialize(
            settings->getValue(settings::Quotes::randomQuotesList, settings::SettingsScope::Global));
        return list.front();
    }

    void RandomizedQuoteModel::shiftIdList()
    {
        auto list = serializer->deserialize(
            settings->getValue(settings::Quotes::randomQuotesList, settings::SettingsScope::Global));
        list.pop_front();
        if (list.empty()) {
            updateList(true);
        }
        settings->setValue(
            settings::Quotes::randomQuotesList, serializer->serialize(list), settings::SettingsScope::Global);
        LOG_DEBUG("Selected quote ID: %d, remaining quotes to next shuffle: %zu", list.front(), list.size());
    }
    auto RandomizedQuoteModel::isIdExpired() -> bool
    {
        auto lastTimestampString =
            settings->getValue(settings::Quotes::randomQuoteIDUpdateTime, settings::SettingsScope::Global);
        long lastTimestamp;

        try {
            lastTimestamp = stol(lastTimestampString);
        }
        catch (std::invalid_argument &e) {
            LOG_ERROR("Invalid timestamp, zeroing! Cause: %s", e.what());
            lastTimestamp = 0;
        }

        auto currentTimestamp = std::time(nullptr);
        if ((currentTimestamp - lastTimestamp) >= quotesChangingInterval) {
            lastTimestamp = currentTimestamp;
            settings->setValue(settings::Quotes::randomQuoteIDUpdateTime,
                               utils::to_string(lastTimestamp),
                               settings::SettingsScope::Global);
            return true;
        }
        return false;
    }
} // namespace Quotes

A module-services/service-db/agents/quotes/RandomizedQuoteModel.hpp => module-services/service-db/agents/quotes/RandomizedQuoteModel.hpp +35 -0
@@ 0,0 1,35 @@
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <AsyncTask.hpp>
#include <ApplicationCommon.hpp>
#include <service-db/Settings.hpp>
#include <service-db/QuotesMessages.hpp>
#include "QuotesSettingsSerializer.hpp"

namespace Quotes
{
    using namespace std::chrono;
    inline constexpr auto quotesChangingInterval = duration_cast<seconds>(hours{24}).count();

    class RandomizedQuoteModel
    {
      private:
        app::ApplicationCommon *app = nullptr;
        std::unique_ptr<settings::Settings> settings;
        Database *quotesDB = nullptr;
        void populateList(std::unique_ptr<QuotesList> quotesList, bool forcedUpdate = false);
        void shiftIdList();
        auto isIdExpired() -> bool;
        void randomize(IdList &list);
        std::unique_ptr<QuotesSettingsSerializer> serializer;

      public:
        RandomizedQuoteModel(std::unique_ptr<settings::Settings> settings, Database *quotesDB);
        void updateList(bool forced);
        [[nodiscard]] auto getId() -> int;
    };

} // namespace Quotes

M module-services/service-db/agents/settings/SystemSettings.hpp => module-services/service-db/agents/settings/SystemSettings.hpp +7 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 73,6 73,12 @@ namespace settings
        constexpr inline auto option = "wallpaper_option";
    } // namespace Wallpaper

    namespace Quotes
    {
        constexpr inline auto randomQuotesList        = "quotes_random_list";
        constexpr inline auto randomQuoteIDUpdateTime = "quotes_random_id_update_time";
    } // namespace Quotes

    namespace Display
    {
        constexpr inline auto invertedMode = "display_inverted_mode";

M module-services/service-db/include/service-db/QuotesMessages.hpp => module-services/service-db/include/service-db/QuotesMessages.hpp +32 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 387,6 387,37 @@ namespace Quotes
            }
        };

        class ReadRandomizedQuoteRequest : public db::Query
        {
          public:
            ReadRandomizedQuoteRequest() : Query(Query::Type::Read)
            {}

            auto debugInfo() const -> std::string
            {
                return "ReadRandomizedQuoteRequest";
            }
        };

        class ReadRandomizedQuoteResponse : public db::QueryResult
        {
          public:
            explicit ReadRandomizedQuoteResponse(
                unsigned int quoteId, unsigned int langId, std::string quote, std::string author, bool enabled)
                : quoteId(quoteId), langId(langId), quote(std::move(quote)), author(std::move(author)), enabled(enabled)
            {}
            const unsigned int quoteId;
            const unsigned int langId;
            const std::string quote;
            const std::string author;
            const bool enabled;

            auto debugInfo() const -> std::string
            {
                return "ReadRandomizedQuoteResponse";
            }
        };

        class WriteQuoteRequest : public db::Query
        {
          public:

M module-services/service-db/include/service-db/Settings.hpp => module-services/service-db/include/service-db/Settings.hpp +5 -5
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 33,9 33,9 @@ namespace settings
        void init(const service::ServiceProxy &interface);
        void deinit();

        void setValue(const std::string &variableName,
                      const std::string &variableValue,
                      SettingsScope scope = SettingsScope::AppLocal);
        virtual void setValue(const std::string &variableName,
                              const std::string &variableValue,
                              SettingsScope scope = SettingsScope::AppLocal);
        void registerValueChange(const std::string &variableName,
                                 ValueChangedCallback cb,
                                 SettingsScope scope = SettingsScope::AppLocal);


@@ 44,7 44,7 @@ namespace settings
                                 SettingsScope scope = SettingsScope::AppLocal);
        void unregisterValueChange(const std::string &variableName, SettingsScope scope = SettingsScope::AppLocal);
        /// unregisters all registered variables (both global and local)
        std::string getValue(const std::string &variableName, SettingsScope scope = SettingsScope::AppLocal);
        virtual std::string getValue(const std::string &variableName, SettingsScope scope = SettingsScope::AppLocal);

        SettingsCache *getCache();


M module-services/service-db/test/test-service-db-quotes.cpp => module-services/service-db/test/test-service-db-quotes.cpp +155 -4
@@ 1,10 1,9 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>

#include "test-service-db-quotes.hpp"

#include <purefs/filesystem_paths.hpp>
#include <iostream>



@@ 19,7 18,10 @@ TEST_CASE("Quotes")
{
    Database::initialize();
    auto database = std::make_unique<Database>((purefs::dir::getUserDiskPath() / "quotes.db").string().c_str());
    auto tester   = std::make_unique<QuotesAgentTester>(database.get());
    std::string timestampString{};
    std::string quotesString{};
    auto settings = std::make_unique<Quotes::SettingsMock>(quotesString, timestampString);
    auto tester   = std::make_unique<QuotesAgentTester>(database.get(), std::move(settings));

    SECTION("Get categories with limit and offset")
    {


@@ 60,6 62,11 @@ TEST_CASE("Quotes")
        bool enable                   = false;
        const unsigned int categoryId = 1;

        // Get current random quote
        auto queryResult   = tester->readRandomizedQuote();
        auto randomQuoteId = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get())->quoteId;
        REQUIRE(randomQuoteId != 0);

        auto success = tester->enableCategory(categoryId, enable);
        REQUIRE(success);



@@ 67,6 74,12 @@ TEST_CASE("Quotes")
        auto quotes = tester->getQuotesByCategoryId(categoryId);
        REQUIRE(quotes.size() == 0);

        // verify rerandomizing the quotes list
        queryResult           = tester->readRandomizedQuote();
        auto newRandomQuoteId = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get())->quoteId;
        REQUIRE(newRandomQuoteId != 0);
        REQUIRE(randomQuoteId != newRandomQuoteId);

        enable = true;

        success = tester->enableCategory(categoryId, enable);


@@ 82,6 95,11 @@ TEST_CASE("Quotes")
        bool enable                = false;
        const unsigned int quoteId = 1;

        // Get current random quote
        auto queryResult   = tester->readRandomizedQuote();
        auto randomQuoteId = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get())->quoteId;
        REQUIRE(randomQuoteId != 0);

        auto success = tester->enableQuote(quoteId, enable);
        REQUIRE(success);



@@ 89,6 107,12 @@ TEST_CASE("Quotes")
        auto quotes = tester->getEnabledQuotes();
        REQUIRE(quotes.size() == totalNumOfQuotesInDb - 1);

        // verify rerandomizing the quotes list
        queryResult           = tester->readRandomizedQuote();
        auto newRandomQuoteId = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get())->quoteId;
        REQUIRE(newRandomQuoteId != 0);
        REQUIRE(randomQuoteId != newRandomQuoteId);

        enable = true;

        success = tester->enableQuote(quoteId, enable);


@@ 106,6 130,11 @@ TEST_CASE("Quotes")
        std::string author  = "TEST AUTHOR";
        bool enabled        = true;

        // Get current random quote
        auto queryResult   = tester->readRandomizedQuote();
        auto randomQuoteId = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get())->quoteId;
        REQUIRE(randomQuoteId != 0);

        // Add a new quote
        auto quoteId = tester->addQuote(langId, quote, author, enabled);



@@ 114,8 143,14 @@ TEST_CASE("Quotes")

        REQUIRE(quotes.size() == totalNumOfQuotesInDb + 1);

        // verify rerandomizing the quotes list
        queryResult           = tester->readRandomizedQuote();
        auto newRandomQuoteId = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get())->quoteId;
        REQUIRE(newRandomQuoteId != 0);
        REQUIRE(randomQuoteId != newRandomQuoteId);

        // Read added quote
        auto queryResult       = tester->readQuote(quoteId);
        queryResult            = tester->readQuote(quoteId);
        auto readQuoteResponse = dynamic_cast<Messages::ReadQuoteResponse *>(queryResult.get());

        REQUIRE(readQuoteResponse->quoteId == quoteId);


@@ 146,5 181,121 @@ TEST_CASE("Quotes")
        REQUIRE(quotes.size() == totalNumOfQuotesInDb);
    }

    SECTION("Randomizer test - double request")
    {

        auto queryResult = tester->readRandomizedQuote();
        auto response    = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get());

        REQUIRE(response);
        auto quoteId = response->quoteId;

        queryResult = tester->readRandomizedQuote();
        response    = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get());
        REQUIRE(response);
        auto newQuoteId = response->quoteId;

        REQUIRE(quoteId == newQuoteId);
    }
    SECTION("Randomizer test - time shift correct")
    {
        constexpr auto timeshift24h = 24 * 3600;

        auto queryResult = tester->readRandomizedQuote();
        auto response    = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get());
        REQUIRE(response);
        auto quoteId = response->quoteId;

        timestampString = std::to_string(std::time(nullptr) - timeshift24h);
        queryResult     = tester->readRandomizedQuote();
        response        = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get());
        REQUIRE(response);
        auto newQuoteId = response->quoteId;

        REQUIRE(quoteId != newQuoteId);
    }
    SECTION("Randomizer test - time shift wrong")
    {
        constexpr auto timeshift23h = 23 * 3600;

        auto queryResult = tester->readRandomizedQuote();
        auto response    = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get());
        REQUIRE(response);
        auto quoteId = response->quoteId;

        timestampString = std::to_string(std::time(nullptr) - timeshift23h);
        queryResult     = tester->readRandomizedQuote();
        response        = dynamic_cast<Messages::ReadRandomizedQuoteResponse *>(queryResult.get());
        REQUIRE(response);
        auto newQuoteId = response->quoteId;

        REQUIRE(quoteId == newQuoteId);
    }

    Database::deinitialize();
}

TEST_CASE("Serializer test")
{

    QuotesSettingsSerializer serializer;

    SECTION("Serialize - correct input")
    {
        IdList list{1, 2, 3, 4};
        auto result = serializer.serialize(list);
        REQUIRE(result == "1,2,3,4,");

        IdList list2{1};
        result = serializer.serialize(list2);
        REQUIRE(result == "1,");
    }

    SECTION("Serialize - wrong input")
    {
        IdList list{};
        auto result = serializer.serialize(list);
        REQUIRE(result.empty());
    }

    SECTION("Deserialize - correct input")
    {
        std::string serializedData = "1,2,3,4,";
        auto result                = serializer.deserialize(serializedData);
        REQUIRE(result.size() == 4);
        REQUIRE(result.front() == 1);
        REQUIRE(result.back() == 4);
    }

    SECTION("Deserialize - empty input")
    {
        std::string serializedData = " ";
        IdList result;
        REQUIRE_NOTHROW(result = serializer.deserialize(serializedData));
        REQUIRE(result.empty());
    }

    SECTION("Deserialize - wrong input 1/3")
    {
        std::string serializedData = "abc";
        IdList result;
        REQUIRE_NOTHROW(result = serializer.deserialize(serializedData));
        REQUIRE(result.empty());
    }

    SECTION("Deserialize - wrong input 2/3")
    {
        std::string serializedData = "a,b,c";
        IdList result;
        REQUIRE_NOTHROW(result = serializer.deserialize(serializedData));
        REQUIRE(result.empty());
    }

    SECTION("Deserialize - wrong input 3/3")
    {
        std::string serializedData = ",1,2,3,4";
        IdList result;
        REQUIRE_NOTHROW(result = serializer.deserialize(serializedData));
        REQUIRE(result.empty());
    }
}

M module-services/service-db/test/test-service-db-quotes.hpp => module-services/service-db/test/test-service-db-quotes.hpp +44 -2
@@ 1,17 1,19 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <service-db/QuotesMessages.hpp>
#include <service-db/agents/quotes/QuotesAgent.hpp>
#include <service-db/agents/settings/SystemSettings.hpp>

namespace Quotes
{
    class QuotesAgentTester : public QuotesAgent
    {
      public:
        QuotesAgentTester(Database *quotesDB) : QuotesAgent(quotesDB){};
        QuotesAgentTester(Database *quotesDB, std::unique_ptr<settings::Settings> settings)
            : QuotesAgent(quotesDB, std::move(settings)){};
        ~QuotesAgentTester() = default;

        auto handleCategoryList(std::shared_ptr<Messages::GetCategoryListRequest> query)


@@ 91,6 93,11 @@ namespace Quotes
            auto request = std::make_shared<Messages::ReadQuoteRequest>(quoteId);
            return handleReadQuote(request);
        }
        auto readRandomizedQuote() -> std::unique_ptr<db::QueryResult>
        {
            auto request = std::make_shared<Messages::ReadRandomizedQuoteRequest>();
            return handleReadRandomizedQuote(request);
        }

        auto writeQuote(unsigned int quoteId, unsigned int langId, std::string quote, std::string author, bool enabled)
            -> bool


@@ 110,4 117,39 @@ namespace Quotes
            return response->success;
        }
    };

    class SettingsMock : public settings::Settings
    {
      public:
        SettingsMock(std::string &quotesStr, std::string &timestampStr)
            : quotesString(quotesStr), timestampString(timestampStr)
        {}
        void setValue(const std::string &variableName,
                      const std::string &variableValue,
                      settings::SettingsScope scope = settings::SettingsScope::AppLocal)
        {
            if (variableName == settings::Quotes::randomQuotesList) {
                quotesString = variableValue;
            }
            else if (variableName == settings::Quotes::randomQuoteIDUpdateTime) {
                timestampString = variableValue;
            }
        }
        std::string getValue(const std::string &variableName,
                             settings::SettingsScope scope = settings::SettingsScope::AppLocal)
        {
            if (variableName == settings::Quotes::randomQuotesList) {
                return quotesString;
            }
            else if (variableName == settings::Quotes::randomQuoteIDUpdateTime) {
                return timestampString;
            }
            else {
                return std::string();
            }
        }
        std::string &quotesString;
        std::string &timestampString;
    };

} // namespace Quotes

M products/PurePhone/services/db/ServiceDB.cpp => products/PurePhone/services/db/ServiceDB.cpp +6 -3
@@ 1,4 1,4 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2022, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <db/ServiceDB.hpp>


@@ 245,10 245,8 @@ sys::ReturnCodes ServiceDB::InitHandler()
    countryCodeRecordInterface = std::make_unique<CountryCodeRecordInterface>(countryCodesDB.get());
    notificationsRecordInterface =
        std::make_unique<NotificationsRecordInterface>(notificationsDB.get(), contactRecordInterface.get());
    quotesRecordInterface = std::make_unique<Quotes::QuotesAgent>(quotesDB.get());
    multimediaFilesRecordInterface =
        std::make_unique<db::multimedia_files::MultimediaFilesRecordInterface>(multimediaFilesDB.get());

    databaseAgents.emplace(std::make_unique<SettingsAgent>(this, "settings_v2.db"));

    for (auto &dbAgent : databaseAgents) {


@@ 256,6 254,11 @@ sys::ReturnCodes ServiceDB::InitHandler()
        dbAgent->registerMessages();
    }

    auto settings = std::make_unique<settings::Settings>();
    settings->init(service::ServiceProxy(shared_from_this()));

    quotesRecordInterface = std::make_unique<Quotes::QuotesAgent>(quotesDB.get(), std::move(settings));

    return sys::ReturnCodes::Success;
}