[gnome-calendar] tests: Add calendar server and tests



commit 2aa9bc1f61c4abbea9735878d3c7ff49be0b3786
Author: Georges Basile Stavracas Neto <georges stavracas gmail com>
Date:   Wed Jul 10 17:14:47 2019 -0300

    tests: Add calendar server and tests
    
    This will be used to write a propert test suite
    for the new calendar discoverer code.

 tests/calendar.ics         |  32 +++++
 tests/gcal-simple-server.c | 330 +++++++++++++++++++++++++++++++++++++++++++++
 tests/gcal-simple-server.h |  45 +++++++
 tests/meson.build          |   2 +
 tests/test-server.c        | 265 ++++++++++++++++++++++++++++++++++++
 5 files changed, 674 insertions(+)
---
diff --git a/tests/calendar.ics b/tests/calendar.ics
new file mode 100644
index 00000000..30d98d8f
--- /dev/null
+++ b/tests/calendar.ics
@@ -0,0 +1,32 @@
+BEGIN:VCALENDAR
+VERSION:2.0
+CALSCALE:GREGORIAN
+BEGIN:VEVENT
+SUMMARY:Access-A-Ride Pickup
+DTSTART;TZID=America/New_York:20130802T103400
+DTEND;TZID=America/New_York:20130802T110400
+LOCATION:1000 Broadway Ave.\, Brooklyn
+DESCRIPTION: Access-A-Ride trip to 900 Jay St.\, Brooklyn
+STATUS:CONFIRMED
+SEQUENCE:3
+BEGIN:VALARM
+TRIGGER:-PT10M
+DESCRIPTION:Pickup Reminder
+ACTION:DISPLAY
+END:VALARM
+END:VEVENT
+BEGIN:VEVENT
+SUMMARY:Access-A-Ride Pickup
+DTSTART;TZID=America/New_York:20130802T200000
+DTEND;TZID=America/New_York:20130802T203000
+LOCATION:900 Jay St.\, Brooklyn
+DESCRIPTION: Access-A-Ride trip to 1000 Broadway Ave.\, Brooklyn
+STATUS:CONFIRMED
+SEQUENCE:3
+BEGIN:VALARM
+TRIGGER:-PT10M
+DESCRIPTION:Pickup Reminder
+ACTION:DISPLAY
+END:VALARM
+END:VEVENT
+END:VCALENDAR
diff --git a/tests/gcal-simple-server.c b/tests/gcal-simple-server.c
new file mode 100644
index 00000000..21ad7db8
--- /dev/null
+++ b/tests/gcal-simple-server.c
@@ -0,0 +1,330 @@
+/* gcal-simple-server.c
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program 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.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#define G_LOG_DOMAIN "GcalSimpleServer"
+
+#include "gcal-simple-server.h"
+
+struct _GcalSimpleServer
+{
+  GObject             parent;
+
+  /* Only valid while running */
+  GThread            *thread;
+  GMainLoop          *thread_mainloop;
+  SoupServer         *server;
+  gchar              *uri;
+
+  GMutex              running_mutex;
+  GCond               running_cond;
+  gboolean            running;
+};
+
+G_DEFINE_TYPE (GcalSimpleServer, gcal_simple_server, G_TYPE_OBJECT)
+
+
+/*
+ * Auxiliary methods
+ */
+
+static void
+process_get (SoupMessage *message,
+             const gchar *prefix,
+             const gchar *path)
+{
+  g_autofree gchar *calendar_path = g_strdup_printf ("%s/calendar", prefix);
+  g_autofree gchar *calendar_file = g_strdup_printf ("%s/calendar.ics", prefix);
+
+  if (g_strcmp0 (path, calendar_path) == 0)
+    {
+      g_debug ("Serving empty calendar");
+      soup_message_set_response (message,
+                                 "text/calendar",
+                                 SOUP_MEMORY_STATIC,
+                                 GCAL_TEST_SERVER_EMPTY_CALENDAR,
+                                 strlen (GCAL_TEST_SERVER_EMPTY_CALENDAR));
+    }
+
+  if (g_strcmp0 (path, calendar_file) == 0)
+    {
+      g_autoptr (SoupBuffer) buffer = NULL;
+      GMappedFile *mapping;
+
+      g_debug ("Serving calendar.ics");
+
+      mapping = g_mapped_file_new (path, FALSE, NULL);
+      if (!mapping)
+        {
+          soup_message_set_status (message, SOUP_STATUS_INTERNAL_SERVER_ERROR);
+          return;
+        }
+
+      buffer = soup_buffer_new_with_owner (g_mapped_file_get_contents (mapping),
+                                           g_mapped_file_get_length (mapping),
+                                           mapping,
+                                           (GDestroyNotify)g_mapped_file_unref);
+      soup_message_body_append_buffer (message->response_body, buffer);
+    }
+}
+
+static void
+process_caldav (SoupMessage *message,
+                const gchar *path)
+{
+  g_message ("Processing CalDAV");
+}
+
+
+/*
+ * Callbacks
+ */
+
+static gboolean
+idle_quit_server_cb (gpointer user_data)
+{
+  GcalSimpleServer *self = user_data;
+
+  g_main_loop_quit (self->thread_mainloop);
+
+  return G_SOURCE_REMOVE;
+}
+
+static void
+no_auth_handler_cb (SoupServer        *server,
+                    SoupMessage       *message,
+                    const gchar       *path,
+                    GHashTable        *query,
+                    SoupClientContext *client,
+                    gpointer           user_data)
+{
+  g_debug ("No authenticaton needed");
+
+  if (message->method == SOUP_METHOD_GET)
+    process_get (message, "/public", path);
+  else if (message->method == SOUP_METHOD_PROPFIND)
+    process_caldav (message, path);
+  else
+    soup_message_set_status (message, SOUP_STATUS_NOT_IMPLEMENTED);
+}
+
+static void
+auth_handler_cb (SoupServer        *server,
+                 SoupMessage       *message,
+                 const gchar       *path,
+                 GHashTable        *query,
+                 SoupClientContext *client,
+                 gpointer           user_data)
+{
+  g_debug ("Needs authenticaton");
+
+  if (message->method == SOUP_METHOD_GET)
+    process_get (message, "/secret-area", path);
+  else if (message->method == SOUP_METHOD_PROPFIND)
+    process_caldav (message, path);
+  else
+    soup_message_set_status (message, SOUP_STATUS_NOT_IMPLEMENTED);
+}
+
+static gboolean
+authorize_cb (SoupAuthDomain *domain,
+              SoupMessage    *message,
+              const char     *username,
+              const char     *password,
+              gpointer        user_data)
+{
+  const struct {
+    const gchar *username;
+    const gchar *password;
+  } valid_credentials[] = {
+    { "unicorn", "iamnotahorse" },
+    { "feaneron", "idonotmaintainanything" },
+    { "purism", "FINISHTHEPHONE!" },
+  };
+  guint i;
+
+  g_debug ("Authorizing username: '%s', password: '%s'", username, password);
+
+  if (!username && !password)
+    return FALSE;
+
+  for (i = 0; i < G_N_ELEMENTS (valid_credentials); i++)
+    {
+      if (g_strcmp0 (valid_credentials[i].username, username) == 0 &&
+          g_strcmp0 (valid_credentials[i].password, password) == 0)
+        {
+          return TRUE;
+        }
+    }
+
+  return FALSE;
+}
+
+static gpointer
+run_server_in_thread (gpointer data)
+{
+  g_autoptr (SoupAuthDomain) domain = NULL;
+  g_autoptr (GMainContext) context = NULL;
+  g_autoptr (SoupServer) server = NULL;
+  g_autoptr (GMainLoop) mainloop = NULL;
+  g_autoslist (SoupURI) uris = NULL;
+  g_autoptr (GError) error = NULL;
+  GcalSimpleServer *self = data;
+  g_autofree gchar *uri = NULL;
+
+  context = g_main_context_new ();
+  g_main_context_push_thread_default (context);
+  mainloop = g_main_loop_new (context, FALSE);
+  self->thread_mainloop = mainloop;
+
+  server = soup_server_new (SOUP_SERVER_SERVER_HEADER, "gcal-simple-server",
+                            NULL);
+
+  /* Anything under /secret-area and /private requires authentication */
+  domain = soup_auth_domain_basic_new (SOUP_AUTH_DOMAIN_REALM, "GcalSimpleServer",
+                                       SOUP_AUTH_DOMAIN_BASIC_AUTH_CALLBACK, authorize_cb,
+                                       SOUP_AUTH_DOMAIN_BASIC_AUTH_DATA, self,
+                                       SOUP_AUTH_DOMAIN_ADD_PATH, "/secret-area",
+                                       NULL);
+  soup_server_add_auth_domain (server, domain);
+
+  soup_server_listen_local (server, 0, SOUP_SERVER_LISTEN_IPV4_ONLY, &error);
+  if (error)
+    {
+      g_critical ("Error starting up server: %s", error->message);
+      return NULL;
+    }
+
+  soup_server_add_handler (server, NULL, no_auth_handler_cb, NULL, NULL);
+  soup_server_add_handler (server, "/public", no_auth_handler_cb, NULL, NULL);
+  soup_server_add_handler (server, "/secret-area", auth_handler_cb, NULL, NULL);
+
+  uris = soup_server_get_uris (server);
+  g_assert_cmpint (g_slist_length (uris), ==, 1);
+
+  uri = soup_uri_to_string (uris->data, FALSE);
+  self->uri = g_strdup (uri);
+
+  g_debug ("Listening on %s", uri);
+
+  g_mutex_lock (&self->running_mutex);
+  self->running = TRUE;
+  g_cond_signal (&self->running_cond);
+  g_mutex_unlock (&self->running_mutex);
+
+  g_main_loop_run (mainloop);
+
+  g_debug ("Stopping");
+
+  soup_server_disconnect (server);
+
+  g_main_context_pop_thread_default (context);
+
+  return NULL;
+}
+
+
+/*
+ * GObject overrides
+ */
+
+static void
+gcal_simple_server_finalize (GObject *object)
+{
+  GcalSimpleServer *self = (GcalSimpleServer *)object;
+
+  if (self->thread)
+    gcal_simple_server_stop (self);
+
+  g_clear_pointer (&self->uri, g_free);
+
+  G_OBJECT_CLASS (gcal_simple_server_parent_class)->finalize (object);
+}
+
+static void
+gcal_simple_server_class_init (GcalSimpleServerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gcal_simple_server_finalize;
+}
+
+static void
+gcal_simple_server_init (GcalSimpleServer *self)
+{
+}
+
+GcalSimpleServer *
+gcal_simple_server_new (void)
+{
+  return g_object_new (GCAL_TYPE_SIMPLE_SERVER, NULL);
+}
+
+void
+gcal_simple_server_start (GcalSimpleServer *self)
+{
+  g_return_if_fail (GCAL_IS_SIMPLE_SERVER (self));
+
+  if (self->thread)
+    {
+      g_warning ("Server already started");
+      return;
+    }
+
+  self->thread = g_thread_new ("gcal_simple_server_thread", run_server_in_thread, self);
+
+  g_mutex_lock (&self->running_mutex);
+  while (!self->running)
+    g_cond_wait (&self->running_cond, &self->running_mutex);
+  g_mutex_unlock (&self->running_mutex);
+}
+
+void
+gcal_simple_server_stop (GcalSimpleServer *self)
+{
+  GMainContext *context;
+
+  g_return_if_fail (GCAL_IS_SIMPLE_SERVER (self));
+
+  if (!self->thread)
+    {
+      g_warning ("Server not running");
+      return;
+    }
+
+  context = g_main_loop_get_context (self->thread_mainloop);
+  soup_add_completion (context, idle_quit_server_cb, self);
+
+  g_mutex_lock (&self->running_mutex);
+  self->running = FALSE;
+  g_cond_signal (&self->running_cond);
+  g_mutex_unlock (&self->running_mutex);
+
+  g_thread_join (self->thread);
+  self->thread = NULL;
+}
+
+SoupURI*
+gcal_simple_server_get_uri (GcalSimpleServer *self)
+{
+  g_return_val_if_fail (GCAL_IS_SIMPLE_SERVER (self), NULL);
+  g_return_val_if_fail (self->thread != NULL, NULL);
+
+  return soup_uri_new (self->uri);
+}
diff --git a/tests/gcal-simple-server.h b/tests/gcal-simple-server.h
new file mode 100644
index 00000000..012041bc
--- /dev/null
+++ b/tests/gcal-simple-server.h
@@ -0,0 +1,45 @@
+/* gcal-simple-server.h
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program 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.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#pragma once
+
+#include <glib-object.h>
+#include <libsoup/soup.h>
+
+G_BEGIN_DECLS
+
+
+#define GCAL_TEST_SERVER_EMPTY_CALENDAR \
+"BEGIN:VCALENDAR\n" \
+"END:VCALENDAR"
+
+
+#define GCAL_TYPE_SIMPLE_SERVER (gcal_simple_server_get_type())
+G_DECLARE_FINAL_TYPE (GcalSimpleServer, gcal_simple_server, GCAL, SIMPLE_SERVER, GObject)
+
+GcalSimpleServer*    gcal_simple_server_new                      (void);
+
+void                 gcal_simple_server_start                    (GcalSimpleServer   *self);
+
+void                 gcal_simple_server_stop                     (GcalSimpleServer   *self);
+
+SoupURI*             gcal_simple_server_get_uri                  (GcalSimpleServer   *self);
+
+G_END_DECLS
diff --git a/tests/meson.build b/tests/meson.build
index 940b44b7..a45a00ef 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -13,6 +13,7 @@ test_cflags = '-DTEST_DATA_DIR="@0@"'.format(join_paths(meson.source_root(), 'da
 ################
 
 sources = files(
+  'gcal-simple-server.c',
   'gcal-stub-calendar.c',
 )
 
@@ -31,6 +32,7 @@ libgcal_test_dep = declare_dependency(
 )
 
 tests = [
+  'server',
   'event',
   'manager',
 ]
diff --git a/tests/test-server.c b/tests/test-server.c
new file mode 100644
index 00000000..6a8e0f9a
--- /dev/null
+++ b/tests/test-server.c
@@ -0,0 +1,265 @@
+/* test-server.c
+ *
+ * Copyright 2019 Georges Basile Stavracas Neto <georges stavracas gmail com>
+ *
+ * This program 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.
+ *
+ * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <libsoup/soup.h>
+
+#include "gcal-simple-server.h"
+
+static GcalSimpleServer*
+init_server (void)
+{
+  g_autoptr (GcalSimpleServer) server = NULL;
+
+  server = gcal_simple_server_new ();
+  gcal_simple_server_start (server);
+
+  return g_steal_pointer (&server);
+}
+
+static void
+fail_authenticate_cb (SoupSession *session,
+                      SoupMessage *message,
+                      SoupAuth    *auth,
+                      gboolean     retrying,
+                      gpointer     user_data)
+{
+  GcalSimpleServer *server = user_data;
+
+  gcal_simple_server_stop (server);
+
+  g_assert_not_reached ();
+}
+
+static void
+authenticate_cb (SoupSession *session,
+                 SoupMessage *message,
+                 SoupAuth    *auth,
+                 gboolean     retrying,
+                 gpointer     user_data)
+{
+  g_debug ("Authenticating...");
+
+  soup_auth_authenticate (auth, "unicorn", "iamnotahorse");
+}
+
+static void
+wrong_authenticate_cb (SoupSession *session,
+                       SoupMessage *message,
+                       SoupAuth    *auth,
+                       gboolean     retrying,
+                       gpointer     user_data)
+{
+  g_debug ("Authenticating with wrong username...");
+
+  if (!retrying)
+    soup_auth_authenticate (auth, "thyartismurder", "popmusic");
+}
+
+/*********************************************************************************************************************/
+
+static void
+server_init (void)
+{
+  g_autoptr (GcalSimpleServer) server = NULL;
+
+  server = gcal_simple_server_new ();
+  gcal_simple_server_start (server);
+}
+
+/*********************************************************************************************************************/
+
+static void
+server_request_no_auth_empty (void)
+{
+  g_autoptr (GcalSimpleServer) server = NULL;
+  g_autoptr (SoupMessage) message = NULL;
+  g_autoptr (SoupSession) session = NULL;
+  g_autoptr (SoupURI) uri = NULL;
+  g_autoptr (GError) error = NULL;
+
+  server = init_server ();
+  uri = gcal_simple_server_get_uri (server);
+
+  session = soup_session_new ();
+  g_signal_connect (session, "authenticate", G_CALLBACK (fail_authenticate_cb), server);
+
+  message = soup_message_new_from_uri ("GET", uri);
+  soup_session_send (session, message, NULL, &error);
+  g_assert_no_error (error);
+}
+
+/*********************************************************************************************************************/
+
+static void
+server_request_no_auth_ics (void)
+{
+  g_autoptr (GcalSimpleServer) server = NULL;
+  g_autoptr (SoupMessage) message = NULL;
+  g_autoptr (SoupSession) session = NULL;
+  g_autoptr (SoupURI) uri = NULL;
+  g_autoptr (GError) error = NULL;
+
+  server = init_server ();
+  uri = gcal_simple_server_get_uri (server);
+  soup_uri_set_path (uri, "/public/calendar.ics");
+
+  session = soup_session_new ();
+  g_signal_connect (session, "authenticate", G_CALLBACK (fail_authenticate_cb), server);
+
+  message = soup_message_new_from_uri ("GET", uri);
+  soup_session_send (session, message, NULL, &error);
+  g_assert_no_error (error);
+}
+
+/*********************************************************************************************************************/
+
+static void
+server_request_no_auth_calendar (void)
+{
+  g_autoptr (GcalSimpleServer) server = NULL;
+  g_autoptr (SoupMessage) message = NULL;
+  g_autoptr (SoupSession) session = NULL;
+  g_autoptr (SoupURI) uri = NULL;
+  g_autoptr (GError) error = NULL;
+
+  server = init_server ();
+  uri = gcal_simple_server_get_uri (server);
+  soup_uri_set_path (uri, "/public/calendar");
+
+  session = soup_session_new ();
+  g_signal_connect (session, "authenticate", G_CALLBACK (fail_authenticate_cb), server);
+
+  message = soup_message_new_from_uri ("GET", uri);
+  soup_session_send (session, message, NULL, &error);
+  g_assert_no_error (error);
+}
+
+/*********************************************************************************************************************/
+
+static void
+server_request_auth_empty (void)
+{
+  g_autoptr (GcalSimpleServer) server = NULL;
+  g_autoptr (SoupMessage) message = NULL;
+  g_autoptr (SoupSession) session = NULL;
+  g_autoptr (SoupURI) uri = NULL;
+  g_autoptr (GError) error = NULL;
+
+  server = init_server ();
+  uri = gcal_simple_server_get_uri (server);
+  soup_uri_set_path (uri, "/secret-area");
+
+  session = soup_session_new ();
+  g_signal_connect (session, "authenticate", G_CALLBACK (authenticate_cb), server);
+
+  message = soup_message_new_from_uri ("GET", uri);
+  soup_session_send (session, message, NULL, &error);
+  g_assert_no_error (error);
+}
+
+/*********************************************************************************************************************/
+
+static void
+server_request_auth_calendar (void)
+{
+  g_autoptr (GcalSimpleServer) server = NULL;
+  g_autoptr (SoupMessage) message = NULL;
+  g_autoptr (SoupSession) session = NULL;
+  g_autoptr (SoupURI) uri = NULL;
+  g_autoptr (GError) error = NULL;
+
+  server = init_server ();
+  uri = gcal_simple_server_get_uri (server);
+  soup_uri_set_path (uri, "/secret-area/calendar");
+
+  session = soup_session_new ();
+  g_signal_connect (session, "authenticate", G_CALLBACK (authenticate_cb), server);
+
+  message = soup_message_new_from_uri ("GET", uri);
+  soup_session_send (session, message, NULL, &error);
+  g_assert_no_error (error);
+}
+
+/*********************************************************************************************************************/
+
+static void
+server_request_auth_ics (void)
+{
+  g_autoptr (GcalSimpleServer) server = NULL;
+  g_autoptr (SoupMessage) message = NULL;
+  g_autoptr (SoupSession) session = NULL;
+  g_autoptr (SoupURI) uri = NULL;
+  g_autoptr (GError) error = NULL;
+
+  server = init_server ();
+  uri = gcal_simple_server_get_uri (server);
+  soup_uri_set_path (uri, "/secret-area/calendar.ics");
+
+  session = soup_session_new ();
+  g_signal_connect (session, "authenticate", G_CALLBACK (authenticate_cb), server);
+
+  message = soup_message_new_from_uri ("GET", uri);
+  soup_session_send (session, message, NULL, &error);
+  g_assert_no_error (error);
+}
+
+/*********************************************************************************************************************/
+
+static void
+server_request_auth_wrong (void)
+{
+  g_autoptr (GcalSimpleServer) server = NULL;
+  g_autoptr (SoupMessage) message = NULL;
+  g_autoptr (SoupSession) session = NULL;
+  g_autoptr (SoupURI) uri = NULL;
+  g_autoptr (GError) error = NULL;
+
+  server = init_server ();
+  uri = gcal_simple_server_get_uri (server);
+  soup_uri_set_path (uri, "/secret-area");
+
+  session = soup_session_new ();
+  g_signal_connect (session, "authenticate", G_CALLBACK (wrong_authenticate_cb), server);
+
+  message = soup_message_new_from_uri ("GET", uri);
+  soup_session_send (session, message, NULL, &error);
+  g_assert_no_error (error);
+  g_assert_cmpuint (message->status_code, ==, SOUP_STATUS_UNAUTHORIZED);
+}
+
+/*********************************************************************************************************************/
+
+gint
+main (gint   argc,
+      gchar *argv[])
+{
+  g_test_init (&argc, &argv, NULL);
+
+  g_test_add_func ("/server/init", server_init);
+  g_test_add_func ("/server/no-auth/empty", server_request_no_auth_empty);
+  g_test_add_func ("/server/no-auth/calendar", server_request_no_auth_calendar);
+  g_test_add_func ("/server/no-auth/ics", server_request_no_auth_ics);
+  g_test_add_func ("/server/auth/empty", server_request_auth_empty);
+  g_test_add_func ("/server/auth/calendar", server_request_auth_calendar);
+  g_test_add_func ("/server/auth/ics", server_request_auth_ics);
+  g_test_add_func ("/server/auth/wrong", server_request_auth_wrong);
+
+  return g_test_run ();
+}


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]