]> jfr.im git - irc/weechat/weechat.git/commitdiff
tests: add tests on relay "irc" protocol
authorSébastien Helleu <redacted>
Fri, 2 Jun 2023 16:44:39 +0000 (18:44 +0200)
committerSébastien Helleu <redacted>
Fri, 2 Jun 2023 16:50:45 +0000 (18:50 +0200)
ChangeLog.adoc
doc/en/weechat_dev.en.adoc
doc/fr/weechat_dev.fr.adoc
doc/ja/weechat_dev.ja.adoc
doc/sr/weechat_dev.sr.adoc
src/plugins/relay/irc/relay-irc.c
src/plugins/relay/relay-client.c
tests/CMakeLists.txt
tests/unit/plugins/irc/test-irc-protocol.cpp
tests/unit/plugins/relay/irc/test-relay-irc.cpp [new file with mode: 0644]

index 1efd7878fc8ec4b31f46b70f7d4078cbea90f4be..793a2fd7dcfed44d78bc9d10f3de846634b39937 100644 (file)
@@ -117,6 +117,7 @@ Tests::
   * irc: check tags in messages displayed by IRC plugin
   * irc: add tests on function irc_server_alloc_with_url
   * irc: add tests on message/notice/action/CTCP sent
+  * relay: add tests on "irc" protocol
 
 Build::
 
index 562a09ad7ad5796956586dceea82f0ee570646e9..ff116c4152e217fca0751a223f8e480e340d4634 100644 (file)
@@ -456,6 +456,8 @@ WeeChat "core" is located in following directories:
 |             test-typing-status.cpp  | Tests: typing status.
 |          relay/                     | Root of unit tests for Relay plugin.
 |             test-relay-auth.cpp     | Tests: clients authentication.
+|             irc/                    | Root of unit tests for Relay "irc" protocol.
+|                test-relay-irc.cpp   | Tests: Relay "irc" protocol.
 |          xfer/                      | Root of unit tests for Xfer plugin.
 |             test-xfer-file.cpp      | Tests: file functions.
 |             test-xfer-network.cpp   | Tests: network functions.
index 33644161e116b86a91c1e0c37dcc1825a9981b7e..07a42bd7cf56f71b83c20aac17f3573edfc54fab 100644 (file)
@@ -458,6 +458,8 @@ Le cœur de WeeChat est situé dans les répertoires suivants :
 |             test-typing-status.cpp  | Tests : statut d'écriture.
 |          relay/                     | Racine des tests unitaires pour l'extension Relay.
 |             test-relay-auth.cpp     | Tests : authentification des clients.
+|             irc/                    | Racine des tests unitaires pour le protocole relay "irc".
+|                test-relay-irc.cpp   | Tests : Protocole relay "irc".
 |          xfer/                      | Racine des tests unitaires pour l'extension Xfer.
 |             test-xfer-file.cpp      | Tests : fonctions sur les fichiers.
 |             test-xfer-network.cpp   | Tests : fonctions réseau.
index 2c55b53de2a521802d8e47bd75dcc0b844df0a6c..7d0fa0bdef1385b3f0a5652309728b9712cfb819 100644 (file)
@@ -518,6 +518,10 @@ WeeChat "core" は以下のディレクトリに配置されています:
 // TRANSLATION MISSING
 |             test-relay-auth.cpp     | Tests: clients authentication.
 // TRANSLATION MISSING
+|             irc/                    | Root of unit tests for Relay "irc" protocol.
+// TRANSLATION MISSING
+|                test-relay-irc.cpp   | Tests: Relay "irc" protocol.
+// TRANSLATION MISSING
 |          xfer/                      | Root of unit tests for Xfer plugin.
 // TRANSLATION MISSING
 |             test-xfer-file.cpp      | Tests: file functions.
index f9ec53f66994e399f218f0be2f30e763fbe78d09..cd7280fe17aa5f5bd7e7a2a0405e77c4695c13ff 100644 (file)
@@ -458,6 +458,10 @@ WeeChat „језгро” се налази у следећим директо
 |             test-typing-status.cpp  | Тестови: typing статус.
 |          relay/                     | Корен unit тестова за Релеј додатак.
 |             test-relay-auth.cpp     | Тестови: аутентификација клијената.
+// TRANSLATION MISSING
+|             irc/                    | Root of unit tests for Relay "irc" protocol.
+// TRANSLATION MISSING
+|                test-relay-irc.cpp   | Tests: Relay "irc" protocol.
 |          xfer/                      | Корен unit тестова за Xfer додатак.
 |             test-xfer-file.cpp      | Тестови: фајл функције.
 |             test-xfer-network.cpp   | Тестови: мрежне функције.
index 6382142db54bdaf495e0c9a4071ac26dd4eab3cc..6685b29ec53141e2692c58efded286706333b135 100644 (file)
@@ -59,13 +59,13 @@ relay_irc_command_relayed (const char *irc_command)
 {
     int i;
 
-    if (irc_command)
+    if (!irc_command)
+        return 0;
+
+    for (i = 0; relay_irc_relay_commands[i]; i++)
     {
-        for (i = 0; relay_irc_relay_commands[i]; i++)
-        {
-            if (weechat_strcasecmp (relay_irc_relay_commands[i], irc_command) == 0)
-                return 1;
-        }
+        if (weechat_strcasecmp (relay_irc_relay_commands[i], irc_command) == 0)
+            return 1;
     }
 
     /* command must NOT be relayed to client */
@@ -85,16 +85,16 @@ relay_irc_command_ignored (const char *irc_command)
 {
     int i;
 
-    if (irc_command)
+    if (!irc_command)
+        return 0;
+
+    for (i = 0; relay_irc_ignore_commands[i]; i++)
     {
-        for (i = 0; relay_irc_ignore_commands[i]; i++)
-        {
-            if (weechat_strcasecmp (relay_irc_ignore_commands[i], irc_command) == 0)
-                return 1;
-        }
+        if (weechat_strcasecmp (relay_irc_ignore_commands[i], irc_command) == 0)
+            return 1;
     }
 
-    /* command must NOT be relayed to client */
+    /* command must be ignored */
     return 0;
 }
 
@@ -158,6 +158,9 @@ relay_irc_message_parse (const char *message)
 {
     struct t_hashtable *hash_msg, *hash_parsed;
 
+    if (!message)
+        return NULL;
+
     hash_msg = NULL;
     hash_parsed = NULL;
 
@@ -384,30 +387,30 @@ relay_irc_tag_relay_client_id (const char *tags)
 
     result = -1;
 
-    if (tags && tags[0])
+    if (!tags || !tags[0])
+        return result;
+
+    argv = weechat_string_split (tags, ",", NULL,
+                                 WEECHAT_STRING_SPLIT_STRIP_LEFT
+                                 | WEECHAT_STRING_SPLIT_STRIP_RIGHT
+                                 | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS,
+                                 0, &argc);
+    if (argv)
     {
-        argv = weechat_string_split (tags, ",", NULL,
-                                     WEECHAT_STRING_SPLIT_STRIP_LEFT
-                                     | WEECHAT_STRING_SPLIT_STRIP_RIGHT
-                                     | WEECHAT_STRING_SPLIT_COLLAPSE_SEPS,
-                                     0, &argc);
-        if (argv)
+        for (i = 0; i < argc; i++)
         {
-            for (i = 0; i < argc; i++)
+            if (strncmp (argv[i], "relay_client_", 13) == 0)
             {
-                if (strncmp (argv[i], "relay_client_", 13) == 0)
+                error = NULL;
+                number = strtol (argv[i] + 13, &error, 10);
+                if (error && !error[0])
                 {
-                    error = NULL;
-                    number = strtol (argv[i] + 13, &error, 10);
-                    if (error && !error[0])
-                    {
-                        result = number;
-                        break;
-                    }
+                    result = number;
+                    break;
                 }
             }
-            weechat_string_free_split (argv);
         }
+        weechat_string_free_split (argv);
     }
 
     return result;
@@ -1189,7 +1192,7 @@ relay_irc_send_join_channels (struct t_relay_client *client)
 
 void
 relay_irc_input_send (struct t_relay_client *client, const char *irc_channel,
-                      char *options, const char *format, ...)
+                      const char *options, const char *format, ...)
 {
     char buf_beginning[1024], *buf;
     int length_beginning, length_vbuffer;
@@ -1459,11 +1462,7 @@ relay_irc_recv (struct t_relay_client *client, const char *data)
     /* server capabilities */
     if (irc_command && (weechat_strcasecmp (irc_command, "cap") == 0))
     {
-        if (num_params > 0)
-        {
-            relay_irc_recv_command_capab (client,
-                                          num_params, (const char **)params);
-        }
+        relay_irc_recv_command_capab (client, num_params, (const char **)params);
     }
     /* if client is not yet "connected" */
     if (!RELAY_IRC_DATA(client, connected))
@@ -1541,6 +1540,7 @@ relay_irc_recv (struct t_relay_client *client, const char *data)
                                          RELAY_IRC_DATA(client, address));
                         relay_client_set_status (client,
                                                  RELAY_STATUS_DISCONNECTED);
+                        weechat_infolist_free (infolist_server);
                         goto end;
                     }
                     if (num_params > 0)
index 9376e5633e87a8fb1240b93a4448654ab35fc267..7863d7b372b8156b1a235c5729804fbc53cd8061 100644 (file)
@@ -591,6 +591,9 @@ relay_client_recv_cb (const void *pointer, void *data, int fd)
 
     client = (struct t_relay_client *)pointer;
 
+    if (client->sock < 0)
+        return WEECHAT_RC_OK;
+
     /*
      * data can be received only during authentication
      * or if connected (authentication was OK)
@@ -800,15 +803,19 @@ relay_client_send_outqueue (struct t_relay_client *client)
     {
         if (client->tls)
         {
-            num_sent = gnutls_record_send (client->gnutls_sess,
-                                           client->outqueue->data,
-                                           client->outqueue->data_size);
+            num_sent = (client->sock >= 0) ?
+                gnutls_record_send (client->gnutls_sess,
+                                    client->outqueue->data,
+                                    client->outqueue->data_size) :
+                client->outqueue->data_size;
         }
         else
         {
-            num_sent = send (client->sock,
-                             client->outqueue->data,
-                             client->outqueue->data_size, 0);
+            num_sent = (client->sock >= 0) ?
+                send (client->sock,
+                      client->outqueue->data,
+                      client->outqueue->data_size, 0) :
+                client->outqueue->data_size;
         }
         if (num_sent >= 0)
         {
@@ -1138,9 +1145,16 @@ relay_client_send (struct t_relay_client *client,
     else
     {
         if (client->tls)
-            num_sent = gnutls_record_send (client->gnutls_sess, ptr_data, data_size);
+        {
+            num_sent = (client->sock >= 0) ?
+                gnutls_record_send (client->gnutls_sess, ptr_data, data_size) :
+                data_size;
+        }
         else
-            num_sent = send (client->sock, ptr_data, data_size, 0);
+        {
+            num_sent = (client->sock >= 0) ?
+                send (client->sock, ptr_data, data_size, 0) : data_size;
+        }
 
         if (num_sent >= 0)
         {
@@ -1307,6 +1321,7 @@ relay_client_new (int sock, const char *address, struct t_relay_server *server)
         new_client->sock = sock;
         new_client->server_port = server->port;
         new_client->tls = server->tls;
+        new_client->gnutls_sess = NULL;
         new_client->hook_timer_handshake = NULL;
         new_client->gnutls_handshake_ok = 0;
         new_client->websocket = RELAY_CLIENT_WEBSOCKET_NOT_USED;
@@ -1464,10 +1479,13 @@ relay_client_new (int sock, const char *address, struct t_relay_server *server)
                 _(relay_client_status_string[new_client->status]));
         }
 
-        new_client->hook_fd = weechat_hook_fd (new_client->sock,
-                                               1, 0, 0,
-                                               &relay_client_recv_cb,
-                                               new_client, NULL);
+        if (new_client->sock >= 0)
+        {
+            new_client->hook_fd = weechat_hook_fd (new_client->sock,
+                                                   1, 0, 0,
+                                                   &relay_client_recv_cb,
+                                                   new_client, NULL);
+        }
 
         relay_client_count++;
 
index 46cea3a359aae9bf80bae58b67740f64b9542931..092ef3a53c8d69f126d02ad54351b3761b8ae3eb 100644 (file)
@@ -97,6 +97,7 @@ endif()
 if (ENABLE_RELAY)
   list(APPEND LIB_WEECHAT_UNIT_TESTS_PLUGINS_SRC
     unit/plugins/relay/test-relay-auth.cpp
+    unit/plugins/relay/irc/test-relay-irc.cpp
   )
 endif()
 
index d15256dc356a0442ccbd3e985877d105abdb0d70..15288b866931b147285fde9bddb3d77e1d69f0a6 100644 (file)
@@ -84,7 +84,7 @@ extern char *irc_protocol_cap_to_enable (const char *capabilities,
 #define CHECK_CORE(__prefix, __message)                                 \
     if (record_search ("core.weechat", __prefix, __message, NULL) < 0)  \
     {                                                                   \
-        char **msg = server_build_error (                               \
+        char **msg = build_error (                                      \
             "Core message not displayed",                               \
             __prefix,                                                   \
             __message,                                                  \
@@ -98,7 +98,7 @@ extern char *irc_protocol_cap_to_enable (const char *capabilities,
     if (record_search ("irc.server." IRC_FAKE_SERVER, __prefix,         \
                        __message, __tags) < 0)                          \
     {                                                                   \
-        char **msg = server_build_error (                               \
+        char **msg = build_error (                                      \
             "Server message not displayed",                             \
             __prefix,                                                   \
             __message,                                                  \
@@ -130,7 +130,7 @@ extern char *irc_protocol_cap_to_enable (const char *capabilities,
     if (record_search ("irc." IRC_FAKE_SERVER ".#test", __prefix,       \
                        __message, __tags) < 0)                          \
     {                                                                   \
-        char **msg = server_build_error (                               \
+        char **msg = build_error (                                      \
             "Channel message not displayed",                            \
             __prefix,                                                   \
             __message,                                                  \
@@ -144,7 +144,7 @@ extern char *irc_protocol_cap_to_enable (const char *capabilities,
     if (record_search ("irc." IRC_FAKE_SERVER "." __nick,               \
                        __prefix, __message, __tags) < 0)                \
     {                                                                   \
-        char **msg = server_build_error (                               \
+        char **msg = build_error (                                      \
             "Private message not displayed",                            \
             __prefix,                                                   \
             __message,                                                  \
@@ -157,7 +157,7 @@ extern char *irc_protocol_cap_to_enable (const char *capabilities,
 #define CHECK_NO_MSG                                                    \
     if (arraylist_size (recorded_messages) > 0)                         \
     {                                                                   \
-        char **msg = server_build_error (                               \
+        char **msg = build_error (                                      \
             "Unexpected message(s) displayed",                          \
             NULL,                                                       \
             NULL,                                                       \
@@ -172,8 +172,8 @@ extern char *irc_protocol_cap_to_enable (const char *capabilities,
         && !arraylist_search (sent_messages, (void *)__message,         \
                               NULL, NULL))                              \
     {                                                                   \
-        char **msg = server_build_error (                               \
-            "Message not sent to server",                               \
+        char **msg = build_error (                                      \
+            "Message not sent to the IRC server",                       \
             NULL,                                                       \
             __message,                                                  \
             NULL,                                                       \
@@ -184,7 +184,7 @@ extern char *irc_protocol_cap_to_enable (const char *capabilities,
     else if ((__message == NULL)                                        \
              && (arraylist_size (sent_messages) > 0))                   \
     {                                                                   \
-        char **msg = server_build_error (                               \
+        char **msg = build_error (                                      \
             "Unexpected response(s) sent to the IRC server",            \
             NULL,                                                       \
             NULL,                                                       \
@@ -220,6 +220,170 @@ TEST_GROUP(IrcProtocol)
 {
 };
 
+TEST_GROUP(IrcProtocolWithServer)
+{
+    void server_recv (const char *command)
+    {
+        char str_command[4096];
+
+        record_start ();
+        arraylist_clear (sent_messages);
+
+        snprintf (str_command, sizeof (str_command),
+                  "/command -buffer irc.server." IRC_FAKE_SERVER " irc "
+                  "/server fakerecv \"%s\"",
+                  command);
+        run_cmd_quiet (str_command);
+
+        record_stop ();
+    }
+
+    void server_input_data (const char *buffer, const char *data)
+    {
+        struct t_gui_buffer *ptr_buffer;
+
+        record_start ();
+        arraylist_clear (sent_messages);
+
+        ptr_buffer = gui_buffer_search_by_full_name (buffer);
+        if (ptr_buffer)
+            input_data (ptr_buffer, data, NULL, 0);
+
+        record_stop ();
+    }
+
+    char **build_error (const char *msg1,
+                        const char *prefix,
+                        const char *message,
+                        const char *tags,
+                        const char *msg2)
+    {
+        char **msg;
+
+        msg = string_dyn_alloc (1024);
+        string_dyn_concat (msg, msg1, -1);
+        if (message)
+        {
+            string_dyn_concat (msg, ": prefix=\"", -1);
+            string_dyn_concat (msg, prefix, -1);
+            string_dyn_concat (msg, "\", message=\"", -1);
+            string_dyn_concat (msg, message, -1);
+            string_dyn_concat (msg, "\", tags=\"", -1);
+            string_dyn_concat (msg, tags, -1);
+            string_dyn_concat (msg, "\"\n", -1);
+        }
+        else
+        {
+            string_dyn_concat (msg, ":\n", -1);
+        }
+        if (msg2)
+        {
+            string_dyn_concat (msg, msg2, -1);
+            string_dyn_concat (msg, ":\n", -1);
+        }
+        return msg;
+    }
+
+    static int signal_irc_out_cb (const void *pointer, void *data,
+                                  const char *signal, const char *type_data,
+                                  void *signal_data)
+    {
+        /* make C++ compiler happy */
+        (void) pointer;
+        (void) data;
+        (void) signal;
+        (void) type_data;
+
+        if (signal_data)
+            arraylist_add (sent_messages, strdup ((const char *)signal_data));
+
+        return WEECHAT_RC_OK;
+    }
+
+    static int sent_msg_cmp_cb (void *data, struct t_arraylist *arraylist,
+                                void *pointer1, void *pointer2)
+    {
+        /* make C++ compiler happy */
+        (void) data;
+        (void) arraylist;
+
+        return strcmp ((char *)pointer1, (char *)pointer2);
+    }
+
+    static void sent_msg_free_cb (void *data, struct t_arraylist *arraylist,
+                                  void *pointer)
+    {
+        /* make C++ compiler happy */
+        (void) data;
+        (void) arraylist;
+
+        free (pointer);
+    }
+
+    void sent_msg_dump (char **msg)
+    {
+        int i;
+
+        for (i = 0; i < arraylist_size (sent_messages); i++)
+        {
+            string_dyn_concat (msg, "  \"", -1);
+            string_dyn_concat (msg,
+                               (const char *)arraylist_get (sent_messages, i),
+                               -1);
+            string_dyn_concat (msg, "\"\n", -1);
+        }
+    }
+
+    void setup ()
+    {
+        /* initialize list of messages sent to the server */
+        if (sent_messages)
+        {
+            arraylist_clear (sent_messages);
+        }
+        else
+        {
+            sent_messages = arraylist_new (16, 0, 1,
+                                           &sent_msg_cmp_cb, NULL,
+                                           &sent_msg_free_cb, NULL);
+        }
+
+        if (!hook_signal_irc_out)
+        {
+            hook_signal_irc_out = hook_signal (NULL,
+                                               IRC_FAKE_SERVER ",irc_out1_*",
+                                               &signal_irc_out_cb, NULL, NULL);
+        }
+
+        /*
+         * disable backlog feature during tests, so we are not polluted by
+         * these messages when buffers are opened
+         */
+        config_file_option_set (logger_config_look_backlog, "0", 1);
+
+        /* create a fake server (no I/O) */
+        run_cmd_quiet ("/mute /server add " IRC_FAKE_SERVER " fake:127.0.0.1 "
+                       "-nicks=nick1,nick2,nick3");
+
+        /* connect to the fake server */
+        run_cmd_quiet ("/connect " IRC_FAKE_SERVER);
+
+        /* get the server pointer */
+        ptr_server = irc_server_search (IRC_FAKE_SERVER);
+    }
+
+    void teardown ()
+    {
+        /* disconnect and delete the fake server */
+        run_cmd_quiet ("/mute /disconnect " IRC_FAKE_SERVER);
+        run_cmd_quiet ("/mute /server del " IRC_FAKE_SERVER);
+        ptr_server = NULL;
+
+        /* restore backlog feature */
+        config_file_option_reset (logger_config_look_backlog, 1);
+    }
+};
+
 /*
  * Tests functions:
  *   irc_protocol_is_numeric_command
@@ -432,170 +596,6 @@ TEST(IrcProtocol, StringParams)
     WEE_TEST_STR("", irc_protocol_string_params (params_3, 3, 3));
 }
 
-TEST_GROUP(IrcProtocolWithServer)
-{
-    void server_recv (const char *command)
-    {
-        char str_command[4096];
-
-        record_start ();
-        arraylist_clear (sent_messages);
-
-        snprintf (str_command, sizeof (str_command),
-                  "/command -buffer irc.server." IRC_FAKE_SERVER " irc "
-                  "/server fakerecv \"%s\"",
-                  command);
-        run_cmd_quiet (str_command);
-
-        record_stop ();
-    }
-
-    void server_input_data (const char *buffer, const char *data)
-    {
-        struct t_gui_buffer *ptr_buffer;
-
-        record_start ();
-        arraylist_clear (sent_messages);
-
-        ptr_buffer = gui_buffer_search_by_full_name (buffer);
-        if (ptr_buffer)
-            input_data (ptr_buffer, data, NULL, 0);
-
-        record_stop ();
-    }
-
-    char **server_build_error (const char *msg1,
-                               const char *prefix,
-                               const char *message,
-                               const char *tags,
-                               const char *msg2)
-    {
-        char **msg;
-
-        msg = string_dyn_alloc (1024);
-        string_dyn_concat (msg, msg1, -1);
-        if (message)
-        {
-            string_dyn_concat (msg, ": prefix=\"", -1);
-            string_dyn_concat (msg, prefix, -1);
-            string_dyn_concat (msg, "\", message=\"", -1);
-            string_dyn_concat (msg, message, -1);
-            string_dyn_concat (msg, "\", tags=\"", -1);
-            string_dyn_concat (msg, tags, -1);
-            string_dyn_concat (msg, "\"\n", -1);
-        }
-        else
-        {
-            string_dyn_concat (msg, ":\n", -1);
-        }
-        if (msg2)
-        {
-            string_dyn_concat (msg, msg2, -1);
-            string_dyn_concat (msg, ":\n", -1);
-        }
-        return msg;
-    }
-
-    static int signal_irc_out_cb (const void *pointer, void *data,
-                                  const char *signal, const char *type_data,
-                                  void *signal_data)
-    {
-        /* make C++ compiler happy */
-        (void) pointer;
-        (void) data;
-        (void) signal;
-        (void) type_data;
-
-        if (signal_data)
-            arraylist_add (sent_messages, strdup ((const char *)signal_data));
-
-        return WEECHAT_RC_OK;
-    }
-
-    static int sent_msg_cmp_cb (void *data, struct t_arraylist *arraylist,
-                                void *pointer1, void *pointer2)
-    {
-        /* make C++ compiler happy */
-        (void) data;
-        (void) arraylist;
-
-        return strcmp ((char *)pointer1, (char *)pointer2);
-    }
-
-    static void sent_msg_free_cb (void *data, struct t_arraylist *arraylist,
-                                  void *pointer)
-    {
-        /* make C++ compiler happy */
-        (void) data;
-        (void) arraylist;
-
-        free (pointer);
-    }
-
-    void sent_msg_dump (char **msg)
-    {
-        int i;
-
-        for (i = 0; i < arraylist_size (sent_messages); i++)
-        {
-            string_dyn_concat (msg, "  \"", -1);
-            string_dyn_concat (msg,
-                               (const char *)arraylist_get (sent_messages, i),
-                               -1);
-            string_dyn_concat (msg, "\"\n", -1);
-        }
-    }
-
-    void setup ()
-    {
-        /* initialize list of messages sent to the server */
-        if (sent_messages)
-        {
-            arraylist_clear (sent_messages);
-        }
-        else
-        {
-            sent_messages = arraylist_new (16, 0, 1,
-                                           &sent_msg_cmp_cb, NULL,
-                                           &sent_msg_free_cb, NULL);
-        }
-
-        if (!hook_signal_irc_out)
-        {
-            hook_signal_irc_out = hook_signal (NULL,
-                                               IRC_FAKE_SERVER ",irc_out1_*",
-                                               &signal_irc_out_cb, NULL, NULL);
-        }
-
-        /*
-         * disable backlog feature during tests, so we are not polluted by
-         * these messages when buffers are opened
-         */
-        config_file_option_set (logger_config_look_backlog, "0", 1);
-
-        /* create a fake server (no I/O) */
-        run_cmd_quiet ("/mute /server add " IRC_FAKE_SERVER " fake:127.0.0.1 "
-                       "-nicks=nick1,nick2,nick3");
-
-        /* connect to the fake server */
-        run_cmd_quiet ("/connect " IRC_FAKE_SERVER);
-
-        /* get the server pointer */
-        ptr_server = irc_server_search (IRC_FAKE_SERVER);
-    }
-
-    void teardown ()
-    {
-        /* disconnect and delete the fake server */
-        run_cmd_quiet ("/mute /disconnect " IRC_FAKE_SERVER);
-        run_cmd_quiet ("/mute /server del " IRC_FAKE_SERVER);
-        ptr_server = NULL;
-
-        /* restore backlog feature */
-        config_file_option_reset (logger_config_look_backlog, 1);
-    }
-};
-
 /*
  * Tests send of messages to channel (STATUSMSG and normal) and nick,
  * without capability "echo-message" enabled:
diff --git a/tests/unit/plugins/relay/irc/test-relay-irc.cpp b/tests/unit/plugins/relay/irc/test-relay-irc.cpp
new file mode 100644 (file)
index 0000000..b3966f7
--- /dev/null
@@ -0,0 +1,811 @@
+/*
+ * test-relay-irc.cpp - test IRC protocol for relay to client
+ *
+ * Copyright (C) 2023 Sébastien Helleu <flashcode@flashtux.org>
+ *
+ * This file is part of WeeChat, the extensible chat client.
+ *
+ * WeeChat is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * WeeChat is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with WeeChat.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "CppUTest/TestHarness.h"
+
+#include "tests/tests.h"
+#include "tests/tests-record.h"
+
+extern "C"
+{
+#include <stdio.h>
+#include <string.h>
+#include "src/core/wee-arraylist.h"
+#include "src/core/wee-config-file.h"
+#include "src/core/wee-hashtable.h"
+#include "src/core/wee-hook.h"
+#include "src/core/wee-string.h"
+#include "src/plugins/plugin.h"
+#include "src/plugins/relay/relay.h"
+#include "src/plugins/relay/relay-client.h"
+#include "src/plugins/relay/relay-config.h"
+#include "src/plugins/relay/relay-server.h"
+#include "src/plugins/relay/irc/relay-irc.h"
+
+extern int relay_irc_command_relayed (const char *irc_command);
+extern int relay_irc_command_ignored (const char *irc_command);
+extern int relay_irc_search_server_capability (const char *capability);
+extern struct t_hashtable *relay_irc_message_parse (const char *message);
+extern void relay_irc_sendf (struct t_relay_client *client,
+                             const char *format, ...);
+extern int relay_irc_tag_relay_client_id (const char *tags);
+extern void relay_irc_input_send (struct t_relay_client *client,
+                                  const char *irc_channel,
+                                  const char *options,
+                                  const char *format, ...);
+}
+
+#define CLIENT_RECV(__irc_msg)                                          \
+    test_client_recv (__irc_msg);
+
+#define CLIENT_SEND(__irc_msg)                                          \
+    test_client_send (__irc_msg);
+
+#define CHECK_SENT_CLIENT(__message)                                    \
+    if ((__message != NULL)                                             \
+        && !arraylist_search (sent_messages_client, (void *)__message,  \
+                              NULL, NULL))                              \
+    {                                                                   \
+        char **msg = test_build_error (                                 \
+            "Message not sent to the relay client",                     \
+            NULL,                                                       \
+            __message,                                                  \
+            NULL,                                                       \
+            "All messages sent");                                       \
+        sent_msg_dump (sent_messages_client, msg);                      \
+        FAIL(string_dyn_free (msg, 0));                                 \
+    }                                                                   \
+    else if ((__message == NULL)                                        \
+             && (arraylist_size (sent_messages_client) > 0))            \
+    {                                                                   \
+        char **msg = test_build_error (                                 \
+            "Unexpected message(s) sent to the relay cleint",           \
+            NULL,                                                       \
+            NULL,                                                       \
+            NULL,                                                       \
+            NULL);                                                      \
+        sent_msg_dump (sent_messages_client, msg);                      \
+        FAIL(string_dyn_free (msg, 0));                                 \
+    }
+
+#define CHECK_SENT_IRC(__message)                                       \
+    if ((__message != NULL)                                             \
+        && !arraylist_search (sent_messages_irc, (void *)__message,     \
+                              NULL, NULL))                              \
+    {                                                                   \
+        char **msg = test_build_error (                                 \
+            "Message not sent to the IRC server",                       \
+            NULL,                                                       \
+            __message,                                                  \
+            NULL,                                                       \
+            "All messages sent");                                       \
+        sent_msg_dump (sent_messages_irc, msg);                         \
+        FAIL(string_dyn_free (msg, 0));                                 \
+    }                                                                   \
+    else if ((__message == NULL)                                        \
+             && (arraylist_size (sent_messages_irc) > 0))               \
+    {                                                                   \
+        char **msg = test_build_error (                                 \
+            "Unexpected message(s) sent to the IRC server",             \
+            NULL,                                                       \
+            NULL,                                                       \
+            NULL,                                                       \
+            NULL);                                                      \
+        sent_msg_dump (sent_messages_irc, msg);                         \
+        FAIL(string_dyn_free (msg, 0));                                 \
+    }
+
+struct t_relay_server *ptr_relay_server = NULL;
+struct t_relay_client *ptr_relay_client = NULL;
+struct t_arraylist *sent_messages_client = NULL;
+struct t_arraylist *sent_messages_irc = NULL;
+struct t_hook *hook_modifier_relay_irc_out = NULL;
+struct t_hook *hook_signal_irc_input_send = NULL;
+
+TEST_GROUP(RelayIrc)
+{
+};
+
+TEST_GROUP(RelayIrcWithClient)
+{
+    void test_client_recv (const char *data)
+    {
+        record_start ();
+
+        arraylist_clear (sent_messages_client);
+        arraylist_clear (sent_messages_irc);
+
+        relay_irc_recv (ptr_relay_client, data);
+
+        record_stop ();
+    }
+
+    void test_client_send (const char *data)
+    {
+        record_start ();
+
+        arraylist_clear (sent_messages_client);
+        arraylist_clear (sent_messages_irc);
+
+        relay_irc_sendf (ptr_relay_client, "%s", data);
+
+        record_stop ();
+    }
+
+    char **test_build_error (const char *msg1,
+                             const char *prefix,
+                             const char *message,
+                             const char *tags,
+                             const char *msg2)
+    {
+        char **msg;
+
+        msg = string_dyn_alloc (1024);
+        string_dyn_concat (msg, msg1, -1);
+        if (message)
+        {
+            string_dyn_concat (msg, ": prefix=\"", -1);
+            string_dyn_concat (msg, prefix, -1);
+            string_dyn_concat (msg, "\", message=\"", -1);
+            string_dyn_concat (msg, message, -1);
+            string_dyn_concat (msg, "\", tags=\"", -1);
+            string_dyn_concat (msg, tags, -1);
+            string_dyn_concat (msg, "\"\n", -1);
+        }
+        else
+        {
+            string_dyn_concat (msg, ":\n", -1);
+        }
+        if (msg2)
+        {
+            string_dyn_concat (msg, msg2, -1);
+            string_dyn_concat (msg, ":\n", -1);
+        }
+        return msg;
+    }
+
+    static char *modifier_relay_irc_out_cb (const void *pointer,
+                                            void *data,
+                                            const char *modifier,
+                                            const char *modifier_data,
+                                            const char *string)
+    {
+        /* make C++ compiler happy */
+        (void) pointer;
+        (void) data;
+        (void) modifier;
+        (void) modifier_data;
+
+        if (string)
+            arraylist_add (sent_messages_client, strdup (string));
+
+        return NULL;
+    }
+
+    static int signal_irc_input_send_cb (const void *pointer, void *data,
+                                         const char *signal,
+                                         const char *type_data,
+                                         void *signal_data)
+    {
+        /* make C++ compiler happy */
+        (void) pointer;
+        (void) data;
+        (void) signal;
+        (void) type_data;
+
+        if (signal_data)
+        {
+            arraylist_add (sent_messages_irc,
+                           strdup ((const char *)signal_data));
+        }
+
+        return WEECHAT_RC_OK;
+    }
+
+    static int sent_msg_cmp_cb (void *data, struct t_arraylist *arraylist,
+                                void *pointer1, void *pointer2)
+    {
+        /* make C++ compiler happy */
+        (void) data;
+        (void) arraylist;
+
+        return strcmp ((char *)pointer1, (char *)pointer2);
+    }
+
+    static void sent_msg_free_cb (void *data, struct t_arraylist *arraylist,
+                                  void *pointer)
+    {
+        /* make C++ compiler happy */
+        (void) data;
+        (void) arraylist;
+
+        free (pointer);
+    }
+
+    void sent_msg_dump (struct t_arraylist *sent_messages, char **msg)
+    {
+        int i;
+
+        for (i = 0; i < arraylist_size (sent_messages); i++)
+        {
+            string_dyn_concat (msg, "  \"", -1);
+            string_dyn_concat (msg,
+                               (const char *)arraylist_get (sent_messages, i),
+                               -1);
+            string_dyn_concat (msg, "\"\n", -1);
+        }
+    }
+
+    void setup ()
+    {
+        /* initialize list of messages sent to the relay client */
+        if (sent_messages_client)
+        {
+            arraylist_clear (sent_messages_client);
+        }
+        else
+        {
+            sent_messages_client = arraylist_new (16, 0, 1,
+                                                  &sent_msg_cmp_cb, NULL,
+                                                  &sent_msg_free_cb, NULL);
+        }
+
+        /* initialize list of messages sent to the IRC server */
+        if (sent_messages_irc)
+        {
+            arraylist_clear (sent_messages_irc);
+        }
+        else
+        {
+            sent_messages_irc = arraylist_new (16, 0, 1,
+                                               &sent_msg_cmp_cb, NULL,
+                                               &sent_msg_free_cb, NULL);
+        }
+
+        /* disable auto-open of relay buffer */
+        config_file_option_set (relay_config_look_auto_open_buffer, "off", 1);
+
+        /* set relay password */
+        config_file_option_set (relay_config_network_password, "secret", 1);
+
+        if (!hook_modifier_relay_irc_out)
+        {
+            hook_modifier_relay_irc_out = hook_modifier (
+                NULL,
+                "relay_client_irc_out1",
+                &modifier_relay_irc_out_cb, NULL, NULL);
+        }
+
+        if (!hook_signal_irc_input_send)
+        {
+            hook_signal_irc_input_send = hook_signal (
+                NULL,
+                "irc_input_send",
+                &signal_irc_input_send_cb, NULL, NULL);
+        }
+
+        /* create a fake server (no I/O) */
+        run_cmd_quiet ("/mute /server add test fake:127.0.0.1 "
+                       "-nicks=nick1,nick2,nick3");
+
+        /* connect to the fake server */
+        run_cmd_quiet ("/connect test");
+
+        /* simulate connection OK to server */
+        run_cmd_quiet ("/command -buffer irc.server.test irc "
+                       "/server fakerecv "
+                       "\":server 001 alice :Welcome on this server, nick1!\"");
+
+        /* create a fake server (no I/O) */
+        ptr_relay_server = relay_server_new (
+            "irc.test",
+            RELAY_PROTOCOL_IRC,
+            "test",
+            9000,
+            NULL,  /* path */
+            1,  /* ipv4 */
+            0,  /* ipv6 */
+            0,  /* tls */
+            0);  /* unix_socket */
+
+        /* create a fake client (no I/O) */
+        ptr_relay_client = relay_client_new (-1, "test", ptr_relay_server);
+    }
+
+    void teardown ()
+    {
+        relay_client_free (ptr_relay_client);
+        relay_server_free (ptr_relay_server);
+
+        ptr_relay_server = NULL;
+        ptr_relay_client = NULL;
+
+        /* disconnect and delete the fake server */
+        run_cmd_quiet ("/mute /disconnect test");
+        run_cmd_quiet ("/mute /server del test");
+
+        /* restore auto-open of relay buffer */
+        config_file_option_reset (relay_config_look_auto_open_buffer, 1);
+
+        /* restore relay password */
+        config_file_option_reset (relay_config_network_password, 1);
+    }
+};
+
+/*
+ * Tests functions:
+ *   relay_irc_command_relayed
+ */
+
+TEST(RelayIrc, RelayIrcCommandRelayed)
+{
+    LONGS_EQUAL(0, relay_irc_command_relayed (NULL));
+    LONGS_EQUAL(0, relay_irc_command_relayed (""));
+    LONGS_EQUAL(0, relay_irc_command_relayed ("unknown"));
+
+    LONGS_EQUAL(1, relay_irc_command_relayed ("privmsg"));
+    LONGS_EQUAL(1, relay_irc_command_relayed ("PRIVMSG"));
+    LONGS_EQUAL(1, relay_irc_command_relayed ("notice"));
+    LONGS_EQUAL(1, relay_irc_command_relayed ("Notice"));
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_command_ignored
+ */
+
+TEST(RelayIrc, RelayIrcCommandIgnored)
+{
+    LONGS_EQUAL(0, relay_irc_command_ignored (NULL));
+    LONGS_EQUAL(0, relay_irc_command_ignored (""));
+    LONGS_EQUAL(0, relay_irc_command_ignored ("unknown"));
+
+    LONGS_EQUAL(1, relay_irc_command_ignored ("cap"));
+    LONGS_EQUAL(1, relay_irc_command_ignored ("CAP"));
+    LONGS_EQUAL(1, relay_irc_command_ignored ("pong"));
+    LONGS_EQUAL(1, relay_irc_command_ignored ("Pong"));
+    LONGS_EQUAL(1, relay_irc_command_ignored ("quit"));
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_search_backlog_commands_tags
+ */
+
+TEST(RelayIrc, RelayIrcSearchBacklogCommandsTags)
+{
+    LONGS_EQUAL(-1, relay_irc_search_backlog_commands_tags (NULL));
+    LONGS_EQUAL(-1, relay_irc_search_backlog_commands_tags (""));
+    LONGS_EQUAL(-1, relay_irc_search_backlog_commands_tags ("unknown"));
+    LONGS_EQUAL(-1, relay_irc_search_backlog_commands_tags ("IRC_JOIN"));
+
+    CHECK(relay_irc_search_backlog_commands_tags ("irc_join") >= 0);
+    CHECK(relay_irc_search_backlog_commands_tags ("irc_privmsg") >= 0);
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_search_server_capability
+ */
+
+TEST(RelayIrc, RelayIrcSearchServerCapability)
+{
+    LONGS_EQUAL(-1, relay_irc_search_server_capability (NULL));
+    LONGS_EQUAL(-1, relay_irc_search_server_capability (""));
+    LONGS_EQUAL(-1, relay_irc_search_server_capability ("unknown"));
+
+    CHECK(relay_irc_search_server_capability ("server-time") >= 0);
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_message_parse
+ */
+
+TEST(RelayIrc, RelayIrcMessageParse)
+{
+    struct t_hashtable *hashtable;
+
+    POINTERS_EQUAL(NULL, relay_irc_message_parse (NULL));
+
+    hashtable = relay_irc_message_parse ("");
+    CHECK(hashtable);
+    LONGS_EQUAL(14, hashtable->items_count);
+    STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "tags"));
+    STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "message_without_tags"));
+    STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "nick"));
+    STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "user"));
+    STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "host"));
+    STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "command"));
+    STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "channel"));
+    STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "arguments"));
+    STRCMP_EQUAL("", (const char *)hashtable_get (hashtable, "text"));
+    STRCMP_EQUAL("0", (const char *)hashtable_get (hashtable, "num_params"));
+    STRCMP_EQUAL("-1", (const char *)hashtable_get (hashtable, "pos_command"));
+    STRCMP_EQUAL("-1", (const char *)hashtable_get (hashtable, "pos_arguments"));
+    STRCMP_EQUAL("-1", (const char *)hashtable_get (hashtable, "pos_channel"));
+    STRCMP_EQUAL("-1", (const char *)hashtable_get (hashtable, "pos_text"));
+    hashtable_free (hashtable);
+
+    hashtable = relay_irc_message_parse (
+        "@time=2015-06-27T16:40:35.000Z :nick!user@host PRIVMSG #weechat :Hello world!");
+    CHECK(hashtable);
+    LONGS_EQUAL(17, hashtable->items_count);
+    STRCMP_EQUAL("time=2015-06-27T16:40:35.000Z", (const char *)hashtable_get (hashtable, "tags"));
+    STRCMP_EQUAL("2015-06-27T16:40:35.000Z", (const char *)hashtable_get (hashtable, "tag_time"));
+    STRCMP_EQUAL(":nick!user@host PRIVMSG #weechat :Hello world!", (const char *)hashtable_get (hashtable, "message_without_tags"));
+    STRCMP_EQUAL("nick", (const char *)hashtable_get (hashtable, "nick"));
+    STRCMP_EQUAL("user", (const char *)hashtable_get (hashtable, "user"));
+    STRCMP_EQUAL("nick!user@host", (const char *)hashtable_get (hashtable, "host"));
+    STRCMP_EQUAL("PRIVMSG", (const char *)hashtable_get (hashtable, "command"));
+    STRCMP_EQUAL("#weechat", (const char *)hashtable_get (hashtable, "channel"));
+    STRCMP_EQUAL("#weechat :Hello world!", (const char *)hashtable_get (hashtable, "arguments"));
+    STRCMP_EQUAL("Hello world!", (const char *)hashtable_get (hashtable, "text"));
+    STRCMP_EQUAL("2", (const char *)hashtable_get (hashtable, "num_params"));
+    STRCMP_EQUAL("#weechat", (const char *)hashtable_get (hashtable, "param1"));
+    STRCMP_EQUAL("Hello world!", (const char *)hashtable_get (hashtable, "param2"));
+    STRCMP_EQUAL("47", (const char *)hashtable_get (hashtable, "pos_command"));
+    STRCMP_EQUAL("55", (const char *)hashtable_get (hashtable, "pos_arguments"));
+    STRCMP_EQUAL("55", (const char *)hashtable_get (hashtable, "pos_channel"));
+    STRCMP_EQUAL("65", (const char *)hashtable_get (hashtable, "pos_text"));
+    hashtable_free (hashtable);
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_sendf
+ */
+
+TEST(RelayIrcWithClient, RelayIrcSendf)
+{
+
+    relay_irc_sendf (NULL, NULL);
+    relay_irc_sendf (NULL, "test");
+    relay_irc_sendf (ptr_relay_client, NULL);
+
+    CLIENT_SEND("PING");
+    CHECK_SENT_CLIENT("PING");
+
+    CLIENT_SEND("PRIVMSG #test :test message");
+    CHECK_SENT_CLIENT("PRIVMSG #test :test message");
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_signal_irc_in2_cb
+ */
+
+TEST(RelayIrc, RelayIrcSignalIrcIn2Cb)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_tag_relay_client_id
+ */
+
+TEST(RelayIrc, RelayIrcTagRelayClientId)
+{
+    LONGS_EQUAL(-1, relay_irc_tag_relay_client_id (NULL));
+    LONGS_EQUAL(-1, relay_irc_tag_relay_client_id (""));
+    LONGS_EQUAL(-1, relay_irc_tag_relay_client_id ("zzz"));
+    LONGS_EQUAL(-1, relay_irc_tag_relay_client_id ("relay_client_abc"));
+
+    LONGS_EQUAL(0, relay_irc_tag_relay_client_id ("relay_client_0"));
+    LONGS_EQUAL(123, relay_irc_tag_relay_client_id ("relay_client_123"));
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_signal_irc_outtags_cb
+ */
+
+TEST(RelayIrc, RelayIrcSignalIrcOuttagsCb)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_signal_irc_disc_cb
+ */
+
+TEST(RelayIrc, RelayIrcSignalIrcDiscCb)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_hsignal_irc_redir_cb
+ */
+
+TEST(RelayIrc, RelayIrcHsignalIrcRedirCb)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_get_line_info
+ */
+
+TEST(RelayIrc, RelayIrcGetLineInfo)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_send_channel_backlog
+ */
+
+TEST(RelayIrc, RelayIrcSendChannelBacklog)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_send_join
+ */
+
+TEST(RelayIrc, RelayIrcSendJoin)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_send_join_channels
+ */
+
+TEST(RelayIrc, RelayIrcSendJoinChannels)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_input_send
+ */
+
+TEST(RelayIrcWithClient, RelayIrcInputSend)
+{
+    arraylist_clear (sent_messages_irc);
+    relay_irc_input_send (ptr_relay_client, "#test", "priority_high",
+                          "this is a test");
+    CHECK_SENT_IRC("test;#test;priority_high;relay_client_1;this is a test");
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_hook_signals
+ */
+
+TEST(RelayIrc, RelayIrcHookSignals)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_recv_command_capab
+ */
+
+TEST(RelayIrcWithClient, RelayIrcRecvCommandCapab)
+{
+    relay_client_set_status (ptr_relay_client, RELAY_STATUS_CONNECTING);
+
+    POINTERS_EQUAL(0, RELAY_IRC_DATA(ptr_relay_client, server_capabilities));
+    POINTERS_EQUAL(0, RELAY_IRC_DATA(ptr_relay_client, cap_ls_received));
+    POINTERS_EQUAL(0, RELAY_IRC_DATA(ptr_relay_client, cap_end_received));
+
+    /* not enough parameters */
+    CLIENT_RECV(":alice!user@host CAP");
+
+    /* list supported capabilities */
+    CLIENT_RECV(":alice!user@host CAP LS");
+    CHECK_SENT_CLIENT(":weechat.relay.irc CAP nick LS :server-time");
+    POINTERS_EQUAL(1, RELAY_IRC_DATA(ptr_relay_client, cap_ls_received));
+    POINTERS_EQUAL(0, RELAY_IRC_DATA(ptr_relay_client, cap_end_received));
+
+    /* request unknown capability: reject */
+    CLIENT_RECV(":alice!user@host CAP REQ unknown");
+    CHECK_SENT_CLIENT(":weechat.relay.irc CAP nick NAK :unknown");
+    POINTERS_EQUAL(0, RELAY_IRC_DATA(ptr_relay_client, server_capabilities));
+    POINTERS_EQUAL(0, RELAY_IRC_DATA(ptr_relay_client, cap_end_received));
+
+    /* request supported capability: accept */
+    CLIENT_RECV(":alice!user@host CAP REQ server-time");
+    CHECK_SENT_CLIENT(":weechat.relay.irc CAP nick ACK :server-time");
+    CHECK(RELAY_IRC_DATA(ptr_relay_client, server_capabilities)
+          & (1 << RELAY_IRC_CAPAB_SERVER_TIME));
+    POINTERS_EQUAL(0, RELAY_IRC_DATA(ptr_relay_client, cap_end_received));
+    RELAY_IRC_DATA(ptr_relay_client, server_capabilities) = 0;
+
+    /* request unknown + supported capabilities: reject */
+    CLIENT_RECV(":alice!user@host CAP REQ :server-time unknown");
+    CHECK_SENT_CLIENT(":weechat.relay.irc CAP nick NAK :server-time unknown");
+    POINTERS_EQUAL(0, RELAY_IRC_DATA(ptr_relay_client, server_capabilities));
+    POINTERS_EQUAL(0, RELAY_IRC_DATA(ptr_relay_client, cap_end_received));
+
+    /* request with empty list: end of capability negociation */
+    CLIENT_RECV(":alice!user@host CAP REQ :");
+    CHECK_SENT_CLIENT(":weechat.relay.irc CAP nick NAK :");
+    POINTERS_EQUAL(1, RELAY_IRC_DATA(ptr_relay_client, cap_end_received));
+
+    RELAY_IRC_DATA(ptr_relay_client, cap_end_received) = 0;
+
+    /* end capability negociation */
+    CLIENT_RECV(":alice!user@host CAP END");
+    POINTERS_EQUAL(1, RELAY_IRC_DATA(ptr_relay_client, cap_end_received));
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_recv
+ */
+
+TEST(RelayIrcWithClient, RelayIrcRecv)
+{
+    relay_client_set_status (ptr_relay_client, RELAY_STATUS_CONNECTING);
+
+    /* NICK */
+    CLIENT_RECV("NICK alice");
+    STRCMP_EQUAL("alice", RELAY_IRC_DATA(ptr_relay_client, nick));
+
+    CLIENT_RECV("NICK bob");
+    STRCMP_EQUAL("bob", RELAY_IRC_DATA(ptr_relay_client, nick));
+
+    /* PASS */
+    LONGS_EQUAL(0, RELAY_IRC_DATA(ptr_relay_client, password_ok));
+
+    CLIENT_RECV("PASS invalid");
+    LONGS_EQUAL(RELAY_STATUS_CONNECTING, ptr_relay_client->status);
+
+    CLIENT_RECV("PASS secret");
+    LONGS_EQUAL(1, RELAY_IRC_DATA(ptr_relay_client, password_ok));
+    LONGS_EQUAL(RELAY_STATUS_CONNECTED, ptr_relay_client->status);
+
+    free (ptr_relay_client->protocol_args);
+    ptr_relay_client->protocol_args = NULL;
+    relay_client_set_status (ptr_relay_client, RELAY_STATUS_CONNECTING);
+    RELAY_IRC_DATA(ptr_relay_client, password_ok) = 0;
+
+    CLIENT_RECV("PASS test2:secret");
+    STRCMP_EQUAL("test2", ptr_relay_client->protocol_args);
+    LONGS_EQUAL(1, RELAY_IRC_DATA(ptr_relay_client, password_ok));
+    LONGS_EQUAL(RELAY_STATUS_CONNECTED, ptr_relay_client->status);
+    free (ptr_relay_client->protocol_args);
+    ptr_relay_client->protocol_args = strdup ("test");
+
+    /* USER */
+    relay_client_set_status (ptr_relay_client, RELAY_STATUS_CONNECTING);
+    CLIENT_RECV("USER alice 0 * :alice");
+    LONGS_EQUAL(1, RELAY_IRC_DATA(ptr_relay_client, user_received));
+    LONGS_EQUAL(1, RELAY_IRC_DATA(ptr_relay_client, connected));
+    STRCMP_EQUAL("alice", RELAY_IRC_DATA(ptr_relay_client, nick));
+    CHECK_SENT_CLIENT(":bob!proxy NICK :alice");
+    CHECK_SENT_CLIENT(":weechat.relay.irc 001 alice :Welcome to the "
+                      "Internet Relay Chat Network alice!weechat@proxy");
+
+    /* JOIN */
+    CLIENT_RECV("JOIN #test");
+    CHECK_SENT_IRC("test;;priority_high;relay_client_1;/join #test");
+
+    /* PART */
+    CLIENT_RECV("PART #test");
+    CHECK_SENT_IRC("test;;priority_high;relay_client_1;/part #test");
+
+    /* PING */
+    CLIENT_RECV("PING :12345");
+    CHECK_SENT_CLIENT(":weechat.relay.irc PONG weechat.relay.irc :12345");
+
+    /* NOTICE */
+    CLIENT_RECV("NOTICE bob :a notice");
+    CHECK_SENT_IRC("test;;priority_high;relay_client_1;/notice bob a notice");
+
+    /* PRIVMSG to channel */
+    CLIENT_RECV("PRIVMSG #test :message to channel");
+    CHECK_SENT_IRC("test;#test;priority_high,user_message;relay_client_1;"
+                   "message to channel");
+
+    /* PRIVMSG to user */
+    CLIENT_RECV("PRIVMSG bob :private message");
+    CHECK_SENT_IRC("test;;priority_high;relay_client_1;"
+                   "/query bob private message");
+
+    /* WHOIS */
+    CLIENT_RECV("WHOIS bob");
+    CHECK_SENT_IRC("test;;priority_high;relay_client_1;/quote WHOIS bob");
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_close_connection
+ */
+
+TEST(RelayIrc, RelayIrcCloseConnection)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_alloc
+ */
+
+TEST(RelayIrc, RelayIrcAlloc)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_alloc_with_infolist
+ */
+
+TEST(RelayIrc, RelayIrcAllocWithInfolist)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_get_initial_status
+ */
+
+TEST(RelayIrc, RelayIrcGetInitialStatus)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_free
+ */
+
+TEST(RelayIrc, RelayIrcFree)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_add_to_infolist
+ */
+
+TEST(RelayIrc, RelayIrcAddToInfolist)
+{
+    /* TODO: write tests */
+}
+
+/*
+ * Tests functions:
+ *   relay_irc_print_log
+ */
+
+TEST(RelayIrc, RelayIrcPrintLog)
+{
+    /* TODO: write tests */
+}