diff options
Diffstat (limited to 'gio/tests/fdo-notification-backend.c')
-rw-r--r-- | gio/tests/fdo-notification-backend.c | 326 |
1 files changed, 326 insertions, 0 deletions
diff --git a/gio/tests/fdo-notification-backend.c b/gio/tests/fdo-notification-backend.c new file mode 100644 index 000000000..aed9aab67 --- /dev/null +++ b/gio/tests/fdo-notification-backend.c @@ -0,0 +1,326 @@ +/* + * Copyright © 2022 Endless OS Foundation, LLC + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + * This library 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. + * + * This library 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 this library; if not, see <http://www.gnu.org/licenses/>. + * + * Author: Philip Withnall <pwithnall@endlessos.org> + */ + +#include <gio/gio.h> +#include <locale.h> + +#include <gio/giomodule-priv.h> +#include "gio/gnotificationbackend.h" + + +static GNotificationBackend * +construct_backend (GApplication **app_out) +{ + GApplication *app = NULL; + GType fdo_type = G_TYPE_INVALID; + GNotificationBackend *backend = NULL; + GError *local_error = NULL; + + /* Construct the app first and withdraw a notification, to ensure that IO modules are loaded. */ + app = g_application_new ("org.gtk.TestApplication", G_APPLICATION_DEFAULT_FLAGS); + g_application_register (app, NULL, &local_error); + g_assert_no_error (local_error); + g_application_withdraw_notification (app, "org.gtk.TestApplication.NonexistentNotification"); + + fdo_type = g_type_from_name ("GFdoNotificationBackend"); + g_assert_cmpuint (fdo_type, !=, G_TYPE_INVALID); + + /* Replicate the behaviour from g_notification_backend_new_default(), which is + * not exported publicly so can‘t be easily used in the test. */ + backend = g_object_new (fdo_type, NULL); + backend->application = app; + backend->dbus_connection = g_application_get_dbus_connection (app); + if (backend->dbus_connection) + g_object_ref (backend->dbus_connection); + + if (app_out != NULL) + *app_out = g_object_ref (app); + + g_clear_object (&app); + + return g_steal_pointer (&backend); +} + +static void +test_construction (void) +{ + GNotificationBackend *backend = NULL; + GApplication *app = NULL; + GTestDBus *bus = NULL; + + g_test_message ("Test constructing a GFdoNotificationBackend"); + + /* Set up a test session bus and connection. */ + bus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (bus); + + backend = construct_backend (&app); + g_assert_nonnull (backend); + + g_application_quit (app); + g_clear_object (&app); + g_clear_object (&backend); + + g_test_dbus_down (bus); + g_clear_object (&bus); +} + +static void +daemon_method_call_cb (GDBusConnection *connection, + const gchar *sender, + const gchar *object_path, + const gchar *interface_name, + const gchar *method_name, + GVariant *parameters, + GDBusMethodInvocation *invocation, + gpointer user_data) +{ + GDBusMethodInvocation **current_method_invocation_out = user_data; + + g_assert_null (*current_method_invocation_out); + *current_method_invocation_out = g_steal_pointer (&invocation); + + g_main_context_wakeup (NULL); +} + +static void +name_acquired_or_lost_cb (GDBusConnection *connection, + const gchar *name, + gpointer user_data) +{ + gboolean *name_acquired = user_data; + + *name_acquired = !*name_acquired; + + g_main_context_wakeup (NULL); +} + +static void +dbus_activate_action_cb (GSimpleAction *action, + GVariant *parameter, + gpointer user_data) +{ + guint *n_activations = user_data; + + *n_activations = *n_activations + 1; + g_main_context_wakeup (NULL); +} + +static void +assert_send_notification (GNotificationBackend *backend, + GDBusMethodInvocation **current_method_invocation, + guint32 notify_id) +{ + GNotification *notification = NULL; + + notification = g_notification_new ("Some Notification"); + G_NOTIFICATION_BACKEND_GET_CLASS (backend)->send_notification (backend, "notification1", notification); + g_clear_object (¬ification); + + while (*current_method_invocation == NULL) + g_main_context_iteration (NULL, TRUE); + + g_assert_cmpstr (g_dbus_method_invocation_get_interface_name (*current_method_invocation), ==, "org.freedesktop.Notifications"); + g_assert_cmpstr (g_dbus_method_invocation_get_method_name (*current_method_invocation), ==, "Notify"); + g_dbus_method_invocation_return_value (g_steal_pointer (current_method_invocation), g_variant_new ("(u)", notify_id)); +} + +static void +assert_emit_action_invoked (GDBusConnection *daemon_connection, + GVariant *parameters) +{ + GError *local_error = NULL; + + g_dbus_connection_emit_signal (daemon_connection, + NULL, + "/org/freedesktop/Notifications", + "org.freedesktop.Notifications", + "ActionInvoked", + parameters, + &local_error); + g_assert_no_error (local_error); +} + +static void +test_dbus_activate_action (void) +{ + /* Very trimmed down version of + * https://specifications.freedesktop.org/notification-spec/notification-spec-latest.html */ + const GDBusArgInfo daemon_notify_in_app_name = { -1, "AppName", "s", NULL }; + const GDBusArgInfo daemon_notify_in_replaces_id = { -1, "ReplacesId", "u", NULL }; + const GDBusArgInfo daemon_notify_in_app_icon = { -1, "AppIcon", "s", NULL }; + const GDBusArgInfo daemon_notify_in_summary = { -1, "Summary", "s", NULL }; + const GDBusArgInfo daemon_notify_in_body = { -1, "Body", "s", NULL }; + const GDBusArgInfo daemon_notify_in_actions = { -1, "Actions", "as", NULL }; + const GDBusArgInfo daemon_notify_in_hints = { -1, "Hints", "a{sv}", NULL }; + const GDBusArgInfo daemon_notify_in_expire_timeout = { -1, "ExpireTimeout", "i", NULL }; + const GDBusArgInfo *daemon_notify_in_args[] = + { + &daemon_notify_in_app_name, + &daemon_notify_in_replaces_id, + &daemon_notify_in_app_icon, + &daemon_notify_in_summary, + &daemon_notify_in_body, + &daemon_notify_in_actions, + &daemon_notify_in_hints, + &daemon_notify_in_expire_timeout, + NULL + }; + const GDBusArgInfo daemon_notify_out_id = { -1, "Id", "u", NULL }; + const GDBusArgInfo *daemon_notify_out_args[] = { &daemon_notify_out_id, NULL }; + const GDBusMethodInfo daemon_notify_info = { -1, "Notify", (GDBusArgInfo **) daemon_notify_in_args, (GDBusArgInfo **) daemon_notify_out_args, NULL }; + const GDBusMethodInfo *daemon_methods[] = { &daemon_notify_info, NULL }; + const GDBusInterfaceInfo daemon_interface_info = { -1, "org.freedesktop.Notifications", (GDBusMethodInfo **) daemon_methods, NULL, NULL, NULL }; + + GTestDBus *bus = NULL; + GDBusConnection *daemon_connection = NULL; + guint daemon_object_id = 0, daemon_name_id = 0; + const GDBusInterfaceVTable vtable = { daemon_method_call_cb, NULL, NULL, { NULL, } }; + GDBusMethodInvocation *current_method_invocation = NULL; + GApplication *app = NULL; + GNotificationBackend *backend = NULL; + guint32 notify_id; + GError *local_error = NULL; + const GActionEntry entries[] = + { + { "undo", dbus_activate_action_cb, NULL, NULL, NULL, { 0 } }, + { "lang", dbus_activate_action_cb, "s", "'latin'", NULL, { 0 } }, + }; + guint n_activations = 0; + gboolean name_acquired = FALSE; + + g_test_summary ("Test how the backend handles valid and invalid ActionInvoked signals from the daemon"); + + /* Set up a test session bus and connection. */ + bus = g_test_dbus_new (G_TEST_DBUS_NONE); + g_test_dbus_up (bus); + + /* Create a mock org.freedesktop.Notifications daemon */ + daemon_connection = g_dbus_connection_new_for_address_sync (g_test_dbus_get_bus_address (bus), + G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_CLIENT | + G_DBUS_CONNECTION_FLAGS_MESSAGE_BUS_CONNECTION, + NULL, NULL, &local_error); + g_assert_no_error (local_error); + + daemon_object_id = g_dbus_connection_register_object (daemon_connection, + "/org/freedesktop/Notifications", + (GDBusInterfaceInfo *) &daemon_interface_info, + &vtable, + ¤t_method_invocation, NULL, &local_error); + g_assert_no_error (local_error); + + daemon_name_id = g_bus_own_name_on_connection (daemon_connection, + "org.freedesktop.Notifications", + G_BUS_NAME_OWNER_FLAGS_DO_NOT_QUEUE, + name_acquired_or_lost_cb, + name_acquired_or_lost_cb, + &name_acquired, NULL); + + while (!name_acquired) + g_main_context_iteration (NULL, TRUE); + + /* Construct our FDO backend under test */ + backend = construct_backend (&app); + g_action_map_add_action_entries (G_ACTION_MAP (app), entries, G_N_ELEMENTS (entries), &n_activations); + + /* Send a notification to ensure that the backend is listening for D-Bus action signals. */ + notify_id = 1233; + assert_send_notification (backend, ¤t_method_invocation, ++notify_id); + + /* Send a valid fake action signal. */ + n_activations = 0; + assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.undo")); + + while (n_activations == 0) + g_main_context_iteration (NULL, TRUE); + + g_assert_cmpuint (n_activations, ==, 1); + + /* Send a valid fake action signal with a target. We have to create a new + * notification first, as invoking an action on a notification removes it, and + * the GLib implementation of org.freedesktop.Notifications doesn’t currently + * support the `resident` hint to avoid that. */ + assert_send_notification (backend, ¤t_method_invocation, ++notify_id); + n_activations = 0; + assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.lang::spanish")); + + while (n_activations == 0) + g_main_context_iteration (NULL, TRUE); + + g_assert_cmpuint (n_activations, ==, 1); + + /* Send a series of invalid action signals, followed by one valid one which + * we should be able to detect. */ + assert_send_notification (backend, ¤t_method_invocation, ++notify_id); + n_activations = 0; + assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.nonexistent")); + assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.lang(13)")); + assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.undo::should-have-no-parameter")); + assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.lang")); + assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "undo")); /* no `app.` prefix */ + assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.lang(")); /* invalid parse format */ + assert_emit_action_invoked (daemon_connection, g_variant_new ("(us)", notify_id, "app.undo")); + + while (n_activations == 0) + g_main_context_iteration (NULL, TRUE); + + g_assert_cmpuint (n_activations, ==, 1); + + /* Shut down. */ + g_assert_null (current_method_invocation); + + g_application_quit (app); + g_clear_object (&app); + g_clear_object (&backend); + + g_dbus_connection_unregister_object (daemon_connection, daemon_object_id); + g_bus_unown_name (daemon_name_id); + + g_dbus_connection_flush_sync (daemon_connection, NULL, &local_error); + g_assert_no_error (local_error); + g_dbus_connection_close_sync (daemon_connection, NULL, &local_error); + g_assert_no_error (local_error); + + g_clear_object (&daemon_connection); + + g_test_dbus_down (bus); + g_clear_object (&bus); +} + +int +main (int argc, + char *argv[]) +{ + setlocale (LC_ALL, ""); + + /* Force use of the FDO backend */ + g_setenv ("GNOTIFICATION_BACKEND", "freedesktop", TRUE); + + g_test_init (&argc, &argv, NULL); + + /* Make sure we don’t send notifications to the actual D-Bus session. */ + g_test_dbus_unset (); + + g_test_add_func ("/fdo-notification-backend/construction", test_construction); + g_test_add_func ("/fdo-notification-backend/dbus/activate-action", test_dbus_activate_action); + + return g_test_run (); +} |