view tests/src/plugins/tictactoe/main.cpp @ 733:bd12709b1975

Irccd: rework server to be simpler Server no longer has signals, now user is responsible of calling connect(), recv() and pass a completion handler. The recv function will complete with a std::variant of all possible events. The server does not manage itself anymore, the reconnection system has been moved to server_service instead. To simplify reconnection, the limit has been removed now you can only enable indefinite reconnection or disable it at all. closes #893 closes #892
author David Demelier <markand@malikania.fr>
date Tue, 24 Jul 2018 21:30:00 +0200
parents e8c4ba5ed1c6
children 64839725f346
line wrap: on
line source

/*
 * main.cpp -- test tictactoe plugin
 *
 * Copyright (c) 2013-2018 David Demelier <markand@malikania.fr>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#define BOOST_TEST_MODULE "Plugin tictactoe"
#include <boost/test/unit_test.hpp>

#include <irccd/string_util.hpp>

#include <irccd/daemon/irccd.hpp>
#include <irccd/daemon/server.hpp>
#include <irccd/daemon/service/plugin_service.hpp>

#include <irccd/test/plugin_test.hpp>

namespace irccd {

namespace {

class test_fixture : public plugin_test {
public:
    test_fixture()
        : plugin_test(PLUGIN_PATH)
    {
        plugin_->set_formats({
            { "draw",       "draw=#{channel}:#{command}:#{nickname}:#{plugin}:#{server}"                },
            { "invalid",    "invalid=#{channel}:#{command}:#{nickname}:#{origin}:#{plugin}:#{server}"   },
            { "running",    "running=#{channel}:#{command}:#{nickname}:#{origin}:#{plugin}:#{server}"   },
            { "turn",       "turn=#{channel}:#{command}:#{nickname}:#{plugin}:#{server}"                },
            { "used",       "used=#{channel}:#{command}:#{nickname}:#{origin}:#{plugin}:#{server}"      },
            { "win",        "win=#{channel}:#{command}:#{nickname}:#{plugin}:#{server}"                 }
        });
    }

    auto next_players() const
    {
        if (server_->cqueue().size() == 0)
            throw std::runtime_error("no message");

        const auto cmd = server_->cqueue().back();
        const auto list = string_util::split(cmd["message"].get<std::string>(), ":");

        BOOST_TEST(list.size() == 5U);
        BOOST_TEST(list[0] == "turn=#tictactoe");
        BOOST_TEST(list[1] == "!tictactoe");
        BOOST_TEST(list[3] == "tictactoe");
        BOOST_TEST(list[4] == "test");

        return list[2] == "a" ? std::make_pair("a", "b") : std::make_pair("b", "a");
    }

    auto start()
    {
        plugin_->handle_command(irccd_, {server_, "a!a@localhost", "#tictactoe", "b"});
        plugin_->handle_names(irccd_, {server_, "#tictactoe", {"a", "b"}});

        return next_players();
    }

    /**
     * Helper to place several tokens on the board and automatically toggling
     * players.
     *
     * This will start the game from "a" with target opponent "b".
     *
     */
    void run(const std::initializer_list<std::string>& points)
    {
        auto players = start();

        for (const auto& p : points) {
            server_->cqueue().clear();
            plugin_->handle_message(irccd_, {server_, players.first, "#tictactoe", p});
            players = next_players();
        }
    }
};

BOOST_FIXTURE_TEST_SUITE(test_fixture_suite, test_fixture)

BOOST_AUTO_TEST_CASE(win)
{
    run({"a 1", "b1", "a 2", "b2"});

    const auto players = next_players();

    plugin_->handle_message(irccd_, {server_, players.first, "#tictactoe", "a 3"});

    const auto cmd = server_->cqueue().back();

    BOOST_TEST(cmd.is_object());
    BOOST_TEST(cmd["command"].get<std::string>() == "message");
    BOOST_TEST(cmd["target"].get<std::string>() == "#tictactoe");

    const auto parts = string_util::split(cmd["message"].get<std::string>(), ":");

    BOOST_TEST(parts.size() == 5U);
    BOOST_TEST(parts[0] == "win=#tictactoe");
    BOOST_TEST(parts[1] == "!tictactoe");
    BOOST_TEST(parts[2] == players.first);
    BOOST_TEST(parts[3] == "tictactoe");
    BOOST_TEST(parts[4] == "test");
}

BOOST_AUTO_TEST_CASE(draw)
{
    /*
     *   a b c
     * 1 o x o
     * 2 o x x
     * 3 x o x
     */
    run({ "b 2", "c 1", "c 3", "b 3", "c 2", "a 2", "a 3", "a 1" });

    const auto players = next_players();

    plugin_->handle_message(irccd_, {server_, players.first, "#tictactoe", "b 1"});

    const auto cmd = server_->cqueue().back();

    BOOST_TEST(cmd.is_object());
    BOOST_TEST(cmd["command"].get<std::string>() == "message");
    BOOST_TEST(cmd["target"].get<std::string>() == "#tictactoe");

    const auto parts = string_util::split(cmd["message"].get<std::string>(), ":");

    BOOST_TEST(parts.size() == 5U);
    BOOST_TEST(parts[0] == "draw=#tictactoe");
    BOOST_TEST(parts[1] == "!tictactoe");
    BOOST_TEST(parts[2] == players.first);
    BOOST_TEST(parts[3] == "tictactoe");
    BOOST_TEST(parts[4] == "test");
}

BOOST_AUTO_TEST_CASE(used)
{
    auto players = start();

    plugin_->handle_message(irccd_, {server_, players.first, "#tictactoe", "a 1"});
    plugin_->handle_message(irccd_, {server_, players.second, "#tictactoe", "a 1"});

    const auto cmd = server_->cqueue().back();

    BOOST_TEST(cmd.is_object());
    BOOST_TEST(cmd["command"].get<std::string>() == "message");
    BOOST_TEST(cmd["target"].get<std::string>() == "#tictactoe");

    const auto parts = string_util::split(cmd["message"].get<std::string>(), ":");

    BOOST_TEST(parts[0] == "used=#tictactoe");
    BOOST_TEST(parts[1] == "!tictactoe");
    BOOST_TEST(parts[2] == players.second);
    BOOST_TEST(parts[3] == players.second);
    BOOST_TEST(parts[4] == "tictactoe");
    BOOST_TEST(parts[5] == "test");
}

BOOST_AUTO_TEST_CASE(invalid)
{
    nlohmann::json cmd;

    // empty name (no names)
    plugin_->handle_command(irccd_, {server_, "jean", "#tictactoe", ""});
    cmd = server_->cqueue().back();

    BOOST_TEST(cmd["command"].get<std::string>() == "message");
    BOOST_TEST(cmd["target"].get<std::string>() == "#tictactoe");
    BOOST_TEST(cmd["message"].get<std::string>() == "invalid=#tictactoe:!tictactoe:jean:jean:tictactoe:test");

    // bot name (no names)
    plugin_->handle_command(irccd_, {server_, "jean", "#tictactoe", "irccd"});
    cmd = server_->cqueue().back();

    BOOST_TEST(cmd["command"].get<std::string>() == "message");
    BOOST_TEST(cmd["target"].get<std::string>() == "#tictactoe");
    BOOST_TEST(cmd["message"].get<std::string>() == "invalid=#tictactoe:!tictactoe:jean:jean:tictactoe:test");

    // target is origin (no names)
    plugin_->handle_command(irccd_, {server_, server_->get_nickname(), "#tictactoe", server_->get_nickname()});
    cmd = server_->cqueue().back();

    BOOST_TEST(cmd["command"].get<std::string>() == "message");
    BOOST_TEST(cmd["target"].get<std::string>() == "#tictactoe");
    BOOST_TEST(cmd["message"].get<std::string>() == "invalid=#tictactoe:!tictactoe:irccd:irccd:tictactoe:test");

    // not existing (names)
    plugin_->handle_command(irccd_, {server_, server_->get_nickname(), "#tictactoe", server_->get_nickname()});
    plugin_->handle_names(irccd_, {server_, "#tictactoe", {"a", "b", "c"}});
    cmd = server_->cqueue().back();

    BOOST_TEST(cmd["command"].get<std::string>() == "message");
    BOOST_TEST(cmd["target"].get<std::string>() == "#tictactoe");
    BOOST_TEST(cmd["message"].get<std::string>() == "invalid=#tictactoe:!tictactoe:irccd:irccd:tictactoe:test");
}

BOOST_AUTO_TEST_CASE(random)
{
    /*
     * Ensure that the first player is not always the originator, start the game
     * for at most 1'000'000 times to avoid forever loop.
     */
    unsigned count = 0;
    bool a = false;
    bool b = false;

    // Last player turn is the winner.
    while (!a && !b && count++ < 1000000U) {
        run({"a 1", "b 1", "a 2", "b 2"});

        const auto players = next_players();

        if (players.first == std::string("a"))
            a = true;
        else
            b = true;

        plugin_->handle_message(irccd_, {server_, players.first, "#tictactoe", "a 3"});
    }
}

BOOST_AUTO_TEST_CASE(disconnect)
{
    auto players = start();

    plugin_->handle_disconnect(irccd_, {server_});
    server_->cqueue().clear();
    plugin_->handle_message(irccd_, {server_, players.first, "#tictactoe", "a 1"});

    BOOST_TEST(server_->cqueue().empty());
}

BOOST_AUTO_TEST_CASE(kick)
{
    auto players = start();

    server_->cqueue().clear();
    plugin_->handle_kick(irccd_, {server_, "kefka", "#tictactoe", players.first, ""});
    plugin_->handle_message(irccd_, {server_, players.first, "#tictactoe", "a 1"});

    BOOST_TEST(server_->cqueue().empty());
}

BOOST_AUTO_TEST_CASE(part)
{
    auto players = start();

    server_->cqueue().clear();
    plugin_->handle_part(irccd_, {server_, players.first, "#tictactoe", ""});
    plugin_->handle_message(irccd_, {server_, players.first, "#tictactoe", "a 1"});

    BOOST_TEST(server_->cqueue().empty());
}

BOOST_AUTO_TEST_SUITE_END()

} // !namespace

} // !irccd