#include "fixture.h"
#include "test_utils.h"

#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/db/include/branch_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/db/include/firmware_gateway.h>
#include <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/db/include/rollout_gateway.h>
#include  <maps/wikimap/mapspro/services/mrc/drive/firmware_updater/libs/db/include/rollout_history_gateway.h>

#include <maps/infra/yacare/include/test_utils.h>
#include <maps/libs/http/include/test_utils.h>

#include <yandex/maps/proto/firmware_updater/storage/rollout.pb.h>

#include <library/cpp/testing/gtest/gtest.h>

namespace proto = yandex::maps::proto::firmware_updater::storage;

namespace maps::fw_updater::storage::tests {

namespace {

const std::string LOGIN = "john";
const std::string HARDWARE_1 = "nanopi-neo4";
const std::string HARDWARE_2 = "raspberry-pi-3b";
const std::string ROOTFS = "rootfs";

const std::string VER_1 = "1.0.0";
const std::string VER_2 = "2.0.0";
const std::string VER_3 = "3.0.0";

const std::string STABLE = "stable";
const std::string TESTING = "testing";

const std::string ACTIVE = "active";
const std::string INACTIVE = "inactive";

const std::string URL_ROLLOUT_CREATE = "http://localhost/v2/rollout/create";
const std::string URL_ROLLOUT_EXPAND = "http://localhost/v2/rollout/expand";
const std::string URL_ROLLOUT_PAUSE = "http://localhost/v2/rollout/pause";
const std::string URL_ROLLOUT_RESUME = "http://localhost/v2/rollout/resume";
const std::string URL_ROLLOUT_TARGET = "http://localhost/v2/rollout/target";
const std::string URL_ROLLOUT_HISTORY = "http://localhost/v2/rollout/history";

http::MockResponse performCreateRolloutRequest(
    const std::string& hardware,
    const std::string& slot,
    const std::string& branch,
    const std::string& version)
{
    auto request = http::MockRequest(http::POST,
        http::URL(URL_ROLLOUT_CREATE)
            .addParam("hardware", hardware)
            .addParam("slot", slot)
            .addParam("branch", branch)
            .addParam("version", version)
        );
    return yacare::performTestRequest(request);
}

std::string createRollout(
    const std::string& hardware,
    const std::string& slot,
    const std::string& branch,
    const std::string& version)
{
    auto response = performCreateRolloutRequest(hardware, slot, branch, version);
    EXPECT_EQ(response.status, 201);
    proto::Rollout rollout;
    Y_PROTOBUF_SUPPRESS_NODISCARD rollout.ParseFromString(TString(response.body));
    return rollout.id();
}

http::MockResponse performExpandRolloutRequest(
    const std::string& rolloutId,
    uint16_t percent)
{
    auto request = http::MockRequest(http::POST,
        http::URL(URL_ROLLOUT_EXPAND)
            .addParam("rollout_id", rolloutId)
            .addParam("percent", percent)
        );
    return yacare::performTestRequest(request);
}

http::MockResponse performPauseRolloutRequest(const std::string& rolloutId)
{
    auto request = http::MockRequest(http::POST,
        http::URL(URL_ROLLOUT_PAUSE).addParam("rollout_id", rolloutId));
    return yacare::performTestRequest(request);
}

http::MockResponse performResumeRolloutRequest(const std::string& rolloutId)
{
    auto request = http::MockRequest(http::POST,
        http::URL(URL_ROLLOUT_RESUME).addParam("rollout_id", rolloutId));
    return yacare::performTestRequest(request);
}

http::MockResponse performTargetRolloutRequest(
    const std::string& hardware,
    const std::optional<std::string>& slot = std::nullopt,
    const std::optional<std::string>& branch = std::nullopt)
{
    http::URL url(URL_ROLLOUT_TARGET);
    url.addParam("hardware", hardware);
    if (slot) {
        url.addParam("slot", *slot);
    }
    if (branch) {
        url.addParam("branch", *branch);
    }

    auto request = http::MockRequest(http::GET, url);
    return yacare::performTestRequest(request);
}

http::MockResponse performRolloutHistoryRequest(
    const std::string& hardware,
    const std::optional<std::string>& slot = std::nullopt,
    const std::optional<std::string>& branch = std::nullopt,
    int results = 0,
    int skip = 0)
{
    http::URL url(URL_ROLLOUT_HISTORY);
    url.addParam("hardware", hardware);
    if (slot) {
        url.addParam("slot", *slot);
    }
    if (branch) {
        url.addParam("branch", *branch);
    }
    if (results) {
        url.addParam("results", results);
    }
    if (skip) {
        url.addParam("skip", skip);
    }

    auto request = http::MockRequest(http::GET, url);
    return yacare::performTestRequest(request);
}

#define EXPECT_ROLLOUT_PARAMS_EQ(rollout, branch_, version_, status_, percent_) \
    EXPECT_EQ(rollout.branch(), branch_);   \
    EXPECT_EQ(rollout.firmware().version(), version_); \
    EXPECT_EQ(rollout.status(), status_);   \
    EXPECT_EQ(rollout.percent(), percent_);


template <typename RequestFunction>
http::MockResponse checkOk(RequestFunction performRequestFunction)
{
    http::MockResponse response = performRequestFunction();
    EXPECT_EQ(response.status, 200);
    return response;
}


struct FillDbFixture : public Fixture
{
    FillDbFixture()
    {
        auto txn = txnHandle();

        idm::addUserRole(*txn, LOGIN, idm::parseSlugPath("project/one/subproject/one-A/role/manager"));

        db::Firmwares firmwares {
            {HARDWARE_1, db::Slot::Rootfs, VER_1, "http://s3.yandex.ru/hw1-rootfs-key1", 4312084, "md5-1"},
            {HARDWARE_1, db::Slot::Rootfs, VER_2, "http://s3.yandex.ru/hw1-rootfs-key2", 4312084, "md5-2"},
            {HARDWARE_1, db::Slot::Rootfs, VER_3, "http://s3.yandex.ru/hw1-rootfs-key3", 4312084, "md5-3"},
            {HARDWARE_2, db::Slot::Rootfs, VER_1, "http://s3.yandex.ru/hw2-rootfs-key4", 4312084, "md5-5"}
        };
        db::FirmwareGateway{*txn}.insert(firmwares);

        db::Branches branches{
            {STABLE},
            {TESTING}
        };
        db::BranchGateway{*txn}.insert(branches);

        txn->commit();
    }
};

} // namespace

TEST_F(FillDbFixture, test_create_rollout_errors)
{
    // No userInfo provided
    auto response = performCreateRolloutRequest(HARDWARE_1, ROOTFS, TESTING, VER_1);
    EXPECT_EQ(response.status, 401);

    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    // No rights in IDM
    response = performCreateRolloutRequest(HARDWARE_2, ROOTFS, TESTING, VER_1);
    EXPECT_EQ(response.status, 403);

    // Unknown hardware
    response = performCreateRolloutRequest("unknown-hardware", ROOTFS, TESTING, VER_1);
    EXPECT_EQ(response.status, 400);

    // Unknown branch
    response = performCreateRolloutRequest(HARDWARE_1, ROOTFS, "unknown-branch", VER_1);
    EXPECT_EQ(response.status, 400);

    // Non-existing firmware
    response = performCreateRolloutRequest(HARDWARE_1, ROOTFS, TESTING, "unknown-version");
    EXPECT_EQ(response.status, 400);
}

TEST_F(FillDbFixture, test_create_rollout_success)
{
    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    auto response = performCreateRolloutRequest(HARDWARE_1, ROOTFS, TESTING, VER_1);
    EXPECT_EQ(response.status, 201);
    EXPECT_EQ(response.headers.at("Content-Type"), PROTOBUF_MEDIA_TYPE);
    proto::Rollout rollout;
    ASSERT_TRUE(rollout.ParseFromString(TString(response.body)));
    EXPECT_EQ(rollout.firmware().hardware(), HARDWARE_1);
    EXPECT_EQ(rollout.firmware().slot(), ROOTFS);
    EXPECT_EQ(rollout.branch(), TESTING);
    EXPECT_EQ(rollout.firmware().version(), VER_1);
    EXPECT_EQ(rollout.percent(), 0u);
    EXPECT_EQ(rollout.status(), INACTIVE);
    EXPECT_EQ(rollout.created_by(), LOGIN);

    // Can't create another rollout for the same firmware ID
    response = performCreateRolloutRequest(HARDWARE_1, ROOTFS, TESTING, VER_1);
    EXPECT_EQ(response.status, 409);

    // Check db records
    auto txn = txnHandle();
    auto dbRolloutHistories = db::RolloutHistoryGateway{*txn}.load();
    ASSERT_EQ(dbRolloutHistories.size(), 1u);
    auto dbRollout = db::RolloutGateway{*txn}.loadById(dbRolloutHistories[0].rolloutId());
    auto dbFirmware = db::FirmwareGateway{*txn}.loadById(dbRollout.firmwareId());
    EXPECT_TRUE(areEqual(rollout, dbRolloutHistories[0], dbRollout, dbFirmware));
}

TEST_F(FillDbFixture, test_cannot_downgrade_version_seq)
{
    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
    const std::string rolloutId = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_2);

    // Can't create rollout with older firmware version
    auto response = performCreateRolloutRequest(HARDWARE_1, ROOTFS, TESTING, VER_1);
    EXPECT_EQ(response.status, 400);

    // Can create rollout with newer firmware version
    response = performCreateRolloutRequest(HARDWARE_1, ROOTFS, TESTING, VER_3);
    EXPECT_EQ(response.status, 201);
}

TEST_F(FillDbFixture, test_expand_experiment_errors)
{
    std::string rolloutId;
    {
        yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
        rolloutId = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_1);
    }

    // No userInfo provided
    auto response = performExpandRolloutRequest(rolloutId, 10);
    EXPECT_EQ(response.status, 401);

    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    // rolloutId not found
    response = performExpandRolloutRequest(rolloutId + "0", 10);
    EXPECT_EQ(response.status, 404);

    auto txn = txnHandle();
    idm::removeUserRole(*txn, LOGIN, idm::parseSlugPath("project/one/subproject/one-A/role/manager"));
    txn->commit();

    // No rights in IDM
    response = performExpandRolloutRequest(rolloutId, 10);
    EXPECT_EQ(response.status, 403);
}

TEST_F(FillDbFixture, test_expand_experiment)
{
    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    auto response = performCreateRolloutRequest(HARDWARE_1, ROOTFS, TESTING, VER_1);
    EXPECT_EQ(response.status, 201);
    EXPECT_EQ(response.headers.at("Content-Type"), PROTOBUF_MEDIA_TYPE);
    proto::Rollout rollout;
    Y_PROTOBUF_SUPPRESS_NODISCARD rollout.ParseFromString(TString(response.body));
    const std::string rolloutId = rollout.id();

    {
        response = performExpandRolloutRequest(rolloutId, 10);
        EXPECT_EQ(response.status, 200);
        proto::Rollout rollout;
        ASSERT_TRUE(rollout.ParseFromString(TString(response.body)));
        EXPECT_EQ(rollout.percent(), 10u);
        EXPECT_EQ(rollout.status(), ACTIVE);
    }
    {
        response = performExpandRolloutRequest(rolloutId, 20);
        EXPECT_EQ(response.status, 200);
        proto::Rollout rollout;
        ASSERT_TRUE(rollout.ParseFromString(TString(response.body)));
        EXPECT_EQ(rollout.percent(), 20u);
        EXPECT_EQ(rollout.status(), ACTIVE);
    }
    {
        // Can't decrease percent
        response = performExpandRolloutRequest(rolloutId, 10);
        EXPECT_EQ(response.status, 400);
    }
    {
        // Percent out of range
        response = performExpandRolloutRequest(rolloutId, 101);
        EXPECT_EQ(response.status, 400);
        response = performExpandRolloutRequest(rolloutId, 0);
        EXPECT_EQ(response.status, 400);
        response = performExpandRolloutRequest(rolloutId, -10);
        EXPECT_EQ(response.status, 400);
    }
    {
        response = performExpandRolloutRequest(rolloutId, 100);
        EXPECT_EQ(response.status, 200);
        proto::Rollout rollout;
        ASSERT_TRUE(rollout.ParseFromString(TString(response.body)));
        EXPECT_EQ(rollout.percent(), 100u);
        EXPECT_EQ(rollout.status(), ACTIVE);
    }
}

TEST_F(FillDbFixture, test_pause_resume_experiment)
{
    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
    const std::string rolloutId = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_1);

    auto response = performExpandRolloutRequest(rolloutId, 10);
    EXPECT_EQ(response.status, 200);

    {
        response = performPauseRolloutRequest(rolloutId);
        EXPECT_EQ(response.status, 200);
        EXPECT_EQ(response.headers.at("Content-Type"), PROTOBUF_MEDIA_TYPE);
        proto::Rollout rollout;
        ASSERT_TRUE(rollout.ParseFromString(TString(response.body)));
        EXPECT_EQ(rollout.percent(), 10u);
        EXPECT_EQ(rollout.status(), INACTIVE);
    }
    {
        response = performResumeRolloutRequest(rolloutId);
        EXPECT_EQ(response.status, 200);
        EXPECT_EQ(response.headers.at("Content-Type"), PROTOBUF_MEDIA_TYPE);
        proto::Rollout rollout;
        ASSERT_TRUE(rollout.ParseFromString(TString(response.body)));
        EXPECT_EQ(rollout.percent(), 10u);
        EXPECT_EQ(rollout.status(), ACTIVE);
    }

    // Expand after pause
    {
        response = performPauseRolloutRequest(rolloutId);
        EXPECT_EQ(response.status, 200);
        proto::Rollout rollout;
        Y_PROTOBUF_SUPPRESS_NODISCARD rollout.ParseFromString(TString(response.body));
        EXPECT_EQ(rollout.percent(), 10u);
        EXPECT_EQ(rollout.status(), INACTIVE);
    }
    {
        response = performExpandRolloutRequest(rolloutId, 20);
        EXPECT_EQ(response.status, 200);
        proto::Rollout rollout;
        Y_PROTOBUF_SUPPRESS_NODISCARD rollout.ParseFromString(TString(response.body));
        EXPECT_EQ(rollout.percent(), 20u);
        EXPECT_EQ(rollout.status(), ACTIVE);
    }
}

TEST_F(FillDbFixture, test_pause_resume_experiments_errors)
{
    std::string rolloutId;
    {
        yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
        rolloutId = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_1);
        auto response = performExpandRolloutRequest(rolloutId, 10);
        EXPECT_EQ(response.status, 200);
    }

    // No userInfo provided
    auto response = performPauseRolloutRequest(rolloutId);
    EXPECT_EQ(response.status, 401);

    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    // rolloutId not found
    response = performPauseRolloutRequest(rolloutId + "0");
    EXPECT_EQ(response.status, 404);

    auto txn = txnHandle();
    idm::removeUserRole(*txn, LOGIN, idm::parseSlugPath("project/one/subproject/one-A/role/manager"));
    txn->commit();

    // No rights in IDM
    response = performPauseRolloutRequest(rolloutId);
    EXPECT_EQ(response.status, 403);
}

TEST_F(FillDbFixture, test_cannot_have_two_active_experiments)
{
    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
    const std::string rolloutIdOne = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_1);

    // Can create another experiment in inactive state
    const std::string rolloutIdTwo = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_2);

    // But can't make both experiments active
    auto response = performExpandRolloutRequest(rolloutIdOne, 10);
    EXPECT_EQ(response.status, 200);

    response = performExpandRolloutRequest(rolloutIdTwo, 10);
    EXPECT_EQ(response.status, 400);
}

TEST_F(FillDbFixture, test_cannot_activate_experiment_from_the_past)
{
    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
    const std::string rolloutIdOne = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_1);
    auto response = performExpandRolloutRequest(rolloutIdOne, 10);
    EXPECT_EQ(response.status, 200);
    response = performPauseRolloutRequest(rolloutIdOne);

    const std::string rolloutIdTwo = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_2);
    response = performExpandRolloutRequest(rolloutIdTwo, 10);
    EXPECT_EQ(response.status, 200);
    response = performPauseRolloutRequest(rolloutIdTwo);

    response = performResumeRolloutRequest(rolloutIdOne);
    EXPECT_EQ(response.status, 400);
}

TEST_F(FillDbFixture, test_rollout_seed_generation)
{
    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    const std::string rolloutIdOne = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_1);
    performExpandRolloutRequest(rolloutIdOne, 10);
    performPauseRolloutRequest(rolloutIdOne);

    const std::string rolloutIdTwo = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_2);
    performExpandRolloutRequest(rolloutIdTwo, 100);

    const std::string rolloutIdThree = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_3);

    auto txn = txnHandle();
    db::RolloutGateway gtw{*txn};
    db::Rollout rolloutOne = gtw.loadById(std::stol(rolloutIdOne));
    db::Rollout rolloutTwo = gtw.loadById(std::stol(rolloutIdTwo));
    db::Rollout rolloutThree = gtw.loadById(std::stol(rolloutIdThree));

    EXPECT_EQ(rolloutOne.seed(), rolloutTwo.seed());
    EXPECT_NE(rolloutOne.seed(), rolloutThree.seed());
}

TEST_F(FillDbFixture, test_rollout_target_state_errors)
{
    {
        yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
        auto rolloutId = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_1);
        auto response = performExpandRolloutRequest(rolloutId, 10);
        EXPECT_EQ(response.status, 200);
    }

    // No userInfo provided
    auto response = performTargetRolloutRequest(HARDWARE_1);
    EXPECT_EQ(response.status, 401);

    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    // Unknown hardware
    response = performTargetRolloutRequest("unknown-hardware");
    EXPECT_EQ(response.status, 404);

    // Unknown branch
    response = performTargetRolloutRequest(HARDWARE_1, ROOTFS, "unknown-branch");
    EXPECT_EQ(response.status, 400);

    // No rights in IDM
    response = performTargetRolloutRequest(HARDWARE_2);
    EXPECT_EQ(response.status, 403);
}

TEST_F(FillDbFixture, test_rollout_target_state)
{
    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    // Test empty response
    auto response = performTargetRolloutRequest(HARDWARE_1);
    EXPECT_EQ(response.status, 204);

    const std::string rolloutIdOne = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_1);
    response = checkOk([&]{ return performExpandRolloutRequest(rolloutIdOne, 100); });

    const std::string rolloutIdTwo = createRollout(HARDWARE_1, ROOTFS, STABLE, VER_1);
    response = checkOk([&]{ return performExpandRolloutRequest(rolloutIdTwo, 10); });

    const std::string rolloutIdThree = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_2);
    response = checkOk([&]{ return performExpandRolloutRequest(rolloutIdThree, 20); });

    {
        // Request target state for all branches
        response = performTargetRolloutRequest(HARDWARE_1);
        EXPECT_EQ(response.status, 200);
        EXPECT_EQ(response.headers.at("Content-Type"), PROTOBUF_MEDIA_TYPE);
        proto::RolloutsList rolloutsList;
        ASSERT_TRUE(rolloutsList.ParseFromString(TString(response.body)));
        ASSERT_EQ(rolloutsList.rollouts_size(), 3);
        for (size_t i = 0; i < 3; ++i) {
            auto& rollout = rolloutsList.rollouts(i);
            if (rollout.id() == rolloutIdOne) {
                EXPECT_ROLLOUT_PARAMS_EQ(rollout, TESTING, VER_1, ACTIVE, 100u);
            } else if (rollout.id() == rolloutIdTwo) {
                EXPECT_ROLLOUT_PARAMS_EQ(rollout, STABLE, VER_1, ACTIVE, 10u);
            } else if (rollout.id() == rolloutIdThree) {
                EXPECT_ROLLOUT_PARAMS_EQ(rollout, TESTING, VER_2, ACTIVE, 20u);
            } else {
                FAIL();
            }
        }
    }
    {
        // Request target state for stable branch
        response = performTargetRolloutRequest(HARDWARE_1, ROOTFS, STABLE);
        EXPECT_EQ(response.status, 200);
        proto::RolloutsList rolloutsList;
        ASSERT_TRUE(rolloutsList.ParseFromString(TString(response.body)));
        ASSERT_EQ(rolloutsList.rollouts_size(), 1);
        EXPECT_EQ(rolloutsList.rollouts(0).id(), rolloutIdTwo);
    }
}

TEST_F(FillDbFixture, test_rollout_history_errors)
{
    {
        yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));
        auto rolloutId = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_1);
        auto response = performExpandRolloutRequest(rolloutId, 10);
        EXPECT_EQ(response.status, 200);
    }

    // No userInfo provided
    auto response = performRolloutHistoryRequest(HARDWARE_1);
    EXPECT_EQ(response.status, 401);

    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    // Unknown hardware
    response = performRolloutHistoryRequest("unknown-hardware");
    EXPECT_EQ(response.status, 404);

    // Unknown branch
    response = performRolloutHistoryRequest(HARDWARE_1, ROOTFS, "unknown-branch");
    EXPECT_EQ(response.status, 400);

    // No rights in IDM
    response = performRolloutHistoryRequest(HARDWARE_2);
    EXPECT_EQ(response.status, 403);
}

TEST_F(FillDbFixture, test_rollout_history)
{
    yacare::tests::UserInfoFixture userInfoFixture(makeUserInfo(LOGIN));

    // Test empty response
    auto response = performRolloutHistoryRequest(HARDWARE_1);
    EXPECT_EQ(response.status, 204);

    const std::string rolloutIdOne = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_1);
    response = checkOk([&]{ return performExpandRolloutRequest(rolloutIdOne, 5); });
    response = checkOk([&]{ return performExpandRolloutRequest(rolloutIdOne, 10); });
    response = checkOk([&]{ return performPauseRolloutRequest(rolloutIdOne); });

    const std::string rolloutIdTwo = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_2);
    response = checkOk([&]{ return performExpandRolloutRequest(rolloutIdTwo, 10); });
    response = checkOk([&]{ return performExpandRolloutRequest(rolloutIdTwo, 100); });

    const std::string rolloutIdThree = createRollout(HARDWARE_1, ROOTFS, STABLE, VER_2);
    response = checkOk([&]{ return performExpandRolloutRequest(rolloutIdThree, 10); });
    response = checkOk([&]{ return performPauseRolloutRequest(rolloutIdThree); });
    response = checkOk([&]{ return performResumeRolloutRequest(rolloutIdThree); });
    response = checkOk([&]{ return performExpandRolloutRequest(rolloutIdThree, 100); });

    const std::string rolloutIdFour = createRollout(HARDWARE_1, ROOTFS, TESTING, VER_3);
    response = checkOk([&]{ return performExpandRolloutRequest(rolloutIdFour, 10); });

    {
        // Request history without filters
        response = performRolloutHistoryRequest(HARDWARE_1);
        EXPECT_EQ(response.status, 200);
        EXPECT_EQ(response.headers.at("Content-Type"), PROTOBUF_MEDIA_TYPE);
        proto::RolloutsList rolloutsList;
        ASSERT_TRUE(rolloutsList.ParseFromString(TString(response.body)));
        ASSERT_EQ(rolloutsList.rollouts_size(), 14);

        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(0), TESTING, VER_3, ACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(1), TESTING, VER_3, INACTIVE, 0u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(2), STABLE, VER_2, ACTIVE, 100u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(3), STABLE, VER_2, ACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(4), STABLE, VER_2, INACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(5), STABLE, VER_2, ACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(6), STABLE, VER_2, INACTIVE, 0u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(7), TESTING, VER_2, ACTIVE, 100u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(8), TESTING, VER_2, ACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(9), TESTING, VER_2, INACTIVE, 0u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(10), TESTING, VER_1, INACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(11), TESTING, VER_1, ACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(12), TESTING, VER_1, ACTIVE, 5u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(13), TESTING, VER_1, INACTIVE, 0u);
    }

    {
        // Request history with filters
        response = performRolloutHistoryRequest(HARDWARE_1, ROOTFS, STABLE);
        EXPECT_EQ(response.status, 200);
        proto::RolloutsList rolloutsList;
        ASSERT_TRUE(rolloutsList.ParseFromString(TString(response.body)));
        ASSERT_EQ(rolloutsList.rollouts_size(), 5);

        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(0), STABLE, VER_2, ACTIVE, 100u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(1), STABLE, VER_2, ACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(2), STABLE, VER_2, INACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(3), STABLE, VER_2, ACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(rolloutsList.rollouts(4), STABLE, VER_2, INACTIVE, 0u);
    }

    {
        // Test pagination
        response = performRolloutHistoryRequest(HARDWARE_1, ROOTFS, STABLE, 3, 0);
        EXPECT_EQ(response.status, 200);
        proto::RolloutsList pageOneRollouts;
        ASSERT_TRUE(pageOneRollouts.ParseFromString(TString(response.body)));
        ASSERT_EQ(pageOneRollouts.rollouts_size(), 3);

        EXPECT_ROLLOUT_PARAMS_EQ(pageOneRollouts.rollouts(0), STABLE, VER_2, ACTIVE, 100u);
        EXPECT_ROLLOUT_PARAMS_EQ(pageOneRollouts.rollouts(1), STABLE, VER_2, ACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(pageOneRollouts.rollouts(2), STABLE, VER_2, INACTIVE, 10u);

        response = performRolloutHistoryRequest(HARDWARE_1, ROOTFS, STABLE, 3, 3);
        EXPECT_EQ(response.status, 200);
        proto::RolloutsList pageTwoRollouts;
        ASSERT_TRUE(pageTwoRollouts.ParseFromString(TString(response.body)));
        ASSERT_EQ(pageTwoRollouts.rollouts_size(), 2);

        EXPECT_ROLLOUT_PARAMS_EQ(pageTwoRollouts.rollouts(0), STABLE, VER_2, ACTIVE, 10u);
        EXPECT_ROLLOUT_PARAMS_EQ(pageTwoRollouts.rollouts(1), STABLE, VER_2, INACTIVE, 0u);
    }
}

} // namespace maps::fw_updater::storage::tests
