/*
 * Copyright 2019 Bloomberg Finance LP
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <buildboxcommon_fileutils.h>
#include <buildboxcommon_systemutils.h>
#include <buildboxrun_chrootmanager.h>
#include <buildboxrun_userchroot.h>

#include <fstream>
#include <gtest/gtest.h>
#include <stdlib.h>
#include <string>
#include <vector>

using namespace buildboxcommon;
using namespace buildboxrun;
using namespace userchroot;

const char *USERCHROOT_BIN = "userchrootmock.sh";
const char *SUDO_BIN = "sudomock.sh";
const char *USERCHROOT_CALLS = "userchrootmock_storage.txt";
// should have no effect on test
const char *ROOT = "/test/";

class ChrootManagerTest : public ::testing::Test {

  protected:
    const std::string CURRENT_WORKING_DIR =
        SystemUtils::getCurrentWorkingDirectory();
    const std::string OLD_PATH = getenv("PATH");

    void SetUp() override
    {
        std::ofstream f(USERCHROOT_CALLS);
        if (f.is_open()) {
            f << "0\n";
        }
        f.close();
        std::string count = FileUtils::getFileContents(USERCHROOT_CALLS);
        ASSERT_EQ(count, "0\n");
        std::string new_path = OLD_PATH + ":" + CURRENT_WORKING_DIR;
        setenv("PATH", new_path.c_str(), 1);
    }

    void TearDown() override
    {
        std::ofstream f(USERCHROOT_CALLS);
        if (f.is_open()) {
            f << "0\n";
        }
        f.close();
        std::string count = FileUtils::getFileContents(USERCHROOT_CALLS);
        ASSERT_EQ(count, "0\n");
        unsetenv("PATH");
        setenv("PATH", OLD_PATH.c_str(), 1);
    }
};

TEST_F(ChrootManagerTest, TestConstructor)
{
    ChrootManager chrootManager(USERCHROOT_BIN, ROOT, SUDO_BIN, "");
    std::string count = FileUtils::getFileContents(USERCHROOT_CALLS);
    EXPECT_EQ(count, "1\n");
}

TEST_F(ChrootManagerTest, AbsolutePathTestConstructor)
{
    unsetenv("PATH");
    setenv("PATH", OLD_PATH.c_str(), 1);
    const std::string path_to_userchroot =
        CURRENT_WORKING_DIR + "/" + "userchrootmock.sh";
    ChrootManager chrootManager(path_to_userchroot, ROOT, SUDO_BIN, "");
    std::string count = FileUtils::getFileContents(USERCHROOT_CALLS);
    EXPECT_EQ(count, "1\n");
}

TEST_F(ChrootManagerTest, TestDestructor)
{
    {
        ChrootManager chrootManager(USERCHROOT_BIN, ROOT, SUDO_BIN, "");
    }
    std::string count = FileUtils::getFileContents(USERCHROOT_CALLS);
    EXPECT_EQ(count, "0\n");
}

TEST_F(ChrootManagerTest, TestGenerateCommandLine)
{
    const std::string command_file_directory = CURRENT_WORKING_DIR + "/tmp";
    FileUtils::createDirectory(command_file_directory.c_str());
    // make sure nothing is in directory
    EXPECT_TRUE(FileUtils::directoryIsEmpty(command_file_directory.c_str()));

    ChrootManager chrootManager(USERCHROOT_BIN, CURRENT_WORKING_DIR, SUDO_BIN,
                                "");

    Command command;
    auto result = chrootManager.generateCommandLine(command);

    const auto &location = result[4];
    // This points to a path of the form `/path/to/temporaryFile_xxxxxx`.
    // We extract its random 6-character suffix:
    const std::string unique_identifier =
        location.substr(location.size() - 6, location.size());

    std::vector<std::string> expected{
        SystemUtils::getPathToCommand(USERCHROOT_BIN), CURRENT_WORKING_DIR,
        "/bin/sh", "-c", "/tmp/command_file_" + unique_identifier};
    EXPECT_EQ(result, expected);
    // make sure command file has not been deleted
    EXPECT_FALSE(FileUtils::directoryIsEmpty(command_file_directory.c_str()));
    FileUtils::deleteDirectory(command_file_directory.c_str());
}

TEST_F(ChrootManagerTest, TestGenerateCommandLineSudo)
{
    const std::string command_file_directory = CURRENT_WORKING_DIR + "/tmp";
    FileUtils::createDirectory(command_file_directory.c_str());
    // make sure nothing is in directory
    EXPECT_TRUE(FileUtils::directoryIsEmpty(command_file_directory.c_str()));

    ChrootManager chrootManager(USERCHROOT_BIN, CURRENT_WORKING_DIR, SUDO_BIN,
                                "foo");

    Command command;
    auto result = chrootManager.generateCommandLine(command);

    const auto &location = result[7];
    // This points to a path of the form `/path/to/temporaryFile_xxxxxx`.
    // We extract its random 6-character suffix:
    const std::string unique_identifier =
        location.substr(location.size() - 6, location.size());

    std::vector<std::string> expected{
        SystemUtils::getPathToCommand(SUDO_BIN),
        "-u",
        "foo",
        SystemUtils::getPathToCommand(USERCHROOT_BIN),
        CURRENT_WORKING_DIR,
        "/bin/sh",
        "-c",
        "/tmp/command_file_" + unique_identifier};
    EXPECT_EQ(result, expected);
    // make sure command file has not been deleted
    EXPECT_FALSE(FileUtils::directoryIsEmpty(command_file_directory.c_str()));
    FileUtils::deleteDirectory(command_file_directory.c_str());
}
