[folks] Tests: port to verbatim copy of tp-glib files



commit 109133c590822eb93fa9c8245fdb2f7347f5acf6
Author: Xavier Claessens <xavier claessens collabora co uk>
Date:   Tue Mar 27 18:05:12 2012 +0200

    Tests: port to verbatim copy of tp-glib files

 tests/lib/telepathy/contactlist/Makefile.am        |   19 +-
 tests/lib/telepathy/contactlist/account-manager.h  |   66 -
 tests/lib/telepathy/contactlist/account.c          |  341 ----
 tests/lib/telepathy/contactlist/account.h          |   60 -
 tests/lib/telepathy/contactlist/backend.c          |  253 ++--
 tests/lib/telepathy/contactlist/backend.h          |   14 +-
 tests/lib/telepathy/contactlist/conn.c             |  972 ---------
 tests/lib/telepathy/contactlist/conn.h             |   75 -
 .../telepathy/contactlist/contact-list-manager.c   | 2139 ++++++--------------
 .../telepathy/contactlist/contact-list-manager.h   |  103 +-
 tests/lib/telepathy/contactlist/contact-list.c     |  632 ------
 tests/lib/telepathy/contactlist/contact-list.h     |  118 --
 tests/lib/telepathy/contactlist/contacts-conn.c    | 1392 +++++++++++++
 tests/lib/telepathy/contactlist/contacts-conn.h    |  190 ++
 tests/lib/telepathy/contactlist/debug.h            |    3 +
 ...{account-manager.c => simple-account-manager.c} |  146 +-
 .../telepathy/contactlist/simple-account-manager.h |   66 +
 tests/lib/telepathy/contactlist/simple-account.c   |  579 ++++++
 tests/lib/telepathy/contactlist/simple-account.h   |   69 +
 tests/lib/telepathy/contactlist/simple-conn.c      |  452 +++++
 tests/lib/telepathy/contactlist/simple-conn.h      |   77 +
 tests/lib/telepathy/contactlist/textchan-null.c    |  571 ++++++
 tests/lib/telepathy/contactlist/textchan-null.h    |  137 ++
 .../telepathy/contactlist/tp-test-contactlist.h    |    7 +-
 tests/telepathy/individual-properties.vala         |    4 +-
 25 files changed, 4389 insertions(+), 4096 deletions(-)
---
diff --git a/tests/lib/telepathy/contactlist/Makefile.am b/tests/lib/telepathy/contactlist/Makefile.am
index f419bd4..1578f67 100644
--- a/tests/lib/telepathy/contactlist/Makefile.am
+++ b/tests/lib/telepathy/contactlist/Makefile.am
@@ -18,18 +18,21 @@ noinst_LTLIBRARIES = libtp-test-contactlist.la
 
 libtp_test_contactlist_la_SOURCES = \
         _gen/param-spec-struct.h \
-        account.c \
-        account.h \
-        account-manager.c \
-        account-manager.h \
         backend.c \
         backend.h \
-        conn.c \
-        conn.h \
-        contact-list.c \
-        contact-list.h \
         contact-list-manager.c \
         contact-list-manager.h \
+        contacts-conn.c \
+        contacts-conn.h \
+        debug.h \
+        simple-account.c \
+        simple-account.h \
+        simple-account-manager.c \
+        simple-account-manager.h \
+        simple-conn.c \
+        simple-conn.h \
+        textchan-null.c \
+        textchan-null.h \
         util.c \
         util.h \
 	$(NULL)
diff --git a/tests/lib/telepathy/contactlist/backend.c b/tests/lib/telepathy/contactlist/backend.c
index 663322b..5defd35 100644
--- a/tests/lib/telepathy/contactlist/backend.c
+++ b/tests/lib/telepathy/contactlist/backend.c
@@ -24,38 +24,31 @@
 #include <telepathy-glib/dbus.h>
 #include <telepathy-glib/svc-account.h>
 
-#include "account.h"
-#include "account-manager.h"
-#include "conn.h"
-#include "contact-list.h"
+#include "simple-account.h"
+#include "simple-account-manager.h"
 #include "util.h"
+#include "contacts-conn.h"
 
 #include "backend.h"
 
 struct _TpTestsBackendPrivate
 {
-  TpAccountManager *am_proxy;
   TpDBusDaemon *daemon;
-  TpTestsAccountManager *account_manager;
+  TpTestsSimpleAccountManager *account_manager;
+  TpAccountManager *client_am;
   GList *accounts;
 };
 
 typedef struct
 {
-  TpTestsAccount *account;
-  TpBaseConnection *conn;
-  gchar *bus_name;
+  TpTestsSimpleAccount *account;
+  TpBaseConnection *base_connection;
+  TpConnection *client_conn;
   gchar *object_path;
 } AccountData;
 
 G_DEFINE_TYPE (TpTestsBackend, tp_tests_backend, G_TYPE_OBJECT)
 
-enum
-{
-  PROP_CONNECTION = 1,
-  N_PROPS
-};
-
 static void
 tp_tests_backend_init (TpTestsBackend *self)
 {
@@ -79,53 +72,12 @@ tp_tests_backend_finalize (GObject *object)
 }
 
 static void
-tp_tests_backend_get_property (GObject *object,
-    guint property_id,
-    GValue *value,
-    GParamSpec *spec)
-{
-  TpTestsBackend *self = TP_TESTS_BACKEND (object);
-
-  switch (property_id)
-    {
-    case PROP_CONNECTION:
-      g_value_set_object (value, tp_tests_backend_get_connection (self));
-      break;
-
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
-    }
-}
-
-static void
-tp_tests_backend_set_property (GObject *object,
-    guint property_id,
-    const GValue *value,
-    GParamSpec *spec)
-{
-  switch (property_id)
-    {
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
-    }
-}
-
-static void
 tp_tests_backend_class_init (TpTestsBackendClass *klass)
 {
   GObjectClass *object_class = G_OBJECT_CLASS (klass);
-  GParamSpec *param_spec;
 
   g_type_class_add_private (klass, sizeof (TpTestsBackendPrivate));
   object_class->finalize = tp_tests_backend_finalize;
-  object_class->get_property = tp_tests_backend_get_property;
-  object_class->set_property = tp_tests_backend_set_property;
-
-  param_spec = g_param_spec_object ("connection", "Connection",
-      "The base ContactListConnection",
-      TP_TESTS_TYPE_CONTACT_LIST_CONNECTION,
-      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
-  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
 }
 
 TpTestsBackend *
@@ -200,79 +152,152 @@ tp_tests_backend_set_up (TpTestsBackend *self)
           TP_ACCOUNT_MANAGER_BUS_NAME, error->message);
     }
 
-  priv->account_manager = tp_tests_account_manager_new ();
+  priv->account_manager = tp_tests_object_new_static_class (
+      TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER, NULL);
   tp_dbus_daemon_register_object (priv->daemon, TP_ACCOUNT_MANAGER_OBJECT_PATH,
       priv->account_manager);
 
-  priv->am_proxy = tp_account_manager_dup ();
+  priv->client_am = tp_account_manager_dup ();
+}
+
+static void
+fill_default_roster (AccountData *data)
+{
+  TpTestsContactsConnection *conn = (TpTestsContactsConnection *) data->base_connection;
+  TpTestsContactListManager *manager;
+  TpHandleRepoIface *repo;
+  TpHandle handle;
+  const gchar *str;
+  TpTestsContactsConnectionPresenceStatusIndex presence;
+  GPtrArray *info;
+  const gchar *single_value[] = { NULL, NULL };
+  GQuark conn_features[] = { TP_CONNECTION_FEATURE_CONNECTED, 0 };
+
+  repo = tp_base_connection_get_handles (data->base_connection,
+      TP_HANDLE_TYPE_CONTACT);
+  manager = tp_tests_contacts_connection_get_contact_list_manager (conn);
+
+  /* Create some contacts and fill some info */
+  handle = tp_handle_ensure (repo, "guillaume example com", NULL, NULL);
+  tp_tests_contact_list_manager_request_subscription (manager, 1, &handle, "");
+
+  handle = tp_handle_ensure (repo, "sjoerd example com", NULL, NULL);
+  tp_tests_contact_list_manager_request_subscription (manager, 1, &handle, "");
+
+  handle = tp_handle_ensure (repo, "travis example com", NULL, NULL);
+  tp_tests_contact_list_manager_request_subscription (manager, 1, &handle, "");
+
+  handle = tp_handle_ensure (repo, "olivier example com", NULL, NULL);
+  tp_tests_contact_list_manager_request_subscription (manager, 1, &handle, "");
+  str = "Olivier";
+  tp_tests_contacts_connection_change_aliases (conn, 1, &handle, &str);
+  presence = TP_TESTS_CONTACTS_CONNECTION_STATUS_AWAY;
+  str = "";
+  tp_tests_contacts_connection_change_presences (conn, 1, &handle, &presence, &str);
+  tp_tests_contact_list_manager_add_to_group (manager, "Montreal", handle);
+  tp_tests_contact_list_manager_add_to_group (manager, "Francophones", handle);
+  info = g_ptr_array_new_with_free_func ((GDestroyNotify) g_value_array_free);
+  single_value[0] = "+15142345678";
+  g_ptr_array_add (info, tp_value_array_build (3,
+      G_TYPE_STRING, "tel",
+      G_TYPE_STRV, NULL,
+      G_TYPE_STRV, single_value,
+      G_TYPE_INVALID));
+  single_value[0] = "Olivier Crete";
+  g_ptr_array_add (info, tp_value_array_build (3,
+      G_TYPE_STRING, "fn",
+      G_TYPE_STRV, NULL,
+      G_TYPE_STRV, single_value,
+      G_TYPE_INVALID));
+  single_value[0] = "olivier example com";
+  g_ptr_array_add (info, tp_value_array_build (3,
+      G_TYPE_STRING, "email",
+      G_TYPE_STRV, NULL,
+      G_TYPE_STRV, single_value,
+      G_TYPE_INVALID));
+  single_value[0] = "ocrete.example.com";
+  g_ptr_array_add (info, tp_value_array_build (3,
+      G_TYPE_STRING, "url",
+      G_TYPE_STRV, NULL,
+      G_TYPE_STRV, single_value,
+      G_TYPE_INVALID));
+  tp_tests_contacts_connection_change_contact_info (conn, handle, info);
+  g_ptr_array_unref (info);
+
+  handle = tp_handle_ensure (repo, "christian example com", NULL, NULL);
+  tp_tests_contact_list_manager_request_subscription (manager, 1, &handle, "");
+
+  handle = tp_handle_ensure (repo, "geraldine example com", NULL, NULL);
+  tp_tests_contact_list_manager_request_subscription (manager, 1, &handle, "");
+
+  handle = tp_handle_ensure (repo, "helen example com", NULL, NULL);
+  tp_tests_contact_list_manager_request_subscription (manager, 1, &handle, "");
+
+  handle = tp_handle_ensure (repo, "wim example com", NULL, NULL);
+  tp_tests_contact_list_manager_request_subscription (manager, 1, &handle, "");
+
+  /* Run until connected */
+  tp_cli_connection_call_connect (data->client_conn, -1, NULL, NULL, NULL, NULL);
+  tp_tests_proxy_run_until_prepared (data->client_conn, conn_features);
 }
 
 /**
  * tp_tests_backend_add_account:
  * @self:
- * @protocol_name:
  * @user_id:
- * @connection_manager_name:
- * @account_name:
  *
  * Return value: (transfer none):
  */
 gpointer
 tp_tests_backend_add_account (TpTestsBackend *self,
-    const gchar *protocol_name,
+    const gchar *protocol,
     const gchar *user_id,
-    const gchar *connection_manager_name,
-    const gchar *account_name)
+    const gchar *cm_name,
+    const gchar *account)
 {
   TpTestsBackendPrivate *priv = self->priv;
-  TpHandleRepoIface *handle_repo;
-  TpHandle self_handle;
-  gchar *object_path;
   AccountData *data;
+  gchar *conn_path;
   GError *error = NULL;
 
   data = g_slice_new (AccountData);
 
   /* Set up a contact list connection */
-  data->conn =
-      TP_BASE_CONNECTION (tp_tests_contact_list_connection_new (user_id,
-          protocol_name, 0, 0));
-
-  tp_base_connection_register (data->conn, connection_manager_name,
-      &data->bus_name, &data->object_path, &error);
-  if (error != NULL)
-    {
-      g_error ("Failed to register connection %p: %s", data->conn,
-          error->message);
-    }
-
-  handle_repo = tp_base_connection_get_handles (data->conn,
-      TP_HANDLE_TYPE_CONTACT);
-  self_handle = tp_handle_ensure (handle_repo, user_id, NULL, &error);
-  if (error != NULL)
-    {
-      g_error ("Couldn't ensure self handle '%s': %s", user_id, error->message);
-    }
-
-  tp_base_connection_set_self_handle (data->conn, self_handle);
-  tp_base_connection_change_status (data->conn,
-      TP_CONNECTION_STATUS_CONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED);
+  data->base_connection = tp_tests_object_new_static_class (
+        TP_TESTS_TYPE_CONTACTS_CONNECTION,
+        "account", user_id,
+        "protocol", protocol,
+        NULL);
+  tp_base_connection_register (data->base_connection, cm_name,
+        NULL, &conn_path, &error);
+  g_assert_no_error (error);
+
+  data->client_conn = tp_connection_new (priv->daemon, NULL, conn_path,
+      &error);
+  g_assert_no_error (error);
 
   /* Create an account */
-  data->account = tp_tests_account_new (data->object_path);
-  object_path =
-      g_strdup_printf ("%s%s/%s/%s", TP_ACCOUNT_OBJECT_PATH_BASE,
-          connection_manager_name, protocol_name, account_name);
-  tp_dbus_daemon_register_object (priv->daemon, object_path, data->account);
+  data->account = tp_tests_object_new_static_class (
+      TP_TESTS_TYPE_SIMPLE_ACCOUNT, NULL);
+  data->object_path = g_strdup_printf ("%s%s/%s/%s", TP_ACCOUNT_OBJECT_PATH_BASE,
+      cm_name, protocol, account);
+  tp_dbus_daemon_register_object (priv->daemon, data->object_path,
+      data->account);
 
-  /* Add the account to the account manager */
-  tp_tests_account_manager_add_account (priv->account_manager, object_path);
+  /* Set the connection on the account */
+  tp_tests_simple_account_set_connection (data->account, conn_path);
 
-  g_free (object_path);
+  /* Add the account to the account manager */
+  tp_tests_simple_account_manager_add_account (priv->account_manager,
+      data->object_path, TRUE);
 
   /* Add the account to the list of accounts and return a handle to it */
   priv->accounts = g_list_prepend (priv->accounts, data);
 
+  fill_default_roster (data);
+
+  g_free (conn_path);
+
   return data;
 }
 
@@ -293,21 +318,19 @@ tp_tests_backend_remove_account (TpTestsBackend *self,
   data = (AccountData *) handle;
 
   /* Remove the account from the account manager */
-  tp_tests_account_manager_remove_account (priv->account_manager,
+  tp_tests_simple_account_manager_remove_account (priv->account_manager,
       data->object_path);
+  tp_tests_simple_account_removed (data->account);
 
   /* Disconnect it */
-  tp_base_connection_change_status (data->conn,
+  tp_base_connection_change_status (data->base_connection,
       TP_CONNECTION_STATUS_DISCONNECTED, TP_CONNECTION_STATUS_REASON_REQUESTED);
 
-  tp_svc_account_emit_removed (data->account);
-
   tp_dbus_daemon_unregister_object (priv->daemon, data->account);
-  tp_clear_object (&data->account);
-
-  tp_clear_object (&data->conn);
 
-  g_free (data->bus_name);
+  tp_clear_object (&data->account);
+  tp_clear_object (&data->base_connection);
+  tp_clear_object (&data->client_conn);
   g_free (data->object_path);
 }
 
@@ -318,8 +341,8 @@ tp_tests_backend_tear_down (TpTestsBackend *self)
   GError *error = NULL;
 
   /* Make sure all dbus trafic is done */
-  tp_tests_proxy_run_until_dbus_queue_processed (priv->am_proxy);
-  g_clear_object (&priv->am_proxy);
+  tp_tests_proxy_run_until_dbus_queue_processed (priv->client_am);
+  g_clear_object (&priv->client_am);
 
   tp_dbus_daemon_unregister_object (priv->daemon, priv->account_manager);
   tp_clear_object (&priv->account_manager);
@@ -336,23 +359,25 @@ tp_tests_backend_tear_down (TpTestsBackend *self)
 }
 
 /**
- * tp_tests_backend_get_connection:
+ * tp_tests_backend_get_connection_for_handle:
  * @self: the backend
  *
  * Returns: (transfer none): the contact list connection or %NULL.
  */
-TpTestsContactListConnection *
-tp_tests_backend_get_connection (TpTestsBackend *self)
+TpTestsContactsConnection *
+tp_tests_backend_get_connection_for_handle (TpTestsBackend *self,
+    gpointer handle)
 {
+  TpTestsBackendPrivate *priv = self->priv;
   AccountData *data;
 
   g_return_val_if_fail (TP_TESTS_IS_BACKEND (self), NULL);
 
-  if (self->priv->accounts == NULL)
+  if (g_list_find (priv->accounts, handle) == NULL)
     {
       return NULL;
     }
 
-  data = (AccountData *) self->priv->accounts->data;
-  return TP_TESTS_CONTACT_LIST_CONNECTION (data->conn);
+  data = (AccountData *) handle;
+  return TP_TESTS_CONTACTS_CONNECTION (data->base_connection);
 }
diff --git a/tests/lib/telepathy/contactlist/backend.h b/tests/lib/telepathy/contactlist/backend.h
index 77447d5..024b491 100644
--- a/tests/lib/telepathy/contactlist/backend.h
+++ b/tests/lib/telepathy/contactlist/backend.h
@@ -24,7 +24,7 @@
 #include <glib.h>
 #include <glib-object.h>
 
-#include "conn.h"
+#include "contacts-conn.h"
 
 G_BEGIN_DECLS
 
@@ -61,14 +61,16 @@ TpTestsBackend *tp_tests_backend_new (void);
 
 void tp_tests_backend_set_up (TpTestsBackend *self);
 void tp_tests_backend_tear_down (TpTestsBackend *self);
-TpTestsContactListConnection *tp_tests_backend_get_connection (
-    TpTestsBackend *self);
+
+TpTestsContactsConnection *tp_tests_backend_get_connection_for_handle (
+    TpTestsBackend *self,
+    gpointer handle);
 
 gpointer tp_tests_backend_add_account (TpTestsBackend *self,
-    const gchar *protocol_name,
+    const gchar *protocol,
     const gchar *user_id,
-    const gchar *connection_manager_name,
-    const gchar *account_name);
+    const gchar *cm_name,
+    const gchar *account);
 void tp_tests_backend_remove_account (TpTestsBackend *self,
     gpointer handle);
 
diff --git a/tests/lib/telepathy/contactlist/contact-list-manager.c b/tests/lib/telepathy/contactlist/contact-list-manager.c
index b8f19e8..0b78281 100644
--- a/tests/lib/telepathy/contactlist/contact-list-manager.c
+++ b/tests/lib/telepathy/contactlist/contact-list-manager.c
@@ -1,8 +1,8 @@
 /*
  * Example channel manager for contact lists
  *
- * Copyright  2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
- * Copyright  2007-2009 Nokia Corporation
+ * Copyright  2007-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright  2007-2010 Nokia Corporation
  *
  * Copying and distribution of this file, with or without modification,
  * are permitted in any medium without royalty provided the copyright
@@ -14,130 +14,90 @@
 #include "contact-list-manager.h"
 
 #include <string.h>
-
-#include <dbus/dbus-glib.h>
-
 #include <telepathy-glib/telepathy-glib.h>
 
-#include "contact-list.h"
+struct _TpTestsContactListManagerPrivate
+{
+  TpBaseConnection *conn;
 
-/* elements 0, 1... of this array must be kept in sync with elements 1, 2...
- * of the enum TpTestsContactList in contact-list-manager.h */
-static const gchar *_contact_lists[NUM_TP_TESTS_CONTACT_LISTS + 1] = {
-    "subscribe",
-    "publish",
-    "stored",
-    NULL
-};
+  gulong status_changed_id;
 
-const gchar **
-tp_tests_contact_lists (void)
-{
-  return _contact_lists;
-}
+  /* TpHandle => ContactDetails */
+  GHashTable *contact_details;
 
-/* this array must be kept in sync with the enum
- * TpTestsContactListPresence in contact-list-manager.h */
-static const TpPresenceStatusSpec _statuses[] = {
-      { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL },
-      { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL },
-      { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL },
-      { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, NULL },
-      { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE, NULL },
-      { NULL }
+  TpHandleRepoIface *contact_repo;
+  TpHandleRepoIface *group_repo;
+  TpHandleSet *groups;
 };
 
-const TpPresenceStatusSpec *
-tp_tests_contact_list_presence_statuses (void)
-{
-  return _statuses;
-}
+static void contact_groups_iface_init (TpContactGroupListInterface *iface);
+static void mutable_contact_groups_iface_init (
+    TpMutableContactGroupListInterface *iface);
+static void mutable_iface_init (
+    TpMutableContactListInterface *iface);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsContactListManager, tp_tests_contact_list_manager,
+    TP_TYPE_BASE_CONTACT_LIST,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_CONTACT_GROUP_LIST,
+      contact_groups_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_MUTABLE_CONTACT_GROUP_LIST,
+      mutable_contact_groups_iface_init)
+    G_IMPLEMENT_INTERFACE (TP_TYPE_MUTABLE_CONTACT_LIST,
+      mutable_iface_init))
 
 typedef struct {
-    gchar *id;
-    gchar *alias;
+  TpSubscriptionState subscribe;
+  TpSubscriptionState publish;
+  gchar *publish_request;
+  TpHandleSet *groups;
 
-    guint subscribe:1;
-    guint publish:1;
-    guint subscribe_requested:1;
-    guint publish_requested:1;
-
-    TpHandleSet *tags;
-
-    GPtrArray *contact_info;
-} TpTestsContactDetails;
-
-static TpTestsContactDetails *
-tp_tests_contact_details_new (void)
-{
-  return g_slice_new0 (TpTestsContactDetails);
-}
+  TpHandle handle;
+  TpHandleRepoIface *contact_repo;
+} ContactDetails;
 
 static void
-tp_tests_contact_details_destroy (gpointer p)
+contact_detail_destroy (gpointer p)
 {
-  TpTestsContactDetails *d = p;
+  ContactDetails *d = p;
 
-  if (d->tags != NULL)
-    tp_handle_set_destroy (d->tags);
+  g_free (d->publish_request);
+  tp_handle_set_destroy (d->groups);
+  tp_handle_unref (d->contact_repo, d->handle);
 
-  if (d->contact_info != NULL)
-    g_ptr_array_unref (d->contact_info);
-
-  g_free (d->id);
-  g_free (d->alias);
-  g_slice_free (TpTestsContactDetails, d);
+  g_slice_free (ContactDetails, d);
 }
 
-static void channel_manager_iface_init (gpointer, gpointer);
-
-G_DEFINE_TYPE_WITH_CODE (TpTestsContactListManager,
-    tp_tests_contact_list_manager,
-    G_TYPE_OBJECT,
-    G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_MANAGER,
-      channel_manager_iface_init))
-
-enum
-{
-  ALIAS_UPDATED,
-  PRESENCE_UPDATED,
-  CONTACT_INFO_UPDATED,
-  N_SIGNALS
-};
-
-static guint signals[N_SIGNALS] = { 0 };
-
-enum
+static ContactDetails *
+lookup_contact (TpTestsContactListManager *self,
+                TpHandle handle)
 {
-  PROP_CONNECTION = 1,
-  PROP_SIMULATION_DELAY,
-  N_PROPS
-};
+  return g_hash_table_lookup (self->priv->contact_details,
+      GUINT_TO_POINTER (handle));
+}
 
-struct _TpTestsContactListManagerPrivate
+static ContactDetails *
+ensure_contact (TpTestsContactListManager *self,
+                TpHandle handle)
 {
-  TpBaseConnection *conn;
-  guint simulation_delay;
-  TpHandleRepoIface *contact_repo;
-  TpHandleRepoIface *group_repo;
+  ContactDetails *d = lookup_contact (self, handle);
 
-  TpHandleSet *contacts;
-  /* GUINT_TO_POINTER (handle borrowed from contacts)
-   *    => TpTestsContactDetails */
-  GHashTable *contact_details;
-
-  TpTestsContactList *lists[NUM_TP_TESTS_CONTACT_LISTS];
-
-  /* GUINT_TO_POINTER (handle borrowed from channel) => TpTestsContactGroup */
-  GHashTable *groups;
+  if (d == NULL)
+    {
+      d = g_slice_new0 (ContactDetails);
+      d->subscribe = TP_SUBSCRIPTION_STATE_NO;
+      d->publish = TP_SUBSCRIPTION_STATE_NO;
+      d->publish_request = NULL;
+      d->groups = tp_handle_set_new (self->priv->group_repo);
+      d->handle = handle;
+      d->contact_repo = self->priv->contact_repo;
+      tp_handle_ref (d->contact_repo, d->handle);
 
-  /* borrowed TpExportableChannel => GSList of gpointer (request tokens) that
-   * will be satisfied by that channel when the contact list has been
-   * downloaded. The requests are in reverse chronological order */
-  GHashTable *queued_requests;
+      g_hash_table_insert (self->priv->contact_details,
+          GUINT_TO_POINTER (handle), d);
+    }
 
-  gulong status_changed_id;
-};
+  return d;
+}
 
 static void
 tp_tests_contact_list_manager_init (TpTestsContactListManager *self)
@@ -146,94 +106,20 @@ tp_tests_contact_list_manager_init (TpTestsContactListManager *self)
       TP_TESTS_TYPE_CONTACT_LIST_MANAGER, TpTestsContactListManagerPrivate);
 
   self->priv->contact_details = g_hash_table_new_full (g_direct_hash,
-      g_direct_equal, NULL, tp_tests_contact_details_destroy);
-  self->priv->groups = g_hash_table_new_full (g_direct_hash, g_direct_equal,
-      NULL, g_object_unref);
-  self->priv->queued_requests = g_hash_table_new_full (g_direct_hash,
-      g_direct_equal, NULL, NULL);
-
-  /* initialized properly in constructed() */
-  self->priv->contact_repo = NULL;
-  self->priv->group_repo = NULL;
-  self->priv->contacts = NULL;
+      g_direct_equal, NULL, contact_detail_destroy);
 }
 
 static void
-tp_tests_contact_list_manager_close_all (TpTestsContactListManager *self)
+close_all (TpTestsContactListManager *self)
 {
-  guint i;
-
-  if (self->priv->queued_requests != NULL)
-    {
-      GHashTable *tmp = self->priv->queued_requests;
-      GHashTableIter iter;
-      gpointer key, value;
-
-      self->priv->queued_requests = NULL;
-      g_hash_table_iter_init (&iter, tmp);
-
-      while (g_hash_table_iter_next (&iter, &key, &value))
-        {
-          GSList *requests = value;
-          GSList *l;
-
-          requests = g_slist_reverse (requests);
-
-          for (l = requests; l != NULL; l = l->next)
-            {
-              tp_channel_manager_emit_request_failed (self,
-                  l->data, TP_ERRORS, TP_ERROR_DISCONNECTED,
-                  "Unable to complete channel request due to disconnection");
-            }
-
-          g_slist_free (requests);
-          g_hash_table_iter_steal (&iter);
-        }
-
-      g_hash_table_destroy (tmp);
-    }
-
-  if (self->priv->contacts != NULL)
-    {
-      tp_handle_set_destroy (self->priv->contacts);
-      self->priv->contacts = NULL;
-    }
-
-  if (self->priv->contact_details != NULL)
-    {
-      GHashTable *tmp = self->priv->contact_details;
-
-      self->priv->contact_details = NULL;
-      g_hash_table_destroy (tmp);
-    }
-
-  if (self->priv->groups != NULL)
-    {
-      GHashTable *tmp = self->priv->groups;
-
-      self->priv->groups = NULL;
-      g_hash_table_destroy (tmp);
-    }
-
-  for (i = 0; i < NUM_TP_TESTS_CONTACT_LISTS; i++)
-    {
-      if (self->priv->lists[i] != NULL)
-        {
-          TpTestsContactList *list = self->priv->lists[i];
-
-          /* set self->priv->lists[i] to NULL here so list_closed_cb does
-           * not try to delete the list again */
-          self->priv->lists[i] = NULL;
-          g_object_unref (list);
-        }
-    }
-
   if (self->priv->status_changed_id != 0)
     {
       g_signal_handler_disconnect (self->priv->conn,
           self->priv->status_changed_id);
       self->priv->status_changed_id = 0;
     }
+  tp_clear_pointer (&self->priv->contact_details, g_hash_table_unref);
+  tp_clear_pointer (&self->priv->groups, tp_handle_set_destroy);
 }
 
 static void
@@ -241,1051 +127,536 @@ dispose (GObject *object)
 {
   TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (object);
 
-  tp_tests_contact_list_manager_close_all (self);
-  g_assert (self->priv->groups == NULL);
-  g_assert (self->priv->lists[0] == NULL);
-  g_assert (self->priv->queued_requests == NULL);
+  close_all (self);
 
   ((GObjectClass *) tp_tests_contact_list_manager_parent_class)->dispose (
     object);
 }
 
-static void
-get_property (GObject *object,
-              guint property_id,
-              GValue *value,
-              GParamSpec *pspec)
+static TpHandleSet *
+contact_list_dup_contacts (TpBaseContactList *base)
 {
-  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (object);
+  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (base);
+  TpHandleSet *set;
+  GHashTableIter iter;
+  gpointer k, v;
 
-  switch (property_id)
-    {
-    case PROP_CONNECTION:
-      g_value_set_object (value, self->priv->conn);
-      break;
+  set = tp_handle_set_new (self->priv->contact_repo);
 
-    case PROP_SIMULATION_DELAY:
-      g_value_set_uint (value, self->priv->simulation_delay);
-      break;
+  g_hash_table_iter_init (&iter, self->priv->contact_details);
+  while (g_hash_table_iter_next (&iter, &k, &v))
+    {
+      ContactDetails *d = v;
 
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      /* add all the interesting items */
+      if (d->subscribe != TP_SUBSCRIPTION_STATE_NO ||
+          d->publish != TP_SUBSCRIPTION_STATE_NO)
+        tp_handle_set_add (set, GPOINTER_TO_UINT (k));
     }
+
+  return set;
 }
 
 static void
-set_property (GObject *object,
-              guint property_id,
-              const GValue *value,
-              GParamSpec *pspec)
+contact_list_dup_states (TpBaseContactList *base,
+    TpHandle contact,
+    TpSubscriptionState *subscribe,
+    TpSubscriptionState *publish,
+    gchar **publish_request)
 {
-  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (object);
+  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (base);
+  ContactDetails *d = lookup_contact (self, contact);
 
-  switch (property_id)
+  if (d == NULL)
     {
-    case PROP_CONNECTION:
-      /* We don't ref the connection, because it owns a reference to the
-       * manager, and it guarantees that the manager's lifetime is
-       * less than its lifetime */
-      self->priv->conn = g_value_get_object (value);
-      break;
+      if (subscribe != NULL)
+        *subscribe = TP_SUBSCRIPTION_STATE_NO;
 
-    case PROP_SIMULATION_DELAY:
-      self->priv->simulation_delay = g_value_get_uint (value);
-      break;
+      if (publish != NULL)
+        *publish = TP_SUBSCRIPTION_STATE_NO;
 
-    default:
-      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      if (publish_request != NULL)
+        *publish_request = NULL;
     }
-}
+  else
+    {
+      if (subscribe != NULL)
+        *subscribe = d->subscribe;
 
-static void
-satisfy_queued_requests (TpExportableChannel *channel,
-                         gpointer user_data)
-{
-  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (user_data);
-  GSList *requests = g_hash_table_lookup (self->priv->queued_requests,
-      channel);
-
-  /* this is all fine even if requests is NULL */
-  g_hash_table_steal (self->priv->queued_requests, channel);
-  requests = g_slist_reverse (requests);
-  tp_channel_manager_emit_new_channel (self, channel, requests);
-  g_slist_free (requests);
-}
+      if (publish != NULL)
+        *publish = d->publish;
 
-static TpTestsContactDetails *
-lookup_contact (TpTestsContactListManager *self,
-                TpHandle contact)
-{
-  return g_hash_table_lookup (self->priv->contact_details,
-      GUINT_TO_POINTER (contact));
+      if (publish_request != NULL)
+        *publish_request = g_strdup (d->publish_request);
+    }
 }
 
-static TpTestsContactDetails *
-ensure_contact (TpTestsContactListManager *self,
-                TpHandle contact,
-                gboolean *created)
+static GStrv
+contact_list_dup_groups (TpBaseContactList *base)
 {
-  TpTestsContactDetails *ret = lookup_contact (self, contact);
+  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (base);
+  GPtrArray *ret;
 
-  if (ret == NULL)
+  if (self->priv->groups != NULL)
     {
-      tp_handle_set_add (self->priv->contacts, contact);
+      TpIntSetFastIter iter;
+      TpHandle group;
 
-      ret = tp_tests_contact_details_new ();
-      ret->alias = g_strdup (tp_handle_inspect (self->priv->contact_repo,
-            contact));
+      ret = g_ptr_array_sized_new (tp_handle_set_size (self->priv->groups) + 1);
 
-      g_hash_table_insert (self->priv->contact_details,
-          GUINT_TO_POINTER (contact), ret);
-
-      if (created != NULL)
-        *created = TRUE;
+      tp_intset_fast_iter_init (&iter, tp_handle_set_peek (self->priv->groups));
+      while (tp_intset_fast_iter_next (&iter, &group))
+        {
+          g_ptr_array_add (ret, g_strdup (tp_handle_inspect (
+              self->priv->group_repo, group)));
+        }
     }
-  else if (created != NULL)
+  else
     {
-      *created = FALSE;
+      ret = g_ptr_array_sized_new (1);
     }
 
-  return ret;
+  g_ptr_array_add (ret, NULL);
+
+  return (GStrv) g_ptr_array_free (ret, FALSE);
 }
 
-static void
-tp_tests_contact_list_manager_foreach_channel (TpChannelManager *manager,
-                                              TpExportableChannelFunc callback,
-                                              gpointer user_data)
+static GStrv
+contact_list_dup_contact_groups (TpBaseContactList *base,
+    TpHandle contact)
 {
-  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (manager);
-  GHashTableIter iter;
-  gpointer handle, channel;
-  guint i;
+  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (base);
+  ContactDetails *d = lookup_contact (self, contact);
+  GPtrArray *ret;
 
-  for (i = 0; i < NUM_TP_TESTS_CONTACT_LISTS; i++)
+  if (d != NULL && d->groups != NULL)
     {
-      if (self->priv->lists[i] != NULL)
-        callback (TP_EXPORTABLE_CHANNEL (self->priv->lists[i]), user_data);
-    }
+      TpIntSetFastIter iter;
+      TpHandle group;
 
-  g_hash_table_iter_init (&iter, self->priv->groups);
+      ret = g_ptr_array_sized_new (tp_handle_set_size (d->groups) + 1);
 
-  while (g_hash_table_iter_next (&iter, &handle, &channel))
+      tp_intset_fast_iter_init (&iter, tp_handle_set_peek (d->groups));
+      while (tp_intset_fast_iter_next (&iter, &group))
+        {
+          g_ptr_array_add (ret, g_strdup (tp_handle_inspect (
+              self->priv->group_repo, group)));
+        }
+    }
+  else
     {
-      callback (TP_EXPORTABLE_CHANNEL (channel), user_data);
+      ret = g_ptr_array_sized_new (1);
     }
-}
-
-static TpTestsContactGroup *ensure_group (TpTestsContactListManager *self,
-    TpHandle handle);
-
-static TpTestsContactList *ensure_list (TpTestsContactListManager *self,
-    TpTestsContactListHandle handle);
-
-/*
- * _insert_contact_field:
- * @contact_info: an array of Contact_Info_Field structures
- * @field_name: a vCard field name in any case combination
- * @field_params: a list of vCard type-parameters, typically of the form
- *  type=xxx; must be in lower-case if case-insensitive
- * @field_values: for unstructured fields, an array containing one element;
- *  for structured fields, the elements of the field in order
- */
-static void
-_insert_contact_field (GPtrArray *contact_info,
-                       const gchar *field_name,
-                       const gchar * const *field_params,
-                       const gchar * const *field_values)
-{
-  const gchar * const *empty_strv = { NULL };
-  gchar *field_name_down = g_ascii_strdown (field_name, -1);
-
-  if (field_params == NULL)
-    field_params = empty_strv;
-  if (field_values == NULL)
-    field_values = empty_strv;
 
-   g_ptr_array_add (contact_info, tp_value_array_build (3,
-         G_TYPE_STRING, field_name_down,
-         G_TYPE_STRV, field_params,
-         G_TYPE_STRV, field_values,
-         G_TYPE_INVALID));
+  g_ptr_array_add (ret, NULL);
 
-   g_free (field_name_down);
+  return (GStrv) g_ptr_array_free (ret, FALSE);
 }
 
-static gboolean
-receive_contact_lists (gpointer p)
+static TpHandleSet *
+contact_list_dup_group_members (TpBaseContactList *base,
+    const gchar *group)
 {
-  TpTestsContactListManager *self = p;
-  TpHandle handle, cambridge, montreal, francophones;
-  const gchar *id;
-  TpTestsContactDetails *d;
-  TpIntSet *set, *cam_set, *mtl_set, *fr_set;
-  TpIntSetFastIter iter;
-  TpTestsContactList *subscribe, *publish, *stored;
-  TpTestsContactGroup *cambridge_group, *montreal_group,
-      *francophones_group;
+  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (base);
+  TpHandleSet *set;
+  TpHandle group_handle;
+  GHashTableIter iter;
+  gpointer k, v;
 
-  if (self->priv->groups == NULL)
+  set = tp_handle_set_new (self->priv->contact_repo);
+  group_handle = tp_handle_lookup (self->priv->group_repo, group, NULL, NULL);
+  if (G_UNLIKELY (group_handle == 0))
     {
-      /* connection already disconnected, so don't process the
-       * "data from the server" */
-      return FALSE;
+      /* clearly it doesn't have members */
+      return set;
     }
 
-  /* In a real CM we'd have received a contact list from the server at this
-   * point. But this isn't a real CM, so we have to make one up... */
+  g_hash_table_iter_init (&iter, self->priv->contact_details);
+  while (g_hash_table_iter_next (&iter, &k, &v))
+    {
+      ContactDetails *d = v;
 
-  subscribe = ensure_list (self, TP_TESTS_CONTACT_LIST_SUBSCRIBE);
-  publish = ensure_list (self, TP_TESTS_CONTACT_LIST_PUBLISH);
-  stored = ensure_list (self, TP_TESTS_CONTACT_LIST_STORED);
+      if (d->groups != NULL &&
+          tp_handle_set_is_member (d->groups, group_handle))
+        tp_handle_set_add (set, GPOINTER_TO_UINT (k));
+    }
 
-  cambridge = tp_handle_ensure (self->priv->group_repo, "Cambridge", NULL,
-      NULL);
-  montreal = tp_handle_ensure (self->priv->group_repo, "Montreal", NULL,
-      NULL);
-  francophones = tp_handle_ensure (self->priv->group_repo, "Francophones",
-      NULL, NULL);
+  return set;
+}
 
-  cambridge_group = ensure_group (self, cambridge);
-  montreal_group = ensure_group (self, montreal);
-  francophones_group = ensure_group (self, francophones);
+static void
+contact_list_set_contact_groups_async (TpBaseContactList *base,
+    TpHandle contact,
+    const gchar * const *names,
+    gsize n,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
+{
+  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (base);
+  ContactDetails *d;
+  TpIntset *set, *added_set, *removed_set;
+  GPtrArray *added_names, *removed_names;
+  GPtrArray *new_groups;
+  TpIntSetFastIter iter;
+  TpHandle group_handle;
+  guint i;
 
-  /* Add various people who are already subscribing and publishing */
+  d = ensure_contact (self, contact);
 
+  new_groups = g_ptr_array_new ();
   set = tp_intset_new ();
-  cam_set = tp_intset_new ();
-  mtl_set = tp_intset_new ();
-  fr_set = tp_intset_new ();
-
-  id = "sjoerd example com";
-  handle = tp_handle_ensure (self->priv->contact_repo, id, NULL, NULL);
-  tp_intset_add (set, handle);
-  tp_intset_add (cam_set, handle);
-  d = ensure_contact (self, handle, NULL);
-  g_free (d->id);
-  d->id = g_strdup (id);
-  g_free (d->alias);
-  d->alias = g_strdup ("Sjoerd");
-  d->subscribe = TRUE;
-  d->publish = TRUE;
-  d->tags = tp_handle_set_new (self->priv->group_repo);
-  tp_handle_set_add (d->tags, cambridge);
-  d->contact_info = dbus_g_type_specialized_construct (
-      TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
-  {
-    const gchar * values[] = { "+14401223357708", NULL };
-    _insert_contact_field (d->contact_info, "tel", NULL,
-        (const gchar * const *) values);
-  }
-  tp_handle_unref (self->priv->contact_repo, handle);
-
-  id = "guillaume example com";
-  handle = tp_handle_ensure (self->priv->contact_repo, id, NULL, NULL);
-  tp_intset_add (set, handle);
-  tp_intset_add (cam_set, handle);
-  tp_intset_add (fr_set, handle);
-  d = ensure_contact (self, handle, NULL);
-  g_free (d->id);
-  d->id = g_strdup (id);
-  g_free (d->alias);
-  d->alias = g_strdup ("Guillaume");
-  d->subscribe = TRUE;
-  d->publish = TRUE;
-  d->tags = tp_handle_set_new (self->priv->group_repo);
-  tp_handle_set_add (d->tags, cambridge);
-  tp_handle_set_add (d->tags, francophones);
-  tp_handle_unref (self->priv->contact_repo, handle);
-
-  id = "olivier example com";
-  handle = tp_handle_ensure (self->priv->contact_repo, id, NULL, NULL);
-  tp_intset_add (set, handle);
-  tp_intset_add (mtl_set, handle);
-  tp_intset_add (fr_set, handle);
-  d = ensure_contact (self, handle, NULL);
-  g_free (d->id);
-  d->id = g_strdup (id);
-  g_free (d->alias);
-  d->alias = g_strdup ("Olivier");
-  d->subscribe = TRUE;
-  d->publish = TRUE;
-  d->tags = tp_handle_set_new (self->priv->group_repo);
-  tp_handle_set_add (d->tags, montreal);
-  tp_handle_set_add (d->tags, francophones);
-  d->contact_info = dbus_g_type_specialized_construct (
-      TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
-  {
-    const gchar * values[] = { "+15142345678", NULL };
-    _insert_contact_field (d->contact_info, "tel", NULL,
-        (const gchar * const *) values);
-  }
-  {
-    const gchar * values[] = { "1982-01-02T13:57Z", NULL };
-    _insert_contact_field (d->contact_info, "tel", NULL,
-        (const gchar * const *) values);
-  }
-  {
-    const gchar * values[] = { "Olivier Crete", NULL };
-    _insert_contact_field (d->contact_info, "fn", NULL,
-        (const gchar * const *) values);
-  }
-  {
-    const gchar * values[] = { id, NULL };
-    _insert_contact_field (d->contact_info, "email", NULL,
-        (const gchar * const *) values);
-  }
-  {
-    const gchar * values[] = { "ocrete.example.com", NULL };
-    _insert_contact_field (d->contact_info, "url", NULL,
-        (const gchar * const *) values);
-  }
-  tp_handle_unref (self->priv->contact_repo, handle);
-
-  id = "travis example com";
-  handle = tp_handle_ensure (self->priv->contact_repo, id, NULL, NULL);
-  tp_intset_add (set, handle);
-  d = ensure_contact (self, handle, NULL);
-  g_free (d->id);
-  d->id = g_strdup (id);
-  g_free (d->alias);
-  d->alias = g_strdup ("Travis");
-  d->subscribe = TRUE;
-  d->publish = TRUE;
-  tp_handle_unref (self->priv->contact_repo, handle);
-
-  tp_group_mixin_change_members ((GObject *) subscribe, "",
-      set, NULL, NULL, NULL,
-      0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_group_mixin_change_members ((GObject *) publish, "",
-      set, NULL, NULL, NULL,
-      0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_group_mixin_change_members ((GObject *) stored, "",
-      set, NULL, NULL, NULL,
-      0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-
-  tp_intset_fast_iter_init (&iter, set);
-
-  while (tp_intset_fast_iter_next (&iter, &handle))
+  for (i = 0; i < n; i++)
     {
-      g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
-      g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
-      g_signal_emit (self, signals[CONTACT_INFO_UPDATED], 0, handle);
-    }
+      group_handle = tp_handle_ensure (self->priv->group_repo, names[i], NULL, NULL);
+      tp_intset_add (set, group_handle);
 
-  tp_intset_destroy (set);
-
-  /* Add a couple of people whose presence we've requested. They are
-   * remote-pending in subscribe */
+      if (!tp_handle_set_is_member (self->priv->groups, group_handle))
+        {
+          tp_handle_set_add (self->priv->groups, group_handle);
+          g_ptr_array_add (new_groups, (gchar *) names[i]);
+        }
+    }
 
-  set = tp_intset_new ();
+  if (new_groups->len > 0)
+    {
+      tp_base_contact_list_groups_created ((TpBaseContactList *) self,
+          (const gchar * const *) new_groups->pdata, new_groups->len);
+    }
 
-  id = "geraldine example com";
-  handle = tp_handle_ensure (self->priv->contact_repo, id, NULL, NULL);
-  tp_intset_add (set, handle);
-  tp_intset_add (cam_set, handle);
-  tp_intset_add (fr_set, handle);
-  d = ensure_contact (self, handle, NULL);
-  g_free (d->id);
-  d->id = g_strdup (id);
-  g_free (d->alias);
-  d->alias = g_strdup ("GÃraldine");
-  d->subscribe_requested = TRUE;
-  d->tags = tp_handle_set_new (self->priv->group_repo);
-  tp_handle_set_add (d->tags, cambridge);
-  tp_handle_set_add (d->tags, francophones);
-  tp_handle_unref (self->priv->contact_repo, handle);
-
-  id = "helen example com";
-  handle = tp_handle_ensure (self->priv->contact_repo, id, NULL, NULL);
-  tp_intset_add (set, handle);
-  tp_intset_add (cam_set, handle);
-  d = ensure_contact (self, handle, NULL);
-  g_free (d->id);
-  d->id = g_strdup (id);
-  g_free (d->alias);
-  d->alias = g_strdup ("Helen");
-  d->subscribe_requested = TRUE;
-  d->tags = tp_handle_set_new (self->priv->group_repo);
-  tp_handle_set_add (d->tags, cambridge);
-  tp_handle_unref (self->priv->contact_repo, handle);
-
-  tp_group_mixin_change_members ((GObject *) subscribe, "",
-      NULL, NULL, NULL, set,
-      0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_group_mixin_change_members ((GObject *) stored, "",
-      set, NULL, NULL, NULL,
-      0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-
-  tp_intset_fast_iter_init (&iter, set);
-
-  while (tp_intset_fast_iter_next (&iter, &handle))
+  added_set = tp_intset_difference (set, tp_handle_set_peek (d->groups));
+  added_names = g_ptr_array_sized_new (tp_intset_size (added_set));
+  tp_intset_fast_iter_init (&iter, added_set);
+  while (tp_intset_fast_iter_next (&iter, &group_handle))
     {
-      g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
-      g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
-      g_signal_emit (self, signals[CONTACT_INFO_UPDATED], 0, handle);
+      g_ptr_array_add (added_names, (gchar *) tp_handle_inspect (
+          self->priv->group_repo, group_handle));
     }
+  tp_intset_destroy (added_set);
 
-  tp_intset_destroy (set);
+  removed_set = tp_intset_difference (tp_handle_set_peek (d->groups), set);
+  removed_names = g_ptr_array_sized_new (tp_intset_size (removed_set));
+  tp_intset_fast_iter_init (&iter, removed_set);
+  while (tp_intset_fast_iter_next (&iter, &group_handle))
+    {
+      g_ptr_array_add (removed_names, (gchar *) tp_handle_inspect (
+          self->priv->group_repo, group_handle));
+    }
+  tp_intset_destroy (removed_set);
 
-  /* Receive a couple of authorization requests too. These people are
-   * local-pending in publish */
-
-  id = "wim example com";
-  handle = tp_handle_ensure (self->priv->contact_repo, id, NULL, NULL);
-  d = ensure_contact (self, handle, NULL);
-  g_free (d->id);
-  d->id = g_strdup (id);
-  g_free (d->alias);
-  d->alias = g_strdup ("Wim");
-  d->publish_requested = TRUE;
-  tp_handle_unref (self->priv->contact_repo, handle);
-
-  set = tp_intset_new_containing (handle);
-  tp_group_mixin_change_members ((GObject *) publish,
-      "I'm more metal than you!",
-      NULL, NULL, set, NULL,
-      handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_group_mixin_change_members ((GObject *) stored, "",
-      set, NULL, NULL, NULL,
-      handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_intset_destroy (set);
-  g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
-  g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
-  g_signal_emit (self, signals[CONTACT_INFO_UPDATED], 0, handle);
-
-  id = "christian example com";
-  handle = tp_handle_ensure (self->priv->contact_repo, id, NULL, NULL);
-  d = ensure_contact (self, handle, NULL);
-  g_free (d->id);
-  d->id = g_strdup (id);
-  g_free (d->alias);
-  d->alias = g_strdup ("Christian");
-  d->publish_requested = TRUE;
-  tp_handle_unref (self->priv->contact_repo, handle);
-
-  set = tp_intset_new_containing (handle);
-  tp_group_mixin_change_members ((GObject *) publish,
-      "I have some fermented herring for you",
-      NULL, NULL, set, NULL,
-      handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_group_mixin_change_members ((GObject *) stored, "",
-      set, NULL, NULL, NULL,
-      handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
+  tp_handle_set_destroy (d->groups);
+  d->groups = tp_handle_set_new_from_intset (self->priv->group_repo, set);
   tp_intset_destroy (set);
-  g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
-  g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
-  g_signal_emit (self, signals[CONTACT_INFO_UPDATED], 0, handle);
-
-  tp_group_mixin_change_members ((GObject *) cambridge_group, "",
-      cam_set, NULL, NULL, NULL,
-      0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_group_mixin_change_members ((GObject *) montreal_group, "",
-      mtl_set, NULL, NULL, NULL,
-      0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_group_mixin_change_members ((GObject *) francophones_group, "",
-      fr_set, NULL, NULL, NULL,
-      0, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-
-  tp_intset_destroy (fr_set);
-  tp_intset_destroy (cam_set);
-  tp_intset_destroy (mtl_set);
 
-  tp_handle_unref (self->priv->group_repo, cambridge);
-  tp_handle_unref (self->priv->group_repo, montreal);
-  tp_handle_unref (self->priv->group_repo, francophones);
-
-  /* Now we've received the roster, we can satisfy all the queued requests */
-
-  tp_tests_contact_list_manager_foreach_channel ((TpChannelManager *) self,
-      satisfy_queued_requests, self);
+  if (added_names->len > 0 || removed_names->len > 0)
+    {
+      tp_base_contact_list_one_contact_groups_changed (base, contact,
+          (const gchar * const *) added_names->pdata, added_names->len,
+          (const gchar * const *) removed_names->pdata, removed_names->len);
+    }
 
-  g_assert (g_hash_table_size (self->priv->queued_requests) == 0);
-  g_hash_table_destroy (self->priv->queued_requests);
-  self->priv->queued_requests = NULL;
+  tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+      user_data, contact_list_set_contact_groups_async);
 
-  return FALSE;
+  g_ptr_array_unref (added_names);
+  g_ptr_array_unref (removed_names);
+  g_ptr_array_unref (new_groups);
 }
 
 static void
-status_changed_cb (TpBaseConnection *conn,
-                   guint status,
-                   guint reason,
-                   TpTestsContactListManager *self)
+contact_list_set_group_members_async (TpBaseContactList *base,
+    const gchar *normalized_group,
+    TpHandleSet *contacts,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
 {
-  switch (status)
-    {
-    case TP_CONNECTION_STATUS_CONNECTED:
-        {
-          /* Do network I/O to get the contact list. This connection manager
-           * doesn't really have a server, so simulate a small network delay
-           * then invent a contact list */
-          g_timeout_add_full (G_PRIORITY_DEFAULT,
-              2 * self->priv->simulation_delay, receive_contact_lists,
-              g_object_ref (self), g_object_unref);
-        }
-      break;
+  GSimpleAsyncResult *simple;
 
-    case TP_CONNECTION_STATUS_CONNECTING:
-      /* nothing to do here */
-      break;
-
-    case TP_CONNECTION_STATUS_DISCONNECTED:
-        {
-          tp_tests_contact_list_manager_close_all (self);
-        }
-      break;
-    default:
-        g_assert_not_reached ();
-    }
+  simple = g_simple_async_result_new_error ((GObject *) base, callback,
+      user_data, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Not implemented");
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
 }
 
 static void
-constructed (GObject *object)
+contact_list_add_to_group_async (TpBaseContactList *base,
+    const gchar *group,
+    TpHandleSet *contacts,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
 {
-  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (object);
-  void (*chain_up) (GObject *) =
-      ((GObjectClass *) tp_tests_contact_list_manager_parent_class)->constructed;
-
-  if (chain_up != NULL)
-    {
-      chain_up (object);
-    }
-
-  self->priv->contact_repo = tp_base_connection_get_handles (self->priv->conn,
-      TP_HANDLE_TYPE_CONTACT);
-  self->priv->group_repo = tp_base_connection_get_handles (self->priv->conn,
-      TP_HANDLE_TYPE_GROUP);
-  self->priv->contacts = tp_handle_set_new (self->priv->contact_repo);
+  GSimpleAsyncResult *simple;
 
-  self->priv->status_changed_id = g_signal_connect (self->priv->conn,
-      "status-changed", (GCallback) status_changed_cb, self);
+  simple = g_simple_async_result_new_error ((GObject *) base, callback,
+      user_data, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Not implemented");
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
 }
 
 static void
-tp_tests_contact_list_manager_class_init (TpTestsContactListManagerClass *klass)
+contact_list_remove_from_group_async (TpBaseContactList *base,
+    const gchar *group,
+    TpHandleSet *contacts,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
 {
-  GParamSpec *param_spec;
-  GObjectClass *object_class = (GObjectClass *) klass;
-
-  object_class->constructed = constructed;
-  object_class->dispose = dispose;
-  object_class->get_property = get_property;
-  object_class->set_property = set_property;
-
-  param_spec = g_param_spec_object ("connection", "Connection object",
-      "The connection that owns this channel manager",
-      TP_TYPE_BASE_CONNECTION,
-      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
-      G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
-  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
-
-  param_spec = g_param_spec_uint ("simulation-delay", "Simulation delay",
-      "Delay between simulated network events",
-      0, G_MAXUINT32, 1000,
-      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
-  g_object_class_install_property (object_class, PROP_SIMULATION_DELAY,
-      param_spec);
-
-  g_type_class_add_private (klass, sizeof (TpTestsContactListManagerPrivate));
+  GSimpleAsyncResult *simple;
 
-  signals[ALIAS_UPDATED] = g_signal_new ("alias-updated",
-      G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST,
-      0,
-      NULL, NULL,
-      g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
-
-  signals[PRESENCE_UPDATED] = g_signal_new ("presence-updated",
-      G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST,
-      0,
-      NULL, NULL,
-      g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
-
-  signals[CONTACT_INFO_UPDATED] = g_signal_new ("contact-info-updated",
-      G_TYPE_FROM_CLASS (klass),
-      G_SIGNAL_RUN_LAST,
-      0,
-      NULL, NULL,
-      g_cclosure_marshal_VOID__UINT, G_TYPE_NONE, 1, G_TYPE_UINT);
+  simple = g_simple_async_result_new_error ((GObject *) base, callback,
+      user_data, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Not implemented");
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
 }
 
 static void
-list_closed_cb (TpTestsContactList *chan,
-                TpTestsContactListManager *self)
+contact_list_remove_group_async (TpBaseContactList *base,
+    const gchar *group,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
 {
-  TpHandle handle;
-
-  tp_channel_manager_emit_channel_closed_for_object (self,
-      TP_EXPORTABLE_CHANNEL (chan));
+  GSimpleAsyncResult *simple;
 
-  g_object_get (chan,
-      "handle", &handle,
-      NULL);
-
-  if (self->priv->lists[handle] == NULL)
-    return;
-
-  g_assert (chan == self->priv->lists[handle]);
-  g_object_unref (self->priv->lists[handle]);
-  self->priv->lists[handle] = NULL;
+  simple = g_simple_async_result_new_error ((GObject *) base, callback,
+      user_data, TP_ERROR, TP_ERROR_NOT_IMPLEMENTED, "Not implemented");
+  g_simple_async_result_complete_in_idle (simple);
+  g_object_unref (simple);
 }
 
 static void
-group_closed_cb (TpTestsContactGroup *chan,
-                 TpTestsContactListManager *self)
+contact_list_request_subscription_async (TpBaseContactList *self,
+    TpHandleSet *contacts,
+    const gchar *message,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
 {
-  tp_channel_manager_emit_channel_closed_for_object (self,
-      TP_EXPORTABLE_CHANNEL (chan));
+  GArray *handles;
 
-  if (self->priv->groups != NULL)
-    {
-      TpHandle handle;
-
-      g_object_get (chan,
-          "handle", &handle,
-          NULL);
+  handles = tp_handle_set_to_array (contacts);
+  tp_tests_contact_list_manager_request_subscription (
+      (TpTestsContactListManager *) self,
+      handles->len, (TpHandle *) handles->data, message);
+  g_array_unref (handles);
 
-      g_hash_table_remove (self->priv->groups, GUINT_TO_POINTER (handle));
-    }
+  tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+      user_data, contact_list_request_subscription_async);
 }
 
-static TpTestsContactListBase *
-new_channel (TpTestsContactListManager *self,
-             TpHandleType handle_type,
-             TpHandle handle,
-             gpointer request_token)
+static void
+contact_list_authorize_publication_async (TpBaseContactList *self,
+    TpHandleSet *contacts,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
 {
-  TpTestsContactListBase *chan;
-  gchar *object_path;
-  GType type;
-  GSList *requests = NULL;
+  GArray *handles;
 
-  if (handle_type == TP_HANDLE_TYPE_LIST)
-    {
-      /* Some Telepathy clients wrongly assume that contact lists of type LIST
-       * have object paths ending with "/subscribe", "/publish" etc. -
-       * telepathy-spec has no such guarantee, so in this tp_test we break
-       * those clients. Please read the spec when implementing it :-) */
-      object_path = g_strdup_printf ("%s/%sContactList",
-          self->priv->conn->object_path, _contact_lists[handle - 1]);
-      type = TP_TESTS_TYPE_CONTACT_LIST;
-    }
-  else
-    {
-      /* Using Group%u (with handle as the value of %u) would be OK here too,
-       * but we'll encode the group name into the object path to be kind
-       * to people reading debug logs. */
-      gchar *id = tp_escape_as_identifier (tp_handle_inspect (
-            self->priv->group_repo, handle));
-
-      g_assert (handle_type == TP_HANDLE_TYPE_GROUP);
-      object_path = g_strdup_printf ("%s/Group/%s",
-          self->priv->conn->object_path, id);
-      type = TP_TESTS_TYPE_CONTACT_GROUP;
-
-      g_free (id);
-    }
-
-  chan = g_object_new (type,
-      "connection", self->priv->conn,
-      "manager", self,
-      "object-path", object_path,
-      "handle-type", handle_type,
-      "handle", handle,
-      NULL);
-
-  g_free (object_path);
-
-  if (handle_type == TP_HANDLE_TYPE_LIST)
-    {
-      g_signal_connect (chan, "closed", (GCallback) list_closed_cb, self);
-      g_assert (self->priv->lists[handle] == NULL);
-      self->priv->lists[handle] = TP_TESTS_CONTACT_LIST (chan);
-    }
-  else
-    {
-      g_signal_connect (chan, "closed", (GCallback) group_closed_cb, self);
-
-      g_assert (g_hash_table_lookup (self->priv->groups,
-            GUINT_TO_POINTER (handle)) == NULL);
-      g_hash_table_insert (self->priv->groups, GUINT_TO_POINTER (handle),
-          TP_TESTS_CONTACT_GROUP (chan));
-    }
+  handles = tp_handle_set_to_array (contacts);
+  tp_tests_contact_list_manager_authorize_publication (
+      (TpTestsContactListManager *) self,
+      handles->len, (TpHandle *) handles->data);
+  g_array_unref (handles);
 
-  if (self->priv->queued_requests == NULL)
-    {
-      if (request_token != NULL)
-        requests = g_slist_prepend (requests, request_token);
-
-      tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
-          requests);
-      g_slist_free (requests);
-    }
-  else if (request_token != NULL)
-    {
-      /* initial contact list not received yet, so we have to wait for it */
-      requests = g_hash_table_lookup (self->priv->queued_requests, chan);
-      g_hash_table_steal (self->priv->queued_requests, chan);
-      requests = g_slist_prepend (requests, request_token);
-      g_hash_table_insert (self->priv->queued_requests, chan, requests);
-    }
-
-  return chan;
+  tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+      user_data, contact_list_authorize_publication_async);
 }
 
-static TpTestsContactList *
-ensure_list (TpTestsContactListManager *self,
-             TpTestsContactListHandle handle)
+static void
+contact_list_remove_contacts_async (TpBaseContactList *self,
+    TpHandleSet *contacts,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
 {
-  if (self->priv->lists[handle] == NULL)
-    {
-      new_channel (self, TP_HANDLE_TYPE_LIST, handle, NULL);
-      g_assert (self->priv->lists[handle] != NULL);
-    }
+  GArray *handles;
+
+  handles = tp_handle_set_to_array (contacts);
+  tp_tests_contact_list_manager_remove (
+      (TpTestsContactListManager *) self,
+      handles->len, (TpHandle *) handles->data);
+  g_array_unref (handles);
 
-  return self->priv->lists[handle];
+  tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+      user_data, contact_list_remove_contacts_async);
 }
 
-static TpTestsContactGroup *
-ensure_group (TpTestsContactListManager *self,
-              TpHandle handle)
+static void
+contact_list_unsubscribe_async (TpBaseContactList *self,
+    TpHandleSet *contacts,
+    GAsyncReadyCallback callback,
+    gpointer user_data)
 {
-  TpTestsContactGroup *group = g_hash_table_lookup (self->priv->groups,
-      GUINT_TO_POINTER (handle));
+  GArray *handles;
 
-  if (group == NULL)
-    {
-      group = TP_TESTS_CONTACT_GROUP (new_channel (self, TP_HANDLE_TYPE_GROUP,
-            handle, NULL));
-    }
+  handles = tp_handle_set_to_array (contacts);
+  tp_tests_contact_list_manager_unsubscribe (
+      (TpTestsContactListManager *) self,
+      handles->len, (TpHandle *) handles->data);
+  g_array_unref (handles);
 
-  return group;
+  tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+      user_data, contact_list_unsubscribe_async);
 }
 
-static const gchar * const fixed_properties[] = {
-    TP_PROP_CHANNEL_CHANNEL_TYPE,
-    TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
-    NULL
-};
-
-static const gchar * const allowed_properties[] = {
-    TP_PROP_CHANNEL_TARGET_HANDLE,
-    TP_PROP_CHANNEL_TARGET_ID,
-    NULL
-};
-
 static void
-tp_tests_contact_list_manager_foreach_channel_class (TpChannelManager *manager,
-    TpChannelManagerChannelClassFunc func,
+contact_list_unpublish_async (TpBaseContactList *self,
+    TpHandleSet *contacts,
+    GAsyncReadyCallback callback,
     gpointer user_data)
 {
-    GHashTable *table = tp_asv_new (
-        TP_PROP_CHANNEL_CHANNEL_TYPE,
-            G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_CONTACT_LIST,
-        TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, TP_HANDLE_TYPE_LIST,
-        NULL);
-
-    func (manager, table, allowed_properties, user_data);
+  GArray *handles;
 
-    g_hash_table_insert (table, (gpointer) TP_PROP_CHANNEL_TARGET_HANDLE_TYPE,
-        tp_g_value_slice_new_uint (TP_HANDLE_TYPE_GROUP));
-    func (manager, table, allowed_properties, user_data);
+  handles = tp_handle_set_to_array (contacts);
+  tp_tests_contact_list_manager_unpublish (
+      (TpTestsContactListManager *) self,
+      handles->len, (TpHandle *) handles->data);
+  g_array_unref (handles);
 
-    g_hash_table_destroy (table);
+  tp_simple_async_report_success_in_idle ((GObject *) self, callback,
+      user_data, contact_list_unpublish_async);
 }
 
-static gboolean
-tp_tests_contact_list_manager_request (TpTestsContactListManager *self,
-                                      gpointer request_token,
-                                      GHashTable *request_properties,
-                                      gboolean require_new)
+static void
+status_changed_cb (TpBaseConnection *conn,
+                   guint status,
+                   guint reason,
+                   TpTestsContactListManager *self)
 {
-  TpHandleType handle_type;
-  TpHandle handle;
-  TpTestsContactListBase *chan;
-  GError *error = NULL;
-
-  if (tp_strdiff (tp_asv_get_string (request_properties,
-          TP_PROP_CHANNEL_CHANNEL_TYPE),
-      TP_IFACE_CHANNEL_TYPE_CONTACT_LIST))
-    {
-      return FALSE;
-    }
-
-  handle_type = tp_asv_get_uint32 (request_properties,
-      TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, NULL);
-
-  if (handle_type != TP_HANDLE_TYPE_LIST &&
-      handle_type != TP_HANDLE_TYPE_GROUP)
+  switch (status)
     {
-      return FALSE;
-    }
-
-  handle = tp_asv_get_uint32 (request_properties,
-      TP_PROP_CHANNEL_TARGET_HANDLE, NULL);
-  g_assert (handle != 0);
+    case TP_CONNECTION_STATUS_CONNECTED:
+        {
+          tp_base_contact_list_set_list_received (TP_BASE_CONTACT_LIST (self));
+        }
+      break;
 
-  if (tp_channel_manager_asv_has_unknown_properties (request_properties,
-        fixed_properties, allowed_properties, &error))
-    {
-      goto error;
+    case TP_CONNECTION_STATUS_DISCONNECTED:
+        {
+          close_all (self);
+        }
+      break;
     }
+}
 
-  if (handle_type == TP_HANDLE_TYPE_LIST)
-    {
-      /* telepathy-glib has already checked that the handle is valid */
-      g_assert (handle < NUM_TP_TESTS_CONTACT_LISTS);
-
-      chan = TP_TESTS_CONTACT_LIST_BASE (self->priv->lists[handle]);
-    }
-  else
-    {
-      chan = g_hash_table_lookup (self->priv->groups,
-          GUINT_TO_POINTER (handle));
-    }
+static void
+constructed (GObject *object)
+{
+  TpTestsContactListManager *self = TP_TESTS_CONTACT_LIST_MANAGER (object);
+  void (*chain_up) (GObject *) =
+      ((GObjectClass *) tp_tests_contact_list_manager_parent_class)->constructed;
 
-  if (chan == NULL)
-    {
-      new_channel (self, handle_type, handle, request_token);
-    }
-  else if (require_new)
-    {
-      g_set_error (&error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-          "A ContactList channel for type #%u, handle #%u already exists",
-          handle_type, handle);
-      goto error;
-    }
-  else
+  if (chain_up != NULL)
     {
-      tp_channel_manager_emit_request_already_satisfied (self,
-          request_token, TP_EXPORTABLE_CHANNEL (chan));
+      chain_up (object);
     }
 
-  return TRUE;
+  self->priv->conn = tp_base_contact_list_get_connection (
+      TP_BASE_CONTACT_LIST (self), NULL);
+  self->priv->status_changed_id = g_signal_connect (self->priv->conn,
+      "status-changed", G_CALLBACK (status_changed_cb), self);
 
-error:
-  tp_channel_manager_emit_request_failed (self, request_token,
-      error->domain, error->code, error->message);
-  g_error_free (error);
-  return TRUE;
+  self->priv->contact_repo = tp_base_connection_get_handles (self->priv->conn,
+      TP_HANDLE_TYPE_CONTACT);
+  self->priv->group_repo = tp_base_connection_get_handles (self->priv->conn,
+      TP_HANDLE_TYPE_GROUP);
+  self->priv->groups = tp_handle_set_new (self->priv->group_repo);
 }
 
-static gboolean
-tp_tests_contact_list_manager_create_channel (TpChannelManager *manager,
-                                             gpointer request_token,
-                                             GHashTable *request_properties)
+static void
+contact_groups_iface_init (TpContactGroupListInterface *iface)
 {
-    return tp_tests_contact_list_manager_request (
-        TP_TESTS_CONTACT_LIST_MANAGER (manager), request_token,
-        request_properties, TRUE);
+  iface->dup_groups = contact_list_dup_groups;
+  iface->dup_contact_groups = contact_list_dup_contact_groups;
+  iface->dup_group_members = contact_list_dup_group_members;
 }
 
-static gboolean
-tp_tests_contact_list_manager_ensure_channel (TpChannelManager *manager,
-                                             gpointer request_token,
-                                             GHashTable *request_properties)
+static void
+mutable_contact_groups_iface_init (
+    TpMutableContactGroupListInterface *iface)
 {
-    return tp_tests_contact_list_manager_request (
-        TP_TESTS_CONTACT_LIST_MANAGER (manager), request_token,
-        request_properties, FALSE);
+  iface->set_contact_groups_async = contact_list_set_contact_groups_async;
+  iface->set_group_members_async = contact_list_set_group_members_async;
+  iface->add_to_group_async = contact_list_add_to_group_async;
+  iface->remove_from_group_async = contact_list_remove_from_group_async;
+  iface->remove_group_async = contact_list_remove_group_async;
 }
 
 static void
-channel_manager_iface_init (gpointer g_iface,
-                            gpointer data G_GNUC_UNUSED)
+mutable_iface_init (TpMutableContactListInterface *iface)
 {
-  TpChannelManagerIface *iface = g_iface;
-
-  iface->foreach_channel = tp_tests_contact_list_manager_foreach_channel;
-  iface->foreach_channel_class =
-      tp_tests_contact_list_manager_foreach_channel_class;
-  iface->create_channel = tp_tests_contact_list_manager_create_channel;
-  iface->ensure_channel = tp_tests_contact_list_manager_ensure_channel;
-  /* In this channel manager, Request has the same semantics as Ensure */
-  iface->request_channel = tp_tests_contact_list_manager_ensure_channel;
+  iface->request_subscription_async = contact_list_request_subscription_async;
+  iface->authorize_publication_async = contact_list_authorize_publication_async;
+  iface->remove_contacts_async = contact_list_remove_contacts_async;
+  iface->unsubscribe_async = contact_list_unsubscribe_async;
+  iface->unpublish_async = contact_list_unpublish_async;
 }
 
 static void
-send_updated_roster (TpTestsContactListManager *self,
-                     TpHandle contact)
+tp_tests_contact_list_manager_class_init (TpTestsContactListManagerClass *klass)
 {
-  TpTestsContactDetails *d = g_hash_table_lookup (self->priv->contact_details,
-      GUINT_TO_POINTER (contact));
-  const gchar *identifier = tp_handle_inspect (self->priv->contact_repo,
-      contact);
-
-  /* In a real connection manager, we'd transmit these new details to the
-   * server, rather than just printing messages. */
-
-  if (d == NULL)
-    {
-      g_message ("Deleting contact %s from server", identifier);
-    }
-  else
-    {
-      TpHandle handle;
-
-      g_message ("Transmitting new state of contact %s to server", identifier);
-      g_message ("\talias = %s", d->alias);
-      g_message ("\tcan see our presence = %s",
-          d->publish ? "yes" :
-          (d->publish_requested ? "no, but has requested it" : "no"));
-      g_message ("\tsends us presence = %s",
-          d->subscribe ? "yes" :
-          (d->subscribe_requested ? "no, but we have requested it" : "no"));
-
-      if (d->tags == NULL || tp_handle_set_size (d->tags) == 0)
-        {
-          g_message ("\tnot in any groups");
-        }
-      else
-        {
-          TpIntSet *set = tp_handle_set_peek (d->tags);
-          TpIntSetFastIter iter;
-          TpHandle member;
-
-          tp_intset_fast_iter_init (&iter, set);
-
-          while (tp_intset_fast_iter_next (&iter, &member))
-            {
-              g_message ("\tin group: %s",
-                  tp_handle_inspect (self->priv->group_repo, member));
-            }
-        }
+  GObjectClass *object_class = (GObjectClass *) klass;
+  TpBaseContactListClass *base_class =(TpBaseContactListClass *) klass;
 
-      if (d->contact_info == NULL)
-        {
-          g_message ("\tno contact info");
-        }
-      else
-        {
-          for (guint i = 0; i < d->contact_info->len; i++)
-            {
-              GValueArray *va = g_ptr_array_index (d->contact_info, i);
-
-              GValue *gv0, *gv1, *gv2;
-              gchar **params, **values;
-              gchar *empty_strv[] = { NULL };
-
-              gv0 = g_value_array_get_nth (va, 0);
-              gv1 = g_value_array_get_nth (va, 1);
-              gv2 = g_value_array_get_nth (va, 2);
-              params = g_value_peek_pointer (gv1);
-              values = g_value_peek_pointer (gv2);
-
-              if (params == NULL)
-                params = empty_strv;
-              if (values == NULL)
-                values = empty_strv;
-
-              g_message ("\tcontact info:");
-              g_message ("\t\tname: %s", g_value_get_string (gv0));
-              g_message ("\t\tparams:");
-              for (guint j = 0; j < g_strv_length (params); j++)
-                {
-                  g_message ("\t\t\t%s", params[j]);
-                }
-              g_message ("\t\tvalues:");
-              for (guint j = 0; j < g_strv_length (values); j++)
-                {
-                  g_message ("\t\t\t%s", values[j]);
-                }
-            }
-        }
+  g_type_class_add_private (klass, sizeof (TpTestsContactListManagerPrivate));
 
-      handle = tp_handle_ensure (self->priv->contact_repo, d->id, NULL, NULL);
-      /* XXX: ideally, we'd only do this if thse specific details really
-       * changed, but it's not terribly unrealistic to think some servers or CMs
-       * would make this mistake as well. */
-      g_signal_emit (self, signals[ALIAS_UPDATED], 0, handle);
-      g_signal_emit (self, signals[PRESENCE_UPDATED], 0, handle);
-      g_signal_emit (self, signals[CONTACT_INFO_UPDATED], 0, handle);
+  object_class->constructed = constructed;
+  object_class->dispose = dispose;
 
-      tp_handle_unref (self->priv->contact_repo, handle);
-    }
+  base_class->dup_states = contact_list_dup_states;
+  base_class->dup_contacts = contact_list_dup_contacts;
 }
 
-gboolean
+void
 tp_tests_contact_list_manager_add_to_group (TpTestsContactListManager *self,
-                                           GObject *channel,
-                                           TpHandle group,
-                                           TpHandle member,
-                                           const gchar *message,
-                                           GError **error)
+    const gchar *group_name, TpHandle member)
 {
-  gboolean updated;
-  TpTestsContactDetails *d = ensure_contact (self, member, &updated);
-  TpTestsContactList *stored = self->priv->lists[
-    TP_TESTS_CONTACT_LIST_STORED];
+  TpBaseContactList *base = TP_BASE_CONTACT_LIST (self);
+  ContactDetails *d = ensure_contact (self, member);
+  TpHandle group_handle;
 
-  if (d->tags == NULL)
-    d->tags = tp_handle_set_new (self->priv->group_repo);
+  group_handle = tp_handle_ensure (self->priv->group_repo,
+      group_name, NULL, NULL);
 
-  if (!tp_handle_set_is_member (d->tags, group))
+  if (!tp_handle_set_is_member (self->priv->groups, group_handle))
     {
-      tp_handle_set_add (d->tags, group);
-      updated = TRUE;
+      tp_handle_set_add (self->priv->groups, group_handle);
+      tp_base_contact_list_groups_created ((TpBaseContactList *) self,
+          &group_name, 1);
     }
 
-  if (updated)
-    {
-      TpIntSet *added = tp_intset_new_containing (member);
-
-      send_updated_roster (self, member);
-      tp_group_mixin_change_members (channel, "", added, NULL, NULL, NULL,
-          self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-      tp_group_mixin_change_members ((GObject *) stored, "",
-          added, NULL, NULL, NULL,
-          self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-      tp_intset_destroy (added);
-    }
-
-  return TRUE;
+  tp_handle_set_add (d->groups, group_handle);
+  tp_base_contact_list_one_contact_groups_changed (base, member,
+      &group_name, 1, NULL, 0);
 }
 
-gboolean
-tp_tests_contact_list_manager_remove_from_group (
-    TpTestsContactListManager *self,
-    GObject *channel,
-    TpHandle group,
-    TpHandle member,
-    const gchar *message,
-    GError **error)
+void
+tp_tests_contact_list_manager_remove_from_group (TpTestsContactListManager *self,
+    const gchar *group_name, TpHandle member)
 {
-  TpTestsContactDetails *d = lookup_contact (self, member);
-
-  /* If not on the roster or not in any groups, we have nothing to do */
-  if (d == NULL || d->tags == NULL)
-    return TRUE;
+  TpBaseContactList *base = TP_BASE_CONTACT_LIST (self);
+  ContactDetails *d = lookup_contact (self, member);
+  TpHandle group_handle;
 
-  if (tp_handle_set_remove (d->tags, group))
-    {
-      TpIntSet *removed = tp_intset_new_containing (member);
+  if (d == NULL)
+    return;
 
-      send_updated_roster (self, member);
-      tp_group_mixin_change_members (channel, "", NULL, removed, NULL, NULL,
-          self->priv->conn->self_handle, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-      tp_intset_destroy (removed);
-    }
+  group_handle = tp_handle_ensure (self->priv->group_repo, group_name, NULL, NULL);
 
-  return TRUE;
+  tp_handle_set_remove (d->groups, group_handle);
+  tp_base_contact_list_one_contact_groups_changed (base, member,
+      NULL, 0, &group_name, 1);
 }
 
 typedef struct {
     TpTestsContactListManager *self;
-    TpHandle contact;
+    TpHandleSet *handles;
 } SelfAndContact;
 
 static SelfAndContact *
 self_and_contact_new (TpTestsContactListManager *self,
-                      TpHandle contact)
+  TpHandleSet *handles)
 {
   SelfAndContact *ret = g_slice_new0 (SelfAndContact);
 
   ret->self = g_object_ref (self);
-  ret->contact = contact;
-  tp_handle_ref (self->priv->contact_repo, contact);
+  ret->handles = tp_handle_set_copy (handles);
+
   return ret;
 }
 
@@ -1294,98 +665,41 @@ self_and_contact_destroy (gpointer p)
 {
   SelfAndContact *s = p;
 
-  tp_handle_unref (s->self->priv->contact_repo, s->contact);
+  tp_handle_set_destroy (s->handles);
   g_object_unref (s->self);
   g_slice_free (SelfAndContact, s);
 }
 
-static void
-receive_auth_request (TpTestsContactListManager *self,
-                      TpHandle contact)
-{
-  TpTestsContactDetails *d;
-  TpIntSet *set;
-  TpTestsContactList *publish = self->priv->lists[
-    TP_TESTS_CONTACT_LIST_PUBLISH];
-  TpTestsContactList *stored = self->priv->lists[
-    TP_TESTS_CONTACT_LIST_STORED];
-
-  /* if shutting down, do nothing */
-  if (publish == NULL)
-    return;
-
-  /* A remote contact has asked to see our presence.
-   *
-   * In a real connection manager this would be the result of incoming
-   * data from the server. */
-
-  g_message ("From server: %s has sent us a publish request",
-      tp_handle_inspect (self->priv->contact_repo, contact));
-
-  d = ensure_contact (self, contact, NULL);
-
-  if (d->publish)
-    return;
-
-  d->publish_requested = TRUE;
-
-  set = tp_intset_new_containing (contact);
-  tp_group_mixin_change_members ((GObject *) publish,
-      "May I see your presence, please?",
-      NULL, NULL, set, NULL,
-      contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_group_mixin_change_members ((GObject *) stored, "",
-      set, NULL, NULL, NULL,
-      contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_intset_destroy (set);
-}
-
 static gboolean
 receive_authorized (gpointer p)
 {
   SelfAndContact *s = p;
-  TpTestsContactDetails *d;
-  TpIntSet *set;
-  TpTestsContactList *subscribe = s->self->priv->lists[
-    TP_TESTS_CONTACT_LIST_SUBSCRIBE];
-  TpTestsContactList *stored = s->self->priv->lists[
-    TP_TESTS_CONTACT_LIST_STORED];
-
-  /* A remote contact has accepted our request to see their presence.
-   *
-   * In a real connection manager this would be the result of incoming
-   * data from the server. */
-
-  g_message ("From server: %s has accepted our subscribe request",
-      tp_handle_inspect (s->self->priv->contact_repo, s->contact));
-
-  d = ensure_contact (s->self, s->contact, NULL);
-
-  /* if we were already subscribed to them, then nothing really happened */
-  if (d->subscribe)
-    return FALSE;
-
-  d->subscribe_requested = FALSE;
-  d->subscribe = TRUE;
-
-  set = tp_intset_new_containing (s->contact);
-  tp_group_mixin_change_members ((GObject *) subscribe, "",
-      set, NULL, NULL, NULL,
-      s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_group_mixin_change_members ((GObject *) stored, "",
-      set, NULL, NULL, NULL,
-      s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_intset_destroy (set);
-
-  /* their presence changes to something other than UNKNOWN */
-  g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact);
+  GArray *handles_array;
+  guint i;
 
-  /* if we're not publishing to them, also pretend they have asked us to
-   * do so */
-  if (!d->publish)
+  handles_array = tp_handle_set_to_array (s->handles);
+  for (i = 0; i < handles_array->len; i++)
     {
-      receive_auth_request (s->self, s->contact);
+      ContactDetails *d = lookup_contact (s->self,
+          g_array_index (handles_array, TpHandle, i));
+
+      if (d == NULL)
+        continue;
+
+      d->subscribe = TP_SUBSCRIPTION_STATE_YES;
+
+      /* if we're not publishing to them, also pretend they have asked us to do so */
+      if (d->publish != TP_SUBSCRIPTION_STATE_YES)
+        {
+          d->publish = TP_SUBSCRIPTION_STATE_ASK;
+          tp_clear_pointer (&d->publish_request, g_free);
+          d->publish_request = g_strdup ("automatic publish request");
+        }
     }
+  g_array_unref (handles_array);
+
+  tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (s->self),
+      s->handles, NULL);
 
   return FALSE;
 }
@@ -1394,477 +708,200 @@ static gboolean
 receive_unauthorized (gpointer p)
 {
   SelfAndContact *s = p;
-  TpTestsContactDetails *d;
-  TpIntSet *set;
-  TpTestsContactList *subscribe = s->self->priv->lists[
-    TP_TESTS_CONTACT_LIST_SUBSCRIBE];
-
-  /* if shutting down, do nothing */
-  if (subscribe == NULL)
-    return FALSE;
-
-  /* A remote contact has rejected our request to see their presence.
-   *
-   * In a real connection manager this would be the result of incoming
-   * data from the server. */
-
-  g_message ("From server: %s has rejected our subscribe request",
-      tp_handle_inspect (s->self->priv->contact_repo, s->contact));
-
-  d = ensure_contact (s->self, s->contact, NULL);
+  GArray *handles_array;
+  guint i;
 
-  if (!d->subscribe && !d->subscribe_requested)
-    return FALSE;
+  handles_array = tp_handle_set_to_array (s->handles);
+  for (i = 0; i < handles_array->len; i++)
+    {
+      ContactDetails *d = lookup_contact (s->self,
+          g_array_index (handles_array, TpHandle, i));
 
-  d->subscribe_requested = FALSE;
-  d->subscribe = FALSE;
+      if (d == NULL)
+        continue;
 
-  set = tp_intset_new_containing (s->contact);
-  tp_group_mixin_change_members ((GObject *) subscribe, "Say 'please'!",
-      NULL, set, NULL, NULL,
-      s->contact, TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_intset_destroy (set);
+      d->subscribe = TP_SUBSCRIPTION_STATE_REMOVED_REMOTELY;
+    }
+  g_array_unref (handles_array);
 
-  /* their presence changes to UNKNOWN */
-  g_signal_emit (s->self, signals[PRESENCE_UPDATED], 0, s->contact);
+  tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (s->self),
+      s->handles, NULL);
 
   return FALSE;
 }
 
-gboolean
-tp_tests_contact_list_manager_add_to_list (TpTestsContactListManager *self,
-                                          GObject *channel,
-                                          TpTestsContactListHandle list,
-                                          TpHandle member,
-                                          const gchar *message,
-                                          GError **error)
+void
+tp_tests_contact_list_manager_request_subscription (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members,  const gchar *message)
 {
-  TpIntSet *set;
-  TpTestsContactList *stored = self->priv->lists[TP_TESTS_CONTACT_LIST_STORED];
+  TpHandleSet *handles;
+  guint i;
+  gchar *message_lc;
 
-  switch (list)
+  handles = tp_handle_set_new (self->priv->contact_repo);
+  for (i = 0; i < n_members; i++)
     {
-    case TP_TESTS_CONTACT_LIST_SUBSCRIBE:
-      /* we would like to see member's presence */
-        {
-          gboolean created;
-          TpTestsContactDetails *d = ensure_contact (self, member, &created);
-          gchar *message_lc;
-
-          /* if they already authorized us, it's a no-op */
-          if (d->subscribe)
-            return TRUE;
-
-          /* In a real connection manager we'd start a network request here */
-          g_message ("Transmitting authorization request to %s: %s",
-              tp_handle_inspect (self->priv->contact_repo, member),
-              message);
-
-          if (created || !d->subscribe_requested)
-            {
-              d->subscribe_requested = TRUE;
-              send_updated_roster (self, member);
-            }
-
-          set = tp_intset_new_containing (member);
-          tp_group_mixin_change_members (channel, message,
-              NULL, NULL, NULL, set,
-              self->priv->conn->self_handle,
-              TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-          /* subscribing to someone implicitly puts them on Stored, too */
-          tp_group_mixin_change_members ((GObject *) stored, "",
-              set, NULL, NULL, NULL,
-              self->priv->conn->self_handle,
-              TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-          tp_intset_destroy (set);
-
-          /* Pretend that after a delay, the contact notices the request
-           * and allows or rejects it. In this tp_test connection manager,
-           * empty requests are allowed, as are requests that contain "please"
-           * case-insensitively. All other requests are denied. */
-          message_lc = g_ascii_strdown (message, -1);
-
-          if (message[0] == '\0' || strstr (message_lc, "please") != NULL)
-            {
-              g_timeout_add_full (G_PRIORITY_DEFAULT,
-                  self->priv->simulation_delay, receive_authorized,
-                  self_and_contact_new (self, member),
-                  self_and_contact_destroy);
-            }
-          else
-            {
-              g_timeout_add_full (G_PRIORITY_DEFAULT,
-                  self->priv->simulation_delay,
-                  receive_unauthorized,
-                  self_and_contact_new (self, member),
-                  self_and_contact_destroy);
-            }
-
-          g_free (message_lc);
-        }
-      return TRUE;
-
-    case TP_TESTS_CONTACT_LIST_PUBLISH:
-      /* We would like member to see our presence. This is meaningless,
-       * unless they have asked for it. */
-        {
-          TpTestsContactDetails *d = lookup_contact (self, member);
-
-          if (d == NULL || !d->publish_requested)
-            {
-              /* the group mixin won't actually allow this to be reached,
-               * because of the flags we set */
-              g_set_error (error, TP_ERRORS, TP_ERROR_NOT_AVAILABLE,
-                  "Can't unilaterally send presence to %s",
-                  tp_handle_inspect (self->priv->contact_repo, member));
-              return FALSE;
-            }
-
-          if (!d->publish)
-            {
-              d->publish = TRUE;
-              d->publish_requested = FALSE;
-              send_updated_roster (self, member);
-
-              set = tp_intset_new_containing (member);
-              tp_group_mixin_change_members (channel, "",
-                  set, NULL, NULL, NULL,
-                  self->priv->conn->self_handle,
-                  TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-              tp_group_mixin_change_members ((GObject *) stored, "",
-                  set, NULL, NULL, NULL,
-                  self->priv->conn->self_handle,
-                  TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-              tp_intset_destroy (set);
-            }
-        }
-      return TRUE;
-
-    case TP_TESTS_CONTACT_LIST_STORED:
-      /* we would like member to be on the roster */
-        {
-          gboolean created;
-
-          ensure_contact (self, member, &created);
+      ContactDetails *d = ensure_contact (self, members[i]);
 
-          if (created)
-            send_updated_roster (self, member);
+      if (d->subscribe == TP_SUBSCRIPTION_STATE_YES)
+        continue;
 
-          set = tp_intset_new_containing (member);
-          tp_group_mixin_change_members (channel, "",
-              set, NULL, NULL, NULL, self->priv->conn->self_handle,
-              TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-          tp_intset_destroy (set);
-        }
-      return TRUE;
-
-    case INVALID_TP_TESTS_CONTACT_LIST:
-    default:
-      g_return_val_if_reached (FALSE);
+      d->subscribe = TP_SUBSCRIPTION_STATE_ASK;
+      tp_handle_set_add (handles, members[i]);
     }
-}
 
-static gboolean
-auth_request_cb (gpointer p)
-{
-  SelfAndContact *s = p;
+  tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), handles,
+      NULL);
 
-  receive_auth_request (s->self, s->contact);
+  message_lc = g_ascii_strdown (message, -1);
+  if (strstr (message_lc, "please") != NULL)
+    {
+      g_idle_add_full (G_PRIORITY_DEFAULT,
+          receive_authorized,
+          self_and_contact_new (self, handles),
+          self_and_contact_destroy);
+    }
+  else if (strstr (message_lc, "no") != NULL)
+    {
+      g_idle_add_full (G_PRIORITY_DEFAULT,
+          receive_unauthorized,
+          self_and_contact_new (self, handles),
+          self_and_contact_destroy);
+    }
 
-  return FALSE;
+  g_free (message_lc);
+  tp_handle_set_destroy (handles);
 }
 
-gboolean
-tp_tests_contact_list_manager_remove_from_list (TpTestsContactListManager *self,
-                                               GObject *channel,
-                                               TpTestsContactListHandle list,
-                                               TpHandle member,
-                                               const gchar *message,
-                                               GError **error)
+void
+tp_tests_contact_list_manager_unsubscribe (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members)
 {
-  TpIntSet *set;
+  TpHandleSet *handles;
+  guint i;
 
-  switch (list)
+  handles = tp_handle_set_new (self->priv->contact_repo);
+  for (i = 0; i < n_members; i++)
     {
-    case TP_TESTS_CONTACT_LIST_PUBLISH:
-      /* we would like member not to see our presence any more, or we
-       * would like to reject a request from them to see our presence */
-        {
-          TpTestsContactDetails *d = lookup_contact (self, member);
-
-          if (d != NULL)
-            {
-              if (d->publish_requested)
-                {
-                  g_message ("Rejecting authorization request from %s",
-                      tp_handle_inspect (self->priv->contact_repo, member));
-                  d->publish_requested = FALSE;
-
-                  set = tp_intset_new_containing (member);
-                  tp_group_mixin_change_members (channel, "",
-                      NULL, set, NULL, NULL,
-                      self->priv->conn->self_handle,
-                      TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-                  tp_intset_destroy (set);
-                }
-              else if (d->publish)
-                {
-                  g_message ("Removing authorization from %s",
-                      tp_handle_inspect (self->priv->contact_repo, member));
-                  d->publish = FALSE;
-
-                  set = tp_intset_new_containing (member);
-                  tp_group_mixin_change_members (channel, "",
-                      NULL, set, NULL, NULL,
-                      self->priv->conn->self_handle,
-                      TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-                  tp_intset_destroy (set);
-
-                  /* Pretend that after a delay, the contact notices the change
-                   * and asks for our presence again */
-                  g_timeout_add_full (G_PRIORITY_DEFAULT,
-                      self->priv->simulation_delay, auth_request_cb,
-                      self_and_contact_new (self, member),
-                      self_and_contact_destroy);
-                }
-              else
-                {
-                  /* nothing to do, avoid "updating the roster" */
-                  return TRUE;
-                }
-
-              send_updated_roster (self, member);
-            }
-        }
-      return TRUE;
+      ContactDetails *d = lookup_contact (self, members[i]);
 
-    case TP_TESTS_CONTACT_LIST_SUBSCRIBE:
-      /* we would like to avoid receiving member's presence any more,
-       * or we would like to cancel an outstanding request for their
-       * presence */
-        {
-          TpTestsContactDetails *d = lookup_contact (self, member);
-
-          if (d != NULL)
-            {
-              if (d->subscribe_requested)
-                {
-                  g_message ("Cancelling our authorization request to %s",
-                      tp_handle_inspect (self->priv->contact_repo, member));
-                  d->subscribe_requested = FALSE;
-
-                  set = tp_intset_new_containing (member);
-                  tp_group_mixin_change_members (channel, "",
-                      NULL, set, NULL, NULL,
-                      self->priv->conn->self_handle,
-                      TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-                  tp_intset_destroy (set);
-                }
-              else if (d->subscribe)
-                {
-                  g_message ("We no longer want presence from %s",
-                      tp_handle_inspect (self->priv->contact_repo, member));
-                  d->subscribe = FALSE;
-
-                  set = tp_intset_new_containing (member);
-                  tp_group_mixin_change_members (channel, "",
-                      NULL, set, NULL, NULL,
-                      self->priv->conn->self_handle,
-                      TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-                  tp_intset_destroy (set);
-
-                  /* since they're no longer on the subscribe list, we can't
-                   * see their presence, so emit a signal changing it to
-                   * UNKNOWN */
-                  g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member);
-                }
-              else
-                {
-                  /* nothing to do, avoid "updating the roster" */
-                  return TRUE;
-                }
-
-              send_updated_roster (self, member);
-            }
-        }
-      return TRUE;
-
-    case TP_TESTS_CONTACT_LIST_STORED:
-      /* we would like to remove member from the roster altogether */
-        {
-          TpTestsContactDetails *d = lookup_contact (self, member);
-
-          if (d != NULL)
-            {
-              g_hash_table_remove (self->priv->contact_details,
-                  GUINT_TO_POINTER (member));
-              send_updated_roster (self, member);
-
-              set = tp_intset_new_containing (member);
-              tp_group_mixin_change_members (channel, "",
-                  NULL, set, NULL, NULL,
-                  self->priv->conn->self_handle,
-                  TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-              tp_group_mixin_change_members (
-                  (GObject *) self->priv->lists[TP_TESTS_CONTACT_LIST_SUBSCRIBE],
-                  "", NULL, set, NULL, NULL,
-                  self->priv->conn->self_handle,
-                  TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-              tp_group_mixin_change_members (
-                  (GObject *) self->priv->lists[TP_TESTS_CONTACT_LIST_PUBLISH],
-                  "", NULL, set, NULL, NULL,
-                  self->priv->conn->self_handle,
-                  TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-              tp_intset_destroy (set);
-
-              tp_handle_set_remove (self->priv->contacts, member);
-
-              /* since they're no longer on the subscribe list, we can't
-               * see their presence, so emit a signal changing it to
-               * UNKNOWN */
-              g_signal_emit (self, signals[PRESENCE_UPDATED], 0, member);
-
-            }
-        }
-      return TRUE;
+      if (d == NULL || d->subscribe == TP_SUBSCRIPTION_STATE_NO)
+        continue;
 
-    case INVALID_TP_TESTS_CONTACT_LIST:
-    default:
-      g_return_val_if_reached (FALSE);
+      d->subscribe = TP_SUBSCRIPTION_STATE_NO;
+      tp_handle_set_add (handles, members[i]);
     }
+
+  tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), handles,
+      NULL);
+
+  tp_handle_set_destroy (handles);
 }
 
-TpTestsContactListPresence
-tp_tests_contact_list_manager_get_presence (TpTestsContactListManager *self,
-                                           TpHandle contact)
+void
+tp_tests_contact_list_manager_authorize_publication (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members)
 {
-  TpTestsContactDetails *d = lookup_contact (self, contact);
-  const gchar *id;
+  TpHandleSet *handles;
+  guint i;
 
-  if (d == NULL || !d->subscribe)
+  handles = tp_handle_set_new (self->priv->contact_repo);
+  for (i = 0; i < n_members; i++)
     {
-      /* we don't know the presence of people not on the subscribe list,
-       * by definition */
-      return TP_TESTS_CONTACT_LIST_PRESENCE_UNKNOWN;
-    }
+      ContactDetails *d = lookup_contact (self, members[i]);
 
-  id = tp_handle_inspect (self->priv->contact_repo, contact);
+      if (d == NULL || d->publish != TP_SUBSCRIPTION_STATE_ASK)
+        continue;
 
-  /* In this tp_test CM, we fake contacts' presence based on their name:
-   * contacts in the first half of the alphabet are available, the rest
-   * (including non-alphabetic and non-ASCII initial letters) are away. */
-  if ((id[0] >= 'A' && id[0] <= 'M') || (id[0] >= 'a' && id[0] <= 'm'))
-    {
-      return TP_TESTS_CONTACT_LIST_PRESENCE_AVAILABLE;
+      d->publish = TP_SUBSCRIPTION_STATE_YES;
+      tp_clear_pointer (&d->publish_request, g_free);
+      tp_handle_set_add (handles, members[i]);
     }
 
-  return TP_TESTS_CONTACT_LIST_PRESENCE_AWAY;
+  tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), handles,
+      NULL);
+
+  tp_handle_set_destroy (handles);
 }
 
-const gchar *
-tp_tests_contact_list_manager_get_alias (TpTestsContactListManager *self,
-                                        TpHandle contact)
+void
+tp_tests_contact_list_manager_unpublish (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members)
 {
-  TpTestsContactDetails *d = lookup_contact (self, contact);
+  TpHandleSet *handles;
+  guint i;
 
-  if (d == NULL)
+  handles = tp_handle_set_new (self->priv->contact_repo);
+  for (i = 0; i < n_members; i++)
     {
-      /* we don't have a user-defined alias for people not on the roster */
-      return tp_handle_inspect (self->priv->contact_repo, contact);
+      ContactDetails *d = lookup_contact (self, members[i]);
+
+      if (d == NULL || d->publish == TP_SUBSCRIPTION_STATE_NO)
+        continue;
+
+      d->publish = TP_SUBSCRIPTION_STATE_NO;
+      tp_clear_pointer (&d->publish_request, g_free);
+      tp_handle_set_add (handles, members[i]);
     }
 
-  return d->alias;
+  tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), handles,
+      NULL);
+
+  tp_handle_set_destroy (handles);
 }
 
 void
-tp_tests_contact_list_manager_set_alias (TpTestsContactListManager *self,
-                                        TpHandle contact,
-                                        const gchar *alias)
+tp_tests_contact_list_manager_remove (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members)
 {
-  gboolean created;
-  TpTestsContactDetails *d = ensure_contact (self, contact, &created);
-  TpTestsContactList *stored = self->priv->lists[
-    TP_TESTS_CONTACT_LIST_STORED];
-  gchar *old = d->alias;
-  TpIntSet *set;
-
-  /* FIXME: if stored list hasn't been retrieved yet, queue the change for
-   * later */
-
-  /* if shutting down, do nothing */
-  if (stored == NULL)
-    return;
+  TpHandleSet *handles;
+  guint i;
 
-  d->alias = g_strdup (alias);
+  handles = tp_handle_set_new (self->priv->contact_repo);
+  for (i = 0; i < n_members; i++)
+    {
+      ContactDetails *d = lookup_contact (self, members[i]);
 
-  if (created || tp_strdiff (old, alias))
-    send_updated_roster (self, contact);
+      if (d == NULL)
+        continue;
 
-  g_free (old);
+      g_hash_table_remove (self->priv->contact_details,
+          GUINT_TO_POINTER (members[i]));
+      tp_handle_set_add (handles, members[i]);
+    }
 
-  set = tp_intset_new_containing (contact);
-  tp_group_mixin_change_members ((GObject *) stored, "",
-      set, NULL, NULL, NULL, self->priv->conn->self_handle,
-      TP_CHANNEL_GROUP_CHANGE_REASON_NONE);
-  tp_intset_destroy (set);
+  tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), NULL,
+      handles);
+
+  tp_handle_set_destroy (handles);
 }
 
-GPtrArray *
-tp_tests_contact_list_manager_get_contact_info (TpTestsContactListManager *self,
-                                               TpHandle contact)
+void
+tp_tests_contact_list_manager_add_initial_contacts (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members)
 {
-  TpTestsContactDetails *d = lookup_contact (self, contact);
+  TpHandleSet *handles;
+  guint i;
 
-  if (d != NULL)
-    return d->contact_info;
+  g_assert_cmpint (self->priv->conn->status, ==,
+      TP_INTERNAL_CONNECTION_STATUS_NEW);
 
-  return NULL;
-}
+  handles = tp_handle_set_new (self->priv->contact_repo);
+  for (i = 0; i < n_members; i++)
+    {
+      ContactDetails *d;
 
-void
-tp_tests_contact_list_manager_set_contact_info (TpTestsContactListManager *self,
-                                               const GPtrArray *contact_info)
-{
-  TpTestsContactList *stored = self->priv->lists[
-    TP_TESTS_CONTACT_LIST_STORED];
-  GPtrArray *old;
-  TpTestsContactDetails *d = ensure_contact (self, self->priv->conn->self_handle,
-      NULL);
-  d->id = g_strdup (tp_handle_inspect (self->priv->contact_repo,
-        self->priv->conn->self_handle));
-  old = d->contact_info;
+      g_assert (lookup_contact (self, members[i]) == NULL);
+      d = ensure_contact (self, members[i]);
 
-  /* FIXME: if stored list hasn't been retrieved yet, queue the change for
-   * later */
+      d->subscribe = TP_SUBSCRIPTION_STATE_YES;
+      d->publish = TP_SUBSCRIPTION_STATE_YES;
 
-  /* if shutting down, do nothing */
-  if (stored == NULL)
-    return;
+      tp_handle_set_add (handles, members[i]);
+    }
+
+  tp_base_contact_list_contacts_changed (TP_BASE_CONTACT_LIST (self), handles,
+      NULL);
 
-  d->contact_info = dbus_g_type_specialized_construct (
-      TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST);
-  {
-    guint i;
-    for (i = 0; i < contact_info->len; i++)
-      {
-        const gchar *name;
-        const gchar * const * params;
-        const gchar * const * values;
-        GValueArray *va = g_ptr_array_index (contact_info, i);
-
-        tp_value_array_unpack (va, 3,
-          &name,
-          &params,
-          &values);
-
-        _insert_contact_field (d->contact_info, name, params, values);
-      }
-  }
-
-  /* always send the updated roster, since it's not worth checking the
-   * contact_info for changes */
-  send_updated_roster (self, self->priv->conn->self_handle);
-
-  if (old != NULL)
-    g_ptr_array_unref (old);
+  tp_handle_set_destroy (handles);
 }
diff --git a/tests/lib/telepathy/contactlist/contact-list-manager.h b/tests/lib/telepathy/contactlist/contact-list-manager.h
index 8c480b9..8be787c 100644
--- a/tests/lib/telepathy/contactlist/contact-list-manager.h
+++ b/tests/lib/telepathy/contactlist/contact-list-manager.h
@@ -1,8 +1,8 @@
 /*
  * Example channel manager for contact lists
  *
- * Copyright  2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
- * Copyright  2007-2009 Nokia Corporation
+ * Copyright  2007-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright  2007-2010 Nokia Corporation
  *
  * Copying and distribution of this file, with or without modification,
  * are permitted in any medium without royalty provided the copyright
@@ -12,30 +12,10 @@
 #ifndef __TP_TESTS_CONTACT_LIST_MANAGER_H__
 #define __TP_TESTS_CONTACT_LIST_MANAGER_H__
 
-#include <glib-object.h>
-
-#include <telepathy-glib/channel-manager.h>
-#include <telepathy-glib/handle.h>
-#include <telepathy-glib/presence-mixin.h>
+#include <telepathy-glib/base-contact-list.h>
 
 G_BEGIN_DECLS
 
-typedef struct _TpTestsContactListManager TpTestsContactListManager;
-typedef struct _TpTestsContactListManagerClass TpTestsContactListManagerClass;
-typedef struct _TpTestsContactListManagerPrivate TpTestsContactListManagerPrivate;
-
-struct _TpTestsContactListManagerClass {
-    GObjectClass parent_class;
-};
-
-struct _TpTestsContactListManager {
-    GObject parent;
-
-    TpTestsContactListManagerPrivate *priv;
-};
-
-GType tp_tests_contact_list_manager_get_type (void);
-
 #define TP_TESTS_TYPE_CONTACT_LIST_MANAGER \
   (tp_tests_contact_list_manager_get_type ())
 #define TP_TESTS_CONTACT_LIST_MANAGER(obj) \
@@ -52,60 +32,39 @@ GType tp_tests_contact_list_manager_get_type (void);
   (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_CONTACT_LIST_MANAGER, \
                               TpTestsContactListManagerClass))
 
-gboolean tp_tests_contact_list_manager_add_to_group (
-    TpTestsContactListManager *self, GObject *channel,
-    TpHandle group, TpHandle member, const gchar *message, GError **error);
-
-gboolean tp_tests_contact_list_manager_remove_from_group (
-    TpTestsContactListManager *self, GObject *channel,
-    TpHandle group, TpHandle member, const gchar *message, GError **error);
-
-/* elements 1, 2... of this enum must be kept in sync with elements 0, 1...
- * of the array _contact_lists in contact-list-manager.h */
-typedef enum {
-    INVALID_TP_TESTS_CONTACT_LIST,
-    TP_TESTS_CONTACT_LIST_SUBSCRIBE = 1,
-    TP_TESTS_CONTACT_LIST_PUBLISH,
-    TP_TESTS_CONTACT_LIST_STORED
-} TpTestsContactListHandle;
-
-#define NUM_TP_TESTS_CONTACT_LISTS TP_TESTS_CONTACT_LIST_STORED + 1
-
-/* this enum must be kept in sync with the array _statuses in
- * contact-list-manager.c */
-typedef enum {
-    TP_TESTS_CONTACT_LIST_PRESENCE_OFFLINE = 0,
-    TP_TESTS_CONTACT_LIST_PRESENCE_UNKNOWN,
-    TP_TESTS_CONTACT_LIST_PRESENCE_ERROR,
-    TP_TESTS_CONTACT_LIST_PRESENCE_AWAY,
-    TP_TESTS_CONTACT_LIST_PRESENCE_AVAILABLE
-} TpTestsContactListPresence;
+typedef struct _TpTestsContactListManager TpTestsContactListManager;
+typedef struct _TpTestsContactListManagerClass TpTestsContactListManagerClass;
+typedef struct _TpTestsContactListManagerPrivate TpTestsContactListManagerPrivate;
 
-const TpPresenceStatusSpec *tp_tests_contact_list_presence_statuses (
-    void);
+struct _TpTestsContactListManagerClass {
+    TpBaseContactListClass parent_class;
+};
 
-gboolean tp_tests_contact_list_manager_add_to_list (
-    TpTestsContactListManager *self, GObject *channel,
-    TpTestsContactListHandle list, TpHandle member, const gchar *message,
-    GError **error);
+struct _TpTestsContactListManager {
+    TpBaseContactList parent;
 
-gboolean tp_tests_contact_list_manager_remove_from_list (
-    TpTestsContactListManager *self, GObject *channel,
-    TpTestsContactListHandle list, TpHandle member, const gchar *message,
-    GError **error);
+    TpTestsContactListManagerPrivate *priv;
+};
 
-const gchar **tp_tests_contact_lists (void);
+GType tp_tests_contact_list_manager_get_type (void);
 
-TpTestsContactListPresence tp_tests_contact_list_manager_get_presence (
-    TpTestsContactListManager *self, TpHandle contact);
-const gchar *tp_tests_contact_list_manager_get_alias (
-    TpTestsContactListManager *self, TpHandle contact);
-void tp_tests_contact_list_manager_set_alias (
-    TpTestsContactListManager *self, TpHandle contact, const gchar *alias);
-GPtrArray * tp_tests_contact_list_manager_get_contact_info (
-    TpTestsContactListManager *self, TpHandle contact);
-void tp_tests_contact_list_manager_set_contact_info (
-    TpTestsContactListManager *self, const GPtrArray *contact_info);
+void tp_tests_contact_list_manager_add_to_group (TpTestsContactListManager *self,
+    const gchar *group_name, TpHandle member);
+void tp_tests_contact_list_manager_remove_from_group (TpTestsContactListManager *self,
+    const gchar *group_name, TpHandle member);
+
+void tp_tests_contact_list_manager_request_subscription (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members,  const gchar *message);
+void tp_tests_contact_list_manager_unsubscribe (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members);
+void tp_tests_contact_list_manager_authorize_publication (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members);
+void tp_tests_contact_list_manager_unpublish (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members);
+void tp_tests_contact_list_manager_remove (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members);
+void tp_tests_contact_list_manager_add_initial_contacts (TpTestsContactListManager *self,
+    guint n_members, TpHandle *members);
 
 G_END_DECLS
 
diff --git a/tests/lib/telepathy/contactlist/contacts-conn.c b/tests/lib/telepathy/contactlist/contacts-conn.c
new file mode 100644
index 0000000..bd434fd
--- /dev/null
+++ b/tests/lib/telepathy/contactlist/contacts-conn.c
@@ -0,0 +1,1392 @@
+/*
+ * contacts-conn.c - connection with contact info
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "config.h"
+
+#include "contacts-conn.h"
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/util.h>
+
+#include "debug.h"
+
+static void init_aliasing (gpointer, gpointer);
+static void init_avatars (gpointer, gpointer);
+static void init_location (gpointer, gpointer);
+static void init_contact_caps (gpointer, gpointer);
+static void init_contact_info (gpointer, gpointer);
+static void conn_avatars_properties_getter (GObject *object, GQuark interface,
+    GQuark name, GValue *value, gpointer getter_data);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsContactsConnection,
+    tp_tests_contacts_connection,
+    TP_TESTS_TYPE_SIMPLE_CONNECTION,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_ALIASING,
+      init_aliasing);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_AVATARS,
+      init_avatars);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_PRESENCE,
+      tp_presence_mixin_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+      tp_presence_mixin_simple_presence_iface_init)
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_LOCATION,
+      init_location)
+    G_IMPLEMENT_INTERFACE (
+      TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+      init_contact_caps)
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_INFO,
+      init_contact_info)
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACTS,
+      tp_contacts_mixin_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_LIST,
+      tp_base_contact_list_mixin_list_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CONTACT_GROUPS,
+      tp_base_contact_list_mixin_groups_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_CLIENT_TYPES,
+      NULL);
+    );
+
+/* type definition stuff */
+
+static const char *mime_types[] = { "image/png", NULL };
+static TpDBusPropertiesMixinPropImpl conn_avatars_properties[] = {
+      { "MinimumAvatarWidth", GUINT_TO_POINTER (1), NULL },
+      { "MinimumAvatarHeight", GUINT_TO_POINTER (2), NULL },
+      { "RecommendedAvatarWidth", GUINT_TO_POINTER (3), NULL },
+      { "RecommendedAvatarHeight", GUINT_TO_POINTER (4), NULL },
+      { "MaximumAvatarWidth", GUINT_TO_POINTER (5), NULL },
+      { "MaximumAvatarHeight", GUINT_TO_POINTER (6), NULL },
+      { "MaximumAvatarBytes", GUINT_TO_POINTER (7), NULL },
+      /* special-cased - it's the only one with a non-guint value */
+      { "SupportedAvatarMIMETypes", NULL, NULL },
+      { NULL }
+};
+
+enum
+{
+  N_SIGNALS
+};
+
+struct _TpTestsContactsConnectionPrivate
+{
+  /* TpHandle => gchar * */
+  GHashTable *aliases;
+  /* TpHandle => AvatarData */
+  GHashTable *avatars;
+  /* TpHandle => ContactsConnectionPresenceStatusIndex */
+  GHashTable *presence_statuses;
+  /* TpHandle => gchar * */
+  GHashTable *presence_messages;
+  /* TpHandle => GHashTable * */
+  GHashTable *locations;
+  /* TpHandle => GPtrArray * */
+  GHashTable *capabilities;
+  /* TpHandle => GPtrArray * */
+  GHashTable *contact_info;
+  GPtrArray *default_contact_info;
+
+  TpTestsContactListManager *list_manager;
+};
+
+typedef struct
+{
+  GArray *data;
+  gchar *mime_type;
+  gchar *token;
+} AvatarData;
+
+static AvatarData *
+avatar_data_new (GArray *data,
+    const gchar *mime_type,
+    const gchar *token)
+{
+  AvatarData *a;
+
+  a = g_slice_new (AvatarData);
+  a->data = data ? g_array_ref (data) : NULL;
+  a->mime_type = g_strdup (mime_type);
+  a->token = g_strdup (token);
+
+  return a;
+}
+
+static void
+avatar_data_free (gpointer data)
+{
+  AvatarData *a = data;
+
+  if (a != NULL)
+    {
+      if (a->data != NULL)
+        g_array_unref (a->data);
+      g_free (a->mime_type);
+      g_free (a->token);
+      g_slice_free (AvatarData, a);
+    }
+}
+
+static void
+free_rcc_list (GPtrArray *rccs)
+{
+  g_boxed_free (TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, rccs);
+}
+
+static void
+tp_tests_contacts_connection_init (TpTestsContactsConnection *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TESTS_TYPE_CONTACTS_CONNECTION,
+      TpTestsContactsConnectionPrivate);
+  self->priv->aliases = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+      NULL, g_free);
+  self->priv->avatars = g_hash_table_new_full (g_direct_hash,
+      g_direct_equal, NULL, avatar_data_free);
+  self->priv->presence_statuses = g_hash_table_new_full (g_direct_hash,
+      g_direct_equal, NULL, NULL);
+  self->priv->presence_messages = g_hash_table_new_full (g_direct_hash,
+      g_direct_equal, NULL, g_free);
+  self->priv->locations = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+      NULL, (GDestroyNotify) g_hash_table_unref);
+  self->priv->capabilities = g_hash_table_new_full (g_direct_hash,
+      g_direct_equal, NULL, (GDestroyNotify) free_rcc_list);
+  self->priv->contact_info = g_hash_table_new_full (g_direct_hash,
+      g_direct_equal, NULL, (GDestroyNotify) g_ptr_array_unref);
+}
+
+static void
+finalize (GObject *object)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+
+  tp_contacts_mixin_finalize (object);
+  g_hash_table_unref (self->priv->aliases);
+  g_hash_table_unref (self->priv->avatars);
+  g_hash_table_unref (self->priv->presence_statuses);
+  g_hash_table_unref (self->priv->presence_messages);
+  g_hash_table_unref (self->priv->locations);
+  g_hash_table_unref (self->priv->capabilities);
+  g_hash_table_unref (self->priv->contact_info);
+
+  if (self->priv->default_contact_info != NULL)
+    g_ptr_array_unref (self->priv->default_contact_info);
+
+  G_OBJECT_CLASS (tp_tests_contacts_connection_parent_class)->finalize (object);
+}
+
+static void
+aliasing_fill_contact_attributes (GObject *object,
+                                  const GArray *contacts,
+                                  GHashTable *attributes)
+{
+  guint i;
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+  TpBaseConnection *base = TP_BASE_CONNECTION (object);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, guint, i);
+      const gchar *alias = g_hash_table_lookup (self->priv->aliases,
+          GUINT_TO_POINTER (handle));
+
+      if (alias == NULL)
+        {
+          alias = tp_handle_inspect (contact_repo, handle);
+        }
+
+      tp_contacts_mixin_set_contact_attribute (attributes, handle,
+          TP_IFACE_CONNECTION_INTERFACE_ALIASING "/alias",
+          tp_g_value_slice_new_string (alias));
+    }
+}
+
+static void
+avatars_fill_contact_attributes (GObject *object,
+                                 const GArray *contacts,
+                                 GHashTable *attributes)
+{
+  guint i;
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, guint, i);
+      AvatarData *a = g_hash_table_lookup (self->priv->avatars,
+          GUINT_TO_POINTER (handle));
+
+      if (a != NULL && a->token != NULL)
+        {
+          tp_contacts_mixin_set_contact_attribute (attributes, handle,
+              TP_IFACE_CONNECTION_INTERFACE_AVATARS "/token",
+              tp_g_value_slice_new_string (a->token));
+        }
+    }
+}
+
+static void
+location_fill_contact_attributes (GObject *object,
+    const GArray *contacts,
+    GHashTable *attributes)
+{
+  guint i;
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, guint, i);
+      GHashTable *location = g_hash_table_lookup (self->priv->locations,
+          GUINT_TO_POINTER (handle));
+
+      if (location != NULL)
+        {
+          tp_contacts_mixin_set_contact_attribute (attributes, handle,
+              TP_IFACE_CONNECTION_INTERFACE_LOCATION "/location",
+              tp_g_value_slice_new_boxed (TP_HASH_TYPE_LOCATION, location));
+        }
+    }
+}
+
+static void
+contact_caps_fill_contact_attributes (GObject *object,
+    const GArray *contacts,
+    GHashTable *attributes)
+{
+  guint i;
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, guint, i);
+      GPtrArray *caps = g_hash_table_lookup (self->priv->capabilities,
+          GUINT_TO_POINTER (handle));
+
+      if (caps != NULL)
+        {
+          tp_contacts_mixin_set_contact_attribute (attributes, handle,
+              TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES "/capabilities",
+              tp_g_value_slice_new_boxed (
+                TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST, caps));
+        }
+    }
+}
+
+static void
+contact_info_fill_contact_attributes (GObject *object,
+    const GArray *contacts,
+    GHashTable *attributes)
+{
+  guint i;
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, guint, i);
+      GPtrArray *info = g_hash_table_lookup (self->priv->contact_info,
+          GUINT_TO_POINTER (handle));
+
+      if (info != NULL)
+        {
+          tp_contacts_mixin_set_contact_attribute (attributes, handle,
+              TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO "/info",
+              tp_g_value_slice_new_boxed (TP_ARRAY_TYPE_CONTACT_INFO_FIELD_LIST,
+                  info));
+        }
+    }
+}
+
+static TpDBusPropertiesMixinPropImpl conn_contact_info_properties[] = {
+      { "ContactInfoFlags", GUINT_TO_POINTER (TP_CONTACT_INFO_FLAG_PUSH |
+          TP_CONTACT_INFO_FLAG_CAN_SET), NULL },
+      { "SupportedFields", NULL, NULL },
+      { NULL }
+};
+
+static void
+conn_contact_info_properties_getter (GObject *object,
+                                     GQuark interface,
+                                     GQuark name,
+                                     GValue *value,
+                                     gpointer getter_data)
+{
+  GQuark q_supported_fields = g_quark_from_static_string ("SupportedFields");
+  static GPtrArray *supported_fields = NULL;
+
+  if (name == q_supported_fields)
+    {
+      if (supported_fields == NULL)
+        {
+          supported_fields = g_ptr_array_new ();
+
+          g_ptr_array_add (supported_fields, tp_value_array_build (4,
+              G_TYPE_STRING, "bday",
+              G_TYPE_STRV, NULL,
+              G_TYPE_UINT, 0,
+              G_TYPE_UINT, 1,
+              G_TYPE_INVALID));
+
+          g_ptr_array_add (supported_fields, tp_value_array_build (4,
+              G_TYPE_STRING, "email",
+              G_TYPE_STRV, NULL,
+              G_TYPE_UINT, 0,
+              G_TYPE_UINT, G_MAXUINT32,
+              G_TYPE_INVALID));
+
+          g_ptr_array_add (supported_fields, tp_value_array_build (4,
+              G_TYPE_STRING, "fn",
+              G_TYPE_STRV, NULL,
+              G_TYPE_UINT, 0,
+              G_TYPE_UINT, 1,
+              G_TYPE_INVALID));
+
+          g_ptr_array_add (supported_fields, tp_value_array_build (4,
+              G_TYPE_STRING, "tel",
+              G_TYPE_STRV, NULL,
+              G_TYPE_UINT, 0,
+              G_TYPE_UINT, G_MAXUINT32,
+              G_TYPE_INVALID));
+
+          g_ptr_array_add (supported_fields, tp_value_array_build (4,
+              G_TYPE_STRING, "url",
+              G_TYPE_STRV, NULL,
+              G_TYPE_UINT, 0,
+              G_TYPE_UINT, G_MAXUINT32,
+              G_TYPE_INVALID));
+        }
+      g_value_set_boxed (value, supported_fields);
+    }
+  else
+    {
+      g_value_set_uint (value, GPOINTER_TO_UINT (getter_data));
+    }
+}
+
+static void
+client_types_fill_contact_attributes (
+    GObject *object,
+    const GArray *contacts,
+    GHashTable *attributes)
+{
+  TpTestsContactsConnectionClass *klass =
+      TP_TESTS_CONTACTS_CONNECTION_GET_CLASS (object);
+
+  if (klass->fill_client_types != NULL)
+    klass->fill_client_types (object, contacts, attributes);
+  /* âelse do nothing: a no-op implementation is valid, relatively speaking.
+   * The spec sez the /client-types attribute should be âomitted from the
+   * result if the contact's client types are not known.â
+   */
+}
+
+static void
+constructed (GObject *object)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+  TpBaseConnection *base = TP_BASE_CONNECTION (object);
+  void (*parent_impl) (GObject *) =
+    G_OBJECT_CLASS (tp_tests_contacts_connection_parent_class)->constructed;
+
+  if (parent_impl != NULL)
+    parent_impl (object);
+
+  tp_contacts_mixin_init (object,
+      G_STRUCT_OFFSET (TpTestsContactsConnection, contacts_mixin));
+  tp_base_connection_register_with_contacts_mixin (base);
+  if (self->priv->list_manager)
+    tp_base_contact_list_mixin_register_with_contacts_mixin (base);
+  tp_contacts_mixin_add_contact_attributes_iface (object,
+      TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+      aliasing_fill_contact_attributes);
+  tp_contacts_mixin_add_contact_attributes_iface (object,
+      TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+      avatars_fill_contact_attributes);
+  tp_contacts_mixin_add_contact_attributes_iface (object,
+      TP_IFACE_CONNECTION_INTERFACE_LOCATION,
+      location_fill_contact_attributes);
+  tp_contacts_mixin_add_contact_attributes_iface (object,
+      TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+      contact_caps_fill_contact_attributes);
+  tp_contacts_mixin_add_contact_attributes_iface (object,
+      TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO,
+      contact_info_fill_contact_attributes);
+  tp_contacts_mixin_add_contact_attributes_iface (object,
+      TP_IFACE_CONNECTION_INTERFACE_CLIENT_TYPES,
+      client_types_fill_contact_attributes);
+
+  tp_presence_mixin_init (object,
+      G_STRUCT_OFFSET (TpTestsContactsConnection, presence_mixin));
+  tp_presence_mixin_simple_presence_register_with_contacts_mixin (object);
+}
+
+static const TpPresenceStatusOptionalArgumentSpec can_have_message[] = {
+      { "message", "s", NULL, NULL },
+      { NULL }
+};
+
+/* Must match TpTestsContactsConnectionPresenceStatusIndex in the .h */
+static const TpPresenceStatusSpec my_statuses[] = {
+      { "available", TP_CONNECTION_PRESENCE_TYPE_AVAILABLE, TRUE,
+        can_have_message },
+      { "busy", TP_CONNECTION_PRESENCE_TYPE_BUSY, TRUE, can_have_message },
+      { "away", TP_CONNECTION_PRESENCE_TYPE_AWAY, TRUE, can_have_message },
+      { "offline", TP_CONNECTION_PRESENCE_TYPE_OFFLINE, FALSE, NULL },
+      { "unknown", TP_CONNECTION_PRESENCE_TYPE_UNKNOWN, FALSE, NULL },
+      { "error", TP_CONNECTION_PRESENCE_TYPE_ERROR, FALSE, NULL },
+      { NULL }
+};
+
+static gboolean
+my_status_available (GObject *object,
+                     guint index)
+{
+  TpBaseConnection *base = TP_BASE_CONNECTION (object);
+
+  if (base->status != TP_CONNECTION_STATUS_CONNECTED)
+    return FALSE;
+
+  return TRUE;
+}
+
+static GHashTable *
+my_get_contact_statuses (GObject *object,
+                         const GArray *contacts,
+                         GError **error)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (object);
+  GHashTable *result = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+      NULL, (GDestroyNotify) tp_presence_status_free);
+  guint i;
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, TpHandle, i);
+      gpointer key = GUINT_TO_POINTER (handle);
+      TpTestsContactsConnectionPresenceStatusIndex index;
+      const gchar *presence_message;
+      GHashTable *parameters;
+
+      index = GPOINTER_TO_UINT (g_hash_table_lookup (
+            self->priv->presence_statuses, key));
+      presence_message = g_hash_table_lookup (
+          self->priv->presence_messages, key);
+
+      parameters = g_hash_table_new_full (g_str_hash,
+          g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+      if (presence_message != NULL)
+        g_hash_table_insert (parameters, "message",
+            tp_g_value_slice_new_string (presence_message));
+
+      g_hash_table_insert (result, key,
+          tp_presence_status_new (index, parameters));
+      g_hash_table_unref (parameters);
+    }
+
+  return result;
+}
+
+static gboolean
+my_set_own_status (GObject *object,
+                   const TpPresenceStatus *status,
+                   GError **error)
+{
+  TpBaseConnection *base_conn = TP_BASE_CONNECTION (object);
+  TpTestsContactsConnectionPresenceStatusIndex index = status->index;
+  const gchar *message = "";
+
+  if (status->optional_arguments != NULL)
+    {
+      message = g_hash_table_lookup (status->optional_arguments, "message");
+
+      if (message == NULL)
+        message = "";
+    }
+
+  tp_tests_contacts_connection_change_presences (TP_TESTS_CONTACTS_CONNECTION (object),
+      1, &(base_conn->self_handle), &index, &message);
+
+  return TRUE;
+}
+
+static guint
+my_get_maximum_status_message_length_cb (GObject *obj)
+{
+  return 512;
+}
+
+static GPtrArray *
+create_channel_managers (TpBaseConnection *conn)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (conn);
+  GPtrArray *ret = g_ptr_array_sized_new (1);
+
+  self->priv->list_manager = g_object_new (TP_TESTS_TYPE_CONTACT_LIST_MANAGER,
+      "connection", conn, NULL);
+
+  g_ptr_array_add (ret, self->priv->list_manager);
+
+  return ret;
+}
+
+static void
+tp_tests_contacts_connection_class_init (TpTestsContactsConnectionClass *klass)
+{
+  TpBaseConnectionClass *base_class =
+      (TpBaseConnectionClass *) klass;
+  GObjectClass *object_class = (GObjectClass *) klass;
+  TpPresenceMixinClass *mixin_class;
+  static const gchar *interfaces_always_present[] = {
+      TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+      TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+      TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+      TP_IFACE_CONNECTION_INTERFACE_CONTACT_LIST,
+      TP_IFACE_CONNECTION_INTERFACE_CONTACT_GROUPS,
+      TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+      TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+      TP_IFACE_CONNECTION_INTERFACE_LOCATION,
+      TP_IFACE_CONNECTION_INTERFACE_CLIENT_TYPES,
+      TP_IFACE_CONNECTION_INTERFACE_CONTACT_CAPABILITIES,
+      TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO,
+      TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+      NULL };
+  static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+        { TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+          conn_avatars_properties_getter,
+          NULL,
+          conn_avatars_properties,
+        },
+        { TP_IFACE_CONNECTION_INTERFACE_CONTACT_INFO,
+          conn_contact_info_properties_getter,
+          NULL,
+          conn_contact_info_properties,
+        },
+        { NULL }
+  };
+
+  object_class->constructed = constructed;
+  object_class->finalize = finalize;
+  g_type_class_add_private (klass, sizeof (TpTestsContactsConnectionPrivate));
+
+  base_class->interfaces_always_present = interfaces_always_present;
+  base_class->create_channel_managers = create_channel_managers;
+
+  tp_contacts_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (TpTestsContactsConnectionClass, contacts_mixin));
+
+  tp_presence_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (TpTestsContactsConnectionClass, presence_mixin),
+      my_status_available, my_get_contact_statuses,
+      my_set_own_status, my_statuses);
+  mixin_class = TP_PRESENCE_MIXIN_CLASS(klass);
+  mixin_class->get_maximum_status_message_length =
+      my_get_maximum_status_message_length_cb;
+
+  tp_presence_mixin_simple_presence_init_dbus_properties (object_class);
+
+  klass->properties_class.interfaces = prop_interfaces;
+  tp_dbus_properties_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (TpTestsContactsConnectionClass, properties_class));
+
+  tp_base_contact_list_mixin_class_init (base_class);
+}
+
+TpTestsContactListManager *
+tp_tests_contacts_connection_get_contact_list_manager (
+    TpTestsContactsConnection *self)
+{
+  return self->priv->list_manager;
+}
+
+/**
+ * tp_tests_contacts_connection_change_aliases:
+ * @self: a #TpTestsContactsConnection
+ * @n: the number of handles
+ * @handles: (array length=n): the handles
+ * @aliases: (array zero-terminated=1): aliases
+ *
+ */
+void
+tp_tests_contacts_connection_change_aliases (TpTestsContactsConnection *self,
+                                    guint n,
+                                    const TpHandle *handles,
+                                    const gchar * const *aliases)
+{
+  GPtrArray *structs = g_ptr_array_sized_new (n);
+  guint i;
+
+  for (i = 0; i < n; i++)
+    {
+      GValueArray *pair = g_value_array_new (2);
+
+      DEBUG ("contact#%u -> %s", handles[i], aliases[i]);
+
+      g_hash_table_insert (self->priv->aliases,
+          GUINT_TO_POINTER (handles[i]), g_strdup (aliases[i]));
+
+      g_value_array_append (pair, NULL);
+      g_value_init (pair->values + 0, G_TYPE_UINT);
+      g_value_set_uint (pair->values + 0, handles[i]);
+
+      g_value_array_append (pair, NULL);
+      g_value_init (pair->values + 1, G_TYPE_STRING);
+      g_value_set_string (pair->values + 1, aliases[i]);
+
+      g_ptr_array_add (structs, pair);
+    }
+
+  tp_svc_connection_interface_aliasing_emit_aliases_changed (self,
+      structs);
+
+  g_ptr_array_foreach (structs, (GFunc) g_value_array_free, NULL);
+  g_ptr_array_unref (structs);
+}
+
+void
+tp_tests_contacts_connection_change_presences (
+    TpTestsContactsConnection *self,
+    guint n,
+    const TpHandle *handles,
+    const TpTestsContactsConnectionPresenceStatusIndex *indexes,
+    const gchar * const *messages)
+{
+  GHashTable *presences = g_hash_table_new_full (g_direct_hash, g_direct_equal,
+      NULL, (GDestroyNotify) tp_presence_status_free);
+  guint i;
+
+  for (i = 0; i < n; i++)
+    {
+      GHashTable *parameters;
+      gpointer key = GUINT_TO_POINTER (handles[i]);
+
+      DEBUG ("contact#%u -> %s \"%s\"", handles[i],
+          my_statuses[indexes[i]].name, messages[i]);
+
+      g_hash_table_insert (self->priv->presence_statuses, key,
+          GUINT_TO_POINTER (indexes[i]));
+      g_hash_table_insert (self->priv->presence_messages, key,
+          g_strdup (messages[i]));
+
+      parameters = g_hash_table_new_full (g_str_hash,
+          g_str_equal, NULL, (GDestroyNotify) tp_g_value_slice_free);
+
+      if (messages[i] != NULL && messages[i][0] != '\0')
+        g_hash_table_insert (parameters, "message",
+            tp_g_value_slice_new_string (messages[i]));
+
+      g_hash_table_insert (presences, key, tp_presence_status_new (indexes[i],
+            parameters));
+      g_hash_table_unref (parameters);
+    }
+
+  tp_presence_mixin_emit_presence_update ((GObject *) self,
+      presences);
+  g_hash_table_unref (presences);
+}
+
+void
+tp_tests_contacts_connection_change_avatar_tokens (TpTestsContactsConnection *self,
+                                          guint n,
+                                          const TpHandle *handles,
+                                          const gchar * const *tokens)
+{
+  guint i;
+
+  for (i = 0; i < n; i++)
+    {
+      DEBUG ("contact#%u -> %s", handles[i], tokens[i]);
+      g_hash_table_insert (self->priv->avatars,
+          GUINT_TO_POINTER (handles[i]), avatar_data_new (NULL, NULL, tokens[i]));
+      tp_svc_connection_interface_avatars_emit_avatar_updated (self,
+          handles[i], tokens[i]);
+    }
+}
+
+void
+tp_tests_contacts_connection_change_avatar_data (
+    TpTestsContactsConnection *self,
+    TpHandle handle,
+    GArray *data,
+    const gchar *mime_type,
+    const gchar *token)
+{
+  g_hash_table_insert (self->priv->avatars,
+      GUINT_TO_POINTER (handle), avatar_data_new (data, mime_type, token));
+
+  tp_svc_connection_interface_avatars_emit_avatar_updated (self,
+      handle, token);
+}
+
+void
+tp_tests_contacts_connection_change_locations (TpTestsContactsConnection *self,
+    guint n,
+    const TpHandle *handles,
+    GHashTable **locations)
+{
+  guint i;
+
+  for (i = 0; i < n; i++)
+    {
+      DEBUG ("contact#%u ->", handles[i]);
+      tp_asv_dump (locations[i]);
+      g_hash_table_insert (self->priv->locations,
+          GUINT_TO_POINTER (handles[i]), g_hash_table_ref (locations[i]));
+
+      tp_svc_connection_interface_location_emit_location_updated (self,
+          handles[i], locations[i]);
+    }
+}
+
+void
+tp_tests_contacts_connection_change_capabilities (
+    TpTestsContactsConnection *self,
+    GHashTable *capabilities)
+{
+  GHashTableIter iter;
+  gpointer handle, caps;
+
+  g_hash_table_iter_init (&iter, capabilities);
+  while (g_hash_table_iter_next (&iter, &handle, &caps))
+    {
+      g_hash_table_insert (self->priv->capabilities,
+          handle,
+          g_boxed_copy (TP_ARRAY_TYPE_REQUESTABLE_CHANNEL_CLASS_LIST,
+            caps));
+    }
+
+  tp_svc_connection_interface_contact_capabilities_emit_contact_capabilities_changed (
+      self, capabilities);
+}
+
+void
+tp_tests_contacts_connection_change_contact_info (
+    TpTestsContactsConnection *self,
+    TpHandle handle,
+    GPtrArray *info)
+{
+  g_hash_table_insert (self->priv->contact_info, GUINT_TO_POINTER (handle),
+      g_ptr_array_ref (info));
+
+  tp_svc_connection_interface_contact_info_emit_contact_info_changed (self,
+      handle, info);
+}
+
+void
+tp_tests_contacts_connection_set_default_contact_info (
+    TpTestsContactsConnection *self,
+    GPtrArray *info)
+{
+  if (self->priv->default_contact_info != NULL)
+    g_ptr_array_unref (self->priv->default_contact_info);
+  self->priv->default_contact_info = g_ptr_array_ref (info);
+}
+
+static void
+my_get_alias_flags (TpSvcConnectionInterfaceAliasing *aliasing,
+                    DBusGMethodInvocation *context)
+{
+  TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+  tp_svc_connection_interface_aliasing_return_from_get_alias_flags (context,
+      0);
+}
+
+static void
+my_get_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+                const GArray *contacts,
+                DBusGMethodInvocation *context)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (aliasing);
+  TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+  GHashTable *result;
+  GError *error = NULL;
+  guint i;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, TpHandle, i);
+      const gchar *alias = g_hash_table_lookup (self->priv->aliases,
+          GUINT_TO_POINTER (handle));
+
+      if (alias == NULL)
+        g_hash_table_insert (result, GUINT_TO_POINTER (handle),
+            (gchar *) tp_handle_inspect (contact_repo, handle));
+      else
+        g_hash_table_insert (result, GUINT_TO_POINTER (handle),
+            (gchar *) alias);
+    }
+
+  tp_svc_connection_interface_aliasing_return_from_get_aliases (context,
+      result);
+  g_hash_table_unref (result);
+}
+
+static void
+my_request_aliases (TpSvcConnectionInterfaceAliasing *aliasing,
+                    const GArray *contacts,
+                    DBusGMethodInvocation *context)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (aliasing);
+  TpBaseConnection *base = TP_BASE_CONNECTION (aliasing);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+  GPtrArray *result;
+  gchar **strings;
+  GError *error = NULL;
+  guint i;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  result = g_ptr_array_sized_new (contacts->len + 1);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, TpHandle, i);
+      const gchar *alias = g_hash_table_lookup (self->priv->aliases,
+          GUINT_TO_POINTER (handle));
+
+      if (alias == NULL)
+        g_ptr_array_add (result,
+            (gchar *) tp_handle_inspect (contact_repo, handle));
+      else
+        g_ptr_array_add (result, (gchar *) alias);
+    }
+
+  g_ptr_array_add (result, NULL);
+  strings = (gchar **) g_ptr_array_free (result, FALSE);
+  tp_svc_connection_interface_aliasing_return_from_request_aliases (context,
+      (const gchar **) strings);
+  g_free (strings);
+}
+
+static void
+init_aliasing (gpointer g_iface,
+               gpointer iface_data)
+{
+  TpSvcConnectionInterfaceAliasingClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_aliasing_implement_##x (\
+    klass, my_##x)
+  IMPLEMENT(get_alias_flags);
+  IMPLEMENT(request_aliases);
+  IMPLEMENT(get_aliases);
+  /* IMPLEMENT(set_aliases); */
+#undef IMPLEMENT
+}
+
+static void
+my_get_avatar_tokens (TpSvcConnectionInterfaceAvatars *avatars,
+                      const GArray *contacts,
+                      DBusGMethodInvocation *context)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (avatars);
+  TpBaseConnection *base = TP_BASE_CONNECTION (avatars);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+  GError *error = NULL;
+  GHashTable *result;
+  guint i;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, TpHandle, i);
+      AvatarData *a = g_hash_table_lookup (self->priv->avatars,
+          GUINT_TO_POINTER (handle));
+
+      if (a == NULL || a->token == NULL)
+        {
+          /* we're expected to do a round-trip to the server to find out
+           * their token, so we have to give some sort of result. Assume
+           * no avatar, here */
+          a = avatar_data_new (NULL, NULL, "");
+          g_hash_table_insert (self->priv->avatars,
+              GUINT_TO_POINTER (handle), a);
+          tp_svc_connection_interface_avatars_emit_avatar_updated (self,
+              handle, a->token);
+        }
+
+      g_hash_table_insert (result, GUINT_TO_POINTER (handle),
+          a->token);
+    }
+
+  tp_svc_connection_interface_avatars_return_from_get_known_avatar_tokens (
+      context, result);
+  g_hash_table_unref (result);
+}
+
+static void
+my_get_known_avatar_tokens (TpSvcConnectionInterfaceAvatars *avatars,
+                            const GArray *contacts,
+                            DBusGMethodInvocation *context)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (avatars);
+  TpBaseConnection *base = TP_BASE_CONNECTION (avatars);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+  GError *error = NULL;
+  GHashTable *result;
+  guint i;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, TpHandle, i);
+      AvatarData *a = g_hash_table_lookup (self->priv->avatars,
+          GUINT_TO_POINTER (handle));
+      const gchar *token = a ? a->token : NULL;
+
+      g_hash_table_insert (result, GUINT_TO_POINTER (handle),
+          (gchar *) (token != NULL ? token : ""));
+    }
+
+  tp_svc_connection_interface_avatars_return_from_get_known_avatar_tokens (
+      context, result);
+  g_hash_table_unref (result);
+}
+
+static void
+my_request_avatars (TpSvcConnectionInterfaceAvatars *avatars,
+    const GArray *contacts,
+    DBusGMethodInvocation *context)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (avatars);
+  TpBaseConnection *base = TP_BASE_CONNECTION (avatars);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+  GError *error = NULL;
+  guint i;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, TpHandle, i);
+      AvatarData *a = g_hash_table_lookup (self->priv->avatars,
+          GUINT_TO_POINTER (handle));
+
+      if (a != NULL)
+        tp_svc_connection_interface_avatars_emit_avatar_retrieved (self, handle,
+            a->token, a->data, a->mime_type);
+    }
+
+  tp_svc_connection_interface_avatars_return_from_request_avatars (context);
+}
+
+static void
+conn_avatars_properties_getter (GObject *object,
+                                GQuark interface,
+                                GQuark name,
+                                GValue *value,
+                                gpointer getter_data)
+{
+  GQuark q_mime_types = g_quark_from_static_string (
+      "SupportedAvatarMIMETypes");
+
+  if (name == q_mime_types)
+    {
+      g_value_set_static_boxed (value, mime_types);
+    }
+  else
+    {
+      g_value_set_uint (value, GPOINTER_TO_UINT (getter_data));
+    }
+}
+
+static void
+init_avatars (gpointer g_iface,
+              gpointer iface_data)
+{
+  TpSvcConnectionInterfaceAvatarsClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_avatars_implement_##x (\
+    klass, my_##x)
+  /* IMPLEMENT(get_avatar_requirements); */
+  IMPLEMENT(get_avatar_tokens);
+  IMPLEMENT(get_known_avatar_tokens);
+  /* IMPLEMENT(request_avatar); */
+  IMPLEMENT(request_avatars);
+  /* IMPLEMENT(set_avatar); */
+  /* IMPLEMENT(clear_avatar); */
+#undef IMPLEMENT
+}
+
+static void
+my_get_locations (TpSvcConnectionInterfaceLocation *avatars,
+    const GArray *contacts,
+    DBusGMethodInvocation *context)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (avatars);
+  TpBaseConnection *base = TP_BASE_CONNECTION (avatars);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+  GError *error = NULL;
+  GHashTable *result;
+  guint i;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, TpHandle, i);
+      GHashTable *location = g_hash_table_lookup (self->priv->locations,
+          GUINT_TO_POINTER (handle));
+
+      if (location != NULL)
+        {
+          g_hash_table_insert (result, GUINT_TO_POINTER (handle), location);
+        }
+    }
+
+  tp_svc_connection_interface_location_return_from_get_locations (
+      context, result);
+  g_hash_table_unref (result);
+}
+
+static void
+init_location (gpointer g_iface,
+    gpointer iface_data)
+{
+  TpSvcConnectionInterfaceLocationClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_location_implement_##x (\
+    klass, my_##x)
+  IMPLEMENT(get_locations);
+#undef IMPLEMENT
+}
+
+static void
+my_get_contact_capabilities (TpSvcConnectionInterfaceContactCapabilities *obj,
+    const GArray *contacts,
+    DBusGMethodInvocation *context)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (obj);
+  TpBaseConnection *base = TP_BASE_CONNECTION (obj);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+  GError *error = NULL;
+  GHashTable *result;
+  guint i;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  result = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, NULL);
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, TpHandle, i);
+      GPtrArray *arr = g_hash_table_lookup (self->priv->capabilities,
+          GUINT_TO_POINTER (handle));
+
+      if (arr != NULL)
+        {
+          g_hash_table_insert (result, GUINT_TO_POINTER (handle), arr);
+        }
+    }
+
+  tp_svc_connection_interface_contact_capabilities_return_from_get_contact_capabilities (
+      context, result);
+
+  g_hash_table_unref (result);
+}
+
+static void
+init_contact_caps (gpointer g_iface,
+    gpointer iface_data)
+{
+  TpSvcConnectionInterfaceContactCapabilitiesClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_contact_capabilities_implement_##x (\
+    klass, my_##x)
+  IMPLEMENT(get_contact_capabilities);
+#undef IMPLEMENT
+}
+
+static GPtrArray *
+lookup_contact_info (TpTestsContactsConnection *self,
+    TpHandle handle)
+{
+  GPtrArray *ret = g_hash_table_lookup (self->priv->contact_info,
+      GUINT_TO_POINTER (handle));
+
+  if (ret == NULL && self->priv->default_contact_info != NULL)
+    {
+      ret = self->priv->default_contact_info;
+      g_hash_table_insert (self->priv->contact_info, GUINT_TO_POINTER (handle),
+          g_ptr_array_ref (ret));
+    }
+
+  if (ret == NULL)
+    return g_ptr_array_new ();
+
+  return g_ptr_array_ref (ret);
+}
+
+static void
+my_refresh_contact_info (TpSvcConnectionInterfaceContactInfo *obj,
+    const GArray *contacts,
+    DBusGMethodInvocation *context)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (obj);
+  TpBaseConnection *base = TP_BASE_CONNECTION (obj);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+  GError *error = NULL;
+  guint i;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  for (i = 0; i < contacts->len; i++)
+    {
+      TpHandle handle = g_array_index (contacts, guint, i);
+      GPtrArray *arr = lookup_contact_info (self, handle);
+
+      tp_svc_connection_interface_contact_info_emit_contact_info_changed (self,
+          handle, arr);
+      g_ptr_array_unref (arr);
+    }
+
+  tp_svc_connection_interface_contact_info_return_from_refresh_contact_info (
+      context);
+}
+
+static void
+my_request_contact_info (TpSvcConnectionInterfaceContactInfo *obj,
+    guint handle,
+    DBusGMethodInvocation *context)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (obj);
+  TpBaseConnection *base = TP_BASE_CONNECTION (obj);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (base,
+      TP_HANDLE_TYPE_CONTACT);
+  GError *error = NULL;
+  GPtrArray *ret;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  if (!tp_handle_is_valid (contact_repo, handle, &error))
+    {
+      dbus_g_method_return_error (context, error);
+      g_error_free (error);
+      return;
+    }
+
+  ret = lookup_contact_info (self, handle);
+
+  tp_svc_connection_interface_contact_info_return_from_request_contact_info (
+      context, ret);
+
+  g_ptr_array_unref (ret);
+}
+
+static void
+my_set_contact_info (TpSvcConnectionInterfaceContactInfo *obj,
+    const GPtrArray *info,
+    DBusGMethodInvocation *context)
+{
+  TpTestsContactsConnection *self = TP_TESTS_CONTACTS_CONNECTION (obj);
+  TpBaseConnection *base = TP_BASE_CONNECTION (obj);
+  GPtrArray *copy;
+  guint i;
+  TpHandle self_handle;
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  /* Deep copy info */
+  copy = g_ptr_array_new_with_free_func ((GDestroyNotify) g_value_array_free);
+  for (i = 0; i < info->len; i++)
+    g_ptr_array_add (copy, g_value_array_copy (g_ptr_array_index (info, i)));
+
+  self_handle = tp_base_connection_get_self_handle (base);
+  tp_tests_contacts_connection_change_contact_info (self, self_handle, copy);
+  g_ptr_array_unref (copy);
+
+  tp_svc_connection_interface_contact_info_return_from_set_contact_info (
+      context);
+}
+
+static void
+init_contact_info (gpointer g_iface,
+    gpointer iface_data)
+{
+  TpSvcConnectionInterfaceContactInfoClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_contact_info_implement_##x (\
+    klass, my_##x)
+  IMPLEMENT (refresh_contact_info);
+  IMPLEMENT (request_contact_info);
+  IMPLEMENT (set_contact_info);
+#undef IMPLEMENT
+}
+
+/* =============== Legacy version (no Contacts interface) ================= */
+
+G_DEFINE_TYPE (TpTestsLegacyContactsConnection,
+    tp_tests_legacy_contacts_connection, TP_TESTS_TYPE_CONTACTS_CONNECTION);
+
+enum
+{
+    LEGACY_PROP_HAS_IMMORTAL_HANDLES = 1
+};
+
+static void
+legacy_contacts_connection_get_property (GObject *object,
+    guint property_id,
+    GValue *value,
+    GParamSpec *pspec)
+{
+  switch (property_id)
+    {
+    case LEGACY_PROP_HAS_IMMORTAL_HANDLES:
+      /* Pretend we don't. */
+      g_value_set_boolean (value, FALSE);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+tp_tests_legacy_contacts_connection_init (TpTestsLegacyContactsConnection *self)
+{
+}
+
+static void
+tp_tests_legacy_contacts_connection_class_init (
+    TpTestsLegacyContactsConnectionClass *klass)
+{
+  /* Leave Contacts out of the interfaces we say are present, so clients
+   * won't use it */
+  static const gchar *interfaces_always_present[] = {
+      TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+      TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+      TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+      TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+      TP_IFACE_CONNECTION_INTERFACE_LOCATION,
+      TP_IFACE_CONNECTION_INTERFACE_REQUESTS,
+      NULL };
+  TpBaseConnectionClass *base_class =
+      (TpBaseConnectionClass *) klass;
+  GObjectClass *object_class = (GObjectClass *) klass;
+
+  object_class->get_property = legacy_contacts_connection_get_property;
+
+  base_class->interfaces_always_present = interfaces_always_present;
+
+  g_object_class_override_property (object_class,
+      LEGACY_PROP_HAS_IMMORTAL_HANDLES, "has-immortal-handles");
+}
+
+/* =============== No Requests and no ContactCapabilities ================= */
+
+G_DEFINE_TYPE (TpTestsNoRequestsConnection, tp_tests_no_requests_connection,
+    TP_TESTS_TYPE_CONTACTS_CONNECTION);
+
+static void
+tp_tests_no_requests_connection_init (TpTestsNoRequestsConnection *self)
+{
+}
+
+static void
+tp_tests_no_requests_connection_class_init (
+    TpTestsNoRequestsConnectionClass *klass)
+{
+  static const gchar *interfaces_always_present[] = {
+      TP_IFACE_CONNECTION_INTERFACE_ALIASING,
+      TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+      TP_IFACE_CONNECTION_INTERFACE_CONTACTS,
+      TP_IFACE_CONNECTION_INTERFACE_PRESENCE,
+      TP_IFACE_CONNECTION_INTERFACE_SIMPLE_PRESENCE,
+      TP_IFACE_CONNECTION_INTERFACE_LOCATION,
+      NULL };
+  TpBaseConnectionClass *base_class =
+      (TpBaseConnectionClass *) klass;
+
+  base_class->interfaces_always_present = interfaces_always_present;
+  base_class->create_channel_managers = NULL;
+}
diff --git a/tests/lib/telepathy/contactlist/contacts-conn.h b/tests/lib/telepathy/contactlist/contacts-conn.h
new file mode 100644
index 0000000..64056e0
--- /dev/null
+++ b/tests/lib/telepathy/contactlist/contacts-conn.h
@@ -0,0 +1,190 @@
+/*
+ * contacts-conn.h - header for a connection with contact info
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_CONTACTS_CONN_H__
+#define __TP_TESTS_CONTACTS_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/contacts-mixin.h>
+#include <telepathy-glib/presence-mixin.h>
+
+#include "simple-conn.h"
+#include "contact-list-manager.h"
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsContactsConnection TpTestsContactsConnection;
+typedef struct _TpTestsContactsConnectionClass TpTestsContactsConnectionClass;
+typedef struct _TpTestsContactsConnectionPrivate TpTestsContactsConnectionPrivate;
+
+struct _TpTestsContactsConnectionClass {
+    TpTestsSimpleConnectionClass parent_class;
+
+    TpPresenceMixinClass presence_mixin;
+    TpContactsMixinClass contacts_mixin;
+    TpDBusPropertiesMixinClass properties_class;
+
+    TpContactsMixinFillContactAttributesFunc fill_client_types;
+};
+
+struct _TpTestsContactsConnection {
+    TpTestsSimpleConnection parent;
+
+    TpPresenceMixin presence_mixin;
+    TpContactsMixin contacts_mixin;
+
+    TpTestsContactsConnectionPrivate *priv;
+};
+
+GType tp_tests_contacts_connection_get_type (void);
+
+/* Must match my_statuses in the .c */
+typedef enum {
+    TP_TESTS_CONTACTS_CONNECTION_STATUS_AVAILABLE,
+    TP_TESTS_CONTACTS_CONNECTION_STATUS_BUSY,
+    TP_TESTS_CONTACTS_CONNECTION_STATUS_AWAY,
+    TP_TESTS_CONTACTS_CONNECTION_STATUS_OFFLINE,
+    TP_TESTS_CONTACTS_CONNECTION_STATUS_UNKNOWN,
+    TP_TESTS_CONTACTS_CONNECTION_STATUS_ERROR
+} TpTestsContactsConnectionPresenceStatusIndex;
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_CONTACTS_CONNECTION \
+  (tp_tests_contacts_connection_get_type ())
+#define TP_TESTS_CONTACTS_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_CONTACTS_CONNECTION, \
+                              TpTestsContactsConnection))
+#define TP_TESTS_CONTACTS_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_CONTACTS_CONNECTION, \
+                           TpTestsContactsConnectionClass))
+#define TP_TESTS_IS_CONTACTS_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_CONTACTS_CONNECTION))
+#define TP_TESTS_IS_CONTACTS_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_CONTACTS_CONNECTION))
+#define TP_TESTS_CONTACTS_CONNECTION_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_CONTACTS_CONNECTION, \
+                              TpTestsContactsConnectionClass))
+
+TpTestsContactListManager *tp_tests_contacts_connection_get_contact_list_manager (
+    TpTestsContactsConnection *self);
+
+void tp_tests_contacts_connection_change_aliases (
+    TpTestsContactsConnection *self, guint n,
+    const TpHandle *handles, const gchar * const *aliases);
+
+void tp_tests_contacts_connection_change_presences (
+    TpTestsContactsConnection *self, guint n, const TpHandle *handles,
+    const TpTestsContactsConnectionPresenceStatusIndex *indexes,
+    const gchar * const *messages);
+
+void tp_tests_contacts_connection_change_avatar_tokens (
+    TpTestsContactsConnection *self, guint n, const TpHandle *handles,
+    const gchar * const *tokens);
+
+void tp_tests_contacts_connection_change_avatar_data (
+    TpTestsContactsConnection *self,
+    TpHandle handle,
+    GArray *data,
+    const gchar *mime_type,
+    const gchar *token);
+
+void tp_tests_contacts_connection_change_locations (
+    TpTestsContactsConnection *self,
+    guint n,
+    const TpHandle *handles,
+    GHashTable **locations);
+
+void tp_tests_contacts_connection_change_capabilities (
+    TpTestsContactsConnection *self, GHashTable *capabilities);
+
+void tp_tests_contacts_connection_change_contact_info (
+    TpTestsContactsConnection *self, TpHandle handle, GPtrArray *info);
+
+void tp_tests_contacts_connection_set_default_contact_info (
+    TpTestsContactsConnection *self,
+    GPtrArray *info);
+
+/* Legacy version (no Contacts interface, and no immortal handles) */
+
+typedef struct _TpTestsLegacyContactsConnection TpTestsLegacyContactsConnection;
+typedef struct _TpTestsLegacyContactsConnectionClass TpTestsLegacyContactsConnectionClass;
+typedef struct _TpTestsLegacyContactsConnectionPrivate
+  TpTestsLegacyContactsConnectionPrivate;
+
+struct _TpTestsLegacyContactsConnectionClass {
+    TpTestsContactsConnectionClass parent_class;
+};
+
+struct _TpTestsLegacyContactsConnection {
+    TpTestsContactsConnection parent;
+
+    TpTestsLegacyContactsConnectionPrivate *priv;
+};
+
+GType tp_tests_legacy_contacts_connection_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION \
+  (tp_tests_legacy_contacts_connection_get_type ())
+#define LEGACY_TP_TESTS_CONTACTS_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION, \
+                              TpTestsLegacyContactsConnection))
+#define LEGACY_TP_TESTS_CONTACTS_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION, \
+                           TpTestsLegacyContactsConnectionClass))
+#define TP_TESTS_LEGACY_CONTACTS_IS_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION))
+#define TP_TESTS_LEGACY_CONTACTS_IS_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION))
+#define LEGACY_TP_TESTS_CONTACTS_CONNECTION_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_LEGACY_CONTACTS_CONNECTION, \
+                              TpTestsLegacyContactsConnectionClass))
+
+/* No Requests version */
+
+typedef struct _TpTestsNoRequestsConnection TpTestsNoRequestsConnection;
+typedef struct _TpTestsNoRequestsConnectionClass TpTestsNoRequestsConnectionClass;
+typedef struct _TpTestsNoRequestsConnectionPrivate
+  TpTestsNoRequestsConnectionPrivate;
+
+struct _TpTestsNoRequestsConnectionClass {
+    TpTestsContactsConnectionClass parent_class;
+};
+
+struct _TpTestsNoRequestsConnection {
+    TpTestsContactsConnection parent;
+
+    TpTestsNoRequestsConnectionPrivate *priv;
+};
+
+GType tp_tests_no_requests_connection_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_NO_REQUESTS_CONNECTION \
+  (tp_tests_no_requests_connection_get_type ())
+#define TP_TESTS_NO_REQUESTS_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_NO_REQUESTS_CONNECTION, \
+                              TpTestsNoRequestsConnection))
+#define TP_TESTS_NO_REQUESTS_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_NO_REQUESTS_CONNECTION, \
+                           TpTestsNoRequestsConnectionClass))
+#define TP_TESTS_NO_REQUESTS_IS_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_NO_REQUESTS_CONNECTION))
+#define TP_TESTS_NO_REQUESTS_IS_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_NO_REQUESTS_CONNECTION))
+#define TP_TESTS_NO_REQUESTS_CONNECTION_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_NO_REQUESTS_CONNECTION, \
+                              TpTestsNoRequestsConnectionClass))
+
+G_END_DECLS
+
+#endif /* ifndef __TP_TESTS_CONTACTS_CONN_H__ */
diff --git a/tests/lib/telepathy/contactlist/debug.h b/tests/lib/telepathy/contactlist/debug.h
new file mode 100644
index 0000000..60e070b
--- /dev/null
+++ b/tests/lib/telepathy/contactlist/debug.h
@@ -0,0 +1,3 @@
+#undef DEBUG
+#define DEBUG(format, ...) \
+  g_debug ("%s: " format, G_STRFUNC, ##__VA_ARGS__)
diff --git a/tests/lib/telepathy/contactlist/account-manager.c b/tests/lib/telepathy/contactlist/simple-account-manager.c
similarity index 54%
rename from tests/lib/telepathy/contactlist/account-manager.c
rename to tests/lib/telepathy/contactlist/simple-account-manager.c
index e8e12b5..8df364a 100644
--- a/tests/lib/telepathy/contactlist/account-manager.c
+++ b/tests/lib/telepathy/contactlist/simple-account-manager.c
@@ -1,27 +1,28 @@
 /*
- * account-manager.c - a simple account manager service.
+ * simple-account-manager.c - a simple account manager service.
  *
- * Copyright (C) 2007-2009 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2012 Collabora Ltd. <http://www.collabora.co.uk/>
  * Copyright (C) 2007-2008 Nokia Corporation
  *
  * Copying and distribution of this file, with or without modification,
  * are permitted in any medium without royalty provided the copyright
  * notice and this notice are preserved.
- *
- * Copied from telepathy-glib/tests/lib/simple-account-manager.c.
  */
 
-#include "account-manager.h"
+#include "config.h"
+
+#include "simple-account-manager.h"
 
 #include <telepathy-glib/gtypes.h>
 #include <telepathy-glib/interfaces.h>
 #include <telepathy-glib/svc-generic.h>
 #include <telepathy-glib/svc-account-manager.h>
+#include <telepathy-glib/util.h>
 
 static void account_manager_iface_init (gpointer, gpointer);
 
-G_DEFINE_TYPE_WITH_CODE (TpTestsAccountManager,
-    tp_tests_account_manager,
+G_DEFINE_TYPE_WITH_CODE (TpTestsSimpleAccountManager,
+    tp_tests_simple_account_manager,
     G_TYPE_OBJECT,
     G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_ACCOUNT_MANAGER,
         account_manager_iface_init);
@@ -33,10 +34,6 @@ G_DEFINE_TYPE_WITH_CODE (TpTestsAccountManager,
 /* TP_IFACE_ACCOUNT_MANAGER is implied */
 static const char *ACCOUNT_MANAGER_INTERFACES[] = { NULL };
 
-static const gchar *INVALID_ACCOUNTS[] = {
-  "/org/freedesktop/Telepathy/Account/fakecm/fakeproto/invalidaccount",
-  NULL };
-
 enum
 {
   PROP_0,
@@ -45,13 +42,14 @@ enum
   PROP_INVALID_ACCOUNTS,
 };
 
-struct _TpTestsAccountManagerPrivate
+struct _TpTestsSimpleAccountManagerPrivate
 {
   GPtrArray *valid_accounts;
+  GPtrArray *invalid_accounts;
 };
 
 static void
-tp_tests_account_manager_create_account (TpSvcAccountManager *self,
+tp_tests_simple_account_manager_create_account (TpSvcAccountManager *self,
     const gchar *in_Connection_Manager,
     const gchar *in_Protocol,
     const gchar *in_Display_Name,
@@ -69,39 +67,29 @@ account_manager_iface_init (gpointer klass,
     gpointer unused G_GNUC_UNUSED)
 {
 #define IMPLEMENT(x) tp_svc_account_manager_implement_##x (\
-  klass, tp_tests_account_manager_##x)
+  klass, tp_tests_simple_account_manager_##x)
   IMPLEMENT (create_account);
 #undef IMPLEMENT
 }
 
 
 static void
-tp_tests_account_manager_init (TpTestsAccountManager *self)
+tp_tests_simple_account_manager_init (TpTestsSimpleAccountManager *self)
 {
   self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
-      TP_TESTS_TYPE_ACCOUNT_MANAGER, TpTestsAccountManagerPrivate);
-
-  self->priv->valid_accounts =
-      g_ptr_array_new_with_free_func ((GDestroyNotify) g_free);
-}
-
-static void
-tp_tests_account_manager_finalize (GObject *obj)
-{
-  g_ptr_array_free (TP_TESTS_ACCOUNT_MANAGER (obj)->priv->valid_accounts, TRUE);
+      TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER, TpTestsSimpleAccountManagerPrivate);
 
-  G_OBJECT_CLASS (tp_tests_account_manager_parent_class)->finalize (obj);
+  self->priv->valid_accounts = g_ptr_array_new_with_free_func (g_free);
+  self->priv->invalid_accounts = g_ptr_array_new_with_free_func (g_free);
 }
 
 static void
-tp_tests_account_manager_get_property (GObject *object,
+tp_tests_simple_account_manager_get_property (GObject *object,
               guint property_id,
               GValue *value,
               GParamSpec *spec)
 {
-  TpTestsAccountManagerPrivate *priv = TP_TESTS_ACCOUNT_MANAGER (object)->priv;
-  GPtrArray *accounts;
-  guint i = 0;
+  TpTestsSimpleAccountManager *self = SIMPLE_ACCOUNT_MANAGER (object);
 
   switch (property_id) {
     case PROP_INTERFACES:
@@ -109,21 +97,11 @@ tp_tests_account_manager_get_property (GObject *object,
       break;
 
     case PROP_VALID_ACCOUNTS:
-      accounts = g_ptr_array_new ();
-
-      for (i = 0; i < priv->valid_accounts->len; i++)
-        g_ptr_array_add (accounts, g_strdup (priv->valid_accounts->pdata[i]));
-
-      g_value_take_boxed (value, accounts);
+      g_value_set_boxed (value, self->priv->valid_accounts);
       break;
 
     case PROP_INVALID_ACCOUNTS:
-      accounts = g_ptr_array_new ();
-
-      for (i=0; INVALID_ACCOUNTS[i] != NULL; i++)
-        g_ptr_array_add (accounts, g_strdup (INVALID_ACCOUNTS[i]));
-
-      g_value_take_boxed (value, accounts);
+      g_value_set_boxed (value, self->priv->invalid_accounts);
       break;
 
     default:
@@ -132,6 +110,18 @@ tp_tests_account_manager_get_property (GObject *object,
   }
 }
 
+static void
+tp_tests_simple_account_manager_finalize (GObject *object)
+{
+  TpTestsSimpleAccountManager *self = SIMPLE_ACCOUNT_MANAGER (object);
+
+  g_ptr_array_unref (self->priv->valid_accounts);
+  g_ptr_array_unref (self->priv->invalid_accounts);
+
+  G_OBJECT_CLASS (tp_tests_simple_account_manager_parent_class)->finalize (
+      object);
+}
+
 /**
   * This class currently only provides the minimum for
   * tp_account_manager_prepare to succeed. This turns out to be only a working
@@ -140,18 +130,18 @@ tp_tests_account_manager_get_property (GObject *object,
   * too.
   */
 static void
-tp_tests_account_manager_class_init (
-    TpTestsAccountManagerClass *klass)
+tp_tests_simple_account_manager_class_init (
+    TpTestsSimpleAccountManagerClass *klass)
 {
   GObjectClass *object_class = (GObjectClass *) klass;
   GParamSpec *param_spec;
 
   static TpDBusPropertiesMixinPropImpl am_props[] = {
-        { "Interfaces", (gpointer) "interfaces", NULL },
-        { "ValidAccounts", (gpointer) "valid-accounts", NULL },
-        { "InvalidAccounts", (gpointer) "invalid-accounts", NULL },
+        { "Interfaces", "interfaces", NULL },
+        { "ValidAccounts", "valid-accounts", NULL },
+        { "InvalidAccounts", "invalid-accounts", NULL },
         /*
-        { "SupportedAccountProperties", (gpointer) "supported-account-properties", NULL },
+        { "SupportedAccountProperties", "supported-account-properties", NULL },
         */
         { NULL }
   };
@@ -165,9 +155,9 @@ tp_tests_account_manager_class_init (
         { NULL },
   };
 
-  g_type_class_add_private (klass, sizeof (TpTestsAccountManagerPrivate));
-  object_class->get_property = tp_tests_account_manager_get_property;
-  object_class->finalize = tp_tests_account_manager_finalize;
+  g_type_class_add_private (klass, sizeof (TpTestsSimpleAccountManagerPrivate));
+  object_class->finalize = tp_tests_simple_account_manager_finalize;
+  object_class->get_property = tp_tests_simple_account_manager_get_property;
 
   param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
       "In this case we only implement AccountManager, so none.",
@@ -187,40 +177,46 @@ tp_tests_account_manager_class_init (
 
   klass->dbus_props_class.interfaces = prop_interfaces;
   tp_dbus_properties_mixin_class_init (object_class,
-      G_STRUCT_OFFSET (TpTestsAccountManagerClass, dbus_props_class));
+      G_STRUCT_OFFSET (TpTestsSimpleAccountManagerClass, dbus_props_class));
 }
 
-TpTestsAccountManager *
-tp_tests_account_manager_new (void)
+static void
+remove_from_array (GPtrArray *array, const gchar *str)
 {
-  return g_object_new (TP_TESTS_TYPE_ACCOUNT_MANAGER, NULL);
+  guint i;
+
+  for (i = 0; i < array->len; i++)
+    if (!tp_strdiff (str, g_ptr_array_index (array, i)))
+      {
+        g_ptr_array_remove_index_fast (array, i);
+        return;
+      }
 }
 
 void
-tp_tests_account_manager_add_account (TpTestsAccountManager *self,
-    const gchar *account_path)
+tp_tests_simple_account_manager_add_account (
+    TpTestsSimpleAccountManager *self,
+    const gchar *object_path,
+    gboolean valid)
 {
-  g_ptr_array_add (self->priv->valid_accounts, g_strdup (account_path));
-  tp_svc_account_manager_emit_account_validity_changed (self, account_path, TRUE);
-  g_object_notify (G_OBJECT (self), "valid-accounts");
+  remove_from_array (self->priv->valid_accounts, object_path);
+  remove_from_array (self->priv->valid_accounts, object_path);
+
+  if (valid)
+    g_ptr_array_add (self->priv->valid_accounts, g_strdup (object_path));
+  else
+    g_ptr_array_add (self->priv->invalid_accounts, g_strdup (object_path));
+
+  tp_svc_account_manager_emit_account_validity_changed (self, object_path, valid);
 }
 
 void
-tp_tests_account_manager_remove_account (TpTestsAccountManager *self,
-    const gchar *account_path)
+tp_tests_simple_account_manager_remove_account (
+    TpTestsSimpleAccountManager *self,
+    const gchar *object_path)
 {
-  TpTestsAccountManagerPrivate *priv = self->priv;
-  guint i;
+  remove_from_array (self->priv->valid_accounts, object_path);
+  remove_from_array (self->priv->valid_accounts, object_path);
 
-  for (i = 0; i < priv->valid_accounts->len; i++)
-    {
-      if (g_strcmp0 (account_path, priv->valid_accounts->pdata[0]) == 0)
-        {
-          g_ptr_array_remove_index_fast (priv->valid_accounts, i);
-          break;
-        }
-    }
-
-  g_object_notify (G_OBJECT (self), "valid-accounts");
-  tp_svc_account_manager_emit_account_removed (self, account_path);
+  tp_svc_account_manager_emit_account_removed (self, object_path);
 }
diff --git a/tests/lib/telepathy/contactlist/simple-account-manager.h b/tests/lib/telepathy/contactlist/simple-account-manager.h
new file mode 100644
index 0000000..c34a6bf
--- /dev/null
+++ b/tests/lib/telepathy/contactlist/simple-account-manager.h
@@ -0,0 +1,66 @@
+/*
+ * simple-account-manager.h - header for a simple account manager service.
+ *
+ * Copyright (C) 2007-2012 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_SIMPLE_ACCOUNT_MANAGER_H__
+#define __TP_TESTS_SIMPLE_ACCOUNT_MANAGER_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsSimpleAccountManager TpTestsSimpleAccountManager;
+typedef struct _TpTestsSimpleAccountManagerClass TpTestsSimpleAccountManagerClass;
+typedef struct _TpTestsSimpleAccountManagerPrivate TpTestsSimpleAccountManagerPrivate;
+
+struct _TpTestsSimpleAccountManagerClass {
+    GObjectClass parent_class;
+    TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _TpTestsSimpleAccountManager {
+    GObject parent;
+
+    TpTestsSimpleAccountManagerPrivate *priv;
+};
+
+GType tp_tests_simple_account_manager_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER \
+  (tp_tests_simple_account_manager_get_type ())
+#define SIMPLE_ACCOUNT_MANAGER(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER, \
+                              TpTestsSimpleAccountManager))
+#define SIMPLE_ACCOUNT_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER, \
+                           TpTestsSimpleAccountManagerClass))
+#define SIMPLE_IS_ACCOUNT_MANAGER(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER))
+#define SIMPLE_IS_ACCOUNT_MANAGER_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER))
+#define SIMPLE_ACCOUNT_MANAGER_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT_MANAGER, \
+                              TpTestsSimpleAccountManagerClass))
+
+void tp_tests_simple_account_manager_add_account (
+    TpTestsSimpleAccountManager *self,
+    const gchar *object_path,
+    gboolean valid);
+
+void tp_tests_simple_account_manager_remove_account (
+    TpTestsSimpleAccountManager *self,
+    const gchar *object_path);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_SIMPLE_ACCOUNT_MANAGER_H__ */
diff --git a/tests/lib/telepathy/contactlist/simple-account.c b/tests/lib/telepathy/contactlist/simple-account.c
new file mode 100644
index 0000000..934643d
--- /dev/null
+++ b/tests/lib/telepathy/contactlist/simple-account.c
@@ -0,0 +1,579 @@
+/*
+ * simple-account.c - a simple account service.
+ *
+ * Copyright (C) 2010-2012 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "config.h"
+
+#include "simple-account.h"
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/defs.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+#include <telepathy-glib/svc-generic.h>
+#include <telepathy-glib/svc-account.h>
+
+static void account_iface_init (gpointer, gpointer);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsSimpleAccount,
+    tp_tests_simple_account,
+    G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_ACCOUNT,
+        account_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_ACCOUNT_INTERFACE_AVATAR,
+        NULL);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_ACCOUNT_INTERFACE_ADDRESSING,
+        NULL);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_ACCOUNT_INTERFACE_STORAGE,
+        NULL);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+        tp_dbus_properties_mixin_iface_init)
+    )
+
+/* TP_IFACE_ACCOUNT is implied */
+static const char *ACCOUNT_INTERFACES[] = {
+    TP_IFACE_ACCOUNT_INTERFACE_ADDRESSING,
+    TP_IFACE_ACCOUNT_INTERFACE_STORAGE,
+    NULL };
+
+enum
+{
+  PROP_0,
+  PROP_INTERFACES,
+  PROP_DISPLAY_NAME,
+  PROP_ICON,
+  PROP_VALID,
+  PROP_ENABLED,
+  PROP_NICKNAME,
+  PROP_PARAMETERS,
+  PROP_AUTOMATIC_PRESENCE,
+  PROP_CONNECT_AUTO,
+  PROP_CONNECTION,
+  PROP_CONNECTION_STATUS,
+  PROP_CONNECTION_STATUS_REASON,
+  PROP_CURRENT_PRESENCE,
+  PROP_REQUESTED_PRESENCE,
+  PROP_NORMALIZED_NAME,
+  PROP_HAS_BEEN_ONLINE,
+  PROP_URI_SCHEMES,
+  PROP_STORAGE_PROVIDER,
+  PROP_STORAGE_IDENTIFIER,
+  PROP_STORAGE_SPECIFIC_INFORMATION,
+  PROP_STORAGE_RESTRICTIONS,
+  PROP_AVATAR,
+  PROP_SUPERSEDES,
+  N_PROPS
+};
+
+struct _TpTestsSimpleAccountPrivate
+{
+  TpConnectionPresenceType presence;
+  gchar *presence_status;
+  gchar *presence_msg;
+  gchar *connection_path;
+  gboolean enabled;
+};
+
+static void
+tp_tests_simple_account_update_parameters (TpSvcAccount *svc,
+    GHashTable *parameters,
+    const gchar **unset_parameters,
+    DBusGMethodInvocation *context)
+{
+  GPtrArray *reconnect_required = g_ptr_array_new ();
+  GHashTableIter iter;
+  gpointer k;
+  guint i;
+
+  /* We don't actually store any parameters, but for the purposes
+   * of this method we pretend that every parameter provided is
+   * valid and requires reconnection. */
+
+  g_hash_table_iter_init (&iter, parameters);
+
+  while (g_hash_table_iter_next (&iter, &k, NULL))
+    g_ptr_array_add (reconnect_required, k);
+
+  for (i = 0; unset_parameters != NULL && unset_parameters[i] != NULL; i++)
+    g_ptr_array_add (reconnect_required, (gchar *) unset_parameters[i]);
+
+  g_ptr_array_add (reconnect_required, NULL);
+
+  tp_svc_account_return_from_update_parameters (context,
+      (const gchar **) reconnect_required->pdata);
+  g_ptr_array_unref (reconnect_required);
+}
+
+static void
+account_iface_init (gpointer klass,
+    gpointer unused G_GNUC_UNUSED)
+{
+#define IMPLEMENT(x) tp_svc_account_implement_##x (\
+  klass, tp_tests_simple_account_##x)
+  IMPLEMENT (update_parameters);
+#undef IMPLEMENT
+}
+
+
+static void
+tp_tests_simple_account_init (TpTestsSimpleAccount *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TESTS_TYPE_SIMPLE_ACCOUNT,
+      TpTestsSimpleAccountPrivate);
+
+  self->priv->presence = TP_CONNECTION_PRESENCE_TYPE_AWAY;
+  self->priv->presence_status = g_strdup ("currently-away");
+  self->priv->presence_msg = g_strdup ("this is my CurrentPresence");
+  self->priv->connection_path = g_strdup ("/");
+  self->priv->enabled = TRUE;
+}
+
+/* you may have noticed this is not entirely realistic */
+static const gchar * const uri_schemes[] = { "about", "telnet", NULL };
+
+static void
+tp_tests_simple_account_get_property (GObject *object,
+              guint property_id,
+              GValue *value,
+              GParamSpec *spec)
+{
+  TpTestsSimpleAccount *self = TP_TESTS_SIMPLE_ACCOUNT (object);
+  GValue identifier = { 0, };
+
+  g_value_init (&identifier, G_TYPE_STRING);
+  g_value_set_string (&identifier, "unique-identifier");
+
+  switch (property_id) {
+    case PROP_INTERFACES:
+      g_value_set_boxed (value, ACCOUNT_INTERFACES);
+      break;
+    case PROP_DISPLAY_NAME:
+      g_value_set_string (value, "Fake Account");
+      break;
+    case PROP_ICON:
+      g_value_set_string (value, "");
+      break;
+    case PROP_VALID:
+      g_value_set_boolean (value, TRUE);
+      break;
+    case PROP_ENABLED:
+      g_value_set_boolean (value, self->priv->enabled);
+      break;
+    case PROP_NICKNAME:
+      g_value_set_string (value, "badger");
+      break;
+    case PROP_PARAMETERS:
+      g_value_take_boxed (value, g_hash_table_new (NULL, NULL));
+      break;
+    case PROP_AUTOMATIC_PRESENCE:
+      g_value_take_boxed (value, tp_value_array_build (3,
+            G_TYPE_UINT, TP_CONNECTION_PRESENCE_TYPE_AVAILABLE,
+            G_TYPE_STRING, "automatically-available",
+            G_TYPE_STRING, "this is my AutomaticPresence",
+            G_TYPE_INVALID));
+      break;
+    case PROP_CONNECT_AUTO:
+      g_value_set_boolean (value, FALSE);
+      break;
+    case PROP_CONNECTION:
+      g_value_set_boxed (value, self->priv->connection_path);
+      break;
+    case PROP_CONNECTION_STATUS:
+      g_value_set_uint (value, TP_CONNECTION_STATUS_CONNECTED);
+      break;
+    case PROP_CONNECTION_STATUS_REASON:
+      g_value_set_uint (value, TP_CONNECTION_STATUS_REASON_REQUESTED);
+      break;
+    case PROP_CURRENT_PRESENCE:
+      g_value_take_boxed (value, tp_value_array_build (3,
+            G_TYPE_UINT, self->priv->presence,
+            G_TYPE_STRING, self->priv->presence_status,
+            G_TYPE_STRING, self->priv->presence_msg,
+            G_TYPE_INVALID));
+      break;
+    case PROP_REQUESTED_PRESENCE:
+      g_value_take_boxed (value, tp_value_array_build (3,
+            G_TYPE_UINT, TP_CONNECTION_PRESENCE_TYPE_BUSY,
+            G_TYPE_STRING, "requesting",
+            G_TYPE_STRING, "this is my RequestedPresence",
+            G_TYPE_INVALID));
+      break;
+    case PROP_NORMALIZED_NAME:
+      g_value_set_string (value, "bob mcbadgers example com");
+      break;
+    case PROP_HAS_BEEN_ONLINE:
+      g_value_set_boolean (value, TRUE);
+      break;
+    case PROP_STORAGE_PROVIDER:
+      g_value_set_string (value, "org.freedesktop.Telepathy.glib.test");
+      break;
+    case PROP_STORAGE_IDENTIFIER:
+      g_value_set_boxed (value, &identifier);
+      break;
+    case PROP_STORAGE_SPECIFIC_INFORMATION:
+      g_value_take_boxed (value, tp_asv_new (
+            "one", G_TYPE_INT, 1,
+            "two", G_TYPE_UINT, 2,
+            "marco", G_TYPE_STRING, "polo",
+            NULL));
+      break;
+    case PROP_STORAGE_RESTRICTIONS:
+      g_value_set_uint (value,
+          TP_STORAGE_RESTRICTION_FLAG_CANNOT_SET_ENABLED |
+          TP_STORAGE_RESTRICTION_FLAG_CANNOT_SET_PARAMETERS);
+      break;
+    case PROP_URI_SCHEMES:
+      g_value_set_boxed (value, uri_schemes);
+      break;
+    case PROP_AVATAR:
+        {
+          GArray *arr = g_array_new (FALSE, FALSE, sizeof (char));
+
+          /* includes NUL for simplicity */
+          g_array_append_vals (arr, ":-)", 4);
+
+          g_value_take_boxed (value,
+              tp_value_array_build (2,
+                TP_TYPE_UCHAR_ARRAY, arr,
+                G_TYPE_STRING, "text/plain",
+                G_TYPE_INVALID));
+          g_array_unref (arr);
+        }
+      break;
+    case PROP_SUPERSEDES:
+        {
+          GPtrArray *arr = g_ptr_array_new ();
+
+          g_ptr_array_add (arr,
+              g_strdup (TP_ACCOUNT_OBJECT_PATH_BASE "super/seded/whatever"));
+          g_value_take_boxed (value, arr);
+        }
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+      break;
+  }
+
+  g_value_unset (&identifier);
+}
+
+static void
+tp_tests_simple_account_finalize (GObject *object)
+{
+  TpTestsSimpleAccount *self = TP_TESTS_SIMPLE_ACCOUNT (object);
+
+  g_free (self->priv->presence_status);
+  g_free (self->priv->presence_msg);
+
+  G_OBJECT_CLASS (tp_tests_simple_account_parent_class)->finalize (object);
+}
+
+/**
+  * This class currently only provides the minimum for
+  * tp_account_prepare to succeed. This turns out to be only a working
+  * Properties.GetAll().
+  */
+static void
+tp_tests_simple_account_class_init (TpTestsSimpleAccountClass *klass)
+{
+  GObjectClass *object_class = (GObjectClass *) klass;
+  GParamSpec *param_spec;
+
+  static TpDBusPropertiesMixinPropImpl a_props[] = {
+        { "Interfaces", "interfaces", NULL },
+        { "DisplayName", "display-name", NULL },
+        { "Icon", "icon", NULL },
+        { "Valid", "valid", NULL },
+        { "Enabled", "enabled", NULL },
+        { "Nickname", "nickname", NULL },
+        { "Parameters", "parameters", NULL },
+        { "AutomaticPresence", "automatic-presence", NULL },
+        { "ConnectAutomatically", "connect-automatically", NULL },
+        { "Connection", "connection", NULL },
+        { "ConnectionStatus", "connection-status", NULL },
+        { "ConnectionStatusReason", "connection-status-reason", NULL },
+        { "CurrentPresence", "current-presence", NULL },
+        { "RequestedPresence", "requested-presence", NULL },
+        { "NormalizedName", "normalized-name", NULL },
+        { "HasBeenOnline", "has-been-online", NULL },
+        { "Supersedes", "supersedes", NULL },
+        { NULL }
+  };
+
+  static TpDBusPropertiesMixinPropImpl ais_props[] = {
+        { "StorageProvider", "storage-provider", NULL },
+        { "StorageIdentifier", "storage-identifier", NULL },
+        { "StorageSpecificInformation", "storage-specific-information", NULL },
+        { "StorageRestrictions", "storage-restrictions", NULL },
+        { NULL },
+  };
+
+  static TpDBusPropertiesMixinPropImpl aia_props[] = {
+        { "URISchemes", "uri-schemes", NULL },
+        { NULL },
+  };
+
+  static TpDBusPropertiesMixinPropImpl avatar_props[] = {
+        { "Avatar", "avatar", NULL },
+        { NULL },
+  };
+
+  static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+        { TP_IFACE_ACCOUNT,
+          tp_dbus_properties_mixin_getter_gobject_properties,
+          NULL,
+          a_props
+        },
+        {
+          TP_IFACE_ACCOUNT_INTERFACE_STORAGE,
+          tp_dbus_properties_mixin_getter_gobject_properties,
+          NULL,
+          ais_props
+        },
+        {
+          TP_IFACE_ACCOUNT_INTERFACE_ADDRESSING,
+          tp_dbus_properties_mixin_getter_gobject_properties,
+          NULL,
+          aia_props
+        },
+        { TP_IFACE_ACCOUNT_INTERFACE_AVATAR,
+          tp_dbus_properties_mixin_getter_gobject_properties,
+          NULL,
+          avatar_props
+        },
+        { NULL },
+  };
+
+  g_type_class_add_private (klass, sizeof (TpTestsSimpleAccountPrivate));
+  object_class->get_property = tp_tests_simple_account_get_property;
+  object_class->finalize = tp_tests_simple_account_finalize;
+
+  param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+      "In this case we only implement Account, so none.",
+      G_TYPE_STRV,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+  param_spec = g_param_spec_string ("display-name", "display name",
+      "DisplayName property",
+      NULL,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_DISPLAY_NAME, param_spec);
+
+  param_spec = g_param_spec_string ("icon", "icon",
+      "Icon property",
+      NULL,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_ICON, param_spec);
+
+  param_spec = g_param_spec_boolean ("valid", "valid",
+      "Valid property",
+      FALSE,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_VALID, param_spec);
+
+  param_spec = g_param_spec_boolean ("enabled", "enabled",
+      "Enabled property",
+      FALSE,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_ENABLED, param_spec);
+
+  param_spec = g_param_spec_string ("nickname", "nickname",
+      "Nickname property",
+      NULL,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_NICKNAME, param_spec);
+
+  param_spec = g_param_spec_boxed ("parameters", "parameters",
+      "Parameters property",
+      TP_HASH_TYPE_STRING_VARIANT_MAP,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_PARAMETERS, param_spec);
+
+  param_spec = g_param_spec_boxed ("automatic-presence", "automatic presence",
+      "AutomaticPresence property",
+      TP_STRUCT_TYPE_SIMPLE_PRESENCE,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_AUTOMATIC_PRESENCE,
+      param_spec);
+
+  param_spec = g_param_spec_boolean ("connect-automatically",
+      "connect automatically", "ConnectAutomatically property",
+      FALSE,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_CONNECT_AUTO, param_spec);
+
+  param_spec = g_param_spec_boxed ("connection", "connection",
+      "Connection property",
+      DBUS_TYPE_G_OBJECT_PATH,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+  param_spec = g_param_spec_uint ("connection-status", "connection status",
+      "ConnectionStatus property",
+      0, NUM_TP_CONNECTION_STATUSES, TP_CONNECTION_STATUS_DISCONNECTED,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_CONNECTION_STATUS,
+      param_spec);
+
+  param_spec = g_param_spec_uint ("connection-status-reason",
+      "connection status reason", "ConnectionStatusReason property",
+      0, NUM_TP_CONNECTION_STATUS_REASONS,
+      TP_CONNECTION_STATUS_REASON_NONE_SPECIFIED,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_CONNECTION_STATUS_REASON,
+      param_spec);
+
+  param_spec = g_param_spec_boxed ("current-presence", "current presence",
+      "CurrentPresence property",
+      TP_STRUCT_TYPE_SIMPLE_PRESENCE,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_CURRENT_PRESENCE,
+      param_spec);
+
+  param_spec = g_param_spec_boxed ("requested-presence", "requested presence",
+      "RequestedPresence property",
+      TP_STRUCT_TYPE_SIMPLE_PRESENCE,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_REQUESTED_PRESENCE,
+      param_spec);
+
+  param_spec = g_param_spec_string ("normalized-name", "normalized name",
+      "NormalizedName property",
+      NULL,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_NORMALIZED_NAME,
+      param_spec);
+
+  param_spec = g_param_spec_boolean ("has-been-online", "has been online",
+      "HasBeenOnline property",
+      FALSE,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_HAS_BEEN_ONLINE,
+      param_spec);
+
+  param_spec = g_param_spec_string ("storage-provider", "storage provider",
+      "StorageProvider property",
+      NULL,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_STORAGE_PROVIDER,
+      param_spec);
+
+  param_spec = g_param_spec_boxed ("storage-identifier", "storage identifier",
+      "StorageIdentifier property",
+      G_TYPE_VALUE,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_STORAGE_IDENTIFIER,
+      param_spec);
+
+  param_spec = g_param_spec_boxed ("storage-specific-information",
+      "storage specific information", "StorageSpecificInformation property",
+      TP_HASH_TYPE_STRING_VARIANT_MAP,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class,
+      PROP_STORAGE_SPECIFIC_INFORMATION, param_spec);
+
+  param_spec = g_param_spec_uint ("storage-restrictions",
+      "storage restrictions", "StorageRestrictions property",
+      0, G_MAXUINT, 0,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_STORAGE_RESTRICTIONS,
+      param_spec);
+
+  param_spec = g_param_spec_boxed ("uri-schemes", "URI schemes",
+      "Some URI schemes",
+      G_TYPE_STRV,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_URI_SCHEMES, param_spec);
+
+  param_spec = g_param_spec_boxed ("avatar",
+      "Avatar", "Avatar",
+      TP_STRUCT_TYPE_AVATAR,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class,
+      PROP_AVATAR, param_spec);
+
+  param_spec = g_param_spec_boxed ("supersedes",
+      "Supersedes", "List of superseded accounts",
+      TP_ARRAY_TYPE_OBJECT_PATH_LIST,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class,
+      PROP_SUPERSEDES, param_spec);
+
+  klass->dbus_props_class.interfaces = prop_interfaces;
+  tp_dbus_properties_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (TpTestsSimpleAccountClass, dbus_props_class));
+}
+
+void
+tp_tests_simple_account_set_presence (TpTestsSimpleAccount *self,
+    TpConnectionPresenceType presence,
+    const gchar *status,
+    const gchar *message)
+{
+  GHashTable *props;
+  GValueArray *v;
+
+  g_free (self->priv->presence_status);
+  g_free (self->priv->presence_msg);
+
+  self->priv->presence = presence;
+  self->priv->presence_status = g_strdup (status);
+  self->priv->presence_msg = g_strdup (message);
+
+  g_object_get (self, "current-presence", &v, NULL);
+
+  props = tp_asv_new (
+      "CurrentPresence", TP_STRUCT_TYPE_SIMPLE_PRESENCE, v,
+      NULL);
+
+  tp_svc_account_emit_account_property_changed (self, props);
+
+  g_boxed_free (TP_STRUCT_TYPE_SIMPLE_PRESENCE, v);
+}
+
+void
+tp_tests_simple_account_set_connection (TpTestsSimpleAccount *self,
+    const gchar *object_path)
+{
+  GHashTable *change;
+
+  if (object_path == NULL)
+    object_path = "/";
+
+  g_free (self->priv->connection_path);
+  self->priv->connection_path = g_strdup (object_path);
+
+  change = tp_asv_new (NULL, NULL);
+  tp_asv_set_string (change, "Connection", object_path);
+  tp_svc_account_emit_account_property_changed (self, change);
+  g_hash_table_unref (change);
+}
+
+void
+tp_tests_simple_account_removed (TpTestsSimpleAccount *self)
+{
+  tp_svc_account_emit_removed (self);
+}
+
+void
+tp_tests_simple_account_set_enabled (TpTestsSimpleAccount *self,
+    gboolean enabled)
+{
+  GHashTable *change;
+
+  self->priv->enabled = enabled;
+
+  change = tp_asv_new (NULL, NULL);
+  tp_asv_set_boolean (change, "Enabled", enabled);
+  tp_svc_account_emit_account_property_changed (self, change);
+  g_hash_table_unref (change);
+}
diff --git a/tests/lib/telepathy/contactlist/simple-account.h b/tests/lib/telepathy/contactlist/simple-account.h
new file mode 100644
index 0000000..2ce3efd
--- /dev/null
+++ b/tests/lib/telepathy/contactlist/simple-account.h
@@ -0,0 +1,69 @@
+/*
+ * simple-account.h - header for a simple account service.
+ *
+ * Copyright (C) 2010-2012 Collabora Ltd. <http://www.collabora.co.uk/>
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_SIMPLE_ACCOUNT_H__
+#define __TP_TESTS_SIMPLE_ACCOUNT_H__
+
+#include <glib-object.h>
+
+#include <telepathy-glib/connection.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsSimpleAccount TpTestsSimpleAccount;
+typedef struct _TpTestsSimpleAccountClass TpTestsSimpleAccountClass;
+typedef struct _TpTestsSimpleAccountPrivate TpTestsSimpleAccountPrivate;
+
+struct _TpTestsSimpleAccountClass {
+    GObjectClass parent_class;
+    TpDBusPropertiesMixinClass dbus_props_class;
+};
+
+struct _TpTestsSimpleAccount {
+    GObject parent;
+
+    TpTestsSimpleAccountPrivate *priv;
+};
+
+GType tp_tests_simple_account_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_SIMPLE_ACCOUNT \
+  (tp_tests_simple_account_get_type ())
+#define TP_TESTS_SIMPLE_ACCOUNT(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT, \
+                              TpTestsSimpleAccount))
+#define TP_TESTS_SIMPLE_ACCOUNT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_SIMPLE_ACCOUNT, \
+                           TpTestsSimpleAccountClass))
+#define TP_TESTS_SIMPLE_IS_ACCOUNT(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT))
+#define TP_TESTS_SIMPLE_IS_ACCOUNT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_SIMPLE_ACCOUNT))
+#define TP_TESTS_SIMPLE_ACCOUNT_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_SIMPLE_ACCOUNT, \
+                              TpTestsSimpleAccountClass))
+
+void tp_tests_simple_account_set_presence (TpTestsSimpleAccount *self,
+    TpConnectionPresenceType presence,
+    const gchar *status,
+    const gchar *message);
+
+void tp_tests_simple_account_set_connection (TpTestsSimpleAccount *self,
+    const gchar *object_path);
+
+void tp_tests_simple_account_removed (TpTestsSimpleAccount *self);
+void tp_tests_simple_account_set_enabled (TpTestsSimpleAccount *self,
+    gboolean enabled);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_SIMPLE_ACCOUNT_H__ */
diff --git a/tests/lib/telepathy/contactlist/simple-conn.c b/tests/lib/telepathy/contactlist/simple-conn.c
new file mode 100644
index 0000000..8a481af
--- /dev/null
+++ b/tests/lib/telepathy/contactlist/simple-conn.c
@@ -0,0 +1,452 @@
+/*
+ * simple-conn.c - a simple connection
+ *
+ * Copyright (C) 2007-2010 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "config.h"
+
+#include "simple-conn.h"
+
+#include <string.h>
+
+#include <dbus/dbus-glib.h>
+
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/handle-repo-dynamic.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/util.h>
+
+#include "textchan-null.h"
+#include "util.h"
+
+static void conn_iface_init (TpSvcConnectionClass *);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsSimpleConnection, tp_tests_simple_connection,
+    TP_TYPE_BASE_CONNECTION,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION, conn_iface_init))
+
+/* type definition stuff */
+
+enum
+{
+  PROP_ACCOUNT = 1,
+  PROP_BREAK_PROPS = 2,
+  PROP_DBUS_STATUS = 3,
+  N_PROPS
+};
+
+enum
+{
+  SIGNAL_GOT_SELF_HANDLE,
+  N_SIGNALS
+};
+
+static guint signals[N_SIGNALS] = {0};
+
+struct _TpTestsSimpleConnectionPrivate
+{
+  gchar *account;
+  guint connect_source;
+  guint disconnect_source;
+  gboolean break_fastpath_props;
+
+  /* TpHandle => reffed TpTestsTextChannelNull */
+  GHashTable *channels;
+
+  GError *get_self_handle_error /* initially NULL */ ;
+};
+
+static void
+tp_tests_simple_connection_init (TpTestsSimpleConnection *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      TP_TESTS_TYPE_SIMPLE_CONNECTION, TpTestsSimpleConnectionPrivate);
+
+  self->priv->channels = g_hash_table_new_full (NULL, NULL, NULL,
+      (GDestroyNotify) g_object_unref);
+}
+
+static void
+get_property (GObject *object,
+              guint property_id,
+              GValue *value,
+              GParamSpec *spec)
+{
+  TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object);
+
+  switch (property_id) {
+    case PROP_ACCOUNT:
+      g_value_set_string (value, self->priv->account);
+      break;
+    case PROP_BREAK_PROPS:
+      g_value_set_boolean (value, self->priv->break_fastpath_props);
+      break;
+    case PROP_DBUS_STATUS:
+      if (self->priv->break_fastpath_props)
+        {
+          g_debug ("returning broken value for Connection.Status");
+          g_value_set_uint (value, 0xdeadbeefU);
+        }
+      else
+        {
+          guint32 status = TP_BASE_CONNECTION (self)->status;
+
+          if (status == TP_INTERNAL_CONNECTION_STATUS_NEW)
+            g_value_set_uint (value, TP_CONNECTION_STATUS_DISCONNECTED);
+          else
+            g_value_set_uint (value, status);
+        }
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+  }
+}
+
+static void
+set_property (GObject *object,
+              guint property_id,
+              const GValue *value,
+              GParamSpec *spec)
+{
+  TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object);
+
+  switch (property_id) {
+    case PROP_ACCOUNT:
+      g_free (self->priv->account);
+      self->priv->account = g_utf8_strdown (g_value_get_string (value), -1);
+      break;
+    case PROP_BREAK_PROPS:
+      self->priv->break_fastpath_props = g_value_get_boolean (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, spec);
+  }
+}
+
+static void
+dispose (GObject *object)
+{
+  TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object);
+
+  g_hash_table_unref (self->priv->channels);
+
+  G_OBJECT_CLASS (tp_tests_simple_connection_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+  TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (object);
+
+  if (self->priv->connect_source != 0)
+    {
+      g_source_remove (self->priv->connect_source);
+    }
+
+  if (self->priv->disconnect_source != 0)
+    {
+      g_source_remove (self->priv->disconnect_source);
+    }
+
+  g_clear_error (&self->priv->get_self_handle_error);
+  g_free (self->priv->account);
+
+  G_OBJECT_CLASS (tp_tests_simple_connection_parent_class)->finalize (object);
+}
+
+static gchar *
+get_unique_connection_name (TpBaseConnection *conn)
+{
+  TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (conn);
+
+  return g_strdup (self->priv->account);
+}
+
+static gchar *
+tp_tests_simple_normalize_contact (TpHandleRepoIface *repo,
+                           const gchar *id,
+                           gpointer context,
+                           GError **error)
+{
+  if (id[0] == '\0')
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+          "ID must not be empty");
+      return NULL;
+    }
+
+  if (strchr (id, ' ') != NULL)
+    {
+      g_set_error (error, TP_ERRORS, TP_ERROR_INVALID_HANDLE,
+          "ID must not contain spaces");
+      return NULL;
+    }
+
+  return g_utf8_strdown (id, -1);
+}
+
+static void
+create_handle_repos (TpBaseConnection *conn,
+                     TpHandleRepoIface *repos[NUM_TP_HANDLE_TYPES])
+{
+  repos[TP_HANDLE_TYPE_CONTACT] = tp_dynamic_handle_repo_new
+      (TP_HANDLE_TYPE_CONTACT, tp_tests_simple_normalize_contact, NULL);
+  repos[TP_HANDLE_TYPE_ROOM] = tp_dynamic_handle_repo_new
+      (TP_HANDLE_TYPE_ROOM, NULL, NULL);
+}
+
+static GPtrArray *
+create_channel_factories (TpBaseConnection *conn)
+{
+  return g_ptr_array_sized_new (0);
+}
+
+void
+tp_tests_simple_connection_inject_disconnect (TpTestsSimpleConnection *self)
+{
+  tp_base_connection_change_status ((TpBaseConnection *) self,
+      TP_CONNECTION_STATUS_DISCONNECTED,
+      TP_CONNECTION_STATUS_REASON_REQUESTED);
+}
+
+static gboolean
+pretend_connected (gpointer data)
+{
+  TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (data);
+  TpBaseConnection *conn = (TpBaseConnection *) self;
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+      TP_HANDLE_TYPE_CONTACT);
+
+  conn->self_handle = tp_handle_ensure (contact_repo, self->priv->account,
+      NULL, NULL);
+
+  if (conn->status == TP_CONNECTION_STATUS_CONNECTING)
+    {
+      tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTED,
+          TP_CONNECTION_STATUS_REASON_REQUESTED);
+    }
+
+  self->priv->connect_source = 0;
+  return FALSE;
+}
+
+static gboolean
+start_connecting (TpBaseConnection *conn,
+                  GError **error)
+{
+  TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (conn);
+
+  tp_base_connection_change_status (conn, TP_CONNECTION_STATUS_CONNECTING,
+      TP_CONNECTION_STATUS_REASON_REQUESTED);
+
+  /* In a real connection manager we'd ask the underlying implementation to
+   * start connecting, then go to state CONNECTED when finished. Here there
+   * isn't actually a connection, so we'll fake a connection process that
+   * takes time. */
+  self->priv->connect_source = g_timeout_add (0, pretend_connected, self);
+
+  return TRUE;
+}
+
+static gboolean
+pretend_disconnected (gpointer data)
+{
+  TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (data);
+
+  /* We are disconnected, all our channels are invalidated */
+  g_hash_table_remove_all (self->priv->channels);
+
+  tp_base_connection_finish_shutdown (TP_BASE_CONNECTION (data));
+  self->priv->disconnect_source = 0;
+  return FALSE;
+}
+
+static void
+shut_down (TpBaseConnection *conn)
+{
+  TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (conn);
+
+  /* In a real connection manager we'd ask the underlying implementation to
+   * start shutting down, then call this function when finished. Here there
+   * isn't actually a connection, so we'll fake a disconnection process that
+   * takes time. */
+  self->priv->disconnect_source = g_timeout_add (0, pretend_disconnected,
+      conn);
+}
+
+static void
+tp_tests_simple_connection_class_init (TpTestsSimpleConnectionClass *klass)
+{
+  TpBaseConnectionClass *base_class =
+      (TpBaseConnectionClass *) klass;
+  GObjectClass *object_class = (GObjectClass *) klass;
+  GParamSpec *param_spec;
+  static const gchar *interfaces_always_present[] = {
+      TP_IFACE_CONNECTION_INTERFACE_REQUESTS, NULL };
+
+  object_class->get_property = get_property;
+  object_class->set_property = set_property;
+  object_class->dispose = dispose;
+  object_class->finalize = finalize;
+  g_type_class_add_private (klass, sizeof (TpTestsSimpleConnectionPrivate));
+
+  base_class->create_handle_repos = create_handle_repos;
+  base_class->get_unique_connection_name = get_unique_connection_name;
+  base_class->create_channel_factories = create_channel_factories;
+  base_class->start_connecting = start_connecting;
+  base_class->shut_down = shut_down;
+
+  base_class->interfaces_always_present = interfaces_always_present;
+
+  param_spec = g_param_spec_string ("account", "Account name",
+      "The username of this user", NULL,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_ACCOUNT, param_spec);
+
+  param_spec = g_param_spec_boolean ("break-0192-properties",
+      "Break 0.19.2 properties",
+      "Break Connection D-Bus properties introduced in spec 0.19.2", FALSE,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_BREAK_PROPS, param_spec);
+
+  param_spec = g_param_spec_uint ("dbus-status",
+      "Connection.Status",
+      "The connection status as visible on D-Bus (overridden so can break it)",
+      TP_CONNECTION_STATUS_CONNECTED, G_MAXUINT,
+      TP_CONNECTION_STATUS_DISCONNECTED,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_DBUS_STATUS, param_spec);
+
+  signals[SIGNAL_GOT_SELF_HANDLE] = g_signal_new ("got-self-handle",
+      G_OBJECT_CLASS_TYPE (klass),
+      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
+      0,
+      NULL, NULL, NULL,
+      G_TYPE_NONE, 0);
+}
+
+void
+tp_tests_simple_connection_set_identifier (TpTestsSimpleConnection *self,
+                                  const gchar *identifier)
+{
+  TpBaseConnection *conn = (TpBaseConnection *) self;
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+      TP_HANDLE_TYPE_CONTACT);
+  TpHandle handle = tp_handle_ensure (contact_repo, identifier, NULL, NULL);
+
+  /* if this fails then the identifier was bad - caller error */
+  g_return_if_fail (handle != 0);
+
+  tp_base_connection_set_self_handle (conn, handle);
+  tp_handle_unref (contact_repo, handle);
+}
+
+TpTestsSimpleConnection *
+tp_tests_simple_connection_new (const gchar *account,
+    const gchar *protocol)
+{
+  return TP_TESTS_SIMPLE_CONNECTION (g_object_new (
+      TP_TESTS_TYPE_SIMPLE_CONNECTION,
+      "account", account,
+      "protocol", protocol,
+      NULL));
+}
+
+gchar *
+tp_tests_simple_connection_ensure_text_chan (TpTestsSimpleConnection *self,
+    const gchar *target_id,
+    GHashTable **props)
+{
+  TpTestsTextChannelNull *chan;
+  gchar *chan_path;
+  TpHandleRepoIface *contact_repo;
+  TpHandle handle;
+  static guint count = 0;
+  TpBaseConnection *base_conn = (TpBaseConnection *) self;
+
+  /* Get contact handle */
+  contact_repo = tp_base_connection_get_handles (base_conn,
+      TP_HANDLE_TYPE_CONTACT);
+  g_assert (contact_repo != NULL);
+
+  handle = tp_handle_ensure (contact_repo, target_id, NULL, NULL);
+
+  chan = g_hash_table_lookup (self->priv->channels, GUINT_TO_POINTER (handle));
+  if (chan != NULL)
+    {
+      /* Channel already exist, reuse it */
+      g_object_get (chan, "object-path", &chan_path, NULL);
+    }
+  else
+    {
+      chan_path = g_strdup_printf ("%s/Channel%u", base_conn->object_path,
+          count++);
+
+       chan = TP_TESTS_TEXT_CHANNEL_NULL (
+          tp_tests_object_new_static_class (
+            TP_TESTS_TYPE_TEXT_CHANNEL_NULL,
+            "connection", self,
+            "object-path", chan_path,
+            "handle", handle,
+            NULL));
+
+      g_hash_table_insert (self->priv->channels, GUINT_TO_POINTER (handle),
+          chan);
+    }
+
+  tp_handle_unref (contact_repo, handle);
+
+  if (props != NULL)
+    *props = tp_tests_text_channel_get_props (chan);
+
+  return chan_path;
+}
+
+void
+tp_tests_simple_connection_set_get_self_handle_error (
+    TpTestsSimpleConnection *self,
+    GQuark domain,
+    gint code,
+    const gchar *message)
+{
+  self->priv->get_self_handle_error = g_error_new_literal (domain, code,
+      message);
+}
+
+static void
+get_self_handle (TpSvcConnection *iface,
+    DBusGMethodInvocation *context)
+{
+  TpTestsSimpleConnection *self = TP_TESTS_SIMPLE_CONNECTION (iface);
+  TpBaseConnection *base = TP_BASE_CONNECTION (iface);
+
+  g_assert (TP_IS_BASE_CONNECTION (base));
+
+  TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (base, context);
+
+  if (self->priv->get_self_handle_error != NULL)
+    {
+      dbus_g_method_return_error (context, self->priv->get_self_handle_error);
+      return;
+    }
+
+  tp_svc_connection_return_from_get_self_handle (context, base->self_handle);
+  g_signal_emit (self, signals[SIGNAL_GOT_SELF_HANDLE], 0);
+}
+
+static void
+conn_iface_init (TpSvcConnectionClass *iface)
+{
+#define IMPLEMENT(prefix,x) \
+  tp_svc_connection_implement_##x (iface, prefix##x)
+  IMPLEMENT(,get_self_handle);
+#undef IMPLEMENT
+}
diff --git a/tests/lib/telepathy/contactlist/simple-conn.h b/tests/lib/telepathy/contactlist/simple-conn.h
new file mode 100644
index 0000000..6322f4b
--- /dev/null
+++ b/tests/lib/telepathy/contactlist/simple-conn.h
@@ -0,0 +1,77 @@
+/*
+ * simple-conn.h - header for a simple connection
+ *
+ * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2007-2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_SIMPLE_CONN_H__
+#define __TP_TESTS_SIMPLE_CONN_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsSimpleConnection TpTestsSimpleConnection;
+typedef struct _TpTestsSimpleConnectionClass TpTestsSimpleConnectionClass;
+typedef struct _TpTestsSimpleConnectionPrivate TpTestsSimpleConnectionPrivate;
+
+struct _TpTestsSimpleConnectionClass {
+    TpBaseConnectionClass parent_class;
+};
+
+struct _TpTestsSimpleConnection {
+    TpBaseConnection parent;
+
+    TpTestsSimpleConnectionPrivate *priv;
+};
+
+GType tp_tests_simple_connection_get_type (void);
+
+/* TYPE MACROS */
+#define TP_TESTS_TYPE_SIMPLE_CONNECTION \
+  (tp_tests_simple_connection_get_type ())
+#define TP_TESTS_SIMPLE_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST((obj), TP_TESTS_TYPE_SIMPLE_CONNECTION, \
+                              TpTestsSimpleConnection))
+#define TP_TESTS_SIMPLE_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST((klass), TP_TESTS_TYPE_SIMPLE_CONNECTION, \
+                           TpTestsSimpleConnectionClass))
+#define TP_TESTS_SIMPLE_IS_CONNECTION(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE((obj), TP_TESTS_TYPE_SIMPLE_CONNECTION))
+#define TP_TESTS_SIMPLE_IS_CONNECTION_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE((klass), TP_TESTS_TYPE_SIMPLE_CONNECTION))
+#define TP_TESTS_SIMPLE_CONNECTION_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_SIMPLE_CONNECTION, \
+                              TpTestsSimpleConnectionClass))
+
+TpTestsSimpleConnection * tp_tests_simple_connection_new (const gchar *account,
+    const gchar *protocol);
+
+/* Cause "network events", for debugging/testing */
+
+void tp_tests_simple_connection_inject_disconnect (
+    TpTestsSimpleConnection *self);
+
+void tp_tests_simple_connection_set_identifier (TpTestsSimpleConnection *self,
+    const gchar *identifier);
+
+gchar * tp_tests_simple_connection_ensure_text_chan (
+    TpTestsSimpleConnection *self,
+    const gchar *target_id,
+    GHashTable **props);
+
+void tp_tests_simple_connection_set_get_self_handle_error (
+    TpTestsSimpleConnection *self,
+    GQuark domain,
+    gint code,
+    const gchar *message);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_SIMPLE_CONN_H__ */
diff --git a/tests/lib/telepathy/contactlist/textchan-null.c b/tests/lib/telepathy/contactlist/textchan-null.c
new file mode 100644
index 0000000..5765944
--- /dev/null
+++ b/tests/lib/telepathy/contactlist/textchan-null.c
@@ -0,0 +1,571 @@
+/*
+ * /dev/null as a text channel
+ *
+ * Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#include "config.h"
+
+#include "textchan-null.h"
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/channel-iface.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/svc-channel.h>
+#include <telepathy-glib/svc-generic.h>
+
+static void text_iface_init (gpointer iface, gpointer data);
+static void channel_iface_init (gpointer iface, gpointer data);
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsTextChannelNull,
+    tp_tests_text_channel_null,
+    G_TYPE_OBJECT,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL, channel_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_TYPE_TEXT, text_iface_init);
+    G_IMPLEMENT_INTERFACE (TP_TYPE_CHANNEL_IFACE, NULL))
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsPropsTextChannel,
+    tp_tests_props_text_channel,
+    TP_TESTS_TYPE_TEXT_CHANNEL_NULL,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_DBUS_PROPERTIES,
+      tp_dbus_properties_mixin_iface_init))
+
+G_DEFINE_TYPE_WITH_CODE (TpTestsPropsGroupTextChannel,
+    tp_tests_props_group_text_channel,
+    TP_TESTS_TYPE_PROPS_TEXT_CHANNEL,
+    G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CHANNEL_INTERFACE_GROUP,
+        tp_group_mixin_iface_init))
+
+static const char *tp_tests_text_channel_null_interfaces[] = { NULL };
+
+/* type definition stuff */
+
+enum
+{
+  PROP_OBJECT_PATH = 1,
+  PROP_CHANNEL_TYPE,
+  PROP_HANDLE_TYPE,
+  PROP_HANDLE,
+  PROP_TARGET_ID,
+  PROP_CONNECTION,
+  PROP_INTERFACES,
+  PROP_REQUESTED,
+  PROP_INITIATOR_HANDLE,
+  PROP_INITIATOR_ID,
+  N_PROPS
+};
+
+struct _TpTestsTextChannelNullPrivate
+{
+  TpBaseConnection *conn;
+  gchar *object_path;
+  TpHandle handle;
+
+  unsigned closed:1;
+  unsigned disposed:1;
+};
+
+static void
+tp_tests_text_channel_null_init (TpTestsTextChannelNull *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+      TP_TESTS_TYPE_TEXT_CHANNEL_NULL, TpTestsTextChannelNullPrivate);
+}
+
+static void
+tp_tests_props_text_channel_init (TpTestsPropsTextChannel *self)
+{
+  self->dbus_property_interfaces_retrieved = g_hash_table_new (NULL, NULL);
+}
+
+static GObject *
+constructor (GType type,
+             guint n_props,
+             GObjectConstructParam *props)
+{
+  GObject *object =
+      G_OBJECT_CLASS (tp_tests_text_channel_null_parent_class)->constructor (type,
+          n_props, props);
+  TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (object);
+  TpHandleRepoIface *contact_repo = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+  tp_handle_ref (contact_repo, self->priv->handle);
+
+  tp_dbus_daemon_register_object (
+      tp_base_connection_get_dbus_daemon (self->priv->conn),
+      self->priv->object_path, self);
+
+  tp_text_mixin_init (object, G_STRUCT_OFFSET (TpTestsTextChannelNull, text),
+      contact_repo);
+
+  tp_text_mixin_set_message_types (object,
+      TP_CHANNEL_TEXT_MESSAGE_TYPE_NORMAL,
+      TP_CHANNEL_TEXT_MESSAGE_TYPE_ACTION,
+      TP_CHANNEL_TEXT_MESSAGE_TYPE_NOTICE,
+      G_MAXUINT);
+
+  return object;
+}
+
+static void
+get_property (GObject *object,
+              guint property_id,
+              GValue *value,
+              GParamSpec *pspec)
+{
+  TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (object);
+
+  switch (property_id)
+    {
+    case PROP_OBJECT_PATH:
+      g_value_set_string (value, self->priv->object_path);
+      break;
+    case PROP_CHANNEL_TYPE:
+      g_value_set_static_string (value, TP_IFACE_CHANNEL_TYPE_TEXT);
+      break;
+    case PROP_HANDLE_TYPE:
+      g_value_set_uint (value, TP_HANDLE_TYPE_CONTACT);
+      break;
+    case PROP_HANDLE:
+      g_value_set_uint (value, self->priv->handle);
+      break;
+    case PROP_TARGET_ID:
+        {
+          TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+              self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+          g_value_set_string (value,
+              tp_handle_inspect (contact_repo, self->priv->handle));
+        }
+      break;
+    case PROP_REQUESTED:
+      g_value_set_boolean (value, TRUE);
+      break;
+    case PROP_INITIATOR_HANDLE:
+      g_value_set_uint (value, self->priv->conn->self_handle);
+      break;
+    case PROP_INITIATOR_ID:
+        {
+          TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (
+              self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+          g_value_set_string (value,
+              tp_handle_inspect (contact_repo, self->priv->conn->self_handle));
+        }
+      break;
+    case PROP_INTERFACES:
+      g_value_set_boxed (value, tp_tests_text_channel_null_interfaces);
+      break;
+    case PROP_CONNECTION:
+      g_value_set_object (value, self->priv->conn);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+set_property (GObject *object,
+              guint property_id,
+              const GValue *value,
+              GParamSpec *pspec)
+{
+  TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (object);
+
+  switch (property_id)
+    {
+    case PROP_OBJECT_PATH:
+      g_free (self->priv->object_path);
+      self->priv->object_path = g_value_dup_string (value);
+      break;
+    case PROP_HANDLE:
+      /* we don't ref it here because we don't necessarily have access to the
+       * contact repo yet - instead we ref it in the constructor.
+       */
+      self->priv->handle = g_value_get_uint (value);
+      break;
+    case PROP_HANDLE_TYPE:
+    case PROP_CHANNEL_TYPE:
+      /* these properties are writable in the interface, but not actually
+       * meaningfully changable on this channel, so we do nothing */
+      break;
+    case PROP_CONNECTION:
+      self->priv->conn = g_value_get_object (value);
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+void
+tp_tests_text_channel_null_close (TpTestsTextChannelNull *self)
+{
+  if (!self->priv->closed)
+    {
+      self->priv->closed = TRUE;
+      tp_svc_channel_emit_closed (self);
+      tp_dbus_daemon_unregister_object (
+          tp_base_connection_get_dbus_daemon (self->priv->conn), self);
+    }
+}
+
+static void
+dispose (GObject *object)
+{
+  TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (object);
+
+  if (self->priv->disposed)
+    return;
+
+  self->priv->disposed = TRUE;
+  tp_tests_text_channel_null_close (self);
+
+  ((GObjectClass *) tp_tests_text_channel_null_parent_class)->dispose (object);
+}
+
+static void
+finalize (GObject *object)
+{
+  TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (object);
+  TpHandleRepoIface *contact_handles = tp_base_connection_get_handles
+      (self->priv->conn, TP_HANDLE_TYPE_CONTACT);
+
+  tp_handle_unref (contact_handles, self->priv->handle);
+  g_free (self->priv->object_path);
+
+  tp_text_mixin_finalize (object);
+
+  ((GObjectClass *) tp_tests_text_channel_null_parent_class)->finalize (object);
+}
+
+static void
+tp_tests_text_channel_null_class_init (TpTestsTextChannelNullClass *klass)
+{
+  GObjectClass *object_class = (GObjectClass *) klass;
+  GParamSpec *param_spec;
+
+  g_type_class_add_private (klass, sizeof (TpTestsTextChannelNullPrivate));
+
+  object_class->constructor = constructor;
+  object_class->set_property = set_property;
+  object_class->get_property = get_property;
+  object_class->dispose = dispose;
+  object_class->finalize = finalize;
+
+  g_object_class_override_property (object_class, PROP_OBJECT_PATH,
+      "object-path");
+  g_object_class_override_property (object_class, PROP_CHANNEL_TYPE,
+      "channel-type");
+  g_object_class_override_property (object_class, PROP_HANDLE_TYPE,
+      "handle-type");
+  g_object_class_override_property (object_class, PROP_HANDLE, "handle");
+
+  param_spec = g_param_spec_object ("connection", "TpBaseConnection object",
+      "Connection object that owns this channel",
+      TP_TYPE_BASE_CONNECTION,
+      G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
+
+  param_spec = g_param_spec_boxed ("interfaces", "Extra D-Bus interfaces",
+      "Additional Channel.Interface.* interfaces",
+      G_TYPE_STRV,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_INTERFACES, param_spec);
+
+  param_spec = g_param_spec_string ("target-id", "Peer's ID",
+      "The string obtained by inspecting the target handle",
+      NULL,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_TARGET_ID, param_spec);
+
+  param_spec = g_param_spec_uint ("initiator-handle", "Initiator's handle",
+      "The contact who initiated the channel",
+      0, G_MAXUINT32, 0,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_INITIATOR_HANDLE,
+      param_spec);
+
+  param_spec = g_param_spec_string ("initiator-id", "Initiator's ID",
+      "The string obtained by inspecting the initiator-handle",
+      NULL,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_INITIATOR_ID,
+      param_spec);
+
+  param_spec = g_param_spec_boolean ("requested", "Requested?",
+      "True if this channel was requested by the local user",
+      FALSE,
+      G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
+  g_object_class_install_property (object_class, PROP_REQUESTED, param_spec);
+
+  tp_text_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (TpTestsTextChannelNullClass, text_class));
+}
+
+static void
+tp_tests_props_text_channel_getter_gobject_properties (GObject *object,
+    GQuark interface,
+    GQuark name,
+    GValue *value,
+    gpointer getter_data)
+{
+  TpTestsPropsTextChannel *self = TP_TESTS_PROPS_TEXT_CHANNEL (object);
+
+  g_hash_table_insert (self->dbus_property_interfaces_retrieved,
+      GUINT_TO_POINTER (interface), GUINT_TO_POINTER (interface));
+
+  tp_dbus_properties_mixin_getter_gobject_properties (object, interface, name,
+      value, getter_data);
+}
+
+static void
+props_finalize (GObject *object)
+{
+  TpTestsPropsTextChannel *self = TP_TESTS_PROPS_TEXT_CHANNEL (object);
+
+  g_hash_table_unref (self->dbus_property_interfaces_retrieved);
+
+  ((GObjectClass *) tp_tests_props_text_channel_parent_class)->finalize (object);
+}
+
+static void
+tp_tests_props_text_channel_class_init (TpTestsPropsTextChannelClass *klass)
+{
+  GObjectClass *object_class = (GObjectClass *) klass;
+  static TpDBusPropertiesMixinPropImpl channel_props[] = {
+      { "TargetHandleType", "handle-type", NULL },
+      { "TargetHandle", "handle", NULL },
+      { "ChannelType", "channel-type", NULL },
+      { "Interfaces", "interfaces", NULL },
+      { "TargetID", "target-id", NULL },
+      { "Requested", "requested", NULL },
+      { "InitiatorHandle", "initiator-handle", NULL },
+      { "InitiatorID", "initiator-id", NULL },
+      { NULL }
+  };
+  static TpDBusPropertiesMixinIfaceImpl prop_interfaces[] = {
+      { TP_IFACE_CHANNEL,
+        tp_tests_props_text_channel_getter_gobject_properties,
+        NULL,
+        channel_props,
+      },
+      { NULL }
+  };
+
+  object_class->finalize = props_finalize;
+
+  klass->dbus_properties_class.interfaces = prop_interfaces;
+  tp_dbus_properties_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (TpTestsPropsTextChannelClass, dbus_properties_class));
+}
+
+static void
+tp_tests_props_group_text_channel_init (TpTestsPropsGroupTextChannel *self)
+{
+}
+
+static void
+group_constructed (GObject *self)
+{
+  TpBaseConnection *conn = TP_TESTS_TEXT_CHANNEL_NULL (self)->priv->conn;
+  void (*chain_up) (GObject *) =
+    ((GObjectClass *) tp_tests_props_group_text_channel_parent_class)->constructed;
+
+  if (chain_up != NULL)
+    chain_up (self);
+
+  tp_group_mixin_init (self,
+      G_STRUCT_OFFSET (TpTestsPropsGroupTextChannel, group),
+      tp_base_connection_get_handles (conn, TP_HANDLE_TYPE_CONTACT),
+      tp_base_connection_get_self_handle (conn));
+  tp_group_mixin_change_flags (self, TP_CHANNEL_GROUP_FLAG_PROPERTIES, 0);
+}
+
+static void
+group_finalize (GObject *self)
+{
+  tp_group_mixin_finalize (self);
+
+  ((GObjectClass *) tp_tests_props_group_text_channel_parent_class)->finalize (self);
+}
+
+static gboolean
+dummy_add_remove_member (GObject *obj,
+    TpHandle handle,
+    const gchar *message,
+    GError **error)
+{
+  return TRUE;
+}
+
+static void
+group_iface_props_getter (GObject *object,
+    GQuark interface,
+    GQuark name,
+    GValue *value,
+    gpointer getter_data)
+{
+  TpTestsPropsTextChannel *self = TP_TESTS_PROPS_TEXT_CHANNEL (object);
+
+  g_hash_table_insert (self->dbus_property_interfaces_retrieved,
+      GUINT_TO_POINTER (interface), GUINT_TO_POINTER (interface));
+
+  tp_group_mixin_get_dbus_property (object, interface, name, value, getter_data);
+}
+
+static void
+tp_tests_props_group_text_channel_class_init (TpTestsPropsGroupTextChannelClass *klass)
+{
+  GObjectClass *object_class = (GObjectClass *) klass;
+  static TpDBusPropertiesMixinPropImpl group_props[] = {
+      { "GroupFlags", NULL, NULL },
+      { "HandleOwners", NULL, NULL },
+      { "LocalPendingMembers", NULL, NULL },
+      { "Members", NULL, NULL },
+      { "RemotePendingMembers", NULL, NULL },
+      { "SelfHandle", NULL, NULL },
+      { NULL }
+  };
+
+  object_class->constructed = group_constructed;
+  object_class->finalize = group_finalize;
+
+  tp_group_mixin_class_init (object_class,
+      G_STRUCT_OFFSET (TpTestsPropsGroupTextChannelClass, group_class),
+      dummy_add_remove_member,
+      dummy_add_remove_member);
+  tp_dbus_properties_mixin_implement_interface (object_class,
+      TP_IFACE_QUARK_CHANNEL_INTERFACE_GROUP, group_iface_props_getter, NULL,
+      group_props);
+}
+
+static void
+channel_close (TpSvcChannel *iface,
+               DBusGMethodInvocation *context)
+{
+  TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (iface);
+
+  tp_tests_text_channel_null_close (self);
+  tp_svc_channel_return_from_close (context);
+}
+
+static void
+channel_get_channel_type (TpSvcChannel *iface,
+                          DBusGMethodInvocation *context)
+{
+  TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (iface);
+
+  self->get_channel_type_called++;
+
+  tp_svc_channel_return_from_get_channel_type (context,
+      TP_IFACE_CHANNEL_TYPE_TEXT);
+}
+
+static void
+channel_get_handle (TpSvcChannel *iface,
+                    DBusGMethodInvocation *context)
+{
+  TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (iface);
+
+  self->get_handle_called++;
+
+  tp_svc_channel_return_from_get_handle (context, TP_HANDLE_TYPE_CONTACT,
+      self->priv->handle);
+}
+
+static void
+channel_get_interfaces (TpSvcChannel *iface,
+                        DBusGMethodInvocation *context)
+{
+  TpTestsTextChannelNull *self = TP_TESTS_TEXT_CHANNEL_NULL (iface);
+
+  self->get_interfaces_called++;
+
+  tp_svc_channel_return_from_get_interfaces (context,
+      tp_tests_text_channel_null_interfaces);
+}
+
+static void
+channel_iface_init (gpointer iface,
+                    gpointer data)
+{
+  TpSvcChannelClass *klass = iface;
+
+#define IMPLEMENT(x) tp_svc_channel_implement_##x (klass, channel_##x)
+  IMPLEMENT (close);
+  IMPLEMENT (get_channel_type);
+  IMPLEMENT (get_handle);
+  IMPLEMENT (get_interfaces);
+#undef IMPLEMENT
+}
+
+static void
+text_send (TpSvcChannelTypeText *iface,
+           guint type,
+           const gchar *text,
+           DBusGMethodInvocation *context)
+{
+  /* silently swallow the message */
+  tp_svc_channel_type_text_return_from_send (context);
+}
+
+static void
+text_iface_init (gpointer iface,
+                 gpointer data)
+{
+  TpSvcChannelTypeTextClass *klass = iface;
+
+  tp_text_mixin_iface_init (iface, data);
+#define IMPLEMENT(x) tp_svc_channel_type_text_implement_##x (klass, text_##x)
+  IMPLEMENT (send);
+#undef IMPLEMENT
+}
+
+GHashTable *
+tp_tests_text_channel_get_props (TpTestsTextChannelNull *self)
+{
+  GHashTable *props;
+  TpHandleType handle_type;
+  TpHandle handle;
+  gchar *target_id;
+  gboolean requested;
+  TpHandle initiator_handle;
+  gchar *initiator_id;
+  GStrv interfaces;
+
+  g_object_get (self,
+      "handle-type", &handle_type,
+      "handle", &handle,
+      "target-id", &target_id,
+      "requested", &requested,
+      "initiator-handle", &initiator_handle,
+      "initiator-id", &initiator_id,
+      "interfaces", &interfaces,
+      NULL);
+
+  props = tp_asv_new (
+      TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING, TP_IFACE_CHANNEL_TYPE_TEXT,
+      TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT, handle_type,
+      TP_PROP_CHANNEL_TARGET_HANDLE, G_TYPE_UINT, handle,
+      TP_PROP_CHANNEL_TARGET_ID, G_TYPE_STRING, target_id,
+      TP_PROP_CHANNEL_REQUESTED, G_TYPE_BOOLEAN, requested,
+      TP_PROP_CHANNEL_INITIATOR_HANDLE, G_TYPE_UINT, initiator_handle,
+      TP_PROP_CHANNEL_INITIATOR_ID, G_TYPE_STRING, initiator_id,
+      TP_PROP_CHANNEL_INTERFACES, G_TYPE_STRV, interfaces,
+      NULL);
+
+  g_free (target_id);
+  g_free (initiator_id);
+  g_strfreev (interfaces);
+  return props;
+}
diff --git a/tests/lib/telepathy/contactlist/textchan-null.h b/tests/lib/telepathy/contactlist/textchan-null.h
new file mode 100644
index 0000000..583bec5
--- /dev/null
+++ b/tests/lib/telepathy/contactlist/textchan-null.h
@@ -0,0 +1,137 @@
+/*
+ * /dev/null as a text channel
+ *
+ * Copyright (C) 2008 Collabora Ltd. <http://www.collabora.co.uk/>
+ * Copyright (C) 2008 Nokia Corporation
+ *
+ * Copying and distribution of this file, with or without modification,
+ * are permitted in any medium without royalty provided the copyright
+ * notice and this notice are preserved.
+ */
+
+#ifndef __TP_TESTS_TEXT_CHANNEL_NULL_H__
+#define __TP_TESTS_TEXT_CHANNEL_NULL_H__
+
+#include <glib-object.h>
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/text-mixin.h>
+#include <telepathy-glib/group-mixin.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpTestsTextChannelNull TpTestsTextChannelNull;
+typedef struct _TpTestsTextChannelNullClass TpTestsTextChannelNullClass;
+typedef struct _TpTestsTextChannelNullPrivate TpTestsTextChannelNullPrivate;
+
+GType tp_tests_text_channel_null_get_type (void);
+
+#define TP_TESTS_TYPE_TEXT_CHANNEL_NULL \
+  (tp_tests_text_channel_null_get_type ())
+#define TP_TESTS_TEXT_CHANNEL_NULL(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_TEXT_CHANNEL_NULL, \
+                               TpTestsTextChannelNull))
+#define TP_TESTS_TEXT_CHANNEL_NULL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_TEXT_CHANNEL_NULL, \
+                            TpTestsTextChannelNullClass))
+#define TP_TESTS_IS_TEXT_CHANNEL_NULL(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_TEXT_CHANNEL_NULL))
+#define TP_TESTS_IS_TEXT_CHANNEL_NULL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_TEXT_CHANNEL_NULL))
+#define TP_TESTS_TEXT_CHANNEL_NULL_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_TEXT_CHANNEL_NULL, \
+                              TpTestsTextChannelNullClass))
+
+struct _TpTestsTextChannelNullClass {
+    GObjectClass parent_class;
+
+    TpTextMixinClass text_class;
+};
+
+struct _TpTestsTextChannelNull {
+    GObject parent;
+    TpTextMixin text;
+
+    guint get_handle_called;
+    guint get_interfaces_called;
+    guint get_channel_type_called;
+
+    TpTestsTextChannelNullPrivate *priv;
+};
+
+/* Subclass with D-Bus properties */
+
+typedef struct _TestPropsTextChannel TpTestsPropsTextChannel;
+typedef struct _TestPropsTextChannelClass TpTestsPropsTextChannelClass;
+
+struct _TestPropsTextChannel {
+    TpTestsTextChannelNull parent;
+
+    GHashTable *dbus_property_interfaces_retrieved;
+};
+
+struct _TestPropsTextChannelClass {
+    TpTestsTextChannelNullClass parent;
+
+    TpDBusPropertiesMixinClass dbus_properties_class;
+};
+
+GType tp_tests_props_text_channel_get_type (void);
+
+#define TP_TESTS_TYPE_PROPS_TEXT_CHANNEL \
+  (tp_tests_props_text_channel_get_type ())
+#define TP_TESTS_PROPS_TEXT_CHANNEL(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_PROPS_TEXT_CHANNEL, \
+                               TpTestsPropsTextChannel))
+#define TP_TESTS_PROPS_TEXT_CHANNEL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_PROPS_TEXT_CHANNEL, \
+                            TpTestsPropsTextChannelClass))
+#define TP_TESTS_IS_PROPS_TEXT_CHANNEL(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_PROPS_TEXT_CHANNEL))
+#define TP_TESTS_IS_PROPS_TEXT_CHANNEL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_PROPS_TEXT_CHANNEL))
+#define TP_TESTS_PROPS_TEXT_CHANNEL_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_PROPS_TEXT_CHANNEL, \
+                              TpTestsPropsTextChannelClass))
+
+/* Subclass with D-Bus properties and Group */
+
+typedef struct _TestPropsGroupTextChannel TpTestsPropsGroupTextChannel;
+typedef struct _TestPropsGroupTextChannelClass TpTestsPropsGroupTextChannelClass;
+
+struct _TestPropsGroupTextChannel {
+    TpTestsPropsTextChannel parent;
+
+    TpGroupMixin group;
+};
+
+struct _TestPropsGroupTextChannelClass {
+    TpTestsPropsTextChannelClass parent;
+
+    TpGroupMixinClass group_class;
+};
+
+GType tp_tests_props_group_text_channel_get_type (void);
+
+#define TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL \
+  (tp_tests_props_group_text_channel_get_type ())
+#define TP_TESTS_PROPS_GROUP_TEXT_CHANNEL(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL, \
+                               TpTestsPropsGroupTextChannel))
+#define TP_TESTS_PROPS_GROUP_TEXT_CHANNEL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL, \
+                            TpTestsPropsGroupTextChannelClass))
+#define TP_TESTS_IS_PROPS_GROUP_TEXT_CHANNEL(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL))
+#define TP_TESTS_IS_PROPS_GROUP_TEXT_CHANNEL_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL))
+#define TP_TESTS_PROPS_GROUP_TEXT_CHANNEL_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), TP_TESTS_TYPE_PROPS_GROUP_TEXT_CHANNEL, \
+                              TpTestsPropsGroupTextChannelClass))
+
+void tp_tests_text_channel_null_close (TpTestsTextChannelNull *self);
+
+GHashTable * tp_tests_text_channel_get_props (TpTestsTextChannelNull *self);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_TESTS_TEXT_CHANNEL_NULL_H__ */
diff --git a/tests/lib/telepathy/contactlist/tp-test-contactlist.h b/tests/lib/telepathy/contactlist/tp-test-contactlist.h
index 2ce4b65..0d952d9 100644
--- a/tests/lib/telepathy/contactlist/tp-test-contactlist.h
+++ b/tests/lib/telepathy/contactlist/tp-test-contactlist.h
@@ -1,11 +1,10 @@
 #ifndef __EXAMPLE_CONTACTLIST_H__
 #define __EXAMPLE_CONTACTLIST_H__
 
-#include <account-manager.h>
-#include <account.h>
+#include <simple-account-manager.h>
+#include <simple-account.h>
 #include <backend.h>
-#include <conn.h>
+#include <contacts-conn.h>
 #include <contact-list-manager.h>
-#include <contact-list.h>
 
 #endif /* __EXAMPLE_CONTACTLIST_H__ */
diff --git a/tests/telepathy/individual-properties.vala b/tests/telepathy/individual-properties.vala
index 8580739..12cd3ad 100644
--- a/tests/telepathy/individual-properties.vala
+++ b/tests/telepathy/individual-properties.vala
@@ -323,9 +323,9 @@ public class IndividualPropertiesTests : Folks.TestCase
 
               /* set the alias through Telepathy and wait for it to hit our
                * alias notification callback above */
-
               var handle = (Handle) ((Tpf.Persona) persona).contact.handle;
-              this.tp_backend.connection.manager.set_alias (handle, new_alias);
+              var conn = this.tp_backend.get_connection_for_handle (this._account_handle);
+              conn.change_aliases ({handle}, {new_alias});
             }
 
           assert (removed.size == 1);



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