cockpit/src/ws/test-webservice.c

1568 lines
49 KiB
C

/*
* This file is part of Cockpit.
*
* Copyright (C) 2013 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* Cockpit 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "cockpitcreds.h"
#include "cockpitwebservice.h"
#include "cockpitws.h"
#include "common/cockpitpipe.h"
#include "common/cockpitpipetransport.h"
#include "common/cockpittransport.h"
#include "common/cockpitjson.h"
#include "common/cockpittest.h"
#include "common/mock-io-stream.h"
#include "common/cockpitwebserver.h"
#include "common/cockpitconf.h"
#include "websocket/websocket.h"
#include <glib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
/* Mock override from cockpitconf.c */
extern const gchar *cockpit_config_file;
/* Mock override from cockpitwebservice.c */
extern const gchar *cockpit_ws_default_protocol_header;
#define TIMEOUT 30
#define WAIT_UNTIL(cond) \
G_STMT_START \
while (!(cond)) g_main_context_iteration (NULL, TRUE); \
G_STMT_END
#define PASSWORD "this is the password"
typedef struct {
/* setup default transport */
CockpitTransport *mock_bridge;
GPid mock_bridge_pid;
/* setup_mock_webserver */
CockpitWebServer *web_server;
gchar *cookie;
CockpitCreds *creds;
/* setup_io_pair */
GIOStream *io_a;
GIOStream *io_b;
/* serve_socket */
CockpitWebService *service;
} TestCase;
typedef struct {
const char *origin;
const char *config;
const char *forward;
const char *bridge;
gboolean for_tls_proxy;
} TestFixture;
static gboolean
on_transport_control (CockpitTransport *transport,
const char *command,
const gchar *channel,
JsonObject *options,
GBytes *payload,
gpointer data)
{
gboolean *flag = data;
g_assert (flag != NULL);
if (g_str_equal (command, "init"))
*flag = TRUE;
return FALSE;
}
static void
setup_mock_bridge (TestCase *test,
gconstpointer data)
{
const TestFixture *fix = data;
CockpitPipe *pipe = NULL;
const gchar *cmd;
if (fix && fix->bridge)
cmd = fix->bridge;
else
cmd = BUILDDIR "/mock-echo";
const gchar *argv[] = {
cmd,
NULL
};
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
test->mock_bridge = cockpit_pipe_transport_new (pipe);
g_assert (cockpit_pipe_get_pid (pipe, &test->mock_bridge_pid));
g_object_unref (pipe);
}
static void
teardown_mock_bridge (TestCase *test,
gconstpointer data)
{
if (test->mock_bridge)
{
cockpit_transport_close (test->mock_bridge, "terminate");
g_object_add_weak_pointer (G_OBJECT (test->mock_bridge), (gpointer *)&test->mock_bridge);
g_object_unref (test->mock_bridge);
g_assert (test->mock_bridge == NULL);
}
}
static void
setup_mock_webserver (TestCase *test,
gconstpointer data)
{
GError *error = NULL;
GBytes *password;
/* Zero port makes server choose its own */
test->web_server = cockpit_web_server_new (NULL, 0, NULL, NULL, &error);
g_assert_no_error (error);
cockpit_web_server_start (test->web_server);
password = g_bytes_new_take (g_strdup (PASSWORD), strlen (PASSWORD));
test->creds = cockpit_creds_new ("cockpit",
COCKPIT_CRED_USER, "me",
COCKPIT_CRED_PASSWORD, password,
COCKPIT_CRED_CSRF_TOKEN, "my-csrf-token",
NULL);
g_bytes_unref (password);
}
static void
teardown_mock_webserver (TestCase *test,
gconstpointer data)
{
g_clear_object (&test->web_server);
if (test->creds)
cockpit_creds_unref (test->creds);
g_free (test->cookie);
}
static void
setup_io_streams (TestCase *test,
gconstpointer data)
{
GSocket *socket1, *socket2;
GError *error = NULL;
int fds[2];
if (socketpair (PF_UNIX, SOCK_STREAM, 0, fds) < 0)
g_assert_not_reached ();
socket1 = g_socket_new_from_fd (fds[0], &error);
g_assert_no_error (error);
socket2 = g_socket_new_from_fd (fds[1], &error);
g_assert_no_error (error);
test->io_a = G_IO_STREAM (g_socket_connection_factory_create_connection (socket1));
test->io_b = G_IO_STREAM (g_socket_connection_factory_create_connection (socket2));
g_object_unref (socket1);
g_object_unref (socket2);
}
static void
teardown_io_streams (TestCase *test,
gconstpointer data)
{
g_clear_object (&test->io_a);
g_clear_object (&test->io_b);
}
static void
setup_for_socket (TestCase *test,
gconstpointer data)
{
alarm (TIMEOUT);
setup_mock_bridge (test, data);
setup_mock_webserver (test, data);
setup_io_streams (test, data);
}
static void
teardown_for_socket (TestCase *test,
gconstpointer data)
{
teardown_mock_bridge (test, data);
teardown_mock_webserver (test, data);
teardown_io_streams (test, data);
cockpit_assert_expected ();
alarm (0);
}
static gboolean
on_error_not_reached (WebSocketConnection *ws,
GError *error,
gpointer user_data)
{
g_assert (error != NULL);
/* At this point we know this will fail, but is informative */
g_assert_no_error (error);
return TRUE;
}
static gboolean
on_error_copy (WebSocketConnection *ws,
GError *error,
gpointer user_data)
{
GError **result = user_data;
g_assert (error != NULL);
g_assert (result != NULL);
g_assert (*result == NULL);
*result = g_error_copy (error);
return TRUE;
}
static gboolean
on_timeout_fail (gpointer data)
{
g_error ("timeout during test: %s", (gchar *)data);
return FALSE;
}
#define BUILD_INTS GINT_TO_POINTER(1)
static GBytes *
builder_to_bytes (JsonBuilder *builder)
{
GBytes *bytes;
gchar *data;
gsize length;
JsonNode *node;
json_builder_end_object (builder);
node = json_builder_get_root (builder);
data = cockpit_json_write (node, &length);
data = g_realloc (data, length + 1);
memmove (data + 1, data, length);
memcpy (data, "\n", 1);
bytes = g_bytes_new_take (data, length + 1);
json_node_free (node);
return bytes;
}
static GBytes *
build_control_va (const gchar *command,
const gchar *channel,
va_list va)
{
GBytes *bytes;
JsonBuilder *builder;
const gchar *option;
gboolean strings = TRUE;
builder = json_builder_new ();
json_builder_begin_object (builder);
json_builder_set_member_name (builder, "command");
json_builder_add_string_value (builder, command);
if (channel)
{
json_builder_set_member_name (builder, "channel");
json_builder_add_string_value (builder, channel);
}
for (;;)
{
option = va_arg (va, const gchar *);
if (option == BUILD_INTS)
{
strings = FALSE;
option = va_arg (va, const gchar *);
}
if (!option)
break;
json_builder_set_member_name (builder, option);
if (strings)
json_builder_add_string_value (builder, va_arg (va, const gchar *));
else
json_builder_add_int_value (builder, va_arg (va, gint));
}
bytes = builder_to_bytes (builder);
g_object_unref (builder);
return bytes;
}
static void
send_control_message (WebSocketConnection *ws,
const gchar *command,
const gchar *channel,
...) G_GNUC_NULL_TERMINATED;
static void
send_control_message (WebSocketConnection *ws,
const gchar *command,
const gchar *channel,
...)
{
GBytes *payload;
va_list va;
va_start (va, channel);
payload = build_control_va (command, channel, va);
va_end (va);
web_socket_connection_send (ws, WEB_SOCKET_DATA_TEXT, NULL, payload);
g_bytes_unref (payload);
}
static void
expect_control_message (GBytes *message,
const gchar *command,
const gchar *expected_channel,
...) G_GNUC_NULL_TERMINATED;
static void
expect_control_message (GBytes *message,
const gchar *expected_command,
const gchar *expected_channel,
...)
{
gchar *outer_channel;
const gchar *message_command;
const gchar *message_channel;
JsonObject *options;
GBytes *payload;
const gchar *expect_option;
const gchar *expect_value;
const gchar *value;
va_list va;
payload = cockpit_transport_parse_frame (message, &outer_channel);
g_assert (payload != NULL);
g_assert_cmpstr (outer_channel, ==, NULL);
g_free (outer_channel);
g_assert (cockpit_transport_parse_command (payload, &message_command,
&message_channel, &options));
g_bytes_unref (payload);
g_assert_cmpstr (expected_command, ==, message_command);
g_assert_cmpstr (expected_channel, ==, expected_channel);
va_start (va, expected_channel);
for (;;) {
expect_option = va_arg (va, const gchar *);
if (!expect_option)
break;
expect_value = va_arg (va, const gchar *);
g_assert (expect_value != NULL);
value = NULL;
if (json_object_has_member (options, expect_option))
value = json_object_get_string_member (options, expect_option);
g_assert_cmpstr (value, ==, expect_value);
}
va_end (va);
json_object_unref (options);
}
static void
start_web_service_and_create_client (TestCase *test,
const TestFixture *fixture,
WebSocketConnection **ws,
CockpitWebService **service)
{
cockpit_config_file = fixture ? fixture->config : NULL;
const char *origin = fixture ? fixture->origin : NULL;
gboolean ready = FALSE;
gulong handler;
if (!origin)
origin = "http://127.0.0.1";
/* This is web_socket_client_new_for_stream() */
*ws = g_object_new (WEB_SOCKET_TYPE_CLIENT,
"url", "ws://127.0.0.1/unused",
"origin", origin,
"io-stream", test->io_a,
NULL);
g_signal_connect (*ws, "error", G_CALLBACK (on_error_not_reached), NULL);
web_socket_client_include_header (WEB_SOCKET_CLIENT (*ws), "Cookie", test->cookie);
/* Matching the above origin */
cockpit_ws_default_host_header = "127.0.0.1";
cockpit_ws_default_protocol_header = fixture ? fixture->forward : NULL;
*service = cockpit_web_service_new (test->creds, test->mock_bridge);
/* Manually created services won't be init'd yet, wait for that before sending data */
handler = g_signal_connect (test->mock_bridge, "control", G_CALLBACK (on_transport_control), &ready);
while (!ready)
g_main_context_iteration (NULL, TRUE);
/* Note, we are forcing the websocket to parse its own headers */
cockpit_web_service_socket (*service, "/unused", test->io_b, NULL, NULL, fixture ? fixture->for_tls_proxy : FALSE);
g_signal_handler_disconnect (test->mock_bridge, handler);
}
static void
start_web_service_and_connect_client (TestCase *test,
const TestFixture *fixture,
WebSocketConnection **ws,
CockpitWebService **service)
{
GBytes *message;
start_web_service_and_create_client (test, fixture, ws, service);
WAIT_UNTIL (web_socket_connection_get_ready_state (*ws) != WEB_SOCKET_STATE_CONNECTING);
g_assert (web_socket_connection_get_ready_state (*ws) == WEB_SOCKET_STATE_OPEN);
/* Send the open control message that starts the bridge. */
send_control_message (*ws, "init", NULL, BUILD_INTS, "version", 1, NULL);
send_control_message (*ws, "open", "4", "payload", "echo", NULL);
/* This message should be echoed */
message = g_bytes_new ("4\ntest", 6);
web_socket_connection_send (*ws, WEB_SOCKET_DATA_TEXT, NULL, message);
g_bytes_unref (message);
}
static void
close_client_and_stop_web_service (TestCase *test,
WebSocketConnection *ws,
CockpitWebService *service)
{
guint timeout;
if (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_OPEN)
{
web_socket_connection_close (ws, 0, NULL);
WAIT_UNTIL (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_CLOSED);
}
g_object_unref (ws);
/* Wait until service is done */
timeout = g_timeout_add_seconds (20, on_timeout_fail, "closing web service");
g_object_add_weak_pointer (G_OBJECT (service), (gpointer *)&service);
g_object_unref (service);
while (service != NULL)
g_main_context_iteration (NULL, TRUE);
g_source_remove (timeout);
cockpit_conf_cleanup ();
}
static void
test_handshake_and_auth (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
CockpitWebService *service;
start_web_service_and_connect_client (test, data, &ws, &service);
close_client_and_stop_web_service (test, ws, service);
}
static void
on_message_get_bytes (WebSocketConnection *ws,
WebSocketDataType type,
GBytes *message,
gpointer user_data)
{
GBytes **received = user_data;
g_assert_cmpint (type, ==, WEB_SOCKET_DATA_TEXT);
if (*received != NULL)
{
gsize length;
gconstpointer data = g_bytes_get_data (message, &length);
g_test_message ("received unexpected extra message: %.*s", (int)length, (gchar *)data);
g_assert_not_reached ();
}
*received = g_bytes_ref (message);
}
static void
on_message_get_non_control (WebSocketConnection *ws,
WebSocketDataType type,
GBytes *message,
gpointer user_data)
{
GBytes **received = user_data;
g_assert_cmpint (type, ==, WEB_SOCKET_DATA_TEXT);
/* Control messages have this prefix: ie: a zero channel */
if (g_str_has_prefix (g_bytes_get_data (message, NULL), "\n"))
return;
g_assert (*received == NULL);
*received = g_bytes_ref (message);
}
static void
on_message_get_control (WebSocketConnection *ws,
WebSocketDataType type,
GBytes *message,
gpointer user_data)
{
JsonObject **received = user_data;
GError *error = NULL;
g_assert_cmpint (type, ==, WEB_SOCKET_DATA_TEXT);
/* Control messages have this prefix: ie: a zero channel */
if (g_str_has_prefix (g_bytes_get_data (message, NULL), "\n"))
{
g_assert (*received == NULL);
*received = cockpit_json_parse_bytes (message, &error);
g_assert_no_error (error);
}
}
static void
test_handshake_and_echo (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
GBytes *received = NULL;
GBytes *control = NULL;
CockpitWebService *service;
CockpitCreds *creds;
GBytes *sent;
gulong handler;
const gchar *token;
/* Sends a "test" message in channel "4" */
start_web_service_and_connect_client (test, data, &ws, &service);
sent = g_bytes_new_static ("4\ntest", 6);
handler = g_signal_connect (ws, "message", G_CALLBACK (on_message_get_bytes), &control);
WAIT_UNTIL (control != NULL);
creds = cockpit_web_service_get_creds (service);
g_assert (creds != NULL);
token = cockpit_creds_get_csrf_token (creds);
g_assert_cmpstr (token, ==, "my-csrf-token");
expect_control_message (control, "init", NULL, "csrf-token", token, NULL);
g_bytes_unref (control);
g_signal_handler_disconnect (ws, handler);
handler = g_signal_connect (ws, "message", G_CALLBACK (on_message_get_non_control), &received);
WAIT_UNTIL (received != NULL);
g_assert (g_bytes_equal (received, sent));
g_bytes_unref (sent);
g_bytes_unref (received);
received = NULL;
g_signal_handler_disconnect (ws, handler);
close_client_and_stop_web_service (test, ws, service);
}
static void
test_echo_large (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
GBytes *received = NULL;
CockpitWebService *service;
gchar *contents;
GBytes *sent;
gulong handler;
start_web_service_and_create_client (test, data, &ws, &service);
while (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_CONNECTING)
g_main_context_iteration (NULL, TRUE);
g_assert (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_OPEN);
/* Send the open control message that starts the bridge. */
send_control_message (ws, "init", NULL, BUILD_INTS, "version", 1, NULL);
send_control_message (ws, "open", "4", "payload", "test-text", NULL);
handler = g_signal_connect (ws, "message", G_CALLBACK (on_message_get_non_control), &received);
/* Medium length */
contents = g_strnfill (1020, '!');
contents[0] = '4'; /* channel */
contents[1] = '\n';
sent = g_bytes_new_take (contents, 1020);
web_socket_connection_send (ws, WEB_SOCKET_DATA_TEXT, NULL, sent);
WAIT_UNTIL (received != NULL);
g_assert (g_bytes_equal (received, sent));
g_bytes_unref (sent);
g_bytes_unref (received);
received = NULL;
/* Extra large */
contents = g_strnfill (100 * 1000, '?');
contents[0] = '4'; /* channel */
contents[1] = '\n';
sent = g_bytes_new_take (contents, 100 * 1000);
web_socket_connection_send (ws, WEB_SOCKET_DATA_TEXT, NULL, sent);
WAIT_UNTIL (received != NULL);
g_assert (g_bytes_equal (received, sent));
g_bytes_unref (sent);
g_bytes_unref (received);
received = NULL;
g_signal_handler_disconnect (ws, handler);
close_client_and_stop_web_service (test, ws, service);
}
static void
test_close_error (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
GBytes *received = NULL;
CockpitWebService *service;
start_web_service_and_connect_client (test, data, &ws, &service);
g_signal_connect (ws, "message", G_CALLBACK (on_message_get_bytes), &received);
WAIT_UNTIL (received != NULL);
expect_control_message (received, "init", NULL, NULL);
g_bytes_unref (received);
received = NULL;
WAIT_UNTIL (received != NULL);
expect_control_message (received, "hint", NULL, NULL);
g_bytes_unref (received);
received = NULL;
/* Silly test echos the "open" message */
WAIT_UNTIL (received != NULL);
expect_control_message (received, "open", "4", NULL);
g_bytes_unref (received);
received = NULL;
WAIT_UNTIL (received != NULL);
g_bytes_unref (received);
received = NULL;
/* Trigger a failure message */
g_assert (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_OPEN);
kill (test->mock_bridge_pid, SIGTERM);
/* We should now get a close command */
WAIT_UNTIL (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_CLOSED);
close_client_and_stop_web_service (test, ws, service);
}
static void
test_no_init (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
GBytes *received = NULL;
CockpitWebService *service;
start_web_service_and_create_client (test, data, &ws, &service);
g_signal_connect (ws, "message", G_CALLBACK (on_message_get_bytes), &received);
while (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_CONNECTING)
g_main_context_iteration (NULL, TRUE);
g_assert (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_OPEN);
cockpit_expect_message ("*socket did not send*init*");
cockpit_expect_log ("WebSocket", G_LOG_LEVEL_MESSAGE, "connection unexpectedly closed*");
/* Sending an open message before init, should cause problems */
send_control_message (ws, "ping", NULL, NULL);
/* The init from the other end */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
expect_control_message (received, "init", NULL, NULL);
g_bytes_unref (received);
received = NULL;
/* A hint from the other end */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
expect_control_message (received, "hint", NULL, NULL);
g_bytes_unref (received);
received = NULL;
/* We should now get a failure */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
expect_control_message (received, "close", NULL, "problem", "protocol-error", NULL);
g_bytes_unref (received);
received = NULL;
close_client_and_stop_web_service (test, ws, service);
}
static void
test_wrong_init_version (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
GBytes *received = NULL;
CockpitWebService *service;
start_web_service_and_create_client (test, data, &ws, &service);
g_signal_connect (ws, "message", G_CALLBACK (on_message_get_bytes), &received);
while (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_CONNECTING)
g_main_context_iteration (NULL, TRUE);
g_assert (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_OPEN);
cockpit_expect_message ("*socket used unsupported*");
cockpit_expect_log ("WebSocket", G_LOG_LEVEL_MESSAGE, "connection unexpectedly closed*");
send_control_message (ws, "init", NULL, BUILD_INTS, "version", 888, NULL);
/* The init from the other end */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
expect_control_message (received, "init", NULL, NULL);
g_bytes_unref (received);
received = NULL;
/* A hint from the other end */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
expect_control_message (received, "hint", NULL, NULL);
g_bytes_unref (received);
received = NULL;
/* We should now get a failure */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
expect_control_message (received, "close", NULL, "problem", "not-supported", NULL);
g_bytes_unref (received);
received = NULL;
close_client_and_stop_web_service (test, ws, service);
}
static void
test_bad_init_version (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
GBytes *received = NULL;
CockpitWebService *service;
start_web_service_and_create_client (test, data, &ws, &service);
g_signal_connect (ws, "message", G_CALLBACK (on_message_get_bytes), &received);
while (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_CONNECTING)
g_main_context_iteration (NULL, TRUE);
g_assert (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_OPEN);
cockpit_expect_warning ("*invalid version field*");
cockpit_expect_log ("WebSocket", G_LOG_LEVEL_MESSAGE, "connection unexpectedly closed*");
send_control_message (ws, "init", NULL, "version", "blah", NULL);
/* The init from the other end */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
expect_control_message (received, "init", NULL, NULL);
g_bytes_unref (received);
received = NULL;
/* A hint from the other end */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
expect_control_message (received, "hint", NULL, NULL);
g_bytes_unref (received);
received = NULL;
/* We should now get a failure */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
expect_control_message (received, "close", NULL, "problem", "protocol-error", NULL);
g_bytes_unref (received);
received = NULL;
close_client_and_stop_web_service (test, ws, service);
}
static void
test_socket_null_creds (TestCase *test,
gconstpointer data)
{
CockpitWebService *service;
CockpitTransport *session;
int pair[2];
/*
* These are tests double checking that we *never*
* open up a real CockpitWebService for NULL creds.
*
* Other code paths do the real checks, but these are
* the last resorts.
*/
cockpit_expect_critical ("*assertion*failed*");
service = cockpit_web_service_new (NULL, NULL);
g_assert (service == NULL);
cockpit_expect_critical ("*assertion*failed*");
g_assert (pipe(pair) >= 0);
session = cockpit_pipe_transport_new_fds ("dummy", pair[0], pair[1]);
service = cockpit_web_service_new (NULL, session);
g_assert (service == NULL);
g_object_unref (session);
}
static const TestFixture fixture_bad_origin_rfc6455 = {
.origin = "http://another-place.com",
.config = NULL
};
static const TestFixture fixture_allowed_origin_rfc6455 = {
.origin = "https://another-place.com",
.config = SRCDIR "/src/ws/mock-config/cockpit/cockpit.conf"
};
static const TestFixture fixture_allowed_origin_proto_header = {
.origin = "https://127.0.0.1",
.forward = "https",
.config = SRCDIR "/src/ws/mock-config/cockpit/cockpit-alt.conf"
};
static const TestFixture fixture_allowed_origin_tls_proxy = {
.origin = "https://127.0.0.1",
.for_tls_proxy = TRUE,
};
static const TestFixture fixture_bad_origin_proto_no_header = {
.origin = "https://127.0.0.1",
.config = SRCDIR "/src/ws/mock-config/cockpit/cockpit-alt.conf"
};
static const TestFixture fixture_bad_origin_proto_no_config = {
.origin = "https://127.0.0.1",
.forward = "https",
.config = NULL
};
static const TestFixture fixture_bad_origin_tls_proxy = {
.origin = "http://127.0.0.1",
.for_tls_proxy = TRUE,
};
static void
test_bad_origin (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
CockpitWebService *service;
GError *error = NULL;
cockpit_expect_log ("WebSocket", G_LOG_LEVEL_MESSAGE, "*received request from bad Origin*");
cockpit_expect_log ("WebSocket", G_LOG_LEVEL_MESSAGE, "*invalid handshake*");
cockpit_expect_log ("WebSocket", G_LOG_LEVEL_MESSAGE, "*unexpected status: 403*");
start_web_service_and_create_client (test, data, &ws, &service);
g_signal_handlers_disconnect_by_func (ws, on_error_not_reached, NULL);
g_signal_connect (ws, "error", G_CALLBACK (on_error_copy), &error);
while (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_CONNECTING ||
web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_CLOSING)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpint (web_socket_connection_get_ready_state (ws), ==, WEB_SOCKET_STATE_CLOSED);
g_assert_error (error, WEB_SOCKET_ERROR, WEB_SOCKET_CLOSE_PROTOCOL);
close_client_and_stop_web_service (test, ws, service);
g_clear_error (&error);
}
static const TestFixture fixture_kill_group = {
.bridge = BUILDDIR "/cockpit-bridge"
};
static void
test_kill_group (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
GBytes *received = NULL;
CockpitWebService *service;
GHashTable *seen;
gchar *ochannel;
const gchar *channel;
const gchar *command;
JsonObject *options;
GBytes *sent;
GBytes *payload;
gulong handler;
/* Sends a "test" message in channel "4" */
start_web_service_and_connect_client (test, data, &ws, &service);
sent = g_bytes_new_static ("4\ntest", 6);
handler = g_signal_connect (ws, "message", G_CALLBACK (on_message_get_non_control), &received);
/* Drain the initial message */
WAIT_UNTIL (received != NULL);
g_assert (g_bytes_equal (sent, received));
g_bytes_unref (received);
received = NULL;
g_signal_handler_disconnect (ws, handler);
handler = g_signal_connect (ws, "message", G_CALLBACK (on_message_get_bytes), &received);
seen = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_add (seen, "a");
g_hash_table_add (seen, "b");
g_hash_table_add (seen, "c");
send_control_message (ws, "open", "a", "payload", "echo", "group", "test", NULL);
send_control_message (ws, "open", "b", "payload", "echo", "group", "test", NULL);
send_control_message (ws, "open", "c", "payload", "echo", "group", "test", NULL);
/* Kill all the above channels */
send_control_message (ws, "kill", NULL, "group", "test", NULL);
/* All the close messages */
while (g_hash_table_size (seen) > 0)
{
WAIT_UNTIL (received != NULL);
payload = cockpit_transport_parse_frame (received, &ochannel);
g_bytes_unref (received);
received = NULL;
g_assert (payload != NULL);
g_assert_cmpstr (ochannel, ==, NULL);
g_free (ochannel);
g_assert (cockpit_transport_parse_command (payload, &command, &channel, &options));
g_bytes_unref (payload);
if (!g_str_equal (command, "open") && !g_str_equal (command, "ready"))
{
g_assert_cmpstr (command, ==, "close");
g_assert_cmpstr (json_object_get_string_member (options, "problem"), ==, "terminated");
g_assert (g_hash_table_remove (seen, channel));
}
json_object_unref (options);
}
g_hash_table_destroy (seen);
g_signal_handler_disconnect (ws, handler);
handler = g_signal_connect (ws, "message", G_CALLBACK (on_message_get_non_control), &received);
/* Now verify that the original channel is still open */
web_socket_connection_send (ws, WEB_SOCKET_DATA_TEXT, NULL, sent);
WAIT_UNTIL (received != NULL);
g_assert (g_bytes_equal (received, sent));
g_bytes_unref (sent);
g_bytes_unref (received);
received = NULL;
g_signal_handler_disconnect (ws, handler);
close_client_and_stop_web_service (test, ws, service);
}
static void
test_kill_host (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
GBytes *received = NULL;
CockpitWebService *service;
GHashTable *seen;
gchar *ochannel;
const gchar *channel;
const gchar *command;
JsonObject *options;
GBytes *payload;
gulong handler;
/* Sends a "test" message in channel "4" */
start_web_service_and_connect_client (test, data, &ws, &service);
handler = g_signal_connect (ws, "message", G_CALLBACK (on_message_get_non_control), &received);
/* Drain the initial message */
WAIT_UNTIL (received != NULL);
g_bytes_unref (received);
received = NULL;
g_signal_handler_disconnect (ws, handler);
handler = g_signal_connect (ws, "message", G_CALLBACK (on_message_get_bytes), &received);
seen = g_hash_table_new (g_str_hash, g_str_equal);
g_hash_table_add (seen, "a");
g_hash_table_add (seen, "b");
g_hash_table_add (seen, "c");
g_hash_table_add (seen, "4");
send_control_message (ws, "open", "a", "payload", "echo", "group", "test", NULL);
send_control_message (ws, "open", "b", "payload", "echo", "group", "test", NULL);
send_control_message (ws, "open", "c", "payload", "echo", "group", "test", NULL);
/* Kill all the above channels */
send_control_message (ws, "kill", NULL, "host", "localhost", NULL);
/* All the close messages */
while (g_hash_table_size (seen) > 0)
{
WAIT_UNTIL (received != NULL);
payload = cockpit_transport_parse_frame (received, &ochannel);
g_bytes_unref (received);
received = NULL;
g_assert (payload != NULL);
g_assert_cmpstr (ochannel, ==, NULL);
g_free (ochannel);
g_assert (cockpit_transport_parse_command (payload, &command, &channel, &options));
g_bytes_unref (payload);
if (!g_str_equal (command, "open") && !g_str_equal (command, "ready"))
{
g_assert_cmpstr (command, ==, "close");
g_assert_cmpstr (json_object_get_string_member (options, "problem"), ==, "terminated");
g_assert (g_hash_table_remove (seen, channel));
}
json_object_unref (options);
}
g_hash_table_destroy (seen);
g_signal_handler_disconnect (ws, handler);
close_client_and_stop_web_service (test, ws, service);
}
static void
on_idling_set_flag (CockpitWebService *service,
gpointer data)
{
gboolean *flag = data;
g_assert (*flag == FALSE);
*flag = TRUE;
}
static void
test_idling (TestCase *test,
gconstpointer data)
{
WebSocketConnection *client;
CockpitWebService *service;
gboolean flag = FALSE;
CockpitTransport *transport;
CockpitPipe *pipe;
const gchar *argv[] = {
BUILDDIR "/cockpit-bridge",
NULL
};
cockpit_ws_default_host_header = "127.0.0.1";
/* This is web_socket_client_new_for_stream() */
client = g_object_new (WEB_SOCKET_TYPE_CLIENT,
"url", "ws://127.0.0.1/unused",
"origin", "http://127.0.0.1",
"io-stream", test->io_a,
NULL);
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
transport = cockpit_pipe_transport_new (pipe);
service = cockpit_web_service_new (test->creds, transport);
g_object_unref (transport);
g_object_unref (pipe);
g_signal_connect (service, "idling", G_CALLBACK (on_idling_set_flag), &flag);
g_assert (cockpit_web_service_get_idling (service));
cockpit_web_service_socket (service, "/unused", test->io_b, NULL, NULL, FALSE);
g_assert (!cockpit_web_service_get_idling (service));
while (web_socket_connection_get_ready_state (client) == WEB_SOCKET_STATE_CONNECTING)
g_main_context_iteration (NULL, TRUE);
g_assert (web_socket_connection_get_ready_state (client) == WEB_SOCKET_STATE_OPEN);
web_socket_connection_close (client, WEB_SOCKET_CLOSE_NORMAL, "aoeuaoeuaoeu");
while (web_socket_connection_get_ready_state (client) != WEB_SOCKET_STATE_CLOSED)
g_main_context_iteration (NULL, TRUE);
/* Now the web service should go idle and fire idling signal */
while (!flag)
g_main_context_iteration (NULL, TRUE);
g_assert (cockpit_web_service_get_idling (service));
g_object_unref (service);
g_object_unref (client);
}
static void
test_dispose (TestCase *test,
gconstpointer data)
{
WebSocketConnection *client;
CockpitWebService *service;
CockpitTransport *transport;
CockpitPipe *pipe;
const gchar *argv[] = {
BUILDDIR "/cockpit-bridge",
NULL
};
cockpit_ws_default_host_header = "127.0.0.1";
/* This is web_socket_client_new_for_stream() */
client = g_object_new (WEB_SOCKET_TYPE_CLIENT,
"url", "ws://127.0.0.1/unused",
"origin", "http://127.0.0.1",
"io-stream", test->io_a,
NULL);
pipe = cockpit_pipe_spawn (argv, NULL, NULL, COCKPIT_PIPE_FLAGS_NONE);
transport = cockpit_pipe_transport_new (pipe);
service = cockpit_web_service_new (test->creds, transport);
g_object_unref (transport);
g_object_unref (pipe);
cockpit_web_service_socket (service, "/unused", test->io_b, NULL, NULL, FALSE);
while (web_socket_connection_get_ready_state (client) == WEB_SOCKET_STATE_CONNECTING)
g_main_context_iteration (NULL, TRUE);
g_assert (web_socket_connection_get_ready_state (client) == WEB_SOCKET_STATE_OPEN);
/* Dispose the WebSocket ... this is what happens on forceful logout */
g_object_run_dispose (G_OBJECT (service));
while (web_socket_connection_get_ready_state (client) != WEB_SOCKET_STATE_CLOSED)
g_main_context_iteration (NULL, TRUE);
g_object_unref (service);
g_object_unref (client);
}
static void
test_logout (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
CockpitWebService *service;
GBytes *message = NULL;
start_web_service_and_create_client (test, data, &ws, &service);
WAIT_UNTIL (web_socket_connection_get_ready_state (ws) != WEB_SOCKET_STATE_CONNECTING);
g_assert (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_OPEN);
/* Send the logout control message */
send_control_message (ws, "init", NULL, BUILD_INTS, "version", 1, NULL);
data = "\n{ \"command\": \"logout\", \"disconnect\": true }";
message = g_bytes_new_static (data, strlen (data));
web_socket_connection_send (ws, WEB_SOCKET_DATA_TEXT, NULL, message);
g_bytes_unref (message);
while (web_socket_connection_get_ready_state (ws) != WEB_SOCKET_STATE_CLOSED)
g_main_context_iteration (NULL, TRUE);
close_client_and_stop_web_service (test, ws, service);
}
static void
test_hint_credential (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
JsonObject *received = NULL;
CockpitWebService *service;
GBytes *message = NULL;
start_web_service_and_create_client (test, data, &ws, &service);
WAIT_UNTIL (web_socket_connection_get_ready_state (ws) != WEB_SOCKET_STATE_CONNECTING);
g_assert (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_OPEN);
/* Send the logout control message */
send_control_message (ws, "init", NULL, BUILD_INTS, "version", 1, NULL);
g_signal_connect (ws, "message", G_CALLBACK (on_message_get_control), &received);
/* First an init message */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpstr (json_object_get_string_member (received, "command"), ==, "init");
json_object_unref (received);
received = NULL;
/* Then a hint that we have a password */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (received, "{\"command\":\"hint\",\"credential\":\"password\"}");
json_object_unref (received);
received = NULL;
/* Now drop privileges */
data = "\n{ \"command\": \"logout\", \"disconnect\": false }";
message = g_bytes_new_static (data, strlen (data));
web_socket_connection_send (ws, WEB_SOCKET_DATA_TEXT, NULL, message);
g_bytes_unref (message);
/* We should now get a hint that we have no password */
while (received == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (received, "{\"command\":\"hint\",\"credential\":\"none\"}");
json_object_unref (received);
close_client_and_stop_web_service (test, ws, service);
}
static void
test_authorize_password (TestCase *test,
gconstpointer data)
{
WebSocketConnection *ws;
JsonObject *control = NULL;
CockpitWebService *service;
GBytes *payload = NULL;
gulong handler1;
gulong handler2;
start_web_service_and_create_client (test, data, &ws, &service);
WAIT_UNTIL (web_socket_connection_get_ready_state (ws) != WEB_SOCKET_STATE_CONNECTING);
g_assert (web_socket_connection_get_ready_state (ws) == WEB_SOCKET_STATE_OPEN);
/* Send the logout control message */
send_control_message (ws, "init", NULL, BUILD_INTS, "version", 1, NULL);
send_control_message (ws, "open", "444", "payload", "echo", NULL);
handler1 = g_signal_connect (ws, "message", G_CALLBACK (on_message_get_control), &control);
handler2 = g_signal_connect (ws, "message", G_CALLBACK (on_message_get_non_control), &payload);
/* First an init message */
while (control == NULL)
g_main_context_iteration (NULL, TRUE);
g_assert_cmpstr (json_object_get_string_member (control, "command"), ==, "init");
json_object_unref (control);
control = NULL;
/* Then a hint that we have a password */
while (control == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"hint\",\"credential\":\"password\"}");
json_object_unref (control);
control = NULL;
/* Then a message that echo channel is open */
while (control == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"open\",\"channel\":\"444\",\"payload\":\"echo\"}");
json_object_unref (control);
control = NULL;
/* Now clear the password */
send_control_message (ws, "authorize", NULL, "response", "basic", NULL);
/* We should now get a hint that we have no password */
while (control == NULL)
g_main_context_iteration (NULL, TRUE);
cockpit_assert_json_eq (control, "{\"command\":\"hint\",\"credential\":\"none\"}");
json_object_unref (control);
control = NULL;
g_signal_handler_disconnect (ws, handler1);
g_signal_handler_disconnect (ws, handler2);
if (control != NULL)
json_object_unref (control);
close_client_and_stop_web_service (test, ws, service);
}
static void
test_parse_external (void)
{
const gchar *content_disposition;
const gchar *content_type;
const gchar *content_encoding;
gchar **protocols;
JsonObject *object;
JsonObject *external;
JsonArray *array;
gboolean ret;
object = json_object_new ();
ret = cockpit_web_service_parse_external (object, NULL, NULL, NULL, NULL);
g_assert (ret == TRUE);
ret = cockpit_web_service_parse_external (object, &content_type, &content_encoding, &content_disposition, &protocols);
g_assert (ret == TRUE);
g_assert (content_type == NULL);
g_assert (content_encoding == NULL);
g_assert (content_disposition == NULL);
g_assert (protocols == NULL);
external = json_object_new ();
json_object_set_object_member (object, "external", external);
ret = cockpit_web_service_parse_external (object, &content_type, &content_encoding, &content_disposition, &protocols);
g_assert (ret == TRUE);
g_assert (content_type == NULL);
g_assert (content_encoding == NULL);
g_assert (content_disposition == NULL);
g_assert (protocols == NULL);
array = json_array_new ();
json_array_add_string_element (array, "one");
json_array_add_string_element (array, "two");
json_array_add_string_element (array, "three");
json_object_set_array_member (external, "protocols", array);
json_object_set_string_member (external, "content-type", "text/plain");
json_object_set_string_member (external, "content-encoding", "gzip");
json_object_set_string_member (external, "content-disposition", "filename; test");
ret = cockpit_web_service_parse_external (object, &content_type, &content_encoding, &content_disposition, &protocols);
g_assert (ret == TRUE);
g_assert_cmpstr (content_type, ==, "text/plain");
g_assert_cmpstr (content_encoding, ==, "gzip");
g_assert_cmpstr (content_disposition, ==, "filename; test");
g_assert (protocols != NULL);
g_assert_cmpstr (protocols[0], ==, "one");
g_assert_cmpstr (protocols[1], ==, "two");
g_assert_cmpstr (protocols[2], ==, "three");
g_assert_cmpstr (protocols[3], ==, NULL);
g_free (protocols);
json_object_unref (object);
}
static void
test_host_checksums (void)
{
CockpitTransport *transport;
CockpitWebService *service;
CockpitCreds *creds;
int fds[2];
if (pipe(fds) < 0)
g_assert_not_reached();
transport = cockpit_pipe_transport_new_fds ("unused", fds[0], fds[1]);
creds = cockpit_creds_new ("cockpit", NULL);
service = cockpit_web_service_new (creds, transport);
cockpit_web_service_set_host_checksum(service, "localhost", "checksum1");
cockpit_web_service_set_host_checksum(service, "host1", "checksum1");
cockpit_web_service_set_host_checksum(service, "host2", "checksum2");
g_assert_cmpstr (cockpit_web_service_get_host (service, "checksum1"), ==, "localhost");
g_assert_cmpstr (cockpit_web_service_get_host (service, "checksum2"), ==, "host2");
g_assert_cmpstr (cockpit_web_service_get_host (service, "bad"), ==, NULL);
g_assert_cmpstr (cockpit_web_service_get_checksum (service, "host1"), ==, "checksum1");
g_assert_cmpstr (cockpit_web_service_get_checksum (service, "host2"), ==, "checksum2");
g_assert_cmpstr (cockpit_web_service_get_checksum (service, "localhost"), ==, "checksum1");
g_assert_cmpstr (cockpit_web_service_get_checksum (service, "bad"), ==, NULL);
cockpit_web_service_set_host_checksum(service, "host2", "checksum3");
g_assert_cmpstr (cockpit_web_service_get_checksum (service, "host2"), ==, "checksum3");
g_assert_cmpstr (cockpit_web_service_get_host (service, "checksum3"), ==, "host2");
g_assert_cmpstr (cockpit_web_service_get_host (service, "checksum2"), ==, NULL);
g_object_unref (service);
g_object_unref (transport);
cockpit_creds_unref (creds);
}
typedef struct {
const gchar *name;
const gchar *input;
const gchar *message;
} ParseExternalFailure;
static ParseExternalFailure external_failure_fixtures[] = {
{ "bad-channel", "{ \"channel\": \"blah\" }", "don't specify \"channel\" on external channel" },
{ "bad-command", "{ \"command\": \"test\" }", "don't specify \"command\" on external channel" },
{ "bad-external", "{ \"external\": \"test\" }", "invalid \"external\" option" },
{ "bad-disposition", "{ \"external\": { \"content-disposition\": 5 } }", "invalid*content-disposition*" },
{ "invalid-disposition", "{ \"external\": { \"content-disposition\": \"xx\nx\" } }", "invalid*content-disposition*" },
{ "bad-type", "{ \"external\": { \"content-type\": 5 } }", "invalid*content-type*" },
{ "invalid-type", "{ \"external\": { \"content-type\": \"xx\nx\" } }", "invalid*content-type*" },
{ "bad-protocols", "{ \"external\": { \"protocols\": \"xx\nx\" } }", "invalid*protocols*" },
};
static void
test_parse_external_failure (gconstpointer data)
{
const ParseExternalFailure *fixture = data;
GError *error = NULL;
JsonObject *object;
gboolean ret;
object = cockpit_json_parse_object (fixture->input, -1, &error);
g_assert_no_error (error);
cockpit_expect_message (fixture->message);
ret = cockpit_web_service_parse_external (object, NULL, NULL, NULL, NULL);
g_assert (ret == FALSE);
json_object_unref (object);
cockpit_assert_expected ();
}
static gboolean
on_hack_raise_sigchld (gpointer user_data)
{
raise (SIGCHLD);
return TRUE;
}
int
main (int argc,
char *argv[])
{
gchar *name;
gint i;
cockpit_test_init (&argc, &argv);
/*
* HACK: Work around races in glib SIGCHLD handling.
*
* https://bugzilla.gnome.org/show_bug.cgi?id=731771
* https://bugzilla.gnome.org/show_bug.cgi?id=711090
*/
g_timeout_add_seconds (1, on_hack_raise_sigchld, NULL);
/* Try to debug crashing during tests */
signal (SIGSEGV, cockpit_test_signal_backtrace);
/* We don't want to test the ping functionality in these tests */
cockpit_ws_ping_interval = G_MAXUINT;
static const TestFixture fixture_rfc6455 = {
.config = NULL
};
g_test_add ("/web-service/handshake-and-auth/rfc6455", TestCase,
&fixture_rfc6455, setup_for_socket,
test_handshake_and_auth, teardown_for_socket);
g_test_add ("/web-service/echo-message/rfc6455", TestCase,
&fixture_rfc6455, setup_for_socket,
test_handshake_and_echo, teardown_for_socket);
g_test_add ("/web-service/echo-message/large", TestCase,
&fixture_rfc6455, setup_for_socket,
test_echo_large, teardown_for_socket);
g_test_add ("/web-service/null-creds", TestCase, NULL,
setup_for_socket, test_socket_null_creds, teardown_for_socket);
g_test_add ("/web-service/no-init", TestCase, NULL,
setup_for_socket, test_no_init, teardown_for_socket);
g_test_add ("/web-service/wrong-init-version", TestCase, NULL,
setup_for_socket, test_wrong_init_version, teardown_for_socket);
g_test_add ("/web-service/bad-init-version", TestCase, NULL,
setup_for_socket, test_bad_init_version, teardown_for_socket);
g_test_add ("/web-service/bad-origin/rfc6455", TestCase,
&fixture_bad_origin_rfc6455, setup_for_socket,
test_bad_origin, teardown_for_socket);
g_test_add ("/web-service/bad-origin/withallowed", TestCase,
&fixture_bad_origin_rfc6455, setup_for_socket,
test_bad_origin, teardown_for_socket);
g_test_add ("/web-service/allowed-origin/rfc6455", TestCase,
&fixture_allowed_origin_rfc6455, setup_for_socket,
test_handshake_and_auth, teardown_for_socket);
g_test_add ("/web-service/bad-origin/protocol-no-config", TestCase,
&fixture_bad_origin_proto_no_config, setup_for_socket,
test_bad_origin, teardown_for_socket);
g_test_add ("/web-service/bad-origin/protocol-no-header", TestCase,
&fixture_bad_origin_proto_no_header, setup_for_socket,
test_bad_origin, teardown_for_socket);
g_test_add ("/web-service/allowed-origin/protocol-header", TestCase,
&fixture_allowed_origin_proto_header, setup_for_socket,
test_handshake_and_auth, teardown_for_socket);
g_test_add ("/web-service/allowed-origin/tls-proxy", TestCase,
&fixture_allowed_origin_tls_proxy, setup_for_socket,
test_handshake_and_auth, teardown_for_socket);
g_test_add ("/web-service/bad-origin/tls-proxy", TestCase,
&fixture_bad_origin_tls_proxy, setup_for_socket,
test_bad_origin, teardown_for_socket);
g_test_add ("/web-service/close-error", TestCase,
NULL, setup_for_socket,
test_close_error, teardown_for_socket);
g_test_add ("/web-service/kill-group", TestCase, &fixture_kill_group,
setup_for_socket, test_kill_group, teardown_for_socket);
g_test_add ("/web-service/kill-host", TestCase, &fixture_kill_group,
setup_for_socket, test_kill_host, teardown_for_socket);
g_test_add ("/web-service/idling-signal", TestCase, NULL,
setup_for_socket, test_idling, teardown_for_socket);
g_test_add ("/web-service/force-dispose", TestCase, NULL,
setup_for_socket, test_dispose, teardown_for_socket);
g_test_add ("/web-service/logout", TestCase, NULL,
setup_for_socket, test_logout, teardown_for_socket);
g_test_add ("/web-service/authorize/hint", TestCase, NULL,
setup_for_socket, test_hint_credential, teardown_for_socket);
g_test_add ("/web-service/authorize/password", TestCase, NULL,
setup_for_socket, test_authorize_password, teardown_for_socket);
g_test_add_func ("/web-service/parse-external/success", test_parse_external);
g_test_add_func ("/web-service/host-checksums", test_host_checksums);
for (i = 0; i < G_N_ELEMENTS (external_failure_fixtures); i++)
{
name = g_strdup_printf ("/web-service/parse-external/%s", external_failure_fixtures[i].name);
g_test_add_data_func (name, external_failure_fixtures + i, test_parse_external_failure);
g_free (name);
}
return g_test_run ();
}