Mercurial > irccd
changeset 995:0d71bfa6c97a
tests: add plugin tests
author | David Demelier <markand@malikania.fr> |
---|---|
date | Thu, 11 Feb 2021 17:39:22 +0100 |
parents | 56114ae85868 |
children | 2a6d753f79f6 |
files | CMakeLists.txt irccd/jsapi-plugin.c irccd/jsapi-server.c irccd/jsapi-util.c lib/CMakeLists.txt lib/irccd.pc lib/irccd/util.c lib/irccd/util.h plugins/hangman/hangman.js plugins/history/history.js plugins/joke/joke.7 plugins/joke/joke.js plugins/plugin/plugin.js plugins/tictactoe/tictactoe.js tests/CMakeLists.txt tests/data/answers.conf tests/data/error.json tests/data/joke/error-empty.json tests/data/joke/error-invalid.json tests/data/joke/error-not-array.json tests/data/joke/error-toobig.json tests/data/joke/jokes.json tests/data/words-seq.conf tests/data/words.conf tests/test-plugin-ask.c tests/test-plugin-auth.c tests/test-plugin-hangman.c tests/test-plugin-history.c tests/test-plugin-joke.c tests/test-plugin-logger.c tests/test-plugin-plugin.c tests/test-plugin-tictactoe.c |
diffstat | 32 files changed, 1834 insertions(+), 83 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Wed Feb 10 21:52:32 2021 +0100 +++ b/CMakeLists.txt Thu Feb 11 17:39:22 2021 +0100 @@ -24,7 +24,7 @@ set(IRCCD_VERSION_MAJOR 4) set(IRCCD_VERSION_MINOR 0) set(IRCCD_VERSION_PATCH 0) -set(IRCCD_VERSION ${IRCCD_VERSION_MAJOR}.${IRCCD_VERSION_MINOR}.${IRCCD_VERSION_PATCH}) +set(IRCCD_VERSION "${IRCCD_VERSION_MAJOR}.${IRCCD_VERSION_MINOR}.${IRCCD_VERSION_PATCH}") option(IRCCD_WITH_JS "Enable Javascript" On) option(IRCCD_WITH_SSL "Enable SSL support" On)
--- a/irccd/jsapi-plugin.c Wed Feb 10 21:52:32 2021 +0100 +++ b/irccd/jsapi-plugin.c Thu Feb 11 17:39:22 2021 +0100 @@ -128,13 +128,7 @@ static struct irc_plugin * find(duk_context *ctx) { - const char *name = duk_require_string(ctx, 0); - struct irc_plugin *plg = irc_bot_plugin_get(name); - - if (!plg) - (void)duk_error(ctx, DUK_ERR_REFERENCE_ERROR, "plugin %s not found", name); - - return plg; + return irc_bot_plugin_get(duk_require_string(ctx, 0)); } static int @@ -151,6 +145,8 @@ return 0; duk_push_object(ctx); + duk_push_string(ctx, p->name); + duk_put_prop_string(ctx, -2, "name"); duk_push_string(ctx, p->author); duk_put_prop_string(ctx, -2, "author"); duk_push_string(ctx, p->license);
--- a/irccd/jsapi-server.c Wed Feb 10 21:52:32 2021 +0100 +++ b/irccd/jsapi-server.c Thu Feb 11 17:39:22 2021 +0100 @@ -220,14 +220,12 @@ static int Server_prototype_isSelf(duk_context *ctx) { - (void)ctx; -#if 0 - return wrap(ctx, [] (auto ctx) { - return duk::push(ctx, self(ctx)->is_self(duk::require<std::string>(ctx, 0))); - }); -#endif + const struct irc_server *s = self(ctx); + const char *target = duk_require_string(ctx, 0); - return 0; + duk_push_boolean(ctx, strncmp(target, s->ident.nickname, strlen(s->ident.nickname)) == 0); + + return 1; } static int
--- a/irccd/jsapi-util.c Wed Feb 10 21:52:32 2021 +0100 +++ b/irccd/jsapi-util.c Thu Feb 11 17:39:22 2021 +0100 @@ -80,18 +80,17 @@ (void)duk_error(ctx, DUK_ERR_TYPE_ERROR, "keyword name must be a string"); } - if (strcmp(duk_get_string(ctx, -2), "date") == 0) { + if (strcmp(duk_get_string(ctx, -2), "date") == 0) pkg->subst.time = duk_get_number(ctx, -1); - continue; + else { + pkg->kw = irc_util_reallocarray(pkg->kw, ++pkg->subst.keywordsz, + sizeof (*pkg->kw)); + pkg->kw[pkg->subst.keywordsz - 1].key = + irc_util_strdup(duk_get_string_default(ctx, -2, "")); + pkg->kw[pkg->subst.keywordsz - 1].value = + irc_util_strdup(duk_get_string_default(ctx, -1, "")); } - pkg->kw = irc_util_reallocarray(pkg->kw, ++pkg->subst.keywordsz, - sizeof (*pkg->kw)); - pkg->kw[pkg->subst.keywordsz - 1].key = - irc_util_strdup(duk_get_string_default(ctx, -2, "")); - pkg->kw[pkg->subst.keywordsz - 1].value = - irc_util_strdup(duk_get_string_default(ctx, -1, "")); - duk_pop_n(ctx, 2); }
--- a/lib/CMakeLists.txt Wed Feb 10 21:52:32 2021 +0100 +++ b/lib/CMakeLists.txt Thu Feb 11 17:39:22 2021 +0100 @@ -58,7 +58,7 @@ ${libirccd_BINARY_DIR}/irccd/config.h ) -add_library(libirccd-static ${SOURCES}) +add_library(libirccd-static ${SOURCES} ${HEADERS}) set_target_properties(libirccd-static PROPERTIES PREFIX "") # This is what we export to the world.
--- a/lib/irccd.pc Wed Feb 10 21:52:32 2021 +0100 +++ b/lib/irccd.pc Thu Feb 11 17:39:22 2021 +0100 @@ -1,5 +1,5 @@ Name: irccd Description: Native C interface for irccd plugins -Version: @IRCCD_VERSION_MAJOR@ +Version: @IRCCD_VERSION@ Cflags: -I@CMAKE_INSTALL_FULL_INCLUDEDIR@ -I@CMAKE_INSTALL_FULL_INCLUDEDIR@/irccd/extern -I@OPENSSL_INCLUDE_DIR@ Libs: @EXTRA_LIBS@
--- a/lib/irccd/util.c Wed Feb 10 21:52:32 2021 +0100 +++ b/lib/irccd/util.c Thu Feb 11 17:39:22 2021 +0100 @@ -132,7 +132,7 @@ } size_t -irc_util_split(char *line, const char **args, size_t max) +irc_util_split(char *line, const char **args, size_t max, char delim) { size_t idx; @@ -140,7 +140,7 @@ return 0; for (idx = 0; idx < max; ++idx) { - char *sp = strchr(line, ' '); + char *sp = strchr(line, delim); if (!sp || idx + 1 >= max) { args[idx++] = line;
--- a/lib/irccd/util.h Wed Feb 10 21:52:32 2021 +0100 +++ b/lib/irccd/util.h Thu Feb 11 17:39:22 2021 +0100 @@ -54,7 +54,7 @@ irc_util_dirname(const char *); size_t -irc_util_split(char *, const char **, size_t); +irc_util_split(char *, const char **, size_t, char); char * irc_util_printf(char *, size_t, const char *, ...);
--- a/plugins/hangman/hangman.js Wed Feb 10 21:52:32 2021 +0100 +++ b/plugins/hangman/hangman.js Thu Feb 11 17:39:22 2021 +0100 @@ -136,11 +136,13 @@ path = Plugin.paths.config + "/words.conf"; try { - Logger.info("loading words..."); + Logger.info("loading words from " + path); var file = new File(path, "r"); var line; + Hangman.words.all = []; + while ((line = file.readline()) !== undefined) if (Hangman.isWord(line)) Hangman.words.all.push(line); @@ -272,7 +274,7 @@ { var kw = { channel: channel, - command: server.info().commandChar + Plugin.info().name, + command: server.info().prefix + Plugin.info().name, nickname: Util.splituser(origin), origin: origin, plugin: Plugin.info().name, @@ -319,7 +321,7 @@ var game = Hangman.find(server, channel); var kw = { channel: channel, - command: server.info().commandChar + Plugin.info().name, + command: server.info().prefix + Plugin.info().name, nickname: Util.splituser(origin), origin: origin, plugin: Plugin.info().name,
--- a/plugins/history/history.js Wed Feb 10 21:52:32 2021 +0100 +++ b/plugins/history/history.js Thu Feb 11 17:39:22 2021 +0100 @@ -48,7 +48,7 @@ function command(server) { - return server.info().commandChar + "history"; + return server.info().prefix + "history"; } function path(server, channel) @@ -193,11 +193,20 @@ function onLoad() { + /* + * If the plugin is loaded on-demand, we ask a name list for every + * server and every channel of them to update our database. + */ var table = Server.list(); - for (var k in table) - for (var c in table[k].info().channels) - table[k].names(c); + for (var k in table) { + var channels = table[k].info().channels; + + for (var i = 0; i < channels.length; ++i) { + if (channels[i].joined) + table[k].names(channels[i].name); + } + } } function onNames(server, channel, list)
--- a/plugins/joke/joke.7 Wed Feb 10 21:52:32 2021 +0100 +++ b/plugins/joke/joke.7 Thu Feb 11 17:39:22 2021 +0100 @@ -95,7 +95,7 @@ .Bl -tag -width 14n -offset Ds .It Va error Template when an internal error occured. Keywords: -.Em channel , nickname , origin , server . +.Em channel , command , nickname , origin , plugin , server . .El .\" SEE ALSO .Sh SEE ALSO
--- a/plugins/joke/joke.js Wed Feb 10 21:52:32 2021 +0100 +++ b/plugins/joke/joke.js Thu Feb 11 17:39:22 2021 +0100 @@ -172,6 +172,8 @@ } catch (e) { Logger.warning(e.message); server.message(channel, Util.format(Plugin.templates.error, { + plugin: Plugin.info().name, + command: server.info().prefix + Plugin.info().name, server: server.toString(), channel: channel, origin: origin,
--- a/plugins/plugin/plugin.js Wed Feb 10 21:52:32 2021 +0100 +++ b/plugins/plugin/plugin.js Thu Feb 11 17:39:22 2021 +0100 @@ -46,7 +46,7 @@ { return { channel: channel, - command: server.info().commandChar + Plugin.info().name, + command: server.info().prefix + Plugin.info().name, nickname: Util.splituser(origin), origin: origin, plugin: Plugin.info().name,
--- a/plugins/tictactoe/tictactoe.js Wed Feb 10 21:52:32 2021 +0100 +++ b/plugins/tictactoe/tictactoe.js Thu Feb 11 17:39:22 2021 +0100 @@ -95,24 +95,6 @@ } /** - * Request a game after the name list gets received. - * - * @param server the server object - * @param channel the channel - * @param origin the originator - * @return the object or undefined if not running - */ -Game.postpone = function (server, channel, origin, target) -{ - /* - * Get list of users on the channel to avoid playing against a non existing - * target. - */ - Game.requests[Game.id(server, channel)] = new Game(server, channel, origin, target); - server.names(channel); -} - -/** * Populate a set of keywords. * * @param server the server object @@ -124,7 +106,7 @@ { var kw = { channel: channel, - command: server.info().commandChar + Plugin.info().name, + command: server.info().prefix + Plugin.info().name, plugin: Plugin.info().name, server: server.info().name }; @@ -179,6 +161,37 @@ } /** + * Check if the target is valid. + * + * @param server the server object + * @param channel the channel string + * @param nickname the nickname who requested the game + * @param target the opponent + * @return true if target is valid + */ +Game.isValid = function (server, channel, nickname, target) +{ + if (target === "" || target === nickname || target === server.info().nickname) + return false; + + var channels = server.info().channels; + var ch; + + for (var i = 0; i < channels.length; ++i) { + if (channels[i].name === channel) { + ch = channels[i]; + break; + } + } + + for (var i = 0; i < ch.users.length; ++i) + if (ch.users[i].nickname === target) + return true; + + return false; +} + +/** * Show the game grid and the next player line. */ Game.prototype.show = function () @@ -289,42 +302,29 @@ return true; } -function onNames(server, channel, list) -{ - var id = Game.id(server, channel); - var game = Game.requests[id]; - - // Names can come from any other plugin/event. - if (!game) - return; - - // Not a valid target? destroy the game. - if (list.indexOf(game.target) < 0) - server.message(channel, Util.format(Plugin.templates.invalid, - Game.keywords(server, channel, game.origin))); - else { - Game.map[id] = game; - game.show(); - } - - delete Game.requests[id]; -} - function onCommand(server, origin, channel, message) { + channel = channel.toLowerCase(); + var target = message.trim(); var nickname = Util.splituser(origin); if (Game.exists(server, channel)) server.message(channel, Util.format(Plugin.templates.running, Game.keywords(server, channel, origin))); - else if (target === "" || target === nickname || target === server.info().nickname) + else if (!Game.isValid(server, channel, nickname, target)) server.message(channel, Util.format(Plugin.templates.invalid, Game.keywords(server, channel, origin))); - else - Game.postpone(server, channel, origin, message); + else { + var game = new Game(server, channel, origin, target); + + Game.map[Game.id(server, channel)] = game; + game.show(); + } } function onMessage(server, origin, channel, message) { + channel = channel.toLowerCase(); + var nickname = Util.splituser(origin); var game = Game.find(server, channel); @@ -351,10 +351,10 @@ function onKick(server, origin, channel, target) { - Game.clear(server, target, channel); + Game.clear(server, target, channel.toLowerCase()); } function onPart(server, origin, channel) { - Game.clear(server, origin, channel); + Game.clear(server, origin, channel.toLowerCase()); }
--- a/tests/CMakeLists.txt Wed Feb 10 21:52:32 2021 +0100 +++ b/tests/CMakeLists.txt Thu Feb 11 17:39:22 2021 +0100 @@ -41,6 +41,14 @@ test-jsapi-timer test-jsapi-unicode test-jsapi-util + test-plugin-ask + test-plugin-auth + test-plugin-hangman + test-plugin-history + test-plugin-joke + test-plugin-logger + test-plugin-plugin + test-plugin-tictactoe ) endif () @@ -53,6 +61,8 @@ ${t} PRIVATE IRCCD_EXECUTABLE="$<TARGET_FILE:irccd>" + CMAKE_SOURCE_DIR="${CMAKE_SOURCE_DIR}" + # TODO: change those names. BINARY="${tests_BINARY_DIR}" SOURCE="${tests_SOURCE_DIR}" )
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/answers.conf Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,2 @@ +NO +YES
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/error.json Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,1 @@ +this is not a json file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/joke/error-empty.json Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,9 @@ +[ + [ + ], + [ + ], + [ + false + ] +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/joke/error-invalid.json Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,12 @@ +[ + [ + ], + [ + 1234, + true, + "still has a string though" + ], + [ + "a" + ] +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/joke/error-not-array.json Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,3 @@ +{ + "reason": "this is not a valid jokes database" +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/joke/error-toobig.json Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,15 @@ +[ + [ + "xxx", + "xxx", + "xxx" + ], + [ + "a" + ], + [ + "yyy", + "yyy", + "yyy" + ] +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/joke/jokes.json Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,9 @@ +[ + [ + "aaa" + ], + [ + "bbbb", + "bbbb" + ] +]
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/words-seq.conf Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,3 @@ +abc +abcd +abcde
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/data/words.conf Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,1 @@ +sky
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-plugin-ask.c Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,111 @@ +/* + * test-plugin-ask.c -- test ask plugin + * + * Copyright (c) 2013-2021 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. + */ + +#include <err.h> + +#define GREATEST_USE_ABBREVS 0 +#include <greatest.h> + +#include <irccd/compat.h> +#include <irccd/js-plugin.h> +#include <irccd/plugin.h> +#include <irccd/server.h> + +static struct irc_server *server; +static struct irc_plugin *plugin; + +static void +setup(void *udata) +{ + (void)udata; + + server = irc_server_new("test", "t", "t", "t", "127.0.0.1", 6667); + plugin = js_plugin_open("test", CMAKE_SOURCE_DIR "/plugins/ask/ask.js"); + + if (!plugin) + errx(1, "could not load plugin"); + + irc_server_incref(server); + irc_plugin_set_option(plugin, "file", SOURCE "/data/answers.conf"); + irc_plugin_load(plugin); + + /* Fake server connected to send data. */ + server->state = IRC_SERVER_STATE_CONNECTED; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + irc_server_decref(server); +} + +GREATEST_TEST +basics_simple(void) +{ + int no = 0, yes = 0; + + /* + * Invoke the plugin 1000 times, it will be very unlucky to not have + * both answers in that amount of tries. + */ + for (int i = 0; i < 1000; ++i) { + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_COMMAND, + .server = server, + .message = { + .message = "", + .origin = "jean", + .channel = "#test" + } + }); + + if (strcmp(server->conn.out, "PRIVMSG #test :jean, NO\r\n") == 0) + yes = 1; + else if (strcmp(server->conn.out, "PRIVMSG #test :jean, YES\r\n") == 0) + no = 1; + + memset(server->conn.out, 0, sizeof (server->conn.out)); + } + + GREATEST_ASSERT(no); + GREATEST_ASSERT(yes); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_simple); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-plugin-auth.c Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,133 @@ +/* + * test-plugin-auth.c -- test auth plugin + * + * Copyright (c) 2013-2021 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. + */ + +#include <err.h> + +#define GREATEST_USE_ABBREVS 0 +#include <greatest.h> + +#include <irccd/compat.h> +#include <irccd/js-plugin.h> +#include <irccd/plugin.h> +#include <irccd/server.h> + +/* + * 0 -> nickserv without nickname + * 1 -> nickserv with nickname + * 2 -> quakenet + */ +static struct irc_server *servers[3]; +static struct irc_plugin *plugin; + +static void +setup(void *udata) +{ + (void)udata; + + servers[0] = irc_server_new("nickserv1", "t", "t", "t", "127.0.0.1", 6667); + servers[1] = irc_server_new("nickserv2", "t", "t", "t", "127.0.0.1", 6667); + servers[2] = irc_server_new("quakenet", "t", "t", "t", "127.0.0.1", 6667); + plugin = js_plugin_open("test", CMAKE_SOURCE_DIR "/plugins/auth/auth.js"); + + if (!plugin) + errx(1, "could not load plugin"); + + irc_server_incref(servers[0]); + irc_server_incref(servers[1]); + irc_server_incref(servers[2]); + irc_plugin_set_option(plugin, "nickserv1.type", "nickserv"); + irc_plugin_set_option(plugin, "nickserv1.password", "plopation"); + irc_plugin_set_option(plugin, "nickserv2.type", "nickserv"); + irc_plugin_set_option(plugin, "nickserv2.password", "something"); + irc_plugin_set_option(plugin, "nickserv2.username", "jean"); + irc_plugin_set_option(plugin, "quakenet.type", "quakenet"); + irc_plugin_set_option(plugin, "quakenet.password", "hello"); + irc_plugin_set_option(plugin, "quakenet.username", "mario"); + irc_plugin_load(plugin); + + /* Fake server connected to send data. */ + servers[0]->state = servers[1]->state = servers[2]->state = IRC_SERVER_STATE_CONNECTED; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + irc_server_decref(servers[0]); + irc_server_decref(servers[1]); + irc_server_decref(servers[2]); +} + +GREATEST_TEST +basics_nickserv1(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_CONNECT, + .server = servers[0] + }); + + GREATEST_ASSERT_STR_EQ("PRIVMSG NickServ :identify plopation\r\n", servers[0]->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_nickserv2(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_CONNECT, + .server = servers[1] + }); + + GREATEST_ASSERT_STR_EQ("PRIVMSG NickServ :identify jean something\r\n", servers[1]->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_quakenet(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_CONNECT, + .server = servers[2] + }); + + GREATEST_ASSERT_STR_EQ("PRIVMSG Q@CServe.quakenet.org :AUTH mario hello\r\n", servers[2]->conn.out); + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_nickserv1); + GREATEST_RUN_TEST(basics_nickserv2); + GREATEST_RUN_TEST(basics_quakenet); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-plugin-hangman.c Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,280 @@ +/* + * main.cpp -- test hangman plugin + * + * Copyright (c) 2013-2021 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. + */ + +#include <err.h> + +#define GREATEST_USE_ABBREVS 0 +#include <greatest.h> + +#include <irccd/compat.h> +#include <irccd/js-plugin.h> +#include <irccd/log.h> +#include <irccd/plugin.h> +#include <irccd/server.h> + +#define CALL(t, m) do { \ + memset(server->conn.out, 0, sizeof (server->conn.out)); \ + irc_plugin_handle(plugin, &(const struct irc_event) { \ + .type = t, \ + .server = server, \ + .message = { \ + .origin = "jean!jean@localhost", \ + .channel = "#hangman", \ + .message = m \ + } \ + }); \ +} while (0) + +#define CALL_EX(t, o, c, m) do { \ + memset(server->conn.out, 0, sizeof (server->conn.out)); \ + irc_plugin_handle(plugin, &(const struct irc_event) { \ + .type = t, \ + .server = server, \ + .message = { \ + .origin = o, \ + .channel = c, \ + .message = m \ + } \ + }); \ +} while (0) + +static struct irc_server *server; +static struct irc_plugin *plugin; + +static void +setup(void *udata) +{ + (void)udata; + + server = irc_server_new("test", "t", "t", "t", "127.0.0.1", 6667); + plugin = js_plugin_open("hangman", CMAKE_SOURCE_DIR "/plugins/hangman/hangman.js"); + + if (!plugin) + errx(1, "could not load plugin"); + + irc_log_to_console(); + irc_server_incref(server); + irc_plugin_set_template(plugin, "asked", "asked=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{letter}"); + irc_plugin_set_template(plugin, "dead", "dead=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}"); + irc_plugin_set_template(plugin, "found", "found=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}"); + irc_plugin_set_template(plugin, "start", "start=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}"); + irc_plugin_set_template(plugin, "running", "running=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}"); + irc_plugin_set_template(plugin, "win", "win=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}"); + irc_plugin_set_template(plugin, "wrong-letter", "wrong-letter=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{letter}"); + irc_plugin_set_template(plugin, "wrong-player", "wrong-player=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{letter}"); + irc_plugin_set_template(plugin, "wrong-word", "wrong-word=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{word}"); + irc_plugin_set_option(plugin, "file", SOURCE "/data/words.conf"); + irc_plugin_set_option(plugin, "collaborative", "false"); + irc_plugin_load(plugin); + + /* Fake server connected to send data. */ + server->state = IRC_SERVER_STATE_CONNECTED; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + irc_server_decref(server); +} + +GREATEST_TEST +basics_asked(void) +{ + CALL(IRC_EVENT_COMMAND, ""); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :start=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:_ _ _\r\n", server->conn.out); + + CALL(IRC_EVENT_MESSAGE, "s"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :found=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:s _ _\r\n", server->conn.out); + + CALL(IRC_EVENT_MESSAGE, "s"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :asked=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:s\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_dead(void) +{ + CALL(IRC_EVENT_COMMAND, ""); + CALL(IRC_EVENT_MESSAGE, "a"); + CALL(IRC_EVENT_MESSAGE, "b"); + CALL(IRC_EVENT_MESSAGE, "c"); + CALL(IRC_EVENT_MESSAGE, "d"); + CALL(IRC_EVENT_MESSAGE, "e"); + CALL(IRC_EVENT_MESSAGE, "f"); + CALL(IRC_EVENT_MESSAGE, "g"); + CALL(IRC_EVENT_MESSAGE, "h"); + CALL(IRC_EVENT_MESSAGE, "i"); + CALL(IRC_EVENT_MESSAGE, "j"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :dead=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:sky\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_found(void) +{ + CALL(IRC_EVENT_COMMAND, ""); + CALL(IRC_EVENT_MESSAGE, "s"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :found=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:s _ _\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_start(void) +{ + CALL(IRC_EVENT_COMMAND, ""); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :start=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:_ _ _\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_win1(void) +{ + CALL(IRC_EVENT_COMMAND, ""); + CALL(IRC_EVENT_MESSAGE, "s"); + CALL(IRC_EVENT_MESSAGE, "k"); + CALL(IRC_EVENT_MESSAGE, "y"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :win=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:sky\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_win2(void) +{ + CALL(IRC_EVENT_COMMAND, ""); + CALL(IRC_EVENT_COMMAND, "sky"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :win=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:sky\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_wrong_letter(void) +{ + CALL(IRC_EVENT_COMMAND, ""); + CALL(IRC_EVENT_MESSAGE, "x"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :wrong-letter=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:x\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_wrong_word(void) +{ + CALL(IRC_EVENT_COMMAND, ""); + CALL(IRC_EVENT_COMMAND, "cheese"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :wrong-word=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:cheese\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_collaborative_enabled(void) +{ + irc_plugin_set_option(plugin, "collaborative", "true"); + + CALL(IRC_EVENT_COMMAND, ""); + CALL(IRC_EVENT_MESSAGE, "s"); + + /* Forbidden to play twice. */ + CALL(IRC_EVENT_MESSAGE, "k"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :wrong-player=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:k\r\n", server->conn.out); + + /* Use a different nickname now. */ + CALL_EX(IRC_EVENT_MESSAGE, "francis!francis@localhost", "#hangman", "k"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :found=hangman:!hangman:test:#hangman:francis!francis@localhost:francis:s k _\r\n", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_case_insensitive(void) +{ + CALL_EX(IRC_EVENT_COMMAND, "jean!jean@localhost", "#hangman", ""); + + CALL_EX(IRC_EVENT_MESSAGE, "jean!jean@localhost", "#HANGMAN", "s"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :found=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:s _ _\r\n", server->conn.out); + + CALL_EX(IRC_EVENT_MESSAGE, "jean!jean@localhost", "#HaNGMaN", "k"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :found=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:s k _\r\n", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_query(void) +{ + /* + * Set collaborative mode but in query it must be ignored since there is + * only one player against the bot. + */ + irc_plugin_set_option(plugin, "collaborative", "true"); + + CALL_EX(IRC_EVENT_COMMAND, "jean!jean@localhost", "t", ""); + GREATEST_ASSERT_STR_EQ("PRIVMSG jean!jean@localhost :start=hangman:!hangman:test:jean!jean@localhost:jean!jean@localhost:jean:_ _ _\r\n", server->conn.out); + + CALL_EX(IRC_EVENT_MESSAGE, "jean!jean@localhost", "t", "s"); + GREATEST_ASSERT_STR_EQ("PRIVMSG jean!jean@localhost :found=hangman:!hangman:test:jean!jean@localhost:jean!jean@localhost:jean:s _ _\r\n", server->conn.out); + + CALL_EX(IRC_EVENT_MESSAGE, "jean!jean@localhost", "t", "k"); + GREATEST_ASSERT_STR_EQ("PRIVMSG jean!jean@localhost :found=hangman:!hangman:test:jean!jean@localhost:jean!jean@localhost:jean:s k _\r\n", server->conn.out); + + CALL_EX(IRC_EVENT_COMMAND, "jean!jean@localhost", "t", "sky"); + GREATEST_ASSERT_STR_EQ("PRIVMSG jean!jean@localhost :win=hangman:!hangman:test:jean!jean@localhost:jean!jean@localhost:jean:sky\r\n", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_running(void) +{ + CALL(IRC_EVENT_COMMAND, ""); + CALL(IRC_EVENT_MESSAGE, "y"); + CALL(IRC_EVENT_COMMAND, ""); + GREATEST_ASSERT_STR_EQ("PRIVMSG #hangman :running=hangman:!hangman:test:#hangman:jean!jean@localhost:jean:_ _ y\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_asked); + GREATEST_RUN_TEST(basics_dead); + GREATEST_RUN_TEST(basics_found); + GREATEST_RUN_TEST(basics_start); + GREATEST_RUN_TEST(basics_win1); + GREATEST_RUN_TEST(basics_win2); + GREATEST_RUN_TEST(basics_wrong_letter); + GREATEST_RUN_TEST(basics_wrong_word); + GREATEST_RUN_TEST(basics_collaborative_enabled); + GREATEST_RUN_TEST(basics_case_insensitive); + GREATEST_RUN_TEST(basics_query); + GREATEST_RUN_TEST(basics_running); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-plugin-history.c Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,178 @@ +/* + * test-plugin-history.c -- test history plugin + * + * Copyright (c) 2013-2021 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. + */ + +#include <err.h> + +#define GREATEST_USE_ABBREVS 0 +#include <greatest.h> + +#include <irccd/compat.h> +#include <irccd/js-plugin.h> +#include <irccd/log.h> +#include <irccd/plugin.h> +#include <irccd/server.h> + +#define CALL(t, m) do { \ + memset(server->conn.out, 0, sizeof (server->conn.out)); \ + irc_plugin_handle(plugin, &(const struct irc_event) { \ + .type = t, \ + .server = server, \ + .message = { \ + .origin = "jean!jean@localhost", \ + .channel = "#history", \ + .message = m \ + } \ + }); \ +} while (0) + +#define CALL_EX(t, o, c, m) do { \ + memset(server->conn.out, 0, sizeof (server->conn.out)); \ + irc_plugin_handle(plugin, &(const struct irc_event) { \ + .type = t, \ + .server = server, \ + .message = { \ + .origin = o, \ + .channel = c, \ + .message = m \ + } \ + }); \ +} while (0) + +static struct irc_server *server; +static struct irc_plugin *plugin; + +static void +setup(void *udata) +{ + (void)udata; + + remove(BINARY "/seen.json"); + + server = irc_server_new("test", "t", "t", "t", "127.0.0.1", 6667); + plugin = js_plugin_open("history", CMAKE_SOURCE_DIR "/plugins/history/history.js"); + + if (!plugin) + errx(1, "could not load plugin"); + + irc_log_to_console(); + irc_server_incref(server); + irc_plugin_set_template(plugin, "error", "error=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}"); + irc_plugin_set_template(plugin, "seen", "seen=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{target}:%H:%M"); + irc_plugin_set_template(plugin, "said", "said=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{target}:#{message}:%H:%M"); + irc_plugin_set_template(plugin, "unknown", "unknown=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{target}"); + irc_plugin_set_option(plugin, "file", BINARY "/seen.json"); + irc_plugin_load(plugin); + + /* Fake server connected to send data. */ + server->state = IRC_SERVER_STATE_CONNECTED; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + irc_server_decref(server); +} + +GREATEST_TEST +basics_error(void) +{ + irc_plugin_set_option(plugin, "file", SOURCE "/data/error.json"); + CALL(IRC_EVENT_COMMAND, "seen francis"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #history :error=history:!history:test:#history:jean!jean@localhost:jean\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_seen(void) +{ + int d1, d2; + + CALL_EX(IRC_EVENT_MESSAGE, "jean!jean@localhost", "#history", "hello"); + CALL_EX(IRC_EVENT_COMMAND, "francis!francis@localhost", "#history", "seen jean"); + + GREATEST_ASSERT_EQ(2, sscanf(server->conn.out, "PRIVMSG #history :seen=history:!history:test:#history:francis!francis@localhost:francis:jean:%d:%d\r\n", &d1, &d2)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_said(void) +{ + int d1, d2; + + CALL_EX(IRC_EVENT_MESSAGE, "jean!jean@localhost", "#history", "hello"); + CALL_EX(IRC_EVENT_COMMAND, "francis!francis@localhost", "#history", "said jean"); + + GREATEST_ASSERT_EQ(2, sscanf(server->conn.out, "PRIVMSG #history :said=history:!history:test:#history:francis!francis@localhost:francis:jean:hello:%d:%d", &d1, &d2)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_unknown(void) +{ + CALL_EX(IRC_EVENT_MESSAGE, "jean!jean@localhost", "#history", "hello"); + CALL_EX(IRC_EVENT_COMMAND, "francis!francis@localhost", "#history", "said nobody"); + + GREATEST_ASSERT_STR_EQ("PRIVMSG #history :unknown=history:!history:test:#history:francis!francis@localhost:francis:nobody\r\n", server->conn.out); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_case_insensitive(void) +{ + int d1, d2; + + CALL_EX(IRC_EVENT_MESSAGE, "JeaN!JeaN@localhost", "#history", "hello"); + + CALL_EX(IRC_EVENT_COMMAND, "destructor!dst@localhost", "#HISTORY", "said JEAN"); + GREATEST_ASSERT_EQ(2, sscanf(server->conn.out, "PRIVMSG #history :said=history:!history:test:#history:destructor!dst@localhost:destructor:jean:hello:%d:%d\r\n", &d1, &d2)); + + CALL_EX(IRC_EVENT_COMMAND, "destructor!dst@localhost", "#HiSToRy", "said JeaN"); + GREATEST_ASSERT_EQ(2, sscanf(server->conn.out, "PRIVMSG #history :said=history:!history:test:#history:destructor!dst@localhost:destructor:jean:hello:%d:%d\r\n", &d1, &d2)); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_error); + GREATEST_RUN_TEST(basics_seen); + GREATEST_RUN_TEST(basics_said); + GREATEST_RUN_TEST(basics_unknown); + GREATEST_RUN_TEST(basics_case_insensitive); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +} + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-plugin-joke.c Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,200 @@ +/* + * main.cpp -- test joke plugin + * + * Copyright (c) 2013-2021 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. + */ + +#include <err.h> + +#define GREATEST_USE_ABBREVS 0 +#include <greatest.h> + +#include <irccd/compat.h> +#include <irccd/js-plugin.h> +#include <irccd/plugin.h> +#include <irccd/server.h> + +#define CALL() do { \ + memset(server->conn.out, 0, sizeof (server->conn.out)); \ + irc_plugin_handle(plugin, &(const struct irc_event) { \ + .type = IRC_EVENT_COMMAND, \ + .server = server, \ + .message = { \ + .origin = "jean!jean@localhost", \ + .channel = "#joke", \ + .message = "" \ + } \ + }); \ +} while (0) + +static struct irc_server *server; +static struct irc_plugin *plugin; + +static void +setup(void *udata) +{ + (void)udata; + + server = irc_server_new("test", "t", "t", "t", "127.0.0.1", 6667); + plugin = js_plugin_open("joke", CMAKE_SOURCE_DIR "/plugins/joke/joke.js"); + + if (!plugin) + errx(1, "could not load plugin"); + + irc_server_incref(server); + irc_plugin_set_template(plugin, "error", "error=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}"); + + irc_plugin_set_option(plugin, "file", SOURCE "/data/joke/jokes.json"); + irc_plugin_load(plugin); + + /* Fake server connected to send data. */ + server->state = IRC_SERVER_STATE_CONNECTED; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + irc_server_decref(server); +} + +GREATEST_TEST +basics_simple(void) +{ + /* + * jokes.json have two jokes. + * + * aaa + * + * And + * + * bbbb + * bbbb + */ + int aaa = 0, bbbb = 0; + + for (int i = 0; i < 2; ++i) { + CALL(); + + if (strcmp(server->conn.out, "PRIVMSG #joke :aaa\r\n") == 0) + aaa = 1; + else if (strcmp(server->conn.out, "PRIVMSG #joke :bbbb\r\nPRIVMSG #joke :bbbb\r\n") == 0) + bbbb = 1; + } + + GREATEST_ASSERT(aaa); + GREATEST_ASSERT(bbbb); + + GREATEST_PASS(); +} + +GREATEST_TEST +errors_toobig(void) +{ + /* + * The jokes "xxx" and "yyy" are both 3-lines which we disallow. only a + * must be said. + */ + irc_plugin_set_option(plugin, "file", SOURCE "/data/joke/error-toobig.json"); + irc_plugin_set_option(plugin, "max-list-lines", "2"); + + for (int i = 0; i < 64; ++i) { + CALL(); + GREATEST_ASSERT_STR_EQ("PRIVMSG #joke :a\r\n", server->conn.out); + } + + GREATEST_PASS(); +} + +GREATEST_TEST +errors_invalid(void) +{ + /* Only a is the valid joke in this file. */ + irc_plugin_set_option(plugin, "file", SOURCE "/data/joke/error-invalid.json"); + irc_plugin_set_option(plugin, "max-list-lines", "2"); + + for (int i = 0; i < 64; ++i) { + CALL(); + GREATEST_ASSERT_STR_EQ("PRIVMSG #joke :a\r\n", server->conn.out); + } + + GREATEST_PASS(); +} + +GREATEST_TEST +errors_not_found(void) +{ + irc_plugin_set_option(plugin, "file", "doesnotexist.json"); + + CALL(); + GREATEST_ASSERT_STR_EQ("PRIVMSG #joke :error=joke:!joke:test:#joke:jean!jean@localhost:jean\r\n", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_TEST +errors_not_array(void) +{ + irc_plugin_set_option(plugin, "file", SOURCE "/data/joke/error-not-array.json"); + + CALL(); + GREATEST_ASSERT_STR_EQ("PRIVMSG #joke :error=joke:!joke:test:#joke:jean!jean@localhost:jean\r\n", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_TEST +errors_empty(void) +{ + irc_plugin_set_option(plugin, "file", SOURCE "/data/joke/error-empty.json"); + + CALL(); + GREATEST_ASSERT_STR_EQ("PRIVMSG #joke :error=joke:!joke:test:#joke:jean!jean@localhost:jean\r\n", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_simple); +} + +GREATEST_SUITE(suite_errors) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(errors_toobig); + GREATEST_RUN_TEST(errors_invalid); + GREATEST_RUN_TEST(errors_not_found); + GREATEST_RUN_TEST(errors_not_array); + GREATEST_RUN_TEST(errors_empty); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_RUN_SUITE(suite_errors); + GREATEST_MAIN_END(); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-plugin-logger.c Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,274 @@ +/* + * test-plugin-logger.c -- test logger plugin + * + * Copyright (c) 2013-2021 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. + */ + +#include <err.h> + +#define GREATEST_USE_ABBREVS 0 +#include <greatest.h> + +#include <irccd/compat.h> +#include <irccd/js-plugin.h> +#include <irccd/log.h> +#include <irccd/plugin.h> +#include <irccd/server.h> + +static struct irc_server *server; +static struct irc_plugin *plugin; + +static void +setup(void *udata) +{ + (void)udata; + + remove(BINARY "/log"); + + server = irc_server_new("test", "t", "t", "t", "127.0.0.1", 6667); + plugin = js_plugin_open("logger", CMAKE_SOURCE_DIR "/plugins/logger/logger.js"); + + if (!plugin) + errx(1, "could not load plugin"); + + irc_log_to_console(); + irc_server_incref(server); + irc_plugin_set_template(plugin, "join", "join=#{server}:#{channel}:#{origin}:#{nickname}"); + irc_plugin_set_template(plugin, "kick", "kick=#{server}:#{channel}:#{origin}:#{nickname}:#{target}:#{reason}"); + irc_plugin_set_template(plugin, "me", "me=#{server}:#{channel}:#{origin}:#{nickname}:#{message}"); + irc_plugin_set_template(plugin, "message", "message=#{server}:#{channel}:#{origin}:#{nickname}:#{message}"); + irc_plugin_set_template(plugin, "mode", "mode=#{server}:#{origin}:#{channel}:#{mode}:#{limit}:#{user}:#{mask}"); + irc_plugin_set_template(plugin, "notice", "notice=#{server}:#{origin}:#{channel}:#{message}"); + irc_plugin_set_template(plugin, "part", "part=#{server}:#{channel}:#{origin}:#{nickname}:#{reason}"); + irc_plugin_set_template(plugin, "query", "query=#{server}:#{origin}:#{nickname}:#{message}"); + irc_plugin_set_template(plugin, "topic", "topic=#{server}:#{channel}:#{origin}:#{nickname}:#{topic}"); + irc_plugin_set_option(plugin, "file", BINARY "/log"); + irc_plugin_load(plugin); + + /* Fake server connected to send data. */ + server->state = IRC_SERVER_STATE_CONNECTED; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + irc_server_decref(server); +} + +static const char * +last(void) +{ + static char buf[1024]; + FILE *fp; + + buf[0] = '\0'; + + if (!(fp = fopen(BINARY "/log", "r"))) + err(1, "fopen"); + if (!(fgets(buf, sizeof (buf), fp))) + err(1, "fgets"); + + fclose(fp); + + buf[strcspn(buf, "\r\n")] = '\0'; + + return buf; +} + +GREATEST_TEST +basics_join(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_JOIN, + .server = server, + .join = { + .origin = "jean!jean@localhost", + .channel = "#staff" + } + }); + + GREATEST_ASSERT_STR_EQ("join=test:#staff:jean!jean@localhost:jean", last()); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_kick(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_KICK, + .server = server, + .kick = { + .origin = "jean!jean@localhost", + .channel = "#staff", + .target = "badboy", + .reason = "please do not flood" + } + }); + + GREATEST_ASSERT_STR_EQ("kick=test:#staff:jean!jean@localhost:jean:badboy:please do not flood", last()); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_me(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_ME, + .server = server, + .message = { + .origin = "jean!jean@localhost", + .channel = "#staff", + .message = "is drinking water" + } + }); + + GREATEST_ASSERT_STR_EQ("me=test:#staff:jean!jean@localhost:jean:is drinking water", last()); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_message(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_MESSAGE, + .server = server, + .message = { + .origin = "jean!jean@localhost", + .channel = "#staff", + .message = "hello guys" + } + }); + + GREATEST_ASSERT_STR_EQ("message=test:#staff:jean!jean@localhost:jean:hello guys", last()); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_mode(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_MODE, + .server = server, + .mode = { + .origin = "jean!jean@localhost", + .channel = "chris", + .mode = "+i", + .limit = "l", + .user = "u", + .mask = "m" + } + }); + + GREATEST_ASSERT_STR_EQ("mode=test:jean!jean@localhost:chris:+i:l:u:m", last()); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_notice(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_NOTICE, + .server = server, + .notice = { + .origin = "jean!jean@localhost", + .channel = "chris", + .notice = "tu veux voir mon chat ?" + } + }); + + GREATEST_ASSERT_STR_EQ("notice=test:jean!jean@localhost:chris:tu veux voir mon chat ?", last()); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_part(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_PART, + .server = server, + .part = { + .origin = "jean!jean@localhost", + .channel = "#staff", + .reason = "too noisy here" + } + }); + + GREATEST_ASSERT_STR_EQ("part=test:#staff:jean!jean@localhost:jean:too noisy here", last()); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_topic(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_TOPIC, + .server = server, + .topic = { + .origin = "jean!jean@localhost", + .channel = "#staff", + .topic = "oh yeah yeaaaaaaaah" + } + }); + + GREATEST_ASSERT_STR_EQ("topic=test:#staff:jean!jean@localhost:jean:oh yeah yeaaaaaaaah", last()); + GREATEST_PASS(); +} + +GREATEST_TEST +basics_case_insensitive(void) +{ + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_MESSAGE, + .server = server, + .message = { + .origin = "jean!jean@localhost", + .channel = "#STAFF", + .message = "hello guys" + } + }); + + GREATEST_ASSERT_STR_EQ("message=test:#staff:jean!jean@localhost:jean:hello guys", last()); + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_join); + GREATEST_RUN_TEST(basics_kick); + GREATEST_RUN_TEST(basics_me); + GREATEST_RUN_TEST(basics_message); + GREATEST_RUN_TEST(basics_mode); + GREATEST_RUN_TEST(basics_notice); + GREATEST_RUN_TEST(basics_part); + GREATEST_RUN_TEST(basics_topic); + GREATEST_RUN_TEST(basics_case_insensitive); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-plugin-plugin.c Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,169 @@ +/* + * test-plugin-plugin.c -- test plugin plugin + * + * Copyright (c) 2013-2021 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. + */ + +#include <err.h> +#include <string.h> + +#define GREATEST_USE_ABBREVS 0 +#include <greatest.h> + +#include <irccd/compat.h> +#include <irccd/irccd.h> +#include <irccd/js-plugin.h> +#include <irccd/log.h> +#include <irccd/plugin.h> +#include <irccd/server.h> +#include <irccd/util.h> + +#define CALL(t, m) do { \ + memset(server->conn.out, 0, sizeof (server->conn.out)); \ + irc_plugin_handle(plugin, &(const struct irc_event) { \ + .type = t, \ + .server = server, \ + .message = { \ + .origin = "jean!jean@localhost", \ + .channel = "#plugin", \ + .message = m \ + } \ + }); \ +} while (0) + +static struct irc_server *server; +static struct irc_plugin *plugin, *fake; + +static struct irc_plugin * +fake_new(int n) +{ + struct irc_plugin *p; + + p = irc_util_calloc(1, sizeof (*p)); + snprintf(p->name, sizeof (p->name), "plugin-n-%d", n); + + return p; +} + +static void +setup(void *udata) +{ + (void)udata; + + server = irc_server_new("test", "t", "t", "t", "127.0.0.1", 6667); + plugin = js_plugin_open("plugin", CMAKE_SOURCE_DIR "/plugins/plugin/plugin.js"); + + if (!plugin) + errx(1, "could not load plugin"); + + /* Prepare a fake plugin. */ + fake = irc_util_calloc(1, sizeof (*fake)); + fake->author = "David"; + fake->version = "0.0.0.0.0.0.1"; + fake->license = "BEER"; + fake->description = "Fake White Beer 2000"; + strcpy(fake->name, "fake"); + + irc_bot_init(); + irc_bot_plugin_add(fake); + + irc_plugin_set_template(plugin, "usage", "usage=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}"); + irc_plugin_set_template(plugin, "info", "info=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{author}:#{license}:#{name}:#{summary}:#{version}"); + irc_plugin_set_template(plugin, "not-found", "not-found=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}:#{name}"); + irc_plugin_set_template(plugin, "too-long", "too-long=#{plugin}:#{command}:#{server}:#{channel}:#{origin}:#{nickname}"); + irc_server_incref(server); + + irc_plugin_load(plugin); + + /* Fake server connected to send data. */ + server->state = IRC_SERVER_STATE_CONNECTED; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_bot_finish(); + irc_plugin_finish(plugin); + irc_server_decref(server); +} + +GREATEST_TEST +basics_usage(void) +{ + CALL(IRC_EVENT_COMMAND, ""); + GREATEST_ASSERT_STR_EQ("PRIVMSG #plugin :usage=plugin:!plugin:test:#plugin:jean!jean@localhost:jean\r\n", server->conn.out); + + CALL(IRC_EVENT_COMMAND, "fail"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #plugin :usage=plugin:!plugin:test:#plugin:jean!jean@localhost:jean\r\n", server->conn.out); + + CALL(IRC_EVENT_COMMAND, "info"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #plugin :usage=plugin:!plugin:test:#plugin:jean!jean@localhost:jean\r\n", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_info(void) +{ + CALL(IRC_EVENT_COMMAND, "info fake"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #plugin :info=plugin:!plugin:test:#plugin:jean!jean@localhost:jean:David:BEER:fake:Fake White Beer 2000:0.0.0.0.0.0.1\r\n", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_not_found(void) +{ + CALL(IRC_EVENT_COMMAND, "info doesnotexist"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #plugin :not-found=plugin:!plugin:test:#plugin:jean!jean@localhost:jean:doesnotexist\r\n", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_too_long(void) +{ + for (int i = 0; i < 100; ++i) + irc_bot_plugin_add(fake_new(i)); + + CALL(IRC_EVENT_COMMAND, "list"); + GREATEST_ASSERT_STR_EQ("PRIVMSG #plugin :too-long=plugin:!plugin:test:#plugin:jean!jean@localhost:jean\r\n", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_usage); + GREATEST_RUN_TEST(basics_info); + GREATEST_RUN_TEST(basics_not_found); + GREATEST_RUN_TEST(basics_too_long); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-plugin-tictactoe.c Thu Feb 11 17:39:22 2021 +0100 @@ -0,0 +1,335 @@ +/* + * test-plugin-tictactoe.c -- test tictactoe plugin + * + * Copyright (c) 2013-2021 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. + */ + + +#include <err.h> + +#define GREATEST_USE_ABBREVS 0 +#include <greatest.h> + +#include <irccd/compat.h> +#include <irccd/js-plugin.h> +#include <irccd/log.h> +#include <irccd/plugin.h> +#include <irccd/server.h> +#include <irccd/util.h> + +#define CALL(t, m) do { \ + memset(server->conn.out, 0, sizeof (server->conn.out)); \ + irc_plugin_handle(plugin, &(const struct irc_event) { \ + .type = t, \ + .server = server, \ + .message = { \ + .origin = "jean!jean@localhost", \ + .channel = "#hangman", \ + .message = m \ + } \ + }); \ +} while (0) + +#define CALL_EX(t, o, c, m) do { \ + memset(server->conn.out, 0, sizeof (server->conn.out)); \ + irc_plugin_handle(plugin, &(const struct irc_event) { \ + .type = t, \ + .server = server, \ + .message = { \ + .origin = o, \ + .channel = c, \ + .message = m \ + } \ + }); \ +} while (0) + +static struct irc_server *server; +static struct irc_plugin *plugin; + +static void +setup(void *udata) +{ + (void)udata; + + server = irc_server_new("test", "t", "t", "t", "127.0.0.1", 6667); + plugin = js_plugin_open("tictactoe", CMAKE_SOURCE_DIR "/plugins/tictactoe/tictactoe.js"); + + if (!plugin) + errx(1, "could not load plugin"); + + irc_log_to_console(); + irc_server_incref(server); + irc_plugin_set_template(plugin, "draw", "draw=#{channel}:#{command}:#{nickname}:#{plugin}:#{server}"); + irc_plugin_set_template(plugin, "invalid", "invalid=#{channel}:#{command}:#{nickname}:#{origin}:#{plugin}:#{server}"); + irc_plugin_set_template(plugin, "running", "running=#{channel}:#{command}:#{nickname}:#{origin}:#{plugin}:#{server}"); + irc_plugin_set_template(plugin, "turn", "turn=#{channel}:#{command}:#{nickname}:#{plugin}:#{server}"); + irc_plugin_set_template(plugin, "used", "used=#{channel}:#{command}:#{nickname}:#{origin}:#{plugin}:#{server}"); + irc_plugin_set_template(plugin, "win", "win=#{channel}:#{command}:#{nickname}:#{plugin}:#{server}"); + irc_plugin_load(plugin); + + /* We need tw players on a channel to play the game. */ + irc_server_join(server, "#tictactoe", NULL); + irc_channel_add(LIST_FIRST(&server->channels), "a", 0, 0); + irc_channel_add(LIST_FIRST(&server->channels), "b", 0, 0); + + /* Fake server connected to send data. */ + server->state = IRC_SERVER_STATE_CONNECTED; +} + +static void +teardown(void *udata) +{ + (void)udata; + + irc_plugin_finish(plugin); + irc_server_decref(server); +} + +static char +next(void) +{ + const char *lines[5] = {0}; + char player = 0, *buf; + + /* We need to skip 4 lines.*/ + buf = irc_util_strdup(server->conn.out); + irc_util_split(buf, lines, 5, '\n'); + + if (!lines[4] || sscanf(lines[4], "PRIVMSG #tictactoe :turn=#tictactoe:!tictactoe:%c:tictactoe:test\r\n", &player) != 1) + errx(1, "could not determine player"); + + free(buf); + + return player; +} + +static void +play(const char *value) +{ + char player[] = { next(), '\0' }; + + CALL_EX(IRC_EVENT_MESSAGE, player, "#tictactoe", (char *)value); +} + +GREATEST_TEST +basics_win(void) +{ + const char *lines[5] = {0}; + char k1, k2; + + CALL_EX(IRC_EVENT_COMMAND, "a", "#tictactoe", "b"); + + play("a 1"); + play("b1"); + play("a 2"); + play("b2"); + play("a3"); + + GREATEST_ASSERT_EQ(5U, irc_util_split(server->conn.out, lines, 5, '\n')); + GREATEST_ASSERT_EQ(0, sscanf(lines[0], "PRIVMSG #tictactoe : a b c\r")); + GREATEST_ASSERT_EQ(2, sscanf(lines[1], "PRIVMSG #tictactoe :1 %c %c .\r", &k1, &k2)); + GREATEST_ASSERT_EQ(2, sscanf(lines[2], "PRIVMSG #tictactoe :2 %c %c .\r", &k1, &k2)); + GREATEST_ASSERT_EQ(1, sscanf(lines[3], "PRIVMSG #tictactoe :3 %c . .\r", &k1)); + GREATEST_ASSERT_EQ(1, sscanf(lines[4], "PRIVMSG #tictactoe :win=#tictactoe:!tictactoe:%c:tictactoe:test\r\n", &k1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_draw(void) +{ + /* + * a b c + * 1 o x o + * 2 o x x + * 3 x o x + */ + const char *lines[5] = {0}; + char k1, k2, k3; + + CALL_EX(IRC_EVENT_COMMAND, "a", "#tictactoe", "b"); + + play("b 2"); + play("c 1"); + play("c 3"); + play("b 3"); + play("c 2"); + play("a 2"); + play("a 3"); + play("a 1"); + play("b 1"); + + GREATEST_ASSERT_EQ(5U, irc_util_split(server->conn.out, lines, 5, '\n')); + GREATEST_ASSERT_EQ(0, sscanf(lines[0], "PRIVMSG #tictactoe : a b c\r")); + GREATEST_ASSERT_EQ(3, sscanf(lines[1], "PRIVMSG #tictactoe :1 %c %c %c\r", &k1, &k2, &k3)); + GREATEST_ASSERT_EQ(3, sscanf(lines[2], "PRIVMSG #tictactoe :2 %c %c %c\r", &k1, &k2, &k3)); + GREATEST_ASSERT_EQ(3, sscanf(lines[3], "PRIVMSG #tictactoe :3 %c %c %c\r", &k1, &k2, &k3)); + GREATEST_ASSERT_EQ(1, sscanf(lines[4], "PRIVMSG #tictactoe :draw=#tictactoe:!tictactoe:%c:tictactoe:test\r\n", &k1)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_used(void) +{ + char k1, k2; + + CALL_EX(IRC_EVENT_COMMAND, "a", "#tictactoe", "b"); + + play("a 1"); + play("a 1"); + + GREATEST_ASSERT_EQ(2, sscanf(server->conn.out, "PRIVMSG #tictactoe :used=#tictactoe:!tictactoe:%c:%c:tictactoe:test\r\n", &k1, &k2)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_invalid(void) +{ + char k1, k2; + + /* Player select itself. */ + CALL_EX(IRC_EVENT_COMMAND, "a", "#tictactoe", "a"); + GREATEST_ASSERT_EQ(2, sscanf(server->conn.out, "PRIVMSG #tictactoe :invalid=#tictactoe:!tictactoe:%c:%c:tictactoe:test\r\n", &k1, &k2)); + + /* Player select the bot. */ + CALL_EX(IRC_EVENT_COMMAND, "a", "#tictactoe", "t"); + GREATEST_ASSERT_EQ(2, sscanf(server->conn.out, "PRIVMSG #tictactoe :invalid=#tictactoe:!tictactoe:%c:%c:tictactoe:test\r\n", &k1, &k2)); + + /* Someone not on the channel. */ + CALL_EX(IRC_EVENT_COMMAND, "a", "#tictactoe", "jean"); + GREATEST_ASSERT_EQ(2, sscanf(server->conn.out, "PRIVMSG #tictactoe :invalid=#tictactoe:!tictactoe:%c:%c:tictactoe:test\r\n", &k1, &k2)); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_random(void) +{ + /* + * Ensure that the first player is not always the originator, start the + * game for at most 100 times to avoid forever loop. + */ + int count = 0, a = 0, b = 0; + + /* Last player turn is the winner. */ + while (count++ < 100) { + CALL_EX(IRC_EVENT_COMMAND, "a", "#tictactoe", "b"); + + play("a 1"); + play("b 1"); + play("a 2"); + play("b 2"); + + /* This is the player that will win. */ + if (next() == 'a') + a = 1; + else + b = 1; + + play("a 3"); + } + + GREATEST_ASSERT(a); + GREATEST_ASSERT(b); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_disconnect(void) +{ + CALL_EX(IRC_EVENT_COMMAND, "a", "#tictactoe", "b"); + + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_DISCONNECT, + .server = server + }); + + play("a 1"); + GREATEST_ASSERT_STR_EQ("", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_kick(void) +{ + CALL_EX(IRC_EVENT_COMMAND, "a", "#tictactoe", "b"); + + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_KICK, + .server = server, + .kick = { + .origin = "god", + .channel = "#TiCTaCToE", + .target = "a", + .reason = "No reason, I do what I want." + } + }); + + play("a 1"); + GREATEST_ASSERT_STR_EQ("", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_TEST +basics_part(void) +{ + CALL_EX(IRC_EVENT_COMMAND, "a", "#tictactoe", "b"); + + irc_plugin_handle(plugin, &(const struct irc_event) { + .type = IRC_EVENT_PART, + .server = server, + .part = { + .origin = "a", + .channel = "#TiCTaCToE", + .reason = "I'm too bad at this game." + } + }); + + play("a 1"); + GREATEST_ASSERT_STR_EQ("", server->conn.out); + + GREATEST_PASS(); +} + +GREATEST_SUITE(suite_basics) +{ + GREATEST_SET_SETUP_CB(setup, NULL); + GREATEST_SET_TEARDOWN_CB(teardown, NULL); + GREATEST_RUN_TEST(basics_win); + GREATEST_RUN_TEST(basics_draw); + GREATEST_RUN_TEST(basics_used); + GREATEST_RUN_TEST(basics_invalid); + GREATEST_RUN_TEST(basics_random); + GREATEST_RUN_TEST(basics_disconnect); + GREATEST_RUN_TEST(basics_kick); + GREATEST_RUN_TEST(basics_part); +} + +GREATEST_MAIN_DEFS(); + +int +main(int argc, char **argv) +{ + GREATEST_MAIN_BEGIN(); + GREATEST_RUN_SUITE(suite_basics); + GREATEST_MAIN_END(); + + return 0; +}