/*
 * SPDX-FileCopyrightText: 2023-2026 Sébastien Helleu <flashcode@flashtux.org>
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 * 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/>.
 */

/* Test HTTP functions */

#include "CppUTest/TestHarness.h"

#include "tests.h"

extern "C"
{
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <gcrypt.h>
#include "src/core/core-config-file.h"
#include "src/core/core-crypto.h"
#include "src/core/core-hashtable.h"
#include "src/core/core-hook.h"
#include "src/core/core-string.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-http.h"
#include "src/plugins/relay/relay-server.h"
#include "src/plugins/relay/relay-websocket.h"
#include "src/plugins/weechat-plugin.h"

extern char *relay_http_url_decode (const char *url);
extern void relay_http_parse_path (const char *url,
                                   char ***paths, int *num_paths,
                                   struct t_hashtable *params);
extern int relay_http_parse_header (struct t_relay_http_request *request,
                                    const char *header,
                                    int ws_deflate_allowed);
extern void relay_http_add_to_body (struct t_relay_http_request *request,
                                    char **partial_message);
extern int relay_http_get_auth_status (struct t_relay_client *client);
extern char *relay_http_compress (struct t_relay_http_request *request,
                                  const char *data, int data_size,
                                  int *compressed_size,
                                  char *http_content_encoding,
                                  int http_content_encoding_size);
extern int relay_http_parse_response_code (struct t_relay_http_response *response,
                                           const char *response_code);
extern int relay_http_parse_response_header (struct t_relay_http_response *response,
                                             const char *header);
}

#define WEE_PARSE_PATH(__path)                                  \
    paths = (char **)0x1;                                       \
    num_paths = -1;                                             \
    hashtable_remove_all (params);                              \
    relay_http_parse_path (__path, &paths, &num_paths, params);

TEST_GROUP(RelayHttp)
{
};

/*
 * Test functions:
 *   relay_http_request_alloc
 *   relay_http_request_reinit
 *   relay_http_request_free
 */

TEST(RelayHttp, RequestAllocReinitFree)
{
    struct t_relay_http_request *request;

    request = relay_http_request_alloc ();
    CHECK(request);

    LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
    CHECK(request->raw);
    STRCMP_EQUAL("", *(request->raw));
    STRCMP_EQUAL(NULL, request->method);
    STRCMP_EQUAL(NULL, request->path);
    POINTERS_EQUAL(NULL, request->path_items);
    LONGS_EQUAL(0, request->num_path_items);
    CHECK(request->params);
    LONGS_EQUAL(0, request->params->items_count);
    STRCMP_EQUAL(NULL, request->http_version);
    CHECK(request->headers);
    LONGS_EQUAL(0, request->headers->items_count);
    CHECK(request->accept_encoding);
    LONGS_EQUAL(0, request->accept_encoding->items_count);
    CHECK(request->ws_deflate);
    LONGS_EQUAL(0, request->ws_deflate->enabled);
    LONGS_EQUAL(0, request->ws_deflate->server_context_takeover);
    LONGS_EQUAL(0, request->ws_deflate->client_context_takeover);
    LONGS_EQUAL(0, request->ws_deflate->window_bits_deflate);
    LONGS_EQUAL(0, request->ws_deflate->window_bits_inflate);
    POINTERS_EQUAL(NULL, request->ws_deflate->strm_deflate);
    POINTERS_EQUAL(NULL, request->ws_deflate->strm_inflate);
    LONGS_EQUAL(0, request->content_length);
    LONGS_EQUAL(0, request->body_size);
    STRCMP_EQUAL(NULL, request->body);

    request->status = RELAY_HTTP_HEADERS;
    string_dyn_concat (request->raw, "test", -1);
    request->method = strdup ("test");
    request->path = strdup ("test");
    request->path_items = string_split ("test,1,2,3", ",", NULL, 0, 0, &request->num_path_items);
    hashtable_set (request->params, "test", "value");
    request->http_version = strdup ("HTTP/1.1");
    hashtable_set (request->headers, "x-test", "value");
    hashtable_set (request->accept_encoding, "gzip", "");
    request->ws_deflate->enabled = 1;
    request->content_length = 100;
    request->body_size = 16;
    request->body = (char *)malloc (16);
    memset (request->body, 0, 16);

    relay_http_request_reinit (request);

    LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
    CHECK(request->raw);
    STRCMP_EQUAL("", *(request->raw));
    STRCMP_EQUAL(NULL, request->method);
    STRCMP_EQUAL(NULL, request->path);
    POINTERS_EQUAL(NULL, request->path_items);
    LONGS_EQUAL(0, request->num_path_items);
    CHECK(request->params);
    LONGS_EQUAL(0, request->params->items_count);
    STRCMP_EQUAL(NULL, request->http_version);
    CHECK(request->headers);
    LONGS_EQUAL(0, request->headers->items_count);
    CHECK(request->accept_encoding);
    LONGS_EQUAL(0, request->accept_encoding->items_count);
    CHECK(request->ws_deflate);
    LONGS_EQUAL(0, request->ws_deflate->enabled);
    LONGS_EQUAL(0, request->ws_deflate->server_context_takeover);
    LONGS_EQUAL(0, request->ws_deflate->client_context_takeover);
    LONGS_EQUAL(0, request->ws_deflate->window_bits_deflate);
    LONGS_EQUAL(0, request->ws_deflate->window_bits_inflate);
    POINTERS_EQUAL(NULL, request->ws_deflate->strm_deflate);
    POINTERS_EQUAL(NULL, request->ws_deflate->strm_inflate);
    LONGS_EQUAL(0, request->content_length);
    LONGS_EQUAL(0, request->body_size);
    STRCMP_EQUAL(NULL, request->body);

    relay_http_request_free (request);
}

/*
 * Test functions:
 *   relay_http_add_to_body (body too large is rejected)
 */

TEST(RelayHttp, AddToBodyLimit)
{
    struct t_relay_http_request *request;
    char *partial;

    request = relay_http_request_alloc ();
    CHECK(request);

    /* announce a body larger than the maximum allowed */
    request->status = RELAY_HTTP_BODY;
    request->content_length = RELAY_HTTP_BODY_MAX_LENGTH + 1;
    partial = strdup ("some body data");

    relay_http_add_to_body (request, &partial);

    /* the body must be rejected: nothing allocated, request ended */
    POINTERS_EQUAL(NULL, request->body);
    LONGS_EQUAL(0, request->body_size);
    POINTERS_EQUAL(NULL, partial);
    LONGS_EQUAL(RELAY_HTTP_END, request->status);

    relay_http_request_free (request);
}

/*
 * Test functions:
 *   relay_http_url_decode
 */

TEST(RelayHttp, UrlDecode)
{
    char *str;

    STRCMP_EQUAL(NULL, relay_http_url_decode (NULL));
    WEE_TEST_STR("", relay_http_url_decode (""));
    WEE_TEST_STR("test", relay_http_url_decode ("test"));
    WEE_TEST_STR("%", relay_http_url_decode ("%"));
    WEE_TEST_STR("%%", relay_http_url_decode ("%%"));
    WEE_TEST_STR("%test", relay_http_url_decode ("%test"));
    WEE_TEST_STR("#test", relay_http_url_decode ("#test"));
    WEE_TEST_STR("#test", relay_http_url_decode ("%23test"));
    WEE_TEST_STR("%#test", relay_http_url_decode ("%%23test"));
    WEE_TEST_STR("%#test hello", relay_http_url_decode ("%%23test%20hello"));
    WEE_TEST_STR("test+z", relay_http_url_decode ("test%2bz"));
    WEE_TEST_STR("test+z", relay_http_url_decode ("test%2Bz"));
    WEE_TEST_STR("test*", relay_http_url_decode ("test%2a"));
}

/*
 * Test functions:
 *   relay_http_get_param_boolean
 */

TEST(RelayHttp, GetParamBoolean)
{
    struct t_relay_http_request *request;
    int value;

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/test?empty=&key1=true&key2=1&key3=off");

    LONGS_EQUAL(0, relay_http_get_param_boolean (request, "key1", 0, NULL));
    LONGS_EQUAL(0, relay_http_get_param_boolean (request, "empty", 0, &value));

    LONGS_EQUAL(1, relay_http_get_param_boolean (request, "key1", 0, &value));
    LONGS_EQUAL(1, value);
    LONGS_EQUAL(1, relay_http_get_param_boolean (request, "key1", 1, &value));
    LONGS_EQUAL(1, value);
    LONGS_EQUAL(1, relay_http_get_param_boolean (request, "key2", 0, &value));
    LONGS_EQUAL(1, value);
    LONGS_EQUAL(1, relay_http_get_param_boolean (request, "key2", 1, &value));
    LONGS_EQUAL(1, value);
    LONGS_EQUAL(1, relay_http_get_param_boolean (request, "key3", 0, &value));
    LONGS_EQUAL(0, value);
    LONGS_EQUAL(1, relay_http_get_param_boolean (request, "key3", 1, &value));
    LONGS_EQUAL(0, value);
    LONGS_EQUAL(1, relay_http_get_param_boolean (request, "xxx", 0, &value));
    LONGS_EQUAL(0, value);
    LONGS_EQUAL(1, relay_http_get_param_boolean (request, "xxx", 1, &value));
    LONGS_EQUAL(1, value);

    relay_http_request_free (request);
}

/*
 * Test functions:
 *   relay_http_get_param_long
 */

TEST(RelayHttp, GetParamLong)
{
    struct t_relay_http_request *request;
    long value;

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/test?empty=&key1=123&key2=-4&key3=abc");

    LONGS_EQUAL(0, relay_http_get_param_long (request, "key1", 0, NULL));
    LONGS_EQUAL(0, relay_http_get_param_long (request, "empty", 0, &value));
    LONGS_EQUAL(0, relay_http_get_param_long (request, "key3", 8, &value));

    LONGS_EQUAL(1, relay_http_get_param_long (request, "key1", 8, &value));
    LONGS_EQUAL(123, value);
    LONGS_EQUAL(1, relay_http_get_param_long (request, "key2", 8, &value));
    LONGS_EQUAL(-4, value);
    LONGS_EQUAL(1, relay_http_get_param_long (request, "xxx", 99, &value));
    LONGS_EQUAL(99, value);

    relay_http_request_free (request);
}

/*
 * Test functions:
 *   relay_http_parse_path
 */

TEST(RelayHttp, ParsePath)
{
    char **paths;
    int num_paths;
    struct t_hashtable *params;

    params = hashtable_new (
        32,
        WEECHAT_HASHTABLE_STRING,
        WEECHAT_HASHTABLE_STRING,
        NULL, NULL);

    WEE_PARSE_PATH(NULL);
    POINTERS_EQUAL(NULL, paths);
    LONGS_EQUAL(0, num_paths);
    LONGS_EQUAL(0, params->items_count);

    WEE_PARSE_PATH("");
    POINTERS_EQUAL(NULL, paths);
    LONGS_EQUAL(0, num_paths);
    LONGS_EQUAL(0, params->items_count);

    WEE_PARSE_PATH("api");
    CHECK(paths);
    LONGS_EQUAL(1, num_paths);
    STRCMP_EQUAL("api", paths[0]);
    STRCMP_EQUAL(NULL, paths[1]);
    LONGS_EQUAL(0, params->items_count);

    WEE_PARSE_PATH("/api");
    CHECK(paths);
    LONGS_EQUAL(1, num_paths);
    STRCMP_EQUAL("api", paths[0]);
    STRCMP_EQUAL(NULL, paths[1]);
    LONGS_EQUAL(0, params->items_count);

    WEE_PARSE_PATH("/api/");
    CHECK(paths);
    LONGS_EQUAL(1, num_paths);
    STRCMP_EQUAL("api", paths[0]);
    STRCMP_EQUAL(NULL, paths[1]);
    LONGS_EQUAL(0, params->items_count);

    WEE_PARSE_PATH("/api/buffers");
    CHECK(paths);
    LONGS_EQUAL(2, num_paths);
    STRCMP_EQUAL("api", paths[0]);
    STRCMP_EQUAL("buffers", paths[1]);
    STRCMP_EQUAL(NULL, paths[2]);
    LONGS_EQUAL(0, params->items_count);

    WEE_PARSE_PATH("/api/buffers?");
    CHECK(paths);
    LONGS_EQUAL(2, num_paths);
    STRCMP_EQUAL("api", paths[0]);
    STRCMP_EQUAL("buffers", paths[1]);
    STRCMP_EQUAL(NULL, paths[2]);
    LONGS_EQUAL(0, params->items_count);

    WEE_PARSE_PATH("/api/buffers?param");
    CHECK(paths);
    LONGS_EQUAL(2, num_paths);
    STRCMP_EQUAL("api", paths[0]);
    STRCMP_EQUAL("buffers", paths[1]);
    STRCMP_EQUAL(NULL, paths[2]);
    LONGS_EQUAL(1, params->items_count);
    STRCMP_EQUAL("", (const char *)hashtable_get (params, "param"));

    WEE_PARSE_PATH("/api/buffers?param=");
    CHECK(paths);
    LONGS_EQUAL(2, num_paths);
    STRCMP_EQUAL("api", paths[0]);
    STRCMP_EQUAL("buffers", paths[1]);
    STRCMP_EQUAL(NULL, paths[2]);
    LONGS_EQUAL(1, params->items_count);
    STRCMP_EQUAL("", (const char *)hashtable_get (params, "param"));

    WEE_PARSE_PATH("/api/buffers?param=off");
    CHECK(paths);
    LONGS_EQUAL(2, num_paths);
    STRCMP_EQUAL("api", paths[0]);
    STRCMP_EQUAL("buffers", paths[1]);
    STRCMP_EQUAL(NULL, paths[2]);
    LONGS_EQUAL(1, params->items_count);
    STRCMP_EQUAL("off", (const char *)hashtable_get (params, "param"));

    WEE_PARSE_PATH("/api/buffers?param=off&test=value2");
    CHECK(paths);
    LONGS_EQUAL(2, num_paths);
    STRCMP_EQUAL("api", paths[0]);
    STRCMP_EQUAL("buffers", paths[1]);
    STRCMP_EQUAL(NULL, paths[2]);
    LONGS_EQUAL(2, params->items_count);
    STRCMP_EQUAL("off", (const char *)hashtable_get (params, "param"));
    STRCMP_EQUAL("value2", (const char *)hashtable_get (params, "test"));

    WEE_PARSE_PATH("/api/buffers/irc.libera.%23weechat?param=off&test=value%202");
    CHECK(paths);
    LONGS_EQUAL(3, num_paths);
    STRCMP_EQUAL("api", paths[0]);
    STRCMP_EQUAL("buffers", paths[1]);
    STRCMP_EQUAL("irc.libera.#weechat", paths[2]);
    STRCMP_EQUAL(NULL, paths[3]);
    LONGS_EQUAL(2, params->items_count);
    STRCMP_EQUAL("off", (const char *)hashtable_get (params, "param"));
    STRCMP_EQUAL("value 2", (const char *)hashtable_get (params, "test"));

    hashtable_free (params);
}

/*
 * Test functions:
 *   relay_http_parse_method_path
 */

TEST(RelayHttp, ParseMethodPath)
{
    struct t_relay_http_request *request;

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (NULL, NULL);
    relay_http_parse_method_path (request, NULL);
    LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
    STRCMP_EQUAL("", *(request->raw));
    STRCMP_EQUAL(NULL, request->path);
    POINTERS_EQUAL(NULL, request->path_items);
    LONGS_EQUAL(0, request->num_path_items);
    LONGS_EQUAL(0, request->params->items_count);
    STRCMP_EQUAL(NULL, request->http_version);
    LONGS_EQUAL(0, request->headers->items_count);
    LONGS_EQUAL(0, request->accept_encoding->items_count);
    LONGS_EQUAL(0, request->content_length);
    LONGS_EQUAL(0, request->body_size);
    STRCMP_EQUAL(NULL, request->body);
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "");
    LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
    STRCMP_EQUAL("", *(request->raw));
    STRCMP_EQUAL(NULL, request->path);
    POINTERS_EQUAL(NULL, request->path_items);
    LONGS_EQUAL(0, request->num_path_items);
    LONGS_EQUAL(0, request->params->items_count);
    STRCMP_EQUAL(NULL, request->http_version);
    LONGS_EQUAL(0, request->headers->items_count);
    LONGS_EQUAL(0, request->accept_encoding->items_count);
    LONGS_EQUAL(0, request->content_length);
    LONGS_EQUAL(0, request->body_size);
    STRCMP_EQUAL(NULL, request->body);
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET");
    LONGS_EQUAL(RELAY_HTTP_END, request->status);
    STRCMP_EQUAL("GET\n", *(request->raw));
    STRCMP_EQUAL(NULL, request->path);
    POINTERS_EQUAL(NULL, request->path_items);
    LONGS_EQUAL(0, request->num_path_items);
    LONGS_EQUAL(0, request->params->items_count);
    STRCMP_EQUAL(NULL, request->http_version);
    LONGS_EQUAL(0, request->headers->items_count);
    LONGS_EQUAL(0, request->accept_encoding->items_count);
    LONGS_EQUAL(0, request->content_length);
    LONGS_EQUAL(0, request->body_size);
    STRCMP_EQUAL(NULL, request->body);
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    STRCMP_EQUAL("GET /api\n", *(request->raw));
    STRCMP_EQUAL("/api", request->path);
    CHECK(request->path_items);
    STRCMP_EQUAL("api", request->path_items[0]);
    STRCMP_EQUAL(NULL, request->path_items[1]);
    LONGS_EQUAL(1, request->num_path_items);
    LONGS_EQUAL(0, request->params->items_count);
    STRCMP_EQUAL(NULL, request->http_version);
    LONGS_EQUAL(0, request->headers->items_count);
    LONGS_EQUAL(0, request->accept_encoding->items_count);
    LONGS_EQUAL(0, request->content_length);
    LONGS_EQUAL(0, request->body_size);
    STRCMP_EQUAL(NULL, request->body);
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/buffers HTTP/1.1");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    STRCMP_EQUAL("GET /api/buffers HTTP/1.1\n", *(request->raw));
    STRCMP_EQUAL("/api/buffers", request->path);
    CHECK(request->path_items);
    STRCMP_EQUAL("api", request->path_items[0]);
    STRCMP_EQUAL("buffers", request->path_items[1]);
    STRCMP_EQUAL(NULL, request->path_items[2]);
    LONGS_EQUAL(2, request->num_path_items);
    LONGS_EQUAL(0, request->params->items_count);
    STRCMP_EQUAL("HTTP/1.1", request->http_version);
    LONGS_EQUAL(0, request->headers->items_count);
    LONGS_EQUAL(0, request->accept_encoding->items_count);
    LONGS_EQUAL(0, request->content_length);
    LONGS_EQUAL(0, request->body_size);
    STRCMP_EQUAL(NULL, request->body);
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/buffers?test=1&var=abc HTTP/1.1");
    /* do it 2 times, to be sure it has no side effect */
    relay_http_parse_method_path (request, "GET /api/buffers?test=1&var=abc HTTP/1.1");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    STRCMP_EQUAL("GET /api/buffers?test=1&var=abc HTTP/1.1\n"
                 "GET /api/buffers?test=1&var=abc HTTP/1.1\n",
                 *(request->raw));
    STRCMP_EQUAL("/api/buffers?test=1&var=abc", request->path);
    CHECK(request->path_items);
    STRCMP_EQUAL("api", request->path_items[0]);
    STRCMP_EQUAL("buffers", request->path_items[1]);
    STRCMP_EQUAL(NULL, request->path_items[2]);
    LONGS_EQUAL(2, request->num_path_items);
    LONGS_EQUAL(2, request->params->items_count);
    STRCMP_EQUAL("1", (const char *)hashtable_get (request->params, "test"));
    STRCMP_EQUAL("abc", (const char *)hashtable_get (request->params, "var"));
    STRCMP_EQUAL("HTTP/1.1", request->http_version);
    LONGS_EQUAL(0, request->headers->items_count);
    LONGS_EQUAL(0, request->accept_encoding->items_count);
    LONGS_EQUAL(0, request->content_length);
    LONGS_EQUAL(0, request->body_size);
    STRCMP_EQUAL(NULL, request->body);
    relay_http_request_free (request);
}

/*
 * Test functions:
 *   relay_http_parse_header
 */

TEST(RelayHttp, ParseHeader)
{
    struct t_relay_http_request *request;

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/version");
    relay_http_parse_header (request, NULL, 1);
    LONGS_EQUAL(RELAY_HTTP_END, request->status);
    STRCMP_EQUAL("GET /api/version\n"
                 "\n",
                 *(request->raw));
    LONGS_EQUAL(0, request->headers->items_count);
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/version");
    relay_http_parse_header (request, "", 1);
    LONGS_EQUAL(RELAY_HTTP_END, request->status);
    STRCMP_EQUAL("GET /api/version\n"
                 "\n",
                 *(request->raw));
    LONGS_EQUAL(0, request->headers->items_count);
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/version");
    relay_http_parse_header (request, "Test", 1);
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    STRCMP_EQUAL("GET /api/version\n"
                 "Test\n",
                 *(request->raw));
    LONGS_EQUAL(0, request->headers->items_count);
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/version");
    relay_http_parse_header (request, "X-Test: value", 1);
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    STRCMP_EQUAL("GET /api/version\n"
                 "X-Test: value\n",
                 *(request->raw));
    LONGS_EQUAL(1, request->headers->items_count);
    STRCMP_EQUAL("value", (const char *)hashtable_get (request->headers, "x-test"));
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/version");
    relay_http_parse_header (request, "Accept-Encoding: gzip, zstd, br", 1);
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    STRCMP_EQUAL("GET /api/version\n"
                 "Accept-Encoding: gzip, zstd, br\n",
                 *(request->raw));
    LONGS_EQUAL(1, request->headers->items_count);
    STRCMP_EQUAL("gzip, zstd, br",
                 (const char *)hashtable_get (request->headers, "accept-encoding"));
    LONGS_EQUAL(3, request->accept_encoding->items_count);
    CHECK(hashtable_has_key (request->accept_encoding, "gzip"));
    CHECK(hashtable_has_key (request->accept_encoding, "zstd"));
    CHECK(hashtable_has_key (request->accept_encoding, "br"));
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/version");
    relay_http_parse_header (request, "Accept-Encoding: gzip", 1);
    relay_http_parse_header (request, "Accept-Encoding: zstd", 1);
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    STRCMP_EQUAL("GET /api/version\n"
                 "Accept-Encoding: gzip\n"
                 "Accept-Encoding: zstd\n",
                 *(request->raw));
    LONGS_EQUAL(1, request->headers->items_count);
    STRCMP_EQUAL("gzip, zstd",
                 (const char *)hashtable_get (request->headers, "accept-encoding"));
    LONGS_EQUAL(2, request->accept_encoding->items_count);
    CHECK(hashtable_has_key (request->accept_encoding, "gzip"));
    CHECK(hashtable_has_key (request->accept_encoding, "zstd"));
    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api/version");
    relay_http_parse_header (request, "Content-Length: 123", 1);
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    STRCMP_EQUAL("GET /api/version\n"
                 "Content-Length: 123\n",
                 *(request->raw));
    LONGS_EQUAL(1, request->headers->items_count);
    LONGS_EQUAL(123, request->content_length);
    relay_http_request_free (request);

    /* websocket request */
    request = relay_http_request_alloc ();
    CHECK(request);
    relay_http_parse_method_path (request, "GET /api HTTP/1.1");
    relay_http_parse_header (
        request,
        "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits",
        1);
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    STRCMP_EQUAL(
        "GET /api HTTP/1.1\n"
        "Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\n",
        *(request->raw));
    LONGS_EQUAL(1, request->headers->items_count);
    CHECK(request->ws_deflate);
    LONGS_EQUAL(1, request->ws_deflate->enabled);
    LONGS_EQUAL(1, request->ws_deflate->server_context_takeover);
    LONGS_EQUAL(1, request->ws_deflate->client_context_takeover);
    LONGS_EQUAL(15, request->ws_deflate->window_bits_deflate);
    LONGS_EQUAL(15, request->ws_deflate->window_bits_inflate);
    POINTERS_EQUAL(NULL, request->ws_deflate->strm_deflate);
    POINTERS_EQUAL(NULL, request->ws_deflate->strm_inflate);
    relay_http_request_free (request);
}

/*
 * Test functions:
 *   relay_http_add_to_body
 */

TEST(RelayHttp, AddToBody)
{
    struct t_relay_http_request *request;
    const char *body_part1 = "abc", *body_part2 = "defghij";
    char *partial_message;

    request = relay_http_request_alloc ();
    CHECK(request);
    LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
    relay_http_parse_method_path (request, "GET /api/version");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    relay_http_parse_header (request, "Content-Length: 10", 1);
    relay_http_parse_header (request, "", 1);
    LONGS_EQUAL(RELAY_HTTP_BODY, request->status);
    LONGS_EQUAL(10, request->content_length);
    LONGS_EQUAL(0, request->body_size);

    partial_message = strdup (body_part1);
    relay_http_add_to_body (request, &partial_message);
    LONGS_EQUAL(RELAY_HTTP_BODY, request->status);
    LONGS_EQUAL(3, request->body_size);
    MEMCMP_EQUAL("abc", request->body, 3);
    STRCMP_EQUAL(NULL, partial_message);

    partial_message = strdup (body_part2);
    relay_http_add_to_body (request, &partial_message);
    LONGS_EQUAL(RELAY_HTTP_END, request->status);
    LONGS_EQUAL(10, request->body_size);
    MEMCMP_EQUAL("abcdefghij", request->body, 10);
    STRCMP_EQUAL(NULL, partial_message);

    relay_http_request_free (request);

    request = relay_http_request_alloc ();
    CHECK(request);
    LONGS_EQUAL(RELAY_HTTP_METHOD, request->status);
    relay_http_parse_method_path (request, "GET /api/version");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, request->status);
    relay_http_parse_header (request, "Content-Length: 5", 1);
    relay_http_parse_header (request, "", 1);
    LONGS_EQUAL(RELAY_HTTP_BODY, request->status);
    LONGS_EQUAL(5, request->content_length);
    LONGS_EQUAL(0, request->body_size);

    partial_message = strdup (body_part1);
    relay_http_add_to_body (request, &partial_message);
    LONGS_EQUAL(RELAY_HTTP_BODY, request->status);
    LONGS_EQUAL(3, request->body_size);
    MEMCMP_EQUAL("abc", request->body, 3);
    STRCMP_EQUAL(NULL, partial_message);

    partial_message = strdup (body_part2);
    relay_http_add_to_body (request, &partial_message);
    LONGS_EQUAL(RELAY_HTTP_END, request->status);
    LONGS_EQUAL(5, request->body_size);
    MEMCMP_EQUAL("abcde", request->body, 5);
    STRCMP_EQUAL("fghij", partial_message);
    free (partial_message);

    relay_http_request_free (request);
}

/*
 * Test functions:
 *   relay_http_get_auth_status
 */

TEST(RelayHttp, GetAuthStatus)
{
    struct t_relay_client *client;
    const char *good_pwd = "secret_password";
    const char *bad_pwd = "test";
    char *totp, *totp2, salt[1024], salt_pass[1024], hash[1024], hash_hexa[2048];
    char auth[4096], auth_base64[4096], auth_header[8192];
    time_t time_now;
    int hash_size;

    config_file_option_set (relay_config_network_password, good_pwd, 1);

    client = (struct t_relay_client *)calloc (1, sizeof (*client));
    CHECK(client);

    client->protocol = RELAY_PROTOCOL_API;

    client->http_req = relay_http_request_alloc ();

    LONGS_EQUAL(-1, relay_http_get_auth_status (client));
    hashtable_set (client->http_req->headers, "authorization", "Basic    ");
    LONGS_EQUAL(-2, relay_http_get_auth_status (client));
    hashtable_set (client->http_req->headers, "authorization", "Basic \u26c4");
    LONGS_EQUAL(-2, relay_http_get_auth_status (client));

    /* test invalid plain-text password ("test") */
    hashtable_set (client->http_req->headers, "authorization", "Basic cGxhaW46dGVzdA==");
    LONGS_EQUAL(-2, relay_http_get_auth_status (client));

    /* test valid plain-text password ("secret_password") */
    hashtable_set (client->http_req->headers,
                   "authorization",
                   "Basic  cGxhaW46c2VjcmV0X3Bhc3N3b3Jk");
    LONGS_EQUAL(0, relay_http_get_auth_status (client));

    /* test invalid hash: "SHA128" */
    time_now = time (NULL);
    snprintf (salt_pass, sizeof (salt_pass),
              "%ld%s", time_now, bad_pwd);
    LONGS_EQUAL(1, weecrypto_hash (salt_pass, strlen (salt_pass),
                                   GCRY_MD_SHA256, hash, &hash_size));
    LONGS_EQUAL(64, string_base_encode ("16", hash, hash_size, hash_hexa));
    snprintf (auth, sizeof (auth),
              "hash:sha128:%ld:%s",
              time_now,
              hash_hexa);
    string_base_encode ("64", auth, strlen (auth), auth_base64);
    snprintf (auth_header, sizeof (auth_header), "Basic %s", auth_base64);
    hashtable_set (client->http_req->headers, "authorization", auth_header);
    LONGS_EQUAL(-5, relay_http_get_auth_status (client));

    /* test invalid password hashed with SHA256: "test" */
    time_now = time (NULL);
    snprintf (salt_pass, sizeof (salt_pass),
              "%ld%s", time_now, bad_pwd);
    LONGS_EQUAL(1, weecrypto_hash (salt_pass, strlen (salt_pass),
                                   GCRY_MD_SHA256, hash, &hash_size));
    LONGS_EQUAL(64, string_base_encode ("16", hash, hash_size, hash_hexa));
    snprintf (auth, sizeof (auth),
              "hash:sha256:%ld:%s",
              time_now,
              hash_hexa);
    string_base_encode ("64", auth, strlen (auth), auth_base64);
    snprintf (auth_header, sizeof (auth_header), "Basic %s", auth_base64);
    hashtable_set (client->http_req->headers, "authorization", auth_header);
    LONGS_EQUAL(-2, relay_http_get_auth_status (client));

    /* test invalid password hashed with SHA512: "test" */
    time_now = time (NULL);
    snprintf (salt_pass, sizeof (salt_pass),
              "%ld%s", time_now, bad_pwd);
    LONGS_EQUAL(1, weecrypto_hash (salt_pass, strlen (salt_pass),
                                   GCRY_MD_SHA512, hash, &hash_size));
    LONGS_EQUAL(128, string_base_encode ("16", hash, hash_size, hash_hexa));
    snprintf (auth, sizeof (auth),
              "hash:sha512:%ld:%s",
              time_now,
              hash_hexa);
    string_base_encode ("64", auth, strlen (auth), auth_base64);
    snprintf (auth_header, sizeof (auth_header), "Basic %s", auth_base64);
    hashtable_set (client->http_req->headers, "authorization", auth_header);
    LONGS_EQUAL(-2, relay_http_get_auth_status (client));

    /* test valid password hashed with SHA256: "secret_password" but too old time (salt) */
    time_now = time (NULL) - 10;
    snprintf (salt_pass, sizeof (salt_pass),
              "%ld%s", time_now, good_pwd);
    LONGS_EQUAL(1, weecrypto_hash (salt_pass, strlen (salt_pass),
                                   GCRY_MD_SHA256, hash, &hash_size));
    LONGS_EQUAL(64, string_base_encode ("16", hash, hash_size, hash_hexa));
    snprintf (auth, sizeof (auth),
              "hash:sha256:%ld:%s",
              time_now,
              hash_hexa);
    string_base_encode ("64", auth, strlen (auth), auth_base64);
    snprintf (auth_header, sizeof (auth_header), "Basic %s", auth_base64);
    hashtable_set (client->http_req->headers, "authorization", auth_header);
    LONGS_EQUAL(-6, relay_http_get_auth_status (client));

    /* test valid password hashed with SHA256: "secret_password" */
    time_now = time (NULL);
    snprintf (salt_pass, sizeof (salt_pass),
              "%ld%s", time_now, good_pwd);
    LONGS_EQUAL(1, weecrypto_hash (salt_pass, strlen (salt_pass),
                                   GCRY_MD_SHA256, hash, &hash_size));
    LONGS_EQUAL(64, string_base_encode ("16", hash, hash_size, hash_hexa));
    snprintf (auth, sizeof (auth),
              "hash:sha256:%ld:%s",
              time_now,
              hash_hexa);
    string_base_encode ("64", auth, strlen (auth), auth_base64);
    snprintf (auth_header, sizeof (auth_header), "Basic %s", auth_base64);
    hashtable_set (client->http_req->headers, "authorization", auth_header);
    LONGS_EQUAL(0, relay_http_get_auth_status (client));

    /* test valid password hashed with SHA512: "secret_password" */
    time_now = time (NULL);
    snprintf (salt_pass, sizeof (salt_pass),
              "%ld%s", time_now, good_pwd);
    LONGS_EQUAL(1, weecrypto_hash (salt_pass, strlen (salt_pass),
                                   GCRY_MD_SHA512, hash, &hash_size));
    LONGS_EQUAL(128, string_base_encode ("16", hash, hash_size, hash_hexa));
    snprintf (auth, sizeof (auth),
              "hash:sha512:%ld:%s",
              time_now,
              hash_hexa);
    string_base_encode ("64", auth, strlen (auth), auth_base64);
    snprintf (auth_header, sizeof (auth_header), "Basic %s", auth_base64);
    hashtable_set (client->http_req->headers, "authorization", auth_header);
    LONGS_EQUAL(0, relay_http_get_auth_status (client));

    /* test invalid number of iterations */
    time_now = time (NULL);
    snprintf (salt, sizeof (salt), "%ld", time_now);
    LONGS_EQUAL(1, weecrypto_hash_pbkdf2 (good_pwd, strlen (good_pwd),
                                          GCRY_MD_SHA256,
                                          salt, strlen (salt),
                                          123,
                                          hash, &hash_size));
    LONGS_EQUAL(64, string_base_encode ("16", hash, hash_size, hash_hexa));
    snprintf (auth, sizeof (auth),
              "hash:pbkdf2+sha256:%ld:123:%s",
              time_now,
              hash_hexa);
    string_base_encode ("64", auth, strlen (auth), auth_base64);
    snprintf (auth_header, sizeof (auth_header), "Basic %s", auth_base64);
    hashtable_set (client->http_req->headers, "authorization", auth_header);
    LONGS_EQUAL(-7, relay_http_get_auth_status (client));

    /* test invalid password hashed with PBKDF2+SHA256: "test" */
    time_now = time (NULL);
    snprintf (salt, sizeof (salt), "%ld", time_now);
    LONGS_EQUAL(1, weecrypto_hash_pbkdf2 (bad_pwd, strlen (bad_pwd),
                                          GCRY_MD_SHA256,
                                          salt, strlen (salt),
                                          100000,
                                          hash, &hash_size));
    LONGS_EQUAL(64, string_base_encode ("16", hash, hash_size, hash_hexa));
    snprintf (auth, sizeof (auth),
              "hash:pbkdf2+sha256:%ld:100000:%s",
              time_now,
              hash_hexa);
    string_base_encode ("64", auth, strlen (auth), auth_base64);
    snprintf (auth_header, sizeof (auth_header), "Basic %s", auth_base64);
    hashtable_set (client->http_req->headers, "authorization", auth_header);
    LONGS_EQUAL(-2, relay_http_get_auth_status (client));

    /* test valid password hashed with PBKDF2+SHA256: "secret_password" */
    time_now = time (NULL);
    snprintf (salt, sizeof (salt), "%ld", time_now);
    LONGS_EQUAL(1, weecrypto_hash_pbkdf2 (good_pwd, strlen (good_pwd),
                                          GCRY_MD_SHA256,
                                          salt, strlen (salt),
                                          100000,
                                          hash, &hash_size));
    LONGS_EQUAL(64, string_base_encode ("16", hash, hash_size, hash_hexa));
    snprintf (auth, sizeof (auth),
              "hash:pbkdf2+sha256:%ld:100000:%s",
              time_now,
              hash_hexa);
    string_base_encode ("64", auth, strlen (auth), auth_base64);
    snprintf (auth_header, sizeof (auth_header), "Basic %s", auth_base64);
    hashtable_set (client->http_req->headers, "authorization", auth_header);
    LONGS_EQUAL(0, relay_http_get_auth_status (client));

    /* test valid password hashed with PBKDF2+SHA512: "secret_password" */
    time_now = time (NULL);
    snprintf (salt, sizeof (salt), "%ld", time_now);
    LONGS_EQUAL(1, weecrypto_hash_pbkdf2 (good_pwd, strlen (good_pwd),
                                          GCRY_MD_SHA512,
                                          salt, strlen (salt),
                                          100000,
                                          hash, &hash_size));
    LONGS_EQUAL(128, string_base_encode ("16", hash, hash_size, hash_hexa));
    snprintf (auth, sizeof (auth),
              "hash:pbkdf2+sha512:%ld:100000:%s",
              time_now,
              hash_hexa);
    string_base_encode ("64", auth, strlen (auth), auth_base64);
    snprintf (auth_header, sizeof (auth_header), "Basic %s", auth_base64);
    hashtable_set (client->http_req->headers, "authorization", auth_header);
    LONGS_EQUAL(0, relay_http_get_auth_status (client));

    /* test invalid TOTP */
    hashtable_set (client->http_req->headers, "x-weechat-totp", "");
    LONGS_EQUAL(-4, relay_http_get_auth_status (client));
    hashtable_set (client->http_req->headers, "x-weechat-totp", "abcdef");
    LONGS_EQUAL(-4, relay_http_get_auth_status (client));
    hashtable_set (client->http_req->headers, "x-weechat-totp", "1234567");
    LONGS_EQUAL(-4, relay_http_get_auth_status (client));
    hashtable_remove (client->http_req->headers, "x-weechat-totp");

    /* test valid TOTP without TOTP configuration */
    hashtable_set (client->http_req->headers, "x-weechat-totp", "123456");
    LONGS_EQUAL(-4, relay_http_get_auth_status (client));
    hashtable_remove (client->http_req->headers, "x-weechat-totp");

    /* test missing/invalid TOTP */
    config_file_option_set (relay_config_network_totp_secret, "secretbase32", 1);
    config_file_option_set (relay_config_network_totp_window, "1", 1);
    LONGS_EQUAL(-3, relay_http_get_auth_status (client));
    totp = hook_info_get (NULL, "totp_generate", "secretbase32");
    CHECK(totp);
    totp2 = strdup (totp);
    totp2[0] = (totp2[0] == '1') ? '2' : '1';
    hashtable_set (client->http_req->headers, "x-weechat-totp", totp2);
    LONGS_EQUAL(-4, relay_http_get_auth_status (client));
    hashtable_set (client->http_req->headers, "x-weechat-totp", totp);
    LONGS_EQUAL(0, relay_http_get_auth_status (client));
    free (totp);
    free (totp2);
    config_file_option_reset (relay_config_network_totp_secret, 1);
    config_file_option_reset (relay_config_network_totp_window, 1);
    hashtable_remove (client->http_req->headers, "x-weechat-totp");

    /* test invalid plain-text password ("test") via Sec-WebSocket-Protocol */
    hashtable_remove (client->http_req->headers, "authorization");
    hashtable_set (client->http_req->headers, "sec-websocket-protocol",
                   WEBSOCKET_SUB_PROTOCOL_API_WEECHAT
                   ", base64url.bearer.authorization.weechat.cGxhaW46dGVzdA");
    LONGS_EQUAL(-2, relay_http_get_auth_status (client));

    /* test valid plain-text password ("secret_password") via Sec-WebSocket-Protocol */
    hashtable_set (client->http_req->headers, "sec-websocket-protocol",
                   WEBSOCKET_SUB_PROTOCOL_API_WEECHAT
                   ", base64url.bearer.authorization.weechat.cGxhaW46c2VjcmV0X3Bhc3N3b3Jk");
    LONGS_EQUAL(0, relay_http_get_auth_status (client));

    /* test auth via Sec-WebSocket-Protocol with base64url specific characters */
    config_file_option_set (relay_config_network_password, "..>..?.", 1);
    hashtable_set (client->http_req->headers, "sec-websocket-protocol",
                   WEBSOCKET_SUB_PROTOCOL_API_WEECHAT
                   ", base64url.bearer.authorization.weechat.cGxhaW46Li4-Li4_Lg");
    LONGS_EQUAL(0, relay_http_get_auth_status (client));
    config_file_option_set (relay_config_network_password, good_pwd, 1);

    config_file_option_reset (relay_config_network_password, 1);

    relay_http_request_free (client->http_req);
    free (client);
}

/*
 * Test functions:
 *   relay_http_check_auth
 */

TEST(RelayHttp, CheckAuth)
{
    /* TODO: write tests */
}

/*
 * Test functions:
 *   relay_http_process_websocket
 */

TEST(RelayHttp, ProcessWebsocket)
{
    /* TODO: write tests */
}

/*
 * Test functions:
 *   relay_http_process_request
 */

TEST(RelayHttp, ProcessRequest)
{
    /* TODO: write tests */
}

/*
 * Test functions:
 *   relay_http_recv
 */

TEST(RelayHttp, Recv)
{
    /* TODO: write tests */
}

/*
 * Test functions:
 *   relay_http_recv (partial message accumulated is bounded)
 *
 * Check that data received without any end-of-line does not grow the partial
 * message buffer without limit.
 */

TEST(RelayHttp, RecvLimit)
{
    struct t_relay_server *server;
    struct t_relay_client *client;
    char *chunk;
    int chunk_size, i;
    size_t length1, length2;

    /* disable auto-open of relay buffer (it would pollute other tests) */
    config_file_option_set (relay_config_look_auto_open_buffer, "off", 1);

    server = relay_server_new ("weechat", RELAY_PROTOCOL_WEECHAT, NULL,
                               9000,
                               NULL,  /* path */
                               1,  /* ipv4 */
                               0,  /* ipv6 */
                               0,  /* tls */
                               0);  /* unix_socket */
    CHECK(server);
    client = relay_client_new (-1, "test", server);
    CHECK(client);

    chunk_size = 1024 * 1024;
    chunk = (char *)malloc (chunk_size + 1);
    CHECK(chunk);
    memset (chunk, 'a', chunk_size);
    chunk[chunk_size] = '\0';

    /* feed more than the maximum, with no end-of-line (16 MB) */
    for (i = 0; i < 16; i++)
    {
        relay_http_recv (client, chunk, chunk_size);
    }
    CHECK(client->partial_message);
    length1 = strlen (client->partial_message);

    /* the partial message must be bounded (not ~16 MB) */
    CHECK(length1 <= RELAY_HTTP_PARTIAL_MESSAGE_MAX_LENGTH + (size_t)chunk_size);

    /* feeding more data must not grow it any further */
    for (i = 0; i < 16; i++)
    {
        relay_http_recv (client, chunk, chunk_size);
    }
    length2 = strlen (client->partial_message);
    LONGS_EQUAL(length1, length2);

    free (chunk);
    relay_client_free (client);
    relay_server_free (server);

    /* restore auto-open of relay buffer */
    config_file_option_reset (relay_config_look_auto_open_buffer, 1);
}

/*
 * Test functions:
 *   relay_http_compress
 */

TEST(RelayHttp, Compress)
{
    struct t_relay_http_request *request;
    char body[256], *buffer, encoding[256];
    int i, size;

    request = relay_http_request_alloc ();

    for (i = 0; i < (int)sizeof (body); i++)
    {
        body[i] = i % 64;
    }

    hashtable_remove_all (request->accept_encoding);
    size = -1;
    buffer = relay_http_compress (request, NULL, 0, &size, NULL, 0);
    STRCMP_EQUAL(NULL, buffer);
    LONGS_EQUAL(0, size);

    hashtable_remove_all (request->accept_encoding);
    size = -1;
    snprintf (encoding, sizeof (encoding), "test");
    buffer = relay_http_compress (request, NULL, 0, &size,
                                  encoding, sizeof (encoding));
    STRCMP_EQUAL(NULL, buffer);
    LONGS_EQUAL(0, size);
    STRCMP_EQUAL("", encoding);

    /* no "Accept-Encoding" header was received => no compression */
    hashtable_remove_all (request->accept_encoding);
    size = -1;
    snprintf (encoding, sizeof (encoding), "test");
    buffer = relay_http_compress (request, body, sizeof (body), &size,
                                  encoding, sizeof (encoding));
    STRCMP_EQUAL(NULL, buffer);
    LONGS_EQUAL(0, size);
    STRCMP_EQUAL("", encoding);

    /* "Accept-Encoding: gzip" => gzip compression */
    hashtable_remove_all (request->accept_encoding);
    hashtable_set (request->accept_encoding, "gzip", "");
    size = -1;
    snprintf (encoding, sizeof (encoding), "test");
    buffer = relay_http_compress (request, body, sizeof (body), &size,
                                  encoding, sizeof (encoding));
    CHECK(buffer);
    CHECK((size > 0) && (size < (int)sizeof (body)));
    STRCMP_EQUAL("Content-Encoding: gzip\r\n", encoding);
    free (buffer);

#ifdef HAVE_ZSTD
    /* "Accept-Encoding: gzip, zstd" => zstd compression */
    hashtable_remove_all (request->accept_encoding);
    hashtable_set (request->accept_encoding, "gzip", "");
    hashtable_set (request->accept_encoding, "zstd", "");
    size = -1;
    snprintf (encoding, sizeof (encoding), "test");
    buffer = relay_http_compress (request, body, sizeof (body), &size,
                                  encoding, sizeof (encoding));
    CHECK(buffer);
    CHECK((size > 0) && (size < (int)sizeof (body)));
    STRCMP_EQUAL("Content-Encoding: zstd\r\n", encoding);
    free (buffer);
#endif
}

/*
 * Test functions:
 *   relay_http_send
 */

TEST(RelayHttp, Send)
{
    /* TODO: write tests */
}

/*
 * Test functions:
 *   relay_http_send_json
 */

TEST(RelayHttp, SendJson)
{
    /* TODO: write tests */
}

/*
 * Test functions:
 *   relay_http_send_error_json
 */

TEST(RelayHttp, SendErrorJson)
{
    /* TODO: write tests */
}

/*
 * Test functions:
 *   relay_http_response_alloc
 *   relay_http_response_free
 */

TEST(RelayHttp, ResponseAllocFree)
{
    struct t_relay_http_response *response;

    response = relay_http_response_alloc ();
    CHECK(response);

    LONGS_EQUAL(RELAY_HTTP_METHOD, response->status);
    STRCMP_EQUAL(NULL, response->http_version);
    LONGS_EQUAL(0, response->return_code);
    STRCMP_EQUAL(NULL, response->message);
    CHECK(response->headers);
    LONGS_EQUAL(0, response->headers->items_count);
    LONGS_EQUAL(0, response->content_length);
    LONGS_EQUAL(0, response->body_size);
    STRCMP_EQUAL(NULL, response->body);

    relay_http_response_free (response);

    /* test free of NULL response */
    relay_http_response_free (NULL);
}

/*
 * Test functions:
 *   relay_http_parse_response_code
 */

TEST(RelayHttp, ParseResponseCode)
{
    struct t_relay_http_response *response;

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (NULL, NULL);
    LONGS_EQUAL(RELAY_HTTP_METHOD, response->status);
    STRCMP_EQUAL(NULL, response->http_version);
    LONGS_EQUAL(0, response->return_code);
    STRCMP_EQUAL(NULL, response->message);
    CHECK(response->headers);
    LONGS_EQUAL(0, response->headers->items_count);
    LONGS_EQUAL(0, response->content_length);
    LONGS_EQUAL(0, response->body_size);
    STRCMP_EQUAL(NULL, response->body);
    relay_http_response_free (response);

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, NULL);
    relay_http_parse_response_code (response, "");
    LONGS_EQUAL(RELAY_HTTP_END, response->status);
    STRCMP_EQUAL(NULL, response->http_version);
    LONGS_EQUAL(0, response->return_code);
    STRCMP_EQUAL(NULL, response->message);
    CHECK(response->headers);
    LONGS_EQUAL(0, response->headers->items_count);
    LONGS_EQUAL(0, response->content_length);
    LONGS_EQUAL(0, response->body_size);
    STRCMP_EQUAL(NULL, response->body);
    relay_http_response_free (response);

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, "HTTP/1.1");
    LONGS_EQUAL(RELAY_HTTP_END, response->status);
    STRCMP_EQUAL(NULL, response->http_version);
    LONGS_EQUAL(0, response->return_code);
    STRCMP_EQUAL(NULL, response->message);
    CHECK(response->headers);
    LONGS_EQUAL(0, response->headers->items_count);
    LONGS_EQUAL(0, response->content_length);
    LONGS_EQUAL(0, response->body_size);
    STRCMP_EQUAL(NULL, response->body);
    relay_http_response_free (response);

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, "HTTP/1.1 200");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status);
    STRCMP_EQUAL("HTTP/1.1", response->http_version);
    LONGS_EQUAL(200, response->return_code);
    STRCMP_EQUAL(NULL, response->message);
    CHECK(response->headers);
    LONGS_EQUAL(0, response->headers->items_count);
    LONGS_EQUAL(0, response->content_length);
    LONGS_EQUAL(0, response->body_size);
    STRCMP_EQUAL(NULL, response->body);
    relay_http_response_free (response);

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, "HTTP/1.1 200 OK");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status);
    STRCMP_EQUAL("HTTP/1.1", response->http_version);
    LONGS_EQUAL(200, response->return_code);
    STRCMP_EQUAL("OK", response->message);
    CHECK(response->headers);
    LONGS_EQUAL(0, response->headers->items_count);
    LONGS_EQUAL(0, response->content_length);
    LONGS_EQUAL(0, response->body_size);
    STRCMP_EQUAL(NULL, response->body);
    relay_http_response_free (response);

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, "HTTP/1.1 101 Switching Protocols");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status);
    STRCMP_EQUAL("HTTP/1.1", response->http_version);
    LONGS_EQUAL(101, response->return_code);
    STRCMP_EQUAL("Switching Protocols", response->message);
    CHECK(response->headers);
    LONGS_EQUAL(0, response->headers->items_count);
    LONGS_EQUAL(0, response->content_length);
    LONGS_EQUAL(0, response->body_size);
    STRCMP_EQUAL(NULL, response->body);
    relay_http_response_free (response);


    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, "HTTP/1.1 401 Unauthorized");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status);
    STRCMP_EQUAL("HTTP/1.1", response->http_version);
    LONGS_EQUAL(401, response->return_code);
    STRCMP_EQUAL("Unauthorized", response->message);
    CHECK(response->headers);
    LONGS_EQUAL(0, response->headers->items_count);
    LONGS_EQUAL(0, response->content_length);
    LONGS_EQUAL(0, response->body_size);
    STRCMP_EQUAL(NULL, response->body);
    relay_http_response_free (response);
}

/*
 * Test functions:
 *   relay_http_parse_response_header
 */

TEST(RelayHttp, ParseResponseHeader)
{
    struct t_relay_http_response *response;

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, "HTTP/1.1 200 OK");
    relay_http_parse_response_header (response, NULL);
    LONGS_EQUAL(RELAY_HTTP_END, response->status);
    STRCMP_EQUAL("HTTP/1.1", response->http_version);
    LONGS_EQUAL(200, response->return_code);
    STRCMP_EQUAL("OK", response->message);
    LONGS_EQUAL(0, response->headers->items_count);
    relay_http_response_free (response);

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, "HTTP/1.1 200 OK");
    relay_http_parse_response_header (response, "");
    LONGS_EQUAL(RELAY_HTTP_END, response->status);
    STRCMP_EQUAL("HTTP/1.1", response->http_version);
    LONGS_EQUAL(200, response->return_code);
    STRCMP_EQUAL("OK", response->message);
    LONGS_EQUAL(0, response->headers->items_count);
    relay_http_response_free (response);

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, "HTTP/1.1 200 OK");
    relay_http_parse_response_header (response, "Test");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status);
    STRCMP_EQUAL("HTTP/1.1", response->http_version);
    LONGS_EQUAL(200, response->return_code);
    STRCMP_EQUAL("OK", response->message);
    LONGS_EQUAL(0, response->headers->items_count);
    relay_http_response_free (response);

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, "HTTP/1.1 200 OK");
    relay_http_parse_response_header (response, "X-Test: value");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status);
    STRCMP_EQUAL("HTTP/1.1", response->http_version);
    LONGS_EQUAL(200, response->return_code);
    STRCMP_EQUAL("OK", response->message);
    LONGS_EQUAL(1, response->headers->items_count);
    STRCMP_EQUAL("value", (const char *)hashtable_get (response->headers, "x-test"));
    relay_http_response_free (response);

    response = relay_http_response_alloc ();
    CHECK(response);
    relay_http_parse_response_code (response, "HTTP/1.1 200 OK");
    relay_http_parse_response_header (response, "Content-Length: 123");
    LONGS_EQUAL(RELAY_HTTP_HEADERS, response->status);
    STRCMP_EQUAL("HTTP/1.1", response->http_version);
    LONGS_EQUAL(200, response->return_code);
    STRCMP_EQUAL("OK", response->message);
    LONGS_EQUAL(1, response->headers->items_count);
    LONGS_EQUAL(123, response->content_length);
    relay_http_response_free (response);
}

/*
 * Test functions:
 *   relay_http_parse_response
 */

TEST(RelayHttp, ParseResponse)
{
    struct t_relay_http_response *response;

    POINTERS_EQUAL(NULL, relay_http_parse_response (NULL));
    POINTERS_EQUAL(NULL, relay_http_parse_response (""));

    response = relay_http_parse_response ("invalid");
    CHECK(response);
    LONGS_EQUAL(RELAY_HTTP_METHOD, response->status);
    STRCMP_EQUAL(NULL, response->http_version);
    LONGS_EQUAL(0, response->return_code);
    STRCMP_EQUAL(NULL, response->message);
    CHECK(response->headers);
    LONGS_EQUAL(0, response->headers->items_count);
    LONGS_EQUAL(0, response->content_length);
    LONGS_EQUAL(0, response->body_size);
    STRCMP_EQUAL(NULL, response->body);
    relay_http_response_free (response);

    response = relay_http_parse_response ("invalid\r\n");
    CHECK(response);
    LONGS_EQUAL(RELAY_HTTP_END, response->status);
    STRCMP_EQUAL(NULL, response->http_version);
    LONGS_EQUAL(0, response->return_code);
    STRCMP_EQUAL(NULL, response->message);
    CHECK(response->headers);
    LONGS_EQUAL(0, response->headers->items_count);
    LONGS_EQUAL(0, response->content_length);
    LONGS_EQUAL(0, response->body_size);
    STRCMP_EQUAL(NULL, response->body);
    relay_http_response_free (response);

    response = relay_http_parse_response ("HTTP/1.1 200 OK\r\n"
                                          "Content-Type: application/json\r\n"
                                          "Content-Length: 13\r\n"
                                          "\r\n"
                                          "{\"test\": 123}");
    CHECK(response);
    LONGS_EQUAL(RELAY_HTTP_END, response->status);
    STRCMP_EQUAL("HTTP/1.1", response->http_version);
    LONGS_EQUAL(200, response->return_code);
    STRCMP_EQUAL("OK", response->message);
    CHECK(response->headers);
    LONGS_EQUAL(2, response->headers->items_count);
    STRCMP_EQUAL("application/json", (const char *)hashtable_get (response->headers,
                                                                  "content-type"));
    STRCMP_EQUAL("13", (const char *)hashtable_get (response->headers,
                                                    "content-length"));
    LONGS_EQUAL(13, response->content_length);
    LONGS_EQUAL(13, response->body_size);
    STRCMP_EQUAL("{\"test\": 123}", response->body);
    relay_http_response_free (response);
}

/*
 * Test functions:
 *   relay_http_print_log_request
 */

TEST(RelayHttp, PrintLogRequest)
{
    /* TODO: write tests */
}

/*
 * Test functions:
 *   relay_http_print_log_response
 */

TEST(RelayHttp, PrintLogResponse)
{
    /* TODO: write tests */
}
